Authentication

Every call carries a customer's access token as a bearer: Authorization: Bearer ctk_.... The store's authorization server issues it, after the customer signs in with a code mailed to them and grants your app what it asks for. Each store is its own server: its metadata is at https://customer-auth.sherpo.it/.well-known/oauth-authorization-server/s/{store}.

A website or a mobile app is a public client: register it once (POST https://customer-auth.sherpo.it/s/{store}/register, RFC 7591), then send the customer to the authorization endpoint with a PKCE challenge (S256) and trade the code at the token endpoint. A backend may use a confidential client the merchant created on the API clients page instead. Ask for no resource: a token issued for the store's MCP server is refused here, and one issued here is refused there.

A site that already signs its own customers in can skip the mailed code: once the merchant turns on "Signs in your own customers" for a confidential client, its backend trades the account it signed in for a token at the token endpoint, authenticated by the client secret: grant_type=urn:sherpo:params:oauth:grant-type:merchant-assertion, account_id (your id for the account; every client that does this shares the store's account ids, so each must name the same customer in all of them), and optionally email with email_verified=true when you proved it, name, locale and scope. The address is kept on the customer unproven. For an account not yet tied to a customer, an address you proved that a customer of the store already proved answers invalid_grant until you link them once, by sending link_token, an access token they granted the same client through the authorization endpoint; their later tokens carry what the client holds. For an account already tied to a customer, an address another customer proved is left off. The secret then acts as any of the store's customers, so keep it on your server; turning the setting off ends every token it got this way, and a leaked secret also needs rotating.

Scopes: read, profile:write, contact:write, checkout:write, review:write, quiz:write, progress:write, subscription:write. Each operation in the reference names the scope it needs. An access token expires; trade the refresh token for a new pair at the token endpoint, and keep only the latest, since a refresh token used twice revokes the token. Revoke a token at POST https://customer-auth.sherpo.it/s/{store}/revoke (RFC 7009) when the customer signs out of your app.

A request without a token answers 401 with WWW-Authenticate: Bearer. An expired or revoked token answers 401 with error="invalid_token"; a missing scope 403 with error="insufficient_scope".

Publishable keys

A store's own front-end can read its catalog and pages before anyone signs in with the store's publishable key, sent instead of a token: Publishable-Key: {key}. The merchant creates the key for the store. It is public by design, since it sits in the page's source: it holds read alone and reaches only the operations the reference marks as taking a publishable key (the store, its products, entries, pages and their bodies, the files they list as open to visitors, its reviews, and certificate checks), each answered as for a visitor. Send either a token or a key, never both. A file's URL asked for with the key already carries it as publishable_key, so it works without the header; a token is never taken in a URL. An unknown or revoked key, or another store's, answers 401; an operation the key doesn't take answers 403 with the reason principal; a store whose plan has no Self-Hosted answers 403 with the reason plan.