Skip to content

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"):

ResourcecreatedAt / updatedAtNotes
PatentYesMay be null on some historical records: treat null as "assume changed".
Patent eventYesAlso carries processingStatusUpdatedAt, the time the execution pipeline last moved.
InvoiceYes
FeesNoComputed live while ESTIMATED; copied from the invoice once INVOICED.
InstructabilityNoComputed live on every read.
Exchange-rate freezeNoKeyed 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_PERIOD events, instructed events not yet FULFILLED).

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&currentPage=$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))
done

At 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 fees object from GET /patents/{patentId}/events/{eventId}/fees while feesStatus is ESTIMATED, and the inline breakdown from include=fees on GET /patent-events. Estimates are recomputed from the current fee matrices and FX rates on every request. Both move without touching any updatedAt: an event can be byte-identical in every persisted field while its estimate has shifted.
  • Instructability: isAllowed, allowedDecisions, blockedReason, opensAt are evaluated against today on every read. An event crosses from TOO_EARLY to instructable, or loses PROCEED when it lapses, purely by the calendar advancing.

The rules that follow:

  1. Never invalidate cached estimates or instructability with updatedAt. They are not versioned by it.
  2. Re-fetch when you need current numbers: display, budgeting, and always immediately before submitting instructions. A stale allowedDecisions can turn into a 422 instruction_lapsed at POST time (see Errors).
  3. INVOICED amounts are stable. Once an event's fees carry feesStatus: "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

CadenceWhat to pullWhy
DailyGET /patent-events for each tracked due month, with include=fees if you display amountsduePeriod, instructability, processingStatus, and estimates all move on a daily scale during the instruction window.
WeeklyFull walk of GET /patents at itemsPerPage=100Bibliographic data changes slowly; a weekly complete snapshot also detects removals.
WeeklyFull walk of GET /invoices, then GET /invoices/{invoiceId} for new or changed rowsInvoices are append-mostly; weekly is usually enough. Fetch related documents as they become available.
On demandGET /patents/{patentId}/events/{eventId}/fees immediately before instructing; GET /patents/{patentId} / GET /patents/{patentId}/events/{eventId} when a user drills into one recordEstimates 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-instructions is the one write with real-world effect: a DIRECT instruction executes immediately and synchronously and cannot be modified afterwards through the API. Two mechanisms make a re-drive safe:

    1. Pre-flight with instructability. Rebuild your candidate set from a fresh event pull and only submit events with instructability.isAllowed: true and your decision in allowedDecisions. Events already handled by the crashed run come back blockedReason: "ALREADY_INSTRUCTED" and drop out of the set. See Instructions and instructability.
    2. 409 instruction_already_set as a success marker. If you submit an already-instructed event anyway (a race, or a stale candidate set), the API rejects it with 409 Conflict, code instruction_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.

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.

Renewr External API v2. Access on invitation.