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": "1250.00",
"components": [
{ "type": "OFFICE", "amount": "845.00" },
{ "type": "AGENT", "amount": "150.00" },
{ "type": "RENEWR", "amount": "255.00" }
]
}| Field | Type | Description |
|---|---|---|
object | string | Always "fees". |
feesStatus | enum | ESTIMATED 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 while feesStatus is ESTIMATED. |
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. |
The component breakdown
components[] is a typed breakdown in the style of Stripe's fee_details[]:
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.
From estimate to invoice
feesStatus tells you which side of invoicing you are looking at:
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. |
INVOICED | Read from the actual invoice line. | Set | Stable: these are the billed amounts. |
GET /patents/{patentId}/events/{eventId}/fees never returns 404 for a real event: before an invoice exists you get the projected amounts (feesStatus: "ESTIMATED"), 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 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": "1250.00",
"components": [
{ "type": "OFFICE", "amount": "845.00" },
{ "type": "AGENT", "amount": "150.00" },
{ "type": "RENEWR", "amount": "255.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. Note that this endpoint returns a bare JSON array. It does not use 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": "1250.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