API surface. Carrier catalog reads, connections and connect sessions are REST. Registering OAuth apps and onboarding federated providers is GraphQL Interface.
Three objects decide which carrier accounts the API ships with, and how it authenticates to them: The same objects serve every integration, not only carriers: chapter 26 generalizes carrier into integration, and adds interfaces for address validation and insurance. A connect_session is the short-lived object that runs the OAuth/OIDC dance and produces a connection. This diagram shows how the four objects relate. The separation exists because of what happens at the second carrier (Shipping Internals, issue 06). Static API keys, OAuth client credentials, per-merchant OAuth grants, reseller accounts, and “bring your own contract” are all common, and a model that encodes only one of them breaks on the next. Here, how to configure is data (the carrier’s schemas), who the client is is an app, and which account is a connection.

1. The carrier object

The carrier catalog is the single source of truth for dynamic UIs and agents. A client renders a “Connect UPS” form directly from credentials_schema and config_schema, with no per-carrier code. x-secret: true fields are masked, and writeOnly fields never come back. This flowchart shows how a client goes from the catalog to a verified connection using the credentials method, with no per-carrier code. The same catalog types every request. shipment_config_schema is the T in carrier_configs.{carrier} (chapter 01 §6), and pickup_config_schema and manifest_config_schema type the single-key carrier_configs on pickups and manifests. Properties marked x-standardized: true are standardized features (chapter 01 §6.1), and the implementer maps them to the carrier’s native codes. The rest are carrier-specific. An agent that wants “adult signature on every carrier” sends "*": { "signature": "adult" }. Services that cannot provide it are excluded from the rates, with the reason given. capabilities is drawn from the registry in chapter 11 §2. Filters: GET /v1/carriers?capability=pickups&country=DE.

2. Dynamic configuration (config_schema)

Credentials identify you to the carrier. Config changes how the implementer behaves for that account. They are kept apart because credentials are secret and rotate, while config is not secret and is edited often. Every carrier’s config_schema MUST include the common config properties below, and MAY add carrier-specific ones: Carrier-specific examples: dhl_express.site_id, canada_post.contract_id, fedex.smart_post_hub_id, dpd.depot, usps.crid, usps.mid, amazon_shipping.business_id (the value sent as Amazon’s x-amzn-shipping-business-id). Implementers validate config against the schema on every write and return 400 parameter_invalid with a JSON Pointer in param (config/label_defaults/format). Schema UI hints. config_schema and credentials_schema MAY use x-ui-group, x-ui-order, x-ui-widget (password, select, textarea, country), x-ui-help (Markdown), and x-ui-docs-url. Renderers that do not support them fall back to plain JSON Schema.

3. The connection object

Endpoints

Create (credentials):
If verify: true (the default), the implementer makes a cheap authenticated call to the carrier, such as a token fetch or a rate for a fixed lane, before saving. It returns 422 carrier_error / connection_invalid_credentials on failure, and nothing is stored. Delete is refused with 409 state_error / connection_in_use while the connection has scheduled pickups or pending manifests. Purchased shipments keep their service.connection ID for history. Deleting an OAuth connection revokes its tokens at the carrier where the carrier supports RFC 7009. Verify returns the connection with a refreshed verification field. It never spends money.

4. The app object (carrier OAuth client)

An app is your OAuth client registration at a carrier. You need one to connect via method: oauth unless the carrier’s descriptor says system_app: true and you are content to use the implementer’s own app.

Endpoints

5. Connect sessions (the OAuth/OIDC flow)

The flow:
  1. The client creates a session and sends the user to url. The implementer hosts this page, which matters because it keeps state, the PKCE verifier and nonce server-side.
  2. The implementer redirects to the carrier’s authorization_endpoint with response_type=code, client_id, redirect_uri, scope (plus openid if app.openid), state, nonce, code_challenge and code_challenge_method=S256.
  3. The carrier redirects back to /v1/oauth/callback?code&state. The implementer exchanges the code, validates the ID token when OIDC is in use, derives oauth.subject, and creates the connection, or updates it if connection was given, which is the re-authorization path.
  4. The user is redirected to return_url?connect_session=cs_4Kk8&status=completed&connection=conn_9f8e.
  5. Events: connect_session.completed plus connection.created, connect_session.expired (unused past expires_at, status: "expired"), or connect_session.failed with error.code set to access_denied, invalid_grant, subject_already_connected, or id_token_invalid.
This sequence shows the redirect flow end to end, for an OIDC carrier. Some carriers return extra credentials in the callback. Some carriers’ authorize flows return a per-account client ID and secret instead, and the implementer then uses client_credentials with them. Each carrier’s connection_methods[].type = "oauth" descriptor declares grant_after_callback: "authorization_code" | "client_credentials_from_callback", and the implementer handles both. The client never sees the difference. Headless agents cannot follow a redirect. They call POST /v1/connect_sessions with mode: "device", show the user url and user_code, and poll GET /v1/connect_sessions/{id} until status is completed.

Token lifecycle

  • The implementer refreshes carrier access tokens proactively, before they expire.
  • If a refresh fails with invalid_grant, the connection moves to reauthorization_required, emits connection.reauthorization_required, and rating skips it with a connection_reauthorization_required entry in rate_quote.errors[].
  • When a carrier signals revocation (a back-channel logout or a revocation webhook), the connection moves to reauthorization_required and emits connection.disconnected.
This state diagram shows how connection.status (§3) moves, including the token lifecycle above.

6. Federated carriers (carriers that implement this spec)

A carrier that implements this spec publishes /.well-known/shipping-configuration and /shipping.md, and runs OAuth 2.1 + OIDC as described in chapter 00 §3.2. An aggregator can then onboard it with no carrier-specific code:
  1. The implementer fetches the carrier’s shipping-configuration, then synthesizes a carrier catalog entry with ID fed_{host} and copies across its services, options, schemas and capabilities.
  2. It registers dynamically at the carrier’s /oauth/register.
  3. Connect sessions and connections then work as in §5. Calls to the carrier use this same spec, and canonical tracking events arrive at the inbound URL already mapped.
This sequence shows an aggregator onboarding a federated carrier, from discovery to the first tracking event. This is the long-term point of the standard. Every carrier that implements the spec turns integration work into configuration.