Skip to content

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"
HeaderStandardMeaning
Deprecation: @1785542400RFC 9745 (Structured Field Date)The route is deprecated since this Unix timestamp, 2026-08-01T00:00:00Z
SunsetRFC 8594Not sent today. It will start appearing once a retirement date is announced, and it will carry that date
Link: <…>; rel="successor-version"RFC 8288The 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+json with machine-readable code values 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 routev2 successorNotes
GET /external/patentGET /v2/external/patentsList envelope, sort allowlist, ?providerId= filter
GET /external/patent/{id}GET /v2/external/patents/{patentId}Breaking; see below
GET /external/patent/with-renewal-event-and-feesGET /v2/external/patents + GET …/events / GET …/events/{eventId}/feesThe RPC-style route is gone; for a due-window portfolio view use GET /v2/external/patent-events?include=fees
GET /external/patent/{id}/renewal-eventGET /v2/external/patents/{patentId}/eventsReturns 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-invoiceGET /v2/external/patents/{patentId}/events/{eventId}/feesUnified estimated/invoiced fees; no more 404 before invoicing
GET /external/renewal-event/{year}/{month}GET /v2/external/patent-events?dueMonth=YYYY-MMYear and month move from the path into a query filter
GET /external/estimationGET /v2/external/patent-events?…&include=feesEstimated fees inline on each event
POST /external/instructions/by-renewal-event-idsPOST /v2/external/patent-instructionsBody format changed; see below
GET /external/invoiceGET /v2/external/invoices
GET /external/invoice/{id}GET /v2/external/invoices/{invoiceId}
GET /external/frozen-exchange-rates/monthsGET /v2/external/exchange-rate-freezesReturns 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}/invoiceGET …/events/{eventId}/documents/invoicePDF
GET …/renewal-event/{renewalEventId}/instructionReceiptGET …/events/{eventId}/documents/instruction-receiptPDF; no more camelCase URL segments
GET …/renewal-event/{renewalEventId}/officialReceiptGET …/events/{eventId}/documents/official-receiptPDF

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 fieldv2 fieldNotes
clientCaseRefclientCaseReference
clientFamilyRefclientFamilyReference
countryofficeAn authority code (WO, EP, EA, or a national ISO 3166-1 code), which is not always a country; value unchanged
nationalFilingDatenationalPhaseEntryDate
priorityApplicationDatepriorityDate
expirationExtendedDateextendedExpirationDate
lapsedDatelapseDate
initialReferenceDaterenewalsManagedFromBase date of the annuity computation
patentStatusstatusValues unchanged; NOT_APPLICABLE becomes null
applicationTypefilingRouteValues remapped; see enum table
claimsclaimCount

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 fieldv2 fieldNotes
paymentDate, renewalDatedueDateMerged; the two v1 fields always carried the same value
renewalYearNumberrenewal.annuityYear
paymentNumberrenewal.paymentSequenceNumbernull when equal to annuityYear
graceEndDategracePeriodEndDate
patentIdpatent.idGrouped as patent: { id, providerId }, with both identifiers everywhere an event references its patent
executedRenewalInstructionappliedDecisionValues remapped: RENEWPROCEED, etc.
instructionStatusduePeriodIt classifies the deadline. It does not describe an instruction; NEXTUPCOMING
processingStatusprocessingStatusKept, but v2 values are finer-grained; see below
frozenFeesisFxRateFrozenBoolean; says what is actually frozen (the FX rate)
feesStatusfeesStatusESTIMATED_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 typeOFFICE | AGENT | RENEWR | GRACE_PERIOD; amounts are decimal strings. See Fees & FX
dateOfPaymentNoneNot exposed in v2; track progress via processingStatus + processingStatusUpdatedAt
warningStatusNoneNot exposed in v2
referenceDate, monthAfterGracePeriod, comments, feeError, detailedInvoiceNoneRemoved; billed fee details now live on GET …/events/{eventId}/fees and under the invoice's fees[]

Invoice

v1 fieldv2 fieldNotes
invoiceFullReferencereferenceThe only field guaranteed across all origins
invoiceNumbernumberNow a string; null for IMPORTED invoices
invoiceDateissueDate
type (managed / unmanaged / regularization)origin (GENERATED / IMPORTED / REGULARIZATION)
clientIdNoneRemoved; your key already identifies your tenant

Frozen exchange rates

For the events listed under a freeze (GET /v2/external/exchange-rate-freezes/{yearMonth}):

v1 fieldv2 field
referenceclientCaseReference
renewalDatedueDate
priceamount (decimal string)
isPriceFrozenisFxRateFrozen
instructionGivenhasInstruction

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 valuev2 value
RENEWPROCEED
NOT_THIS_TIMESKIP_THIS_TIME
NEVERDROP

Due period (v1 instructionStatus → v2 duePeriod)

v1 valuev2 value
LATELATE
GRACE_PERIODGRACE_PERIOD
CURRENTCURRENT
NEXTUPCOMING
LAPSEDLAPSED

Fees status

v1 valuev2 equivalent
ESTIMATEDfeesStatus: "ESTIMATED"
CONFIRMEDfeesStatus: "INVOICED"; an invoice exists and the amounts are billed, no longer projections
ESTIMATED_FROZEN_FXfeesStatus: "ESTIMATED" + isFxRateFrozen: true; the FX axis is a separate boolean

Filing route (v1 applicationType → v2 filingRoute)

v1 valuev2 value
APPNATIONAL
PCTPCT
EPEP
EAEA
DIVDIVISIONAL

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 valuev1 equivalent
NOT_INSTRUCTEDNOT_INSTRUCTED
INSTRUCTEDINSTRUCTED
PAYMENT_SENTPAID
PAYMENT_CONFIRMEDPAID
UNPROCESSABLEPAID
FULFILLEDRENEWED

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 instructability object 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 example PROCEED on a lapsed event, where only SKIP_THIS_TIME / DROP remain.
  • instruction_too_early (422) and instruction_skipped (422) map the window and ordering rules above.
  • Expired keys are distinguishable: 401 api_key_expired vs 401 invalid_api_key. See Authentication.
  • Malformed path params (a non-UUID patentId, an invalid yearMonth) return a standard problem+json validation_failed 400 with field-level errors[] pointers. You never get a raw validation dump.
  • Every authenticated response carries RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset; a 429 carries Retry-After instead.

Migration checklist

  1. Check your API key and scopes. v2 uses the same x-api-key header; v2 endpoints additionally require scopes (patents:read, patents:instructions:write, invoices:read, exchange-rate-freezes:write). See Authentication.
  2. Inventory your v1 traffic. Log the Deprecation header; every request that carries it needs a row from the URL mapping.
  3. 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.
  4. Migrate the monthly cycle. Replace /renewal-event/{year}/{month} and /estimation with GET /v2/external/patent-events?dueMonth=…&include=fees. See The monthly renewal cycle.
  5. Apply the field renames and enum remaps from the tables above to your data mapping layer, including the NOT_APPLICABLEnull change and the processingStatus split.
  6. Rewrite the instruction POST to the instructions[] body, send v2 decision values, submit at most one event per patent per call, and handle 409 instruction_already_set plus the 422 codes.
  7. Migrate document downloads to the documents/… routes. See Downloading documents.
  8. Migrate invoices and FX freezes: GET /v2/external/invoices, and the freeze POST becomes an idempotent PUT /v2/external/exchange-rate-freezes/{yearMonth}.
  9. Replace error handling with problem+json code switching. See Errors.
  10. Review your sync jobs against the v2 pagination envelope and polling guidance in Syncing data and Conventions.
  11. Watch the deprecation headers on any v1 call still in place: when a retirement date is announced, the Sunset header will start carrying it.

Renewr External API v2. Access on invitation.