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
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
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 client creates a session and sends the user to
url. The implementer hosts this page, which matters because it keepsstate, the PKCE verifier andnonceserver-side. - The implementer redirects to the carrier’s
authorization_endpointwithresponse_type=code,client_id,redirect_uri,scope(plusopenidifapp.openid),state,nonce,code_challengeandcode_challenge_method=S256. - The carrier redirects back to
/v1/oauth/callback?code&state. The implementer exchanges the code, validates the ID token when OIDC is in use, derivesoauth.subject, and creates the connection, or updates it ifconnectionwas given, which is the re-authorization path. - The user is redirected to
return_url?connect_session=cs_4Kk8&status=completed&connection=conn_9f8e. - Events:
connect_session.completedplusconnection.created,connect_session.expired(unused pastexpires_at,status: "expired"), orconnect_session.failedwitherror.codeset toaccess_denied,invalid_grant,subject_already_connected, orid_token_invalid.
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 toreauthorization_required, emitsconnection.reauthorization_required, and rating skips it with aconnection_reauthorization_requiredentry inrate_quote.errors[]. - When a carrier signals revocation (a back-channel logout or a revocation webhook), the connection moves to
reauthorization_requiredand emitsconnection.disconnected.
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:
- The implementer fetches the carrier’s
shipping-configuration, then synthesizes acarriercatalog entry with IDfed_{host}and copies across its services, options, schemas and capabilities. - It registers dynamically at the carrier’s
/oauth/register. - 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.