Skip to main content
The provenance release shipped on September 27, 2026, in one deploy. It is a breaking release: update your integration and @signa-so/sdk 0.14.0 together. Webhook deliveries move to payload_version 2 the same day, a few hours after the API change (details). The changelog entry lists every change. This guide walks through them with before and after examples.

The idea in one paragraph

Every value now says who produced it. Top-level fields are the office’s own values, null when the office published none. Everything Signa computes lives under derived. The office’s verbatim codes sit in raw {code, label}, and status.basis says how a status was reached. A search hit, a list row, a portfolio row and a screening hit are the same row, and the detail extends it.

The five changes most integrations need

Also: screening_id values change once, ETags change once (one 200 per cached validator), and webhook handlers must accept payload_version: 2.

Trademark rows

These changes apply everywhere a trademark row appears: GET and POST /v1/trademarks, GET /v1/trademarks/{id}, POST /v1/trademarks/batch, the owner, attorney, firm and entity trademark lists, GET /v1/portfolios/{id} rows, POST /v1/watches/preview, image search, and screening hits (trademark).

Status

Dates

  • expiry_date and renewal_due_date are office-published only.
  • expiry_date_basis and renewal_due_date_basis are removed.
  • last_renewal_date is new: the last renewal the office recorded (USPTO, CIPO and OEPM today). In every jurisdiction the rulebook treats the renewal cycle it paid, and every earlier cycle, as paid, so an early-renewed registration no longer lists the paid cycle as due. last_renewal_date on a POST /v1/deadlines/compute item works the same way (it used to be read for Canada only).
  • A paid cycle is not a missed one either. urgency=missed on GET /v1/deadlines and portfolio deadlines, and include_missed: true on POST /v1/deadlines/compute, no longer list a cycle before the office-stated expiry, the cycle a recorded renewal paid, or the restoration window such a cycle would open.
  • The expiry to show is expiry_date ?? derived.expiry_date. derived.expiry_date is the term end before any weekend or holiday roll, so the filing deadline (due_date) can fall a day or two later.
  • An IR’s expiry_date is the expiry WIPO published on its designations. Known limitation: the search index does not hold it on the IR’s own record yet, so expiry_date_* does not match the IR’s own record and sort=expiry_date orders it with rows that have no office expiry. The next index rebuild fixes this.

The derived block

derived is now on every row, search hits included (it used to be on the detail only).
occurrence_key identifies one deadline occurrence within one trademark across reads: rule_id:<cycle>, or rule_id:t<term>-<n> when the record has no statutory anchor date or the row is anchored on an office-stated expiry. A one-off rule is rule_id:1. derived.opposition_window is one of two shapes:
Unsupported reasons are unsupported_office, requires_office_date, publication_date_kind_mismatch and stage_not_opposable.

Identity, office record and grain

Coverage territories:
The IR’s own WO record is no longer listed as a territory, so territory_count counts designations. IR family status is {primary: "mixed", reason: null, basis: "computed", raw: null} (it was reason: "mixed", source: "computed").

Owners, attorneys, addresses, register, classes and design codes

Search and filters

With the second call, USPTO records return nothing for the renewal filter, because the USPTO publishes no renewal due date. For computed deadlines, use GET /v1/deadlines?portfolio_id=...&due_before=2027-01-01. For incremental sync with sort=family_updated_at, see Pagination.

Other endpoints

Batch

POST /v1/trademarks/batch identifiers name their office with office_code:
office is now a 400. data answers every submitted item in input order, and a repeated id repeats. not_found echoes unmatched items in input order.

Trademark events

GET /v1/trademarks/{id}/events:
description is null unless it adds office text to raw.label. The organization feed GET /v1/events keeps object: "event" and evt_ ids. USPTO event types change for two groups of entries. Stored history is retyped after the release, with no webhook or alert:

Entities, owners, attorneys and firms

Proceedings

On GET /v1/proceedings/{id}, trademark.serial_number and trademarks[].serial_number are application_number.

Event types

GET /v1/event-types:
Rows carry raw {code, label} like a trademark event. The label follows the verbatim-label rule, so WIPO rows have label: null. The filter is office_code (as on GET /v1/status-codes); office and any other unknown query parameter are a 400.

Assignments

GET /v1/assignments, GET /v1/assignments/{id} and GET /v1/trademarks/{id}/assignments:

Deadlines, compute and reconcile

Calendar feed

In GET /v1/portfolios/{id}/deadlines.ics, the event UID <trademark uuid>-<type>-<due date>@signa.so becomes tm_<id>:<occurrence_key>@signa.so. A file downloaded after the release adds every event again beside the ones imported earlier, so delete those once. After that, re-importing updates an event whose due date was corrected instead of duplicating it.

Offices, citations, goods and services, analytics, screening and ETags

New: status codes

List Status Codes (GET /v1/status-codes?office_code=US) lists each office status code with its verbatim label and the normalised status it maps to. Unknown query parameters are a 400. raw.label is null when we hold no verbatim office label for the code (for example USPTO codes today). It is also available as the MCP tool list_status_codes and as references.statusCodes() in the SDK.

Watches

A renewal WIPO publishes on the designations moves the IR’s expiry_date without raising a trademark.updated event or alert for the IR’s own id. See Watches.

TypeScript SDK 0.13 to 0.14

Upgrade to @signa-so/sdk 0.14.0. Its types follow every change above. New exports include TrademarkSummary (the shared row type), TrademarkEvent, Address, StatusCode and SignaTruncatedError. TrademarkSearchResult still compiles as a deprecated alias of TrademarkSummary.

Expiry, deadlines and status

Filters, batch, event types and search meta

The old filter names are not translated. On GET the API answers them with a 400 naming the replacement, and list()’s POST fallback throws a SignaError that names it. Party types are now the generated API types, so owner.stats?.latest_filing is owner.stats?.latest_filing_date. Fields the API stopped serving are gone from the types: entity_id_type, related_entities, attorney email and phone, and summary registration_rate, grant_rate and latest_filing.

toArray() never truncates silently

toArray() throws SignaTruncatedError for one of two reasons:
  • client_limit: you passed no limit and the list holds more than 10,000 items.
  • server_window: the API ended a relevance-ranked or sorted search at its 10,000-row window (has_more: false) while more rows match. The signal is the last page’s result_window_reached warning; counts alone never throw.
A limit you pass that is reached first returns normally. for await and getNextPage() never throw; they stop where the API stops.

Webhook handlers

MCP

At the cutover

  • Every trademark search cursor minted before the release returns 400 cursor_invalid. Restart the walk without a cursor.
  • Every cached ETag gets one 200, then 304s resume.
  • screening_id values change once.

Webhooks and the events feed

Webhook deliveries and GET /v1/events switch from payload_version: 1 to 2 on release day, a few hours after the API change. From then on, every event keeps the version it was stored with: automatic retries, /redeliver and GET /v1/events/{id} of a v1 event stay v1 for the 30-day replay window. Handle both. See Webhooks. office_action.issued:
Change field names and values in v2 (trademark.status_changed changed_fields and changes, alert.created alert.event.diff[], and REST alerts once v2 is emitted): Changes recorded before the release carry no basis or code context, so those fields are null or omitted in v2. A format fix applies to both versions. On events emitted from the release on, data.occurred_at is ISO 8601 UTC with milliseconds (2026-09-26T22:23:21.191Z), as GET /v1/events serves it. It used to carry database timestamp text (2026-09-26 22:23:21.191924+00), which strict ISO parsers rejected. Retries and redeliveries of events emitted before the release keep the old text.

Old-client compatibility

A client built for the old shape keeps working only where it ignores the changed fields. Reads of removed keys return undefined. Requests using removed filter names or batch office fail with 400. Long paginations fail once with 400 cursor_invalid. Entity lookups by ent_<owner-uuid> fail with 404. Nothing is silently widened: a removed filter is rejected, never ignored.

Upgrade checklist

  1. Read expiry as expiry_date ?? derived.expiry_date, and deadlines from derived.deadlines.items.
  2. Rename the removed filters, and batch office to office_code.
  3. Treat owners[].entity_id as nullable, and use owner_id for unlinked owners.
  4. Restart any paginated walk that spans the release.
  5. Branch webhook handlers on payload_version.
  6. Upgrade to @signa-so/sdk 0.14.0 (TypeScript SDK).