Authentication
Artos agent commerce uses three credential classes. The single most common
integration bug is mixing them: the API identity guard prefers X-API-Key over
a bearer, so a buyer-bound call that sends both resolves as the platform
identity and the buyer (and their spend caps) are never loaded.
The load-bearing rule. On buyer-bound calls — crypto
prepare_checkout_payment, crypto/complete_checkout, and every/account/mcptool — send the buyer OAuth bearer only. Never attachX-API-Keyto those calls. Everything else (catalog, cart, checkout create/update, card completion, storeget_order) uses the platform key.
On Path A the bridge applies this rule for you. On Path B the
@artos-commerce/ucp-client SDK applies it too
(UcpClient sends the buyer bearer only on buyer-bound calls and relays a 401
via onAuthChallenge) — but the SDK does not acquire or refresh the bearer.
Obtaining it through the OAuth flow below is still yours.
Credential classes
| Class | How obtained | Used for |
|---|---|---|
Platform X-API-Key | Seeded platform credential (ck_artos_agent.…) — the bridge holds it | Catalog, cart, checkout create/update, store get_order, card complete_checkout |
| Buyer OAuth bearer | OAuth 2.1 + PKCE consent via My Artos | Crypto prepare_checkout_payment + complete_checkout; all /account/mcp tools |
| AP2 mandate | Minted as a compact ES256 JWS (bridge mints it on Path A) | Authorization over the priced checkout terms — not a payment rail |
The merchant client_credentials key is a fourth, store-owned variant of the
platform key — see For merchants.
OAuth 2.1 + PKCE (buyer connect)
The buyer connects once and stays connected (~30 days) because the client silently swaps an expired access token for a fresh one using a rotating refresh token. Access tokens are short-lived (default 1h).
Discover the Authorization Server
curl https://api.artos.sh/.well-known/oauth-authorization-server{
"issuer": "https://api.artos.sh",
"authorization_endpoint": "https://my.artos.sh/oauth/consent",
"token_endpoint": "https://api.artos.sh/ucp/oauth2/token",
"grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"],
"response_types_supported": ["code"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_post"],
"scopes_supported": ["purchase:complete", "offline_access"],
"client_id_metadata_document_supported": true
}Exchange the authorization code
POST /ucp/oauth2/token (form-urlencoded). Request offline_access during
consent to receive a refresh token.
curl -X POST https://api.artos.sh/ucp/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=authorization_code \
-d code=AUTH_CODE_FROM_REDIRECT \
-d code_verifier=YOUR_PKCE_VERIFIER \
-d client_id=https://your-app.example/oauth-client.json \
-d redirect_uri=https://your-app.example/callback{
"access_token": "eyJ…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "purchase:complete",
"refresh_token": "rt_…"
}Refresh silently
curl -X POST https://api.artos.sh/ucp/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=refresh_token \
-d refresh_token=rt_…Refresh tokens are single-use and rotated — store the new refresh_token
from every response. Silent refresh only works if (1) the AS advertises
offline_access, (2) the resource returns 401 with
WWW-Authenticate: Bearer error="invalid_token", and (3) your client relays that
401 + header rather than flattening it into a transport error.
CIMD — client registration without pre-registration
Artos supports Client ID Metadata Documents (SEP-991): your client_id is a
stable HTTPS URL pointing at a JSON document describing your client.
- Host an
oauth-client.jsonat a public URL and use that URL as yourclient_id. - List every
redirect_uriyou use (a callback not in the document is rejected withredirect_uri is not registered). - The API TTL-caches CIMD docs (default 600s); after editing, wait or restart the local API in dev.
- For local
mcp-remotedev, point--static-oauth-client-infoat a CIMD doc and includehttp://localhost:8898/oauth/callbackin itsredirect_uris.
Scopes
| Scope | Grants |
|---|---|
purchase:complete | Complete a checkout / place an order |
orders:read | Read store orders via get_order |
offline_access | Receive a refresh token (request at consent) |
Trust tiers
| Tier | How achieved | Typical use |
|---|---|---|
anonymous | No credentials | Catalog browse / search |
token | Valid API key or OAuth bearer | Cart, checkout, orders (with scopes) |
signed | Valid RFC 9421 HTTP Message Signature | Elevated agent-verified requests |
Operations matrix
| Operation | Min tier | Permission |
|---|---|---|
| Global / store catalog | anonymous | — |
| Cart / checkout create/update | token | — |
| Complete checkout | token | purchase:complete |
Crypto prepare_checkout_payment | token (buyer bearer) | purchase:complete |
Store get_order | token | orders:read |
| Buyer account tools | buyer bearer | — |
Agent authorization & spend caps (crypto)
OAuth consent (Connected apps) is not sufficient for autonomous crypto checkout. The buyer must also authorize the agent under My Artos → Agents, which records a stored spending allowance:
maxPerOrderAmount— per-order ceiling (minor units).dailyCapAmount— optional rolling daily cap;spentTodayAmountaccrues against it.currencyandexpiresAt.
The server enforces this allowance up front on every rail when the AP2 mandate
carries a payment_mandate_id, and consumes it once on settlement. An agent can
read the live caps via get_buyer_context. Exceeding a
cap fails the completion before any order is placed.
Rate limits are planned but not implemented in Phase 1.