Every endpoint in this spec follows the rules in this chapter. Resource chapters only describe where they differ. The checks a request passes through, in a typical order, with the section that governs each one: A complete request and response, showing the headers this chapter defines:

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 is POST /v1/files, which takes multipart/form-data.
  • Responses are JSON. Field names are snake_case. Unknown fields in a request MUST be rejected with invalid_request_error / parameter_unknown, because silently dropping a misspelled signature_required is how wrong labels get bought.
  • Clients MUST ignore unknown fields in responses. Implementers may add fields within a version.
  • Verbs: GET reads, POST creates, updates and runs actions, DELETE deletes. There is no PUT or PATCH. A POST to a resource URL is a partial update: fields you omit are unchanged, and null clears 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 unknown or other.

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_code with PKCE (S256, required), refresh_token, client_credentials for machine-to-machine, and urn:ietf:params:oauth:grant-type:device_code for 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 sub is the account ID (acct_...). The shipping_mode claim is test or live.
  • Scopes use the form {resource}:{read|write}. The full list is in shipping.md §2. write implies read.
  • 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).
This is separate from carrier apps (chapter 09). There, the implementer is the OAuth client of a carrier.

3.3 Connected accounts

Platforms that manage many shipper accounts send Shipping-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 string id 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

  • created and updated are Unix timestamps in seconds. Calendar fields that are not instants (ship date, pickup date) are YYYY-MM-DD strings in the origin’s local time zone, and their *_timezone is an IANA name.
  • livemode is false for 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_metadata is the only one a client can write. Sending carrier_metadata or system_metadata in a request returns 400 parameter_unknown.
  • carrier_metadata is 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 in carriers/{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_metadata appears 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_metadata registered keys:
  • Use reference fields, not user_metadata, for anything a carrier should print.

6. Money

Money is always an object with an integer amount in the currency’s minor unit (ISO 4217 exponent) and a lowercase ISO currency code:
JPY has exponent 0, so 1299 means ¥1299. KWD has exponent 3, so 1299 means KWD 1.299. Floats are never used for money.

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.
  • value is 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 POST accepts an Idempotency-Key header (1–255 characters, UUIDv4 recommended).
  • The key is required on POST /v1/shipments when it carries a purchase instruction, on POST /v1/shipments/{id}/purchase, POST /v1/pickups, and POST /v1/manifests. A request without it gets 400 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 the Idempotent-Replayed: true header.
  • A replay with a different body returns 409 idempotency_error / idempotency_key_reused.
  • Replays after reconciliation. If the stored first response was a 202 for an operation that has since completed (a purchase that was purchasing), a replay returns 200 with the current object, not the stale 202. 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 after Retry-After.
How the implementer answers the same 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 emits shipment.purchased or shipment.purchase_failed when it knows the outcome. This rule is the difference between a timeout and a double-billed label.
A purchase where the carrier times out, and the implementer reconciles before reporting an outcome:

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:
  • limit is 1–100, default 10.
  • starting_after / ending_before take an object ID. Results are newest first.
  • Filters use bracket syntax: created[gte]=1791302400, status=purchased, carrier=fedex, user_metadata[order_id]=1001.
Search endpoints (/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. Pass expand[]=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 carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (IETF draft ratelimit-headers).

13. Test mode

  • Test keys produce test objects. Test labels carry a SAMPLE / VOID watermark 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}/advance steps a test tracker through a scripted lifecycle (chapter 08 §7).

14. Localization

Send Accept-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} with redact: true once 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 extensions carry 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.