Appearance
Fees & exchange rates
Every renewal has a cost, and that cost has a life of its own: it starts as a projection, moves with exchange rates, and eventually lands on an invoice. This page explains the single fees object Renewr uses to represent that cost everywhere, how it evolves from estimate to invoice, and how exchange-rate freezes let you pin the numbers ahead of time.
One fees object, everywhere
The API uses exactly one shape for renewal costs, wherever they appear:
- Under a patent event, via
GET /patents/{patentId}/events/{eventId}/feesor inline withinclude=feesonGET /patent-events. - As the line items of an invoice, in the
fees[]array returned byGET /invoices/{invoiceId}.
Same fields, same semantics, on both sides of invoicing. A typical estimate for an EP patent's fifth annuity:
json
{
"object": "fees",
"feesStatus": "ESTIMATED",
"isFxRateFrozen": false,
"invoiceId": null,
"eventId": "8f4c9a2e-1d3b-4c5a-8e7f-2a6b9c0d1e42",
"patent": {
"id": "3b8e1c9a-7f2d-4e6b-9a1c-5d8f2e7b4a10",
"providerId": "ACME-EP-0042"
},
"currency": "EUR",
"total": "1020.00",
"components": [
{ "type": "OFFICE", "amount": "845.00" },
{ "type": "AGENT", "amount": "150.00" },
{ "type": "RENEWR", "amount": "25.00" }
]
}| Field | Type | Description |
|---|---|---|
object | string | Always "fees". |
feesStatus | enum | ESTIMATED, LOCKED or INVOICED; see the lifecycle below. |
isFxRateFrozen | boolean | true when the exchange rates behind these amounts are frozen. |
invoiceId | UUID, nullable | Back-reference to the invoice these fees belong to. null until feesStatus is INVOICED. |
eventId | UUID | The patent event these fees belong to; use it to trace each fee line back to its event. |
patent | object | The patent, identified both ways: id (Renewr UUID) and providerId (your own matching reference, null if you never set one). |
currency | string | ISO 4217 code of the currency you are invoiced in. All amounts in the object are in this currency. |
total | string | Total of all components, as a decimal string. |
components | array | Per-type breakdown; each entry is { type, amount }, described below. |
regularizedInvoiceId | UUID, nullable | Lines of a REGULARIZATION invoice only: the original invoice whose line is corrected. null on every other line, and on a corrected line whose original fee line was never invoiced. See Regularization invoices. |
regularizationReason | enum, nullable | Lines of a REGULARIZATION invoice only: NOT_PAYABLE, ANNUITY_ERROR or ACCOUNTING_ERROR. null on every other line. |
The component breakdown
components[] is a typed breakdown:
type | Covers | Present when |
|---|---|---|
OFFICE | The official maintenance fee charged by the patent office. | The office charges one, which is essentially always. |
AGENT | The local agent's fee for filing the payment with the office. | A local agent is involved in that jurisdiction. |
RENEWR | Renewr's service fee. | Always. |
GRACE_PERIOD | The statutory late surcharge the office charges after the due date. | Only when the payment falls inside the grace period. |
Three rules to build against:
- A component that does not apply is absent from the array. It is never present as
null. NoAGENTentry means no agent fee, full stop. - Trust
total, do not re-sum. The components do sum tototal, buttotalis the authoritative figure; treat the breakdown as explanatory detail. - Amounts are decimal strings. They are never floats. They are expressed in the invoice
currencyand scaled per currency: 2 decimals for EUR or USD ("1250.00"), 0 for JPY ("185000"). See Conventions for the full monetary format rules.
The breakdown is extensible
New component types may be added over time without notice. Sum-agnostic code that iterates components[] and ignores unknown type values will keep working; code with an exhaustive switch that throws on unknown types will not.
Regularization invoices
A REGULARIZATION invoice (origin: "REGULARIZATION" on GET /invoices) corrects fee lines that were invoiced earlier. Its fees[] uses the very same shape, with three differences:
- Amounts are signed. A credit is negative: a
NOT_PAYABLEline cancelling an office fee of 350 reads{ "type": "OFFICE", "amount": "-350.00" }, and itstotal(still the sum of the components) is negative too. The invoice totals follow the same sign. - A component left untouched is absent. Only the fee types actually corrected appear: an
ANNUITY_ERRORline that only corrects the office fee carries a singleOFFICEcomponent. - Two extra back-references,
nulleverywhere else.regularizedInvoiceIdis the original invoice whose line is corrected (nullwhen that original fee line was never invoiced), andregularizationReasonsays why:NOT_PAYABLE(the original fees are credited back),ANNUITY_ERROR(the office and grace fees are corrected) orACCOUNTING_ERROR(the invoiced amounts are corrected).
feesStatus is INVOICED (the regularization invoice exists) and isFxRateFrozen is false: a regularization re-bills fixed amounts, no live exchange rate stands behind them. eventId and patent point to the same event and patent as the original line, so a credit reconciles against your records exactly like the line it corrects.
From estimate to invoice
feesStatus tells you where the amounts stand in their lifecycle:
feesStatus | Source of the amounts | invoiceId | Stability |
|---|---|---|---|
ESTIMATED | Computed live from Renewr's fee matrices and current exchange rates. | null | Volatile: moves with FX rates and fee-schedule updates, without notice. |
LOCKED | The fee line fixed when your PROCEED instruction was executed. | null | Stable: these are the amounts you will be invoiced; the invoice itself is not issued yet. |
INVOICED | Read from the actual invoice line. | Set | Stable: these are the billed amounts. |
The sequence is ESTIMATED → LOCKED → INVOICED. LOCKED starts the moment Renewr executes your PROCEED instruction (see instructions and instructability). INVOICED starts when Renewr issues the invoice, a billing step that is independent of the payment pipeline tracked by processingStatus: it usually happens before the payment is sent, but an event can be PAYMENT_SENT or even FULFILLED while its fees are still LOCKED. INVOICED strictly means that invoiceId is set.
GET /patents/{patentId}/events/{eventId}/fees never returns 404 for a real event: before you instruct you get the projected amounts (feesStatus: "ESTIMATED"), once your PROCEED instruction is executed the fixed amounts (feesStatus: "LOCKED"), and once the event is invoiced the same call returns the billed amounts (feesStatus: "INVOICED"). Same shape, same URL, no special casing needed.
bash
curl "https://api.renewr.example/api/v2/external/patents/3b8e1c9a-7f2d-4e6b-9a1c-5d8f2e7b4a10/events/8f4c9a2e-1d3b-4c5a-8e7f-2a6b9c0d1e42/fees" \
-H "x-api-key: $RENEWR_API_KEY"Estimates are computed data: do not cache them on updatedAt
An ESTIMATED fees object can change without any updatedAt bump on the event, because FX rates and fee matrices move independently of the event record. Re-fetch estimates whenever you need fresh numbers. See Syncing data.
For quick scanning, feesStatus and isFxRateFrozen are also summarized directly on the patent event itself (returned by GET /patents/{patentId}/events, GET /patents/{patentId}/events/{eventId} and GET /patent-events), so you can spot locked, invoiced or frozen events in a list without fetching each fees object. Add include=fees on GET /patent-events to get the full breakdown inline (unknown include values are silently ignored).
Reconciling invoices
An invoice aggregates renewals across many patents and events into one document. Its fees[] array reuses the exact fees shape above, with one entry per invoice line, and every line carries its eventId and patent { id, providerId } back-references:
json
{
"object": "invoice",
"id": "b2a7d9c4-5e1f-4a3b-8c6d-9e0f1a2b3c4d",
"reference": "INV-2026-0187",
"currency": "EUR",
"fees": [
{
"object": "fees",
"feesStatus": "INVOICED",
"isFxRateFrozen": true,
"invoiceId": "b2a7d9c4-5e1f-4a3b-8c6d-9e0f1a2b3c4d",
"eventId": "8f4c9a2e-1d3b-4c5a-8e7f-2a6b9c0d1e42",
"patent": {
"id": "3b8e1c9a-7f2d-4e6b-9a1c-5d8f2e7b4a10",
"providerId": "ACME-EP-0042"
},
"currency": "EUR",
"total": "1020.00",
"components": [
{ "type": "OFFICE", "amount": "845.00" },
{ "type": "AGENT", "amount": "150.00" },
{ "type": "RENEWR", "amount": "25.00" }
]
}
]
}One call to GET /invoices/{invoiceId} is therefore self-sufficient for reconciliation: you can post every line against your own docketing system by providerId (or by Renewr id) without N follow-up lookups. List invoices with GET /invoices; the per-event invoice PDF is available via GET /patents/{patentId}/events/{eventId}/documents/invoice, as covered in Downloading documents.
Exchange-rate freezes
The problem
Renewal fees are paid to offices and agents in dozens of local currencies (JPY in Japan, USD at the USPTO, KRW in Korea), but you are invoiced in one currency. Every ESTIMATED fees object therefore embeds currency conversion, and as long as the rates are live, the estimate moves with the FX market. For budgeting a renewal campaign, that is exactly what you do not want.
The solution: freeze a month
PUT /exchange-rate-freezes/{yearMonth} captures the current exchange rates and pins them, for your account, for every event due in that month:
bash
curl -X PUT "https://api.renewr.example/api/v2/external/exchange-rate-freezes/2026-09" \
-H "x-api-key: $RENEWR_API_KEY"From that moment on, estimates for events with a dueDate in 2026-09 are computed with the frozen rates instead of live ones. Those fees report isFxRateFrozen: true, and, as far as currency conversion is concerned, the price you see is the price you will be invoiced. A frozen estimate no longer drifts with the FX market; only a genuine change in an office's fee schedule could still move it.
PUT is idempotent, so retry freely
Re-freezing an already-frozen month never re-captures rates: currency pairs that are already frozen for that month keep their original rates. A network timeout followed by a retry can never silently re-price your month, so PUT is always safe to repeat.
A natural pattern is to freeze the month at the start of each monthly renewal cycle, before pulling estimates and collecting decisions.
Reading freezes
GET /exchange-rate-freezes lists your frozen months. This endpoint returns a bare JSON array, not the usual list envelope:
json
[
{ "object": "exchangeRateFreeze", "yearMonth": "2026-08" },
{ "object": "exchangeRateFreeze", "yearMonth": "2026-09" }
]GET /exchange-rate-freezes/{yearMonth} returns one month with the renewal events affected by its freeze:
json
{
"object": "exchangeRateFreeze",
"yearMonth": "2026-09",
"events": [
{
"object": "frozenRateEvent",
"clientCaseReference": "ACME-EP-0042",
"dueDate": "2026-09-30",
"amount": "1020.00",
"currency": "EUR",
"isFxRateFrozen": true,
"hasInstruction": false
}
]
}Guards
| Condition | Result |
|---|---|
| The freeze capability is not enabled for your account | 403 with code forbidden. Freezing is a contractual option; contact Renewr to enable it. |
| The month is in the past | 400. You cannot freeze retroactively. |
| The month is further ahead than your contractual freeze horizon | 400. Each account has a limit on how far into the future it may freeze. |
All three exchange-rate-freeze endpoints, including the reads, require the exchange-rate-freezes:write API scope. See Errors for the problem-detail format these failures use.
Related pages
- Events & due periods: when the
GRACE_PERIODsurcharge kicks in - Instructions & instructability: deciding on the events these fees price
- The patent lifecycle: where estimation and invoicing sit in the pipeline
- Conventions: decimal strings, currency scales, list envelopes
- Glossary: annuity, grace period, office, and friends