USPTO ODP: shipping against an evolving API

Share
USPTO ODP: shipping against an evolving API

Among patent offices, the USPTO runs one of the most ambitious open-data programs out there. The Open Data Portal (ODP) consolidates what used to be a scatter of legacy hosts into a single, key-authenticated, documented surface: patents, assignments, continuity, office actions, PTAB, bulk datasets. There is a real OpenAPI spec, real release notes, and a team that ships against them. For anyone who remembers the old Developer Hub, ODP is a big step up.

I build uspto-odp, a Go client for the portal, and the Patent Connector that consumes it. Working that closely against an API surfaces the rough edges before most users hit them, and I report what I find back to the office. Several of the things below are already being addressed.

This post is two things: a deep look at a few migrations and quirks that will save other ODP builders time, and a short, constructive wish list of where an already strong API could get even stronger. The biggest story, the Office Action migration, has a satisfying arc: announced, a few weeks in flight, and now solidly real on ODP.

Portal version vs. spec version

ODP bumps its portal version (3.0, 3.5, 3.6, 4.0) whenever anything ships under the data.uspto.gov umbrella: a new bulk dataset, a UI refresh, a new API surface. That is a reasonable way to version a whole platform. The subtlety for an API consumer is that the underlying OpenAPI specs are versioned independently, and most of them are still 1.0.x. So "compatible with ODP 4.0" is only half the picture; the half that binds your client is the per-spec info.version.

The library README now carries a table mapping each spec file to its version and each portal release to what actually changed for API consumers, so a "4.0" headline translates into the handful of endpoints a client author needs to care about. Surfacing the per-spec version next to the portal version in the release notes would make this trivial for everyone, and it is a small change.

The Office Action migration: announced, in flight, then real


ODP 3.5 release notes (2026-03-24):

The legacy Office Action APIs from Developer Hub have been migrated to
ODP.

The swagger duly published endpoints like:

https://api.uspto.gov/api/v1/patent/oa/oa_actions/v1/fields
https://api.uspto.gov/api/v1/patent/oa/oa_rejections/v2/records

For a few weeks the new routes returned a CloudFront HTML 404 while the data still served from the legacy Developer Hub:

https://developer.uspto.gov/ds-api/oa_actions/v1/fields
https://developer.uspto.gov/ds-api/oa_rejections/v2/records

A migration of this size landing in stages is normal; the useful skill is telling where in the rollout you are. The tell is the shape of the 404. A real application 404 from ODP is JSON:

{"code": "404", "message": "Not Found",
 "detailedMessage": "No matching records found...",
 "requestIdentifier": "be7db161-..."}

The OA routes at api.uspto.gov instead returned an HTML page reading 404 - Not Found: the CloudFront gateway saying the route is not mapped yet, not the application saying it found nothing. Those are two different states, and reading them apart let me ship cleanly through the transition.

So I built a stopgap designed to flip with one line on the day the migration completed. A Config.OABaseURL pointed at the working legacy host, plus a path rewrite in cmd/gen that turned /api/v1/patent/oa/<api>/<v>/<x> into /ds-api/<api>/<v>/<x> at bundle time. The client was generated from the real ODP spec but talked to the host that served data, with the switch staged and ready.

That day came on 2026-05-29. The USPTO retired the legacy Developer Hub and the two endpoints traded places:

legacy  developer.uspto.gov/ds-api/oa_actions/v1/fields -> HTTP 503 (text/html)
new ODP api.uspto.gov/api/v1/patent/oa/oa_actions/v1/fields -> HTTP 403 (application/json) {"message":"Forbidden"}

The 403 is the good news: the route is mapped and serving, it just wants an API key now. The same CloudFront-versus-application distinction that made the gap diagnosable made the completion obvious at a glance.

The fix was the one-line flip the stopgap was built for: set the default base URL to https://api.uspto.gov and drop the path rewrite, so the generated client uses the real /api/v1/patent/oa/... paths. One genuine improvement came with the move: the ODP Office Action endpoints require an API key where the legacy host did not, which is the right call for a governed data surface. The client already sent X-API-Key, so for keyed callers the switch was transparent. Live against ODP with a key, all four datasets are back and consolidated under one authenticated host: text retrieval (~19.3M records), citations (~134M), rejections, and enriched citations.

Trademark Decisions: the next one I would love to see as a real spec


ODP 3.3 release notes (2026-03-04):

Trademark (TM) Decisions & Proceedings are now available via the Open
Data Portal.

The data is genuinely there. Inspecting the portal's TM decisions search page reveals the endpoint behind it:

POST https://data.uspto.gov/ui/trademark/tm-decisions-api/decisionsProceedings

The URL contains /ui/, and it authenticates with an aws-waf-token cookie plus same-origin Origin and Referer headers rather than the documented X-API-Key flow. It powers the portal's own JavaScript app, which is exactly what it was built for. What is missing is a published OpenAPI spec and the keyed X-API-Key path that the rest of ODP offers, so a third-party client can bind to it the same way.

So the library defers TM Decisions until that spec lands. This is high on my wish list precisely because the hard part, the data and a working service, is already done; promoting it to a first-class documented API would be a small step with real payoff.

PCT numbers: a change done right

ODP 3.6 (2026-04-10) standardized US PCT numbers, and this one is a model of how to ship a format change. The API uses the 15-character form PCTUS2025058371 (no slashes); the UI displays the 17-character form PCT/US2025/058371; and legacy 12-character entries like PCTUS0719317 are still accepted by the application-number path parameter. Clear, documented, backward compatible.

The library handles all three forms in NormalizePatentNumber, exposes a FormatAsPCT() helper for display, and accepts PCT input through GetPatent and ResolvePatentNumber without an extra search round-trip, because USPTO accepts PCT numbers directly as the path parameter.

The 3.6 assignment payload change

Also in 3.6: the patent assignment response moved the location from countryOrStateCode to geographicRegionCode. The field stayed in the OpenAPI spec; it just stopped being populated on new records. A short note in the release that the field had moved would have made this a non-event, which is the kind of small consistency improvement that pays off across every consumer.

The library reads geographicRegionCode first and falls back to countryOrStateCode for older records, so consumers see consistent data across the transition without needing to know which side of the rollout a record was created on.

What the library shipped

Beyond the Office Action move to ODP, the changes worth knowing across
these releases:

  • PCT number normalize, resolve and format across all three
    USPTO-accepted forms (PCTUS2025058371PCT/US2025/058371, and legacy
    PCTUS0719317), with PCT input accepted directly by GetPatent and
    ResolvePatentNumber.
  • A sturdier retry policy: a MaxRetryAfter cap, so an over-long
    Retry-After surfaces as a non-retryable error the caller can decide on
    instead of the client truncating silently, and Retry-After: 0 honored
    as retry-immediately.
  • Typed responses where the API distinguishes "absent" from zero
    (metadata, foreign priority, and assignments split into assignors and
    assignees rather than a comma-joined string), and config durations as
    time.Duration rather than bare seconds.
  • A reflection-plus-AST coverage test that fails the build if a generated
    endpoint is left unwrapped, so a future spec regeneration cannot
    silently add a route nobody calls.

No other API surface changed; consumers update by bumping the dependency,
with no code changes unless they had pinned OABaseURL to the old host.

A short wish list

None of these are blockers. ODP is already one of the strongest patent-office APIs to build against. These are the few changes that would take it from strong to excellent, and I am looking forward to seeing them land the way earlier improvements I reported already have:

  1. Surface the per-spec version alongside the portal version. The
    portal number tells you the platform moved; the per-spec info.version
    tells you whether your endpoints did. Showing both in the release
    notes would remove all the guesswork.
  2. Note staged rollouts in the release notes. A migration landing over
    a few weeks is fine; a one-line "rolling out through [date]" turns a
    confusing 404 into an expected one.
  3. Keep CloudFront and application errors distinct, and document the
    difference.
     The JSON-404-versus-HTML-404 distinction is genuinely
    useful once you know it. Calling it out would help every new consumer.
  4. Promote the portal-only surfaces to documented APIs. TM Decisions
    already works behind the UI; a published spec and an X-API-Key path
    would make it a first-class API like the rest.
  5. Flag field moves in the payload. When a value relocates (like the
    assignment location field in 3.6), a single release-note line saves
    every downstream client a debugging session.

I will keep reporting what I find, and it is a pleasure to build on a platform where the team acts on it.

Source: github.com/patent-dev/uspto-odp