@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_dateandrenewal_due_dateare office-published only.expiry_date_basisandrenewal_due_date_basisare removed.last_renewal_dateis 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_dateon aPOST /v1/deadlines/computeitem works the same way (it used to be read for Canada only).- A paid cycle is not a missed one either.
urgency=missedonGET /v1/deadlinesand portfolio deadlines, andinclude_missed: trueonPOST /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_dateis 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_dateis the expiry WIPO published on its designations. Known limitation: the search index does not hold it on the IR’s own record yet, soexpiry_date_*does not match the IR’s own record andsort=expiry_dateorders 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_office, requires_office_date,
publication_date_kind_mismatch and stage_not_opposable.
Identity, office record and grain
Coverage territories:
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
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
OnGET /v1/proceedings/{id}, trademark.serial_number and
trademarks[].serial_number are application_number.
Event types
GET /v1/event-types:
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
InGET /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
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’sresult_window_reachedwarning; counts alone never throw.
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, then304s resume. screening_idvalues change once.
Webhooks and the events feed
Webhook deliveries andGET /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:
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 returnundefined. 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
- Read expiry as
expiry_date ?? derived.expiry_date, and deadlines fromderived.deadlines.items. - Rename the removed filters, and batch
officetooffice_code. - Treat
owners[].entity_idas nullable, and useowner_idfor unlinked owners. - Restart any paginated walk that spans the release.
- Branch webhook handlers on
payload_version. - Upgrade to
@signa-so/sdk0.14.0 (TypeScript SDK).