Appearance
Keeping data in sync
The External API v2 has no webhooks: Renewr never calls your systems. If you mirror patents, events, or invoices, or drive a docketing tool from Renewr data, you need a scheduled, pull-based sync. This guide describes the timestamps the API gives you, the two pull patterns that work well in practice, and the data you must never cache.
If you are building your first integration, start with Getting started and the monthly renewal cycle. A working monthly cycle already covers most of what a sync needs.
No webhooks: design a scheduled pull
Everything in v2 is read on demand with your API key. There is no push channel, no change feed, and no event subscription. A production integration therefore runs on a scheduler (cron or equivalent) and re-pulls the resources it cares about.
Budget your calls
Each API key has a daily call quota, reset at midnight (server local time). Every authenticated response carries RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers so your scheduler can observe its budget; a 429 carries Retry-After instead. Size your cadences to fit comfortably inside the quota. The patterns below are designed to be cheap. See Conventions for header details and Errors for rate_limit_exceeded handling.
The timestamps you have, and their limits
Persisted resources carry createdAt and updatedAt, as ISO 8601 UTC timestamps (e.g. "2026-07-22T09:15:00.000Z"):
| Resource | createdAt / updatedAt | Notes |
|---|---|---|
| Patent | Yes | May be null on some historical records: treat null as "assume changed". |
| Patent event | Yes | Also carries processingStatusUpdatedAt, the time the execution pipeline last moved. |
| Invoice | Yes | |
| Fees | No | Computed live while ESTIMATED; copied from the invoice once INVOICED. |
| Instructability | No | Computed live on every read. |
| Exchange-rate freeze | No | Keyed by yearMonth. |
Now the honest part:
Server-side delta sync by timestamp is not possible today
No endpoint offers filtering or sorting by updatedAt. The only sortable endpoint is GET /patents, and its sort allowlist is clientCaseReference, office, applicationDate, publicationNumber, status. updatedAt is not on it. You cannot ask the server for "everything changed since T".
updatedAt remains useful client-side: when you mirror data, compare the fetched updatedAt against your stored copy to decide whether a row actually changed and whether to trigger downstream processing. It just cannot reduce how much you fetch.
Use the two patterns below in place of a timestamp delta. Both exploit a property of the domain: the data that changes often is bounded by time windows, and the data that is unbounded changes rarely.
Pattern 1: window-driven refresh for events
Renewal events are naturally scoped by due month, and GET /patent-events filters by exactly that. The state that moves daily (duePeriod, processingStatus, instructability, estimated fees) only matters for the months you are actively working. So there is no need to chase individual changes: re-pull the whole window you track and overwrite your local state:
bash
curl "https://api.renewr.example/api/v2/external/patent-events?dueMonth=2026-09&include=fees&itemsPerPage=100" \
-H "x-api-key: $RENEWR_API_KEY"Repeat per tracked month. A sensible window is:
- the due months whose instruction window is open. The window opens on
opensAt, the first day of the month two months before the due month, so that is the current month through two months ahead (today:2026-07,2026-08,2026-09); - plus any earlier months you still track through grace or execution follow-up (
GRACE_PERIODevents, instructed events not yetFULFILLED).
This is cheap and bounded: a few paginated calls per month tracked, regardless of portfolio size, and each response is a complete, current snapshot of that month. No reconciliation logic is needed. include=fees adds the estimated amounts in the same call (unknown include values are silently ignored). See Events and due periods for what the per-event state means, and the monthly renewal cycle for how this refresh feeds the instruction workflow.
For a single patent, GET /patents/{patentId}/events returns a bare JSON array of all the patent's events (no pagination envelope). One call gives you the complete event chain, which makes per-patent reconciliation trivial.
Pattern 2: periodic full re-list for patents and invoices
Patents and invoices are not window-scoped. They change slowly: bibliographic data moves on office actions, invoices are appended and then essentially immutable. For these, a periodic full walk is simpler and safer than any delta scheme.
Both GET /patents and GET /invoices return the standard list envelope:
json
{
"pagination": {
"totalItems": 1180,
"currentPage": 1,
"itemsPerPage": 100,
"totalPages": 12,
"hasMore": true
},
"data": ["..."]
}Walk it with the maximum page size (itemsPerPage defaults to 10, hard cap 100; larger values are silently clamped) until pagination.hasMore is false:
bash
page=1
while : ; do
response=$(curl -s "https://api.renewr.example/api/v2/external/patents?itemsPerPage=100¤tPage=$page" \
-H "x-api-key: $RENEWR_API_KEY")
# Upsert response.data into your mirror.
# If you store updatedAt, compare it per row to detect real changes
# and skip no-op writes / downstream triggers.
hasMore=$(echo "$response" | jq -r '.pagination.hasMore')
[ "$hasMore" = "true" ] || break
page=$((page + 1))
doneAt 100 items per page, a 5 000-patent portfolio is 50 calls, a rounding error against a daily quota, even weekly. The same loop works verbatim for GET /invoices; a full invoice with its fees[] lines comes from GET /invoices/{invoiceId}, which you only need for invoices that are new or changed in your mirror.
Detecting deletions
A full walk also catches what a delta sync structurally cannot: items that disappeared from your tenant. Any locally mirrored row you did not see during a complete walk is gone upstream, so flag or remove it.
Computed data: never cache on updatedAt
Some of the most-read data in the API is computed live at read time and has no timestamps at all:
- Estimated fees: the
feesobject fromGET /patents/{patentId}/events/{eventId}/feeswhilefeesStatusisESTIMATED, and the inline breakdown frominclude=feesonGET /patent-events. Estimates are recomputed from the current fee matrices and FX rates on every request. Both move without touching anyupdatedAt: an event can be byte-identical in every persisted field while its estimate has shifted. - Instructability:
isAllowed,allowedDecisions,blockedReason,opensAtare evaluated against today on every read. An event crosses fromTOO_EARLYto instructable, or losesPROCEEDwhen it lapses, purely by the calendar advancing.
The rules that follow:
- Never invalidate cached estimates or instructability with
updatedAt. They are not versioned by it. - Re-fetch when you need current numbers: display, budgeting, and always immediately before submitting instructions. A stale
allowedDecisionscan turn into a422 instruction_lapsedat POST time (see Errors). INVOICEDamounts are stable. Once an event's fees carryfeesStatus: "INVOICED", they are copied from the invoice line and safe to store permanently.
If FX drift on estimates is a problem for your budgeting, you can pin the rates for a month with PUT /exchange-rate-freezes/{yearMonth}. Frozen estimates report isFxRateFrozen: true and stop moving with the market. See Fees and FX.
Suggested cadences
| Cadence | What to pull | Why |
|---|---|---|
| Daily | GET /patent-events for each tracked due month, with include=fees if you display amounts | duePeriod, instructability, processingStatus, and estimates all move on a daily scale during the instruction window. |
| Weekly | Full walk of GET /patents at itemsPerPage=100 | Bibliographic data changes slowly; a weekly complete snapshot also detects removals. |
| Weekly | Full walk of GET /invoices, then GET /invoices/{invoiceId} for new or changed rows | Invoices are append-mostly; weekly is usually enough. Fetch related documents as they become available. |
| On demand | GET /patents/{patentId}/events/{eventId}/fees immediately before instructing; GET /patents/{patentId} / GET /patents/{patentId}/events/{eventId} when a user drills into one record | Estimates and instructability must be current at the moment of decision. Values from the last sync may already be stale. |
Tune the daily window to your process: firms that instruct early in the month often refresh more frequently around their instruction date and drop to weekly elsewhere.
Re-running a sync safely
Sync jobs crash, timeouts happen, schedulers fire twice. The API is built so that re-driving a run is safe:
Every read is side-effect free. Re-pulling a month, re-walking a list, or re-fetching a fees object any number of times changes nothing. If a walk dies at page 7, restart it from page 1. The extra calls only cost quota.
Freezing a month is idempotent.
PUT /exchange-rate-freezes/{yearMonth}only captures rates not already frozen for that month; a retry never re-freezes at new rates.Instruction submission is guarded twice.
POST /patent-instructionsis the one write with real-world effect: aDIRECTinstruction executes immediately and synchronously and cannot be modified afterwards through the API. Two mechanisms make a re-drive safe:- Pre-flight with instructability. Rebuild your candidate set from a fresh event pull and only submit events with
instructability.isAllowed: trueand your decision inallowedDecisions. Events already handled by the crashed run come backblockedReason: "ALREADY_INSTRUCTED"and drop out of the set. See Instructions and instructability. 409 instruction_already_setas a success marker. If you submit an already-instructed event anyway (a race, or a stale candidate set), the API rejects it with409 Conflict, codeinstruction_already_set. The decision from the earlier run stands, and nothing is duplicated. In a re-drive, treat this response as "already done" rather than counting it as a failure.
Remember the batch shape: one POST can span many patents, but only the chronologically earliest un-instructed event of each patent is accepted, meaning one event per patent per call. A re-drive loop that submits per patent, in due-date order, converges naturally.
- Pre-flight with instructability. Rebuild your candidate set from a fresh event pull and only submit events with
The safe mental model
Reads are cheap and repeatable; the single consequential write is protected by a fresh-read pre-flight and a conflict response. A sync that always re-reads before it writes, and treats instruction_already_set as confirmation, cannot double-execute an instruction no matter how many times it restarts.
Related pages
- Monthly renewal cycle: the workflow your daily window refresh feeds
- Conventions: pagination envelope, timestamps, rate-limit headers
- Errors: problem+json envelope and the stable error codes
- Instructions and instructability: the rules behind the pre-flight
- Fees and FX: why estimates move and how freezing works
- Downloading documents: fetching receipts and invoice PDFs as they appear