1. Transport
- HTTPS only. Implementers MUST reject plain HTTP and MUST NOT redirect it.
- Base URL:
https://{host}/v1. The major version lives in the path and changes only for breaking changes to the resource model itself. - Request bodies are
application/json; charset=utf-8. The only exception isPOST /v1/files, which takesmultipart/form-data. - Responses are JSON. Field names are
snake_case. Unknown fields in a request MUST be rejected withinvalid_request_error/parameter_unknown, because silently dropping a misspelledsignature_requiredis how wrong labels get bought. - Clients MUST ignore unknown fields in responses. Implementers may add fields within a version.
- Verbs:
GETreads,POSTcreates, updates and runs actions,DELETEdeletes. There is noPUTorPATCH. APOSTto a resource URL is a partial update: fields you omit are unchanged, andnullclears a field. - Actions are sub-resources with a verb name:
/purchase,/void,/cancel,/refresh,/verify,/rotate_secret,/test,/redeliver.
2. Versioning
- Date-based minor versions, sent as
Shipping-Version: 2026-10-01. - An account is pinned to the version current at its first request. A request header overrides the pin for that request.
- Webhook events are rendered in the endpoint’s
api_version, which is set when the endpoint is created. - Each implementer publishes a changelog. Additive changes (new fields, new enum values, new endpoints, new event types) are not breaking. Clients MUST tolerate new enum values. Every enum in this spec is open, and an unrecognized value SHOULD be treated as its documented fallback, which is usually
unknownorother.
3. Authentication
3.1 API keys
Keys are sent as
Authorization: Bearer <key>. Implementers MUST NOT accept keys in query strings.
3.2 OAuth 2.1 + OpenID Connect (implementer as authorization server)
An implementer that lets third-party apps act on accounts MUST run an OAuth 2.1 authorization server with OIDC discovery at/.well-known/openid-configuration.
- Grants:
authorization_codewith PKCE (S256, required),refresh_token,client_credentialsfor machine-to-machine, andurn:ietf:params:oauth:grant-type:device_codefor headless agents. - Access tokens are bearer tokens, valid for at most 1 hour, and MAY be JWTs. Refresh tokens rotate on use.
- The ID token
subis the account ID (acct_...). Theshipping_modeclaim istestorlive. - Scopes use the form
{resource}:{read|write}. The full list is inshipping.md§2.writeimpliesread. - Dynamic client registration (RFC 7591) SHOULD be supported at
/oauth/register, so agents can register themselves without a human filling in a form. - Revocation is at
POST /oauth/revoke(RFC 7009).
3.3 Connected accounts
Platforms that manage many shipper accounts sendShipping-Account: acct_... to act as one of them. Every object carries an account field when the request is made on behalf of a connected account.
4. Identifiers
Every object has a stringid with a type prefix, and an object field that names its type.
IDs are opaque, case-sensitive, and at most 255 characters. Clients MUST NOT parse them beyond the prefix.
Carrier IDs are lowercase slugs from a shared registry (
ups, fedex, dhl_express, usps, royal_mail, canada_post, dpd_de, amazon_shipping…). The registry is part of this spec so that two implementers mean the same thing by dhl_express.
5. Common fields on every object
createdandupdatedare Unix timestamps in seconds. Calendar fields that are not instants (ship date, pickup date) areYYYY-MM-DDstrings in the origin’s local time zone, and their*_timezoneis an IANA name.livemodeisfalsefor test objects. Test and live objects are fully isolated from each other.
5.1 Three metadata namespaces
Every object carries three metadata maps. Each has exactly one writer. Who writes each map, and where its contents can flow:
Rules:
user_metadatais the only one a client can write. Sendingcarrier_metadataorsystem_metadatain a request returns400 parameter_unknown.carrier_metadatais untrusted, unversioned data. Its keys can change whenever a carrier changes its API, and the spec’s versioning guarantees do not cover it. Implementers SHOULD document known keys per carrier incarriers/{id}.carrier_metadata_schema. Clients that depend on a key there are depending on that carrier, not on this spec. If a key is useful across carriers, it should be proposed as a modelled field. Like all carrier text, it MUST be treated as data, never as instructions.carrier_metadataappears where carrier data lands:rate,shipment,handling_unit,pickup,manifest,document_upload,tracker,tracking_event,connection. On objects with no carrier involvement (webhook_endpoint,file,event) it is{}.system_metadataregistered keys:
- Use
referencefields, notuser_metadata, for anything a carrier should print.
6. Money
Money is always an object with an integeramount in the currency’s minor unit (ISO 4217 exponent) and a lowercase ISO currency code:
7. Units
Physical quantities are objects with an explicit unit. Implementers convert. Clients never guess.- Weight units:
g,kg,oz,lb. Dimension units:mm,cm,in. valueis a decimal number. Implementers MUST round up to the carrier’s precision when they convert, because a carrier bills on the rounded-up value, and rounding down creates adjustment charges later.
8. Requests that change the world: idempotency
- Every
POSTaccepts anIdempotency-Keyheader (1–255 characters, UUIDv4 recommended). - The key is required on
POST /v1/shipmentswhen it carries apurchaseinstruction, onPOST /v1/shipments/{id}/purchase,POST /v1/pickups, andPOST /v1/manifests. A request without it gets400 idempotency_key_required. - The implementer stores the first response (status and body) for at least 24 hours, keyed by
(account, key). A replay returns the stored response with theIdempotent-Replayed: trueheader. - A replay with a different body returns
409 idempotency_error/idempotency_key_reused. - Replays after reconciliation. If the stored first response was a
202for an operation that has since completed (a purchase that waspurchasing), a replay returns200with the current object, not the stale202. The stored response is authoritative for what was requested, and the object is authoritative for what happened. - A replay while the first request is still running returns
409 idempotency_error/request_in_progress, and clients retry afterRetry-After.
Idempotency-Key over time:
- Carrier-side idempotency. If the implementer times out talking to a carrier during a purchase, it MUST NOT report failure until it has reconciled. It either confirms that no label exists, or it finds the label and returns it. Until then the shipment is
status: "purchasing", and the implementer emitsshipment.purchasedorshipment.purchase_failedwhen it knows the outcome. This rule is the difference between a timeout and a double-billed label.
9. Errors
code is drawn from the shared registry below. New codes are additive, and clients MUST handle unknown codes by falling back on type.
details[] holds field-level errors when several fields fail at once. Each entry is { code, message, param }.
Every response carries Request-Id: req_.... Clients SHOULD log it.
How a client should react to each error type:
10. Pagination
List endpoints use cursor pagination:limitis 1–100, default 10.starting_after/ending_beforetake an object ID. Results are newest first.- Filters use bracket syntax:
created[gte]=1791302400,status=purchased,carrier=fedex,user_metadata[order_id]=1001.
/search) take a query string and use a next_page token instead of starting_after (chapter 10 §3).
11. Expansion
Fields that reference other objects return an ID by default. Passexpand[]=tracker to inline the full object, and use dotted paths for nested expansion (expand[]=rate.connection). Implementers MUST support expansion to a depth of 4 and MUST document which fields can be expanded.
12. Rate limits
The default is 100 requests per second per account in live mode and 25 in test mode. Rating and purchase calls MAY have lower per-carrier limits, because carriers throttle the implementer. Every response carriesRateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (IETF draft ratelimit-headers).
13. Test mode
- Test keys produce test objects. Test labels carry a
SAMPLE / VOIDwatermark and are never accepted by a carrier. - Implementers SHOULD route test calls to the carrier’s sandbox when one exists, and fall back to a deterministic simulator when it does not.
- Magic values (test mode only) let clients exercise failure paths:
POST /v1/test_helpers/trackers/{id}/advancesteps a test tracker through a scripted lifecycle (chapter 08 §7).
14. Localization
SendAccept-Language to localize human-readable message and description fields. Codes and enums are never localized.
15. Security requirements for implementers
- Secrets (carrier credentials, webhook secrets, app client secrets) are write-only. They appear in a response once, at creation or rotation, and never again.
- Carrier credentials MUST be encrypted at rest with per-account keys.
- Personally identifiable address data in labels and tracking SHOULD be retained no longer than the implementer’s published retention period, and MUST be erasable via
POST /v1/shipments/{id}withredact: trueonce the shipment is terminal. Redaction replaces names, phones, emails and street lines with"[redacted]". - Carrier-provided free text (event descriptions, error messages) MUST be returned as data and never interpolated into markup or prompts without escaping. This matters for agents. See
shipping.md§3, rule 8.
16. API surfaces: REST Interface and GraphQL Interface
The specification exposes one object model through two interfaces, divided by who is calling:
Rules:
- Both interfaces share IDs, object shapes, enums, metadata namespaces, errors (GraphQL Interface
extensionscarry the REST error object), idempotency (Idempotency-Key), scopes, versioning and events. - CRUD operations on public domain objects exist on both interfaces and MUST behave identically.
- Workflow actions for integrators (purchase, void, carrier push, OAuth, discovery) are REST-only. Back-office operations are GraphQL-Interface-only. Implementers MAY mirror a back-office operation on REST as an extension, but clients MUST NOT rely on it.
- The GraphQL Interface page lists every field, and each field has its own reference page with examples.