Appearance
Migrating from v1
API v2 is a full redesign of the external surface: resource-oriented URLs, a single unified fees shape, machine-readable errors with stable codes, and an explicit instructability contract. v1 and the legacy unversioned routes keep working, and no retirement date has been announced, so you can migrate endpoint by endpoint at your own pace.
This page maps every v1 concept to its v2 successor. If you are integrating Renewr for the first time, skip this page and start with Getting started.
Why migrate, and by when
v1 and the legacy /api/external/* routes are deprecated since August 1, 2026. No retirement date has been announced, and v1 keeps working in the meantime. The routes behave exactly as before; the only change is two response headers announcing the deprecation:
http
HTTP/1.1 200 OK
Deprecation: @1785542400
Link: </api/v2/external/patents>; rel="successor-version"| Header | Standard | Meaning |
|---|---|---|
Deprecation: @1785542400 | RFC 9745 (Structured Field Date) | The route is deprecated since this Unix timestamp, 2026-08-01T00:00:00Z |
Sunset | RFC 8594 | Not sent today. It will start appearing once a retirement date is announced, and it will carry that date |
Link: <…>; rel="successor-version" | RFC 8288 | The v2 route that replaces this one |
The headers appear on both the legacy /api/external/* prefix and the /api/v1/external/* prefix. v2 responses (/api/v2/external/*) never carry them.
Detect stragglers automatically
Log the Deprecation header in your HTTP client. Any request that still triggers it after your migration is a call you forgot to move. The Link: rel="successor-version" header tells you exactly where it should go.
Beyond the deprecation itself, the practical reasons to move:
- Stable error contract:
application/problem+jsonwith machine-readablecodevalues instead of free-text messages. See Errors. - Instructability: every event tells you whether it can be instructed right now, with the same rules the server enforces. See Instructions & instructability.
- Unified fees: one shape from estimate to invoice, with a per-component breakdown. See Fees & FX.
- A real period query:
?dueMonth=/?dueFrom=&dueTo=with office/owner/family filters replaces the rigid/{year}/{month}path. See The monthly renewal cycle.
URL mapping
All paths below are relative to https://api.renewr.example/api. Every legacy row also exists with the /v1 prefix (/v1/external/...) and maps to the same successor.
| Legacy / v1 route | v2 successor | Notes |
|---|---|---|
GET /external/patent | GET /v2/external/patents | List envelope, sort allowlist, ?providerId= filter |
GET /external/patent/{id} | GET /v2/external/patents/{patentId} | Breaking; see below |
GET /external/patent/with-renewal-event-and-fees | GET /v2/external/patents + GET …/events / GET …/events/{eventId}/fees | The RPC-style route is gone; for a due-window portfolio view use GET /v2/external/patent-events?include=fees |
GET /external/patent/{id}/renewal-event | GET /v2/external/patents/{patentId}/events | Returns a bare JSON array |
GET /external/patent/{id}/renewal-event/{renewalEventId} | GET /v2/external/patents/{patentId}/events/{eventId} | |
GET /external/patent/{id}/renewal-event/{renewalEventId}/detailed-invoice | GET /v2/external/patents/{patentId}/events/{eventId}/fees | Unified estimated/invoiced fees; no more 404 before invoicing |
GET /external/renewal-event/{year}/{month} | GET /v2/external/patent-events?dueMonth=YYYY-MM | Year and month move from the path into a query filter |
GET /external/estimation | GET /v2/external/patent-events?…&include=fees | Estimated fees inline on each event |
POST /external/instructions/by-renewal-event-ids | POST /v2/external/patent-instructions | Body format changed; see below |
GET /external/invoice | GET /v2/external/invoices | |
GET /external/invoice/{id} | GET /v2/external/invoices/{invoiceId} | |
GET /external/frozen-exchange-rates/months | GET /v2/external/exchange-rate-freezes | Returns a bare JSON array of frozen months |
GET /external/frozen-exchange-rates/{year}/{month} | GET /v2/external/exchange-rate-freezes/{yearMonth} | Single YYYY-MM path segment, e.g. 2026-09 |
POST /external/frozen-exchange-rates/{year}/{month} | PUT /v2/external/exchange-rate-freezes/{yearMonth} | POST becomes an idempotent PUT, safe to retry |
GET …/renewal-event/{renewalEventId}/invoice | GET …/events/{eventId}/documents/invoice | |
GET …/renewal-event/{renewalEventId}/instructionReceipt | GET …/events/{eventId}/documents/instruction-receipt | PDF; no more camelCase URL segments |
GET …/renewal-event/{renewalEventId}/officialReceipt | GET …/events/{eventId}/documents/official-receipt |
See Downloading documents for the document routes and Conventions for pagination, sorting and the list envelope.
Breaking: patent lookups are UUID-only in v2
On v1, GET /external/patent/{id} interpreted {id} as your providerId by default (unless useProviderId=false). On v2, GET /v2/external/patents/{patentId} always takes the Renewr UUID, and the useProviderId parameter no longer exists. To look up a patent by your own reference, use the list filter. It matches exactly and returns an empty page (never a 404) when nothing matches:
bash
curl "https://api.renewr.example/api/v2/external/patents?providerId=MY-REF-001" \
-H "x-api-key: $RENEWR_API_KEY"Renamed fields
v2 renames fields for precision and consistency; values are unchanged unless an enum remap applies (next section). Full field-by-field definitions live in the reference pages; the Glossary explains the vocabulary.
Patent
| v1 field | v2 field | Notes |
|---|---|---|
clientCaseRef | clientCaseReference | |
clientFamilyRef | clientFamilyReference | |
country | office | An authority code (WO, EP, EA, or a national ISO 3166-1 code), which is not always a country; value unchanged |
nationalFilingDate | nationalPhaseEntryDate | |
priorityApplicationDate | priorityDate | |
expirationExtendedDate | extendedExpirationDate | |
lapsedDate | lapseDate | |
initialReferenceDate | renewalsManagedFrom | Base date of the annuity computation |
patentStatus | status | Values unchanged; NOT_APPLICABLE becomes null |
applicationType | filingRoute | Values remapped; see enum table |
claims | claimCount |
Internal fields such as holderId, manager, clientEntity, agent and agentBranchId are no longer exposed in v2.
Renewal event → patent event
The v1 "renewal event" becomes the v2 patent event (see Events & due periods). It gains eventType (only RENEWAL today; tolerate new values) and the instructability object.
| v1 field | v2 field | Notes |
|---|---|---|
paymentDate, renewalDate | dueDate | Merged; the two v1 fields always carried the same value |
renewalYearNumber | renewal.annuityYear | |
paymentNumber | renewal.paymentSequenceNumber | null when equal to annuityYear |
graceEndDate | gracePeriodEndDate | |
patentId | patent.id | Grouped as patent: { id, providerId }, with both identifiers everywhere an event references its patent |
executedRenewalInstruction | appliedDecision | Values remapped: RENEW → PROCEED, etc. |
instructionStatus | duePeriod | It classifies the deadline. It does not describe an instruction; NEXT → UPCOMING |
processingStatus | processingStatus | Kept, but v2 values are finer-grained; see below |
frozenFees | isFxRateFrozen | Boolean; says what is actually frozen (the FX rate) |
feesStatus | feesStatus | ESTIMATED_FROZEN_FX decomposed; see enum table |
fees (officeFees / agentFees / renewrFees / gracePeriodOfficeFees objects + invoiceTotalValue, invoiceCurrency) | fees (total, currency, components[]) | One flat breakdown: components[] entries of { type, amount } with type ∈ OFFICE | AGENT | RENEWR | GRACE_PERIOD; amounts are decimal strings. See Fees & FX |
dateOfPayment | None | Not exposed in v2; track progress via processingStatus + processingStatusUpdatedAt |
warningStatus | None | Not exposed in v2 |
referenceDate, monthAfterGracePeriod, comments, feeError, detailedInvoice | None | Removed; billed fee details now live on GET …/events/{eventId}/fees and under the invoice's fees[] |
Invoice
| v1 field | v2 field | Notes |
|---|---|---|
invoiceFullReference | reference | The only field guaranteed across all origins |
invoiceNumber | number | Now a string; null for IMPORTED invoices |
invoiceDate | issueDate | |
type (managed / unmanaged / regularization) | origin (GENERATED / IMPORTED / REGULARIZATION) | |
clientId | None | Removed; your key already identifies your tenant |
Frozen exchange rates
For the events listed under a freeze (GET /v2/external/exchange-rate-freezes/{yearMonth}):
| v1 field | v2 field |
|---|---|
reference | clientCaseReference |
renewalDate | dueDate |
price | amount (decimal string) |
isPriceFrozen | isFxRateFrozen |
instructionGiven | hasInstruction |
Enum remaps
Instruction decisions (both directions)
Applies to the decision you send on POST /v2/external/patent-instructions and to the appliedDecision you read back on events:
| v1 value | v2 value |
|---|---|
RENEW | PROCEED |
NOT_THIS_TIME | SKIP_THIS_TIME |
NEVER | DROP |
Due period (v1 instructionStatus → v2 duePeriod)
| v1 value | v2 value |
|---|---|
LATE | LATE |
GRACE_PERIOD | GRACE_PERIOD |
CURRENT | CURRENT |
NEXT | UPCOMING |
LAPSED | LAPSED |
Fees status
| v1 value | v2 equivalent |
|---|---|
ESTIMATED | feesStatus: "ESTIMATED" |
CONFIRMED | feesStatus: "INVOICED"; an invoice exists and the amounts are billed, no longer projections |
ESTIMATED_FROZEN_FX | feesStatus: "ESTIMATED" + isFxRateFrozen: true; the FX axis is a separate boolean |
Filing route (v1 applicationType → v2 filingRoute)
| v1 value | v2 value |
|---|---|
APP | NATIONAL |
PCT | PCT |
EP | EP |
EA | EA |
DIV | DIVISIONAL |
NOT_APPLICABLE becomes null
Wherever v1 used the sentinel NOT_APPLICABLE (patentType, status, filingRoute, applicantType), v2 serializes null. The field is always present, never omitted.
Processing status
v2 keeps the field name but reports finer-grained values. Mapping from v2 back to what v1 would have shown:
| v2 value | v1 equivalent |
|---|---|
NOT_INSTRUCTED | NOT_INSTRUCTED |
INSTRUCTED | INSTRUCTED |
PAYMENT_SENT | PAID |
PAYMENT_CONFIRMED | PAID |
UNPROCESSABLE | PAID |
FULFILLED | RENEWED |
v1 PAID was ambiguous
In v1, PAID covered three very different situations: payment sent, payment confirmed, and UNPROCESSABLE, a renewal problem recorded at the official-receipt stage. If your integration treated PAID as "all good", split that logic: in v2, UNPROCESSABLE is a terminal problem state (contact support), while FULFILLED is the proven-paid happy end. See Lifecycle.
New instruction body
v1 took a JSON map with dynamic UUID keys. v2 takes an explicit array that is self-describing and cleanly typed in OpenAPI.
v1 (POST /external/instructions/by-renewal-event-ids):
json
{
"9f4b2c1e-6a3d-4e8f-b2a1-0c5d7e9f1a3b": "RENEW",
"1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f": "NOT_THIS_TIME"
}v2 (POST /v2/external/patent-instructions):
json
{
"instructions": [
{ "eventId": "9f4b2c1e-6a3d-4e8f-b2a1-0c5d7e9f1a3b", "decision": "PROCEED" },
{ "eventId": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "decision": "SKIP_THIS_TIME" }
]
}The semantics also tightened. Read Instructions & instructability before rewriting this call:
- Instructions are executed immediately and synchronously (
mode: DIRECT) and are not modifiable via the API afterwards. The 201 response is an instruction receipt. - The instruction window opens on
opensAt, the first day of the month two months before the due month. - Only the chronologically earliest un-instructed event of a patent can be instructed. One batch can span many patents, but only one event per patent per call; a second event of the same patent fails the whole batch.
- Check each event's
instructabilityobject before posting instead of re-implementing these rules.
New error format
v1 errors were ad-hoc JSON ({ "type": …, "message": … } on 422, { "message": …, "issues": … } on 400). v2 returns RFC 9457 application/problem+json on every error, with a stable code you can switch on and a requestId for support. See Errors for the full catalog.
json
{
"type": "/errors/instruction-already-set",
"title": "Instruction already set",
"status": 409,
"detail": "The event already has an executed instruction.",
"instance": "/api/v2/external/patent-instructions",
"code": "instruction_already_set",
"requestId": "6b0c1a2d-3e4f-4a5b-8c7d-9e0f1a2b3c4d"
}Error-handling changes to fold into your migration:
- Re-instructing an event is now
409 Conflict(instruction_already_set). v1 answered this case with a free-text 422 message. instruction_lapsed(422) rejects a decision the event no longer offers, for examplePROCEEDon a lapsed event, where onlySKIP_THIS_TIME/DROPremain.instruction_too_early(422) andinstruction_skipped(422) map the window and ordering rules above.- Expired keys are distinguishable: 401
api_key_expiredvs 401invalid_api_key. See Authentication. - Malformed path params (a non-UUID
patentId, an invalidyearMonth) return a standard problem+jsonvalidation_failed400 with field-levelerrors[]pointers. You never get a raw validation dump. - Every authenticated response carries
RateLimit-Limit/RateLimit-Remaining/RateLimit-Reset; a 429 carriesRetry-Afterinstead.
Migration checklist
- Check your API key and scopes. v2 uses the same
x-api-keyheader; v2 endpoints additionally require scopes (patents:read,patents:instructions:write,invoices:read,exchange-rate-freezes:write). See Authentication. - Inventory your v1 traffic. Log the
Deprecationheader; every request that carries it needs a row from the URL mapping. - Migrate patent reads. Switch to
GET /v2/external/patents/GET /v2/external/patents/{patentId}, and replace every by-reference path lookup with the?providerId=filter. - Migrate the monthly cycle. Replace
/renewal-event/{year}/{month}and/estimationwithGET /v2/external/patent-events?dueMonth=…&include=fees. See The monthly renewal cycle. - Apply the field renames and enum remaps from the tables above to your data mapping layer, including the
NOT_APPLICABLE→nullchange and theprocessingStatussplit. - Rewrite the instruction POST to the
instructions[]body, send v2 decision values, submit at most one event per patent per call, and handle409 instruction_already_setplus the 422 codes. - Migrate document downloads to the
documents/…routes. See Downloading documents. - Migrate invoices and FX freezes:
GET /v2/external/invoices, and the freeze POST becomes an idempotentPUT /v2/external/exchange-rate-freezes/{yearMonth}. - Replace error handling with problem+json
codeswitching. See Errors. - Review your sync jobs against the v2 pagination envelope and polling guidance in Syncing data and Conventions.
- Watch the deprecation headers on any v1 call still in place: when a retirement date is announced, the
Sunsetheader will start carrying it.