Skip to content

Exchange a Shopify customer access token for a session token

POST /session-tokens/exchange
ScopeNone: called without an API key
Customer session tokenNot needed
Rate limit classread (limits)
Idempotency keyNot needed: this call has no side effect
MCP toolNot exposed: the exchange runs on the shopper’s device

For headless storefronts with no server of their own: called from the browser or app, without an API key. Send the signed-in shopper’s Customer Account API access token; Charm asks Shopify who it belongs to and mints a session token for that customer, exactly like POST /customers/{customer_id}/session-tokens. Classic Storefront API customerAccessToken values are not accepted. Limited to 20 exchanges per minute per IP address. Theme stores get the same token from /apps/charm/session-token on their own storefront.

FieldTypeDescription
shopstringThe store’s .myshopify.com domain.
customer_access_tokenstringThe shopper’s Customer Account API access token (Hydrogen: customerAccount).
scopesarray of string: read, writeDefaults to ["read"]. (optional)
ttl_secondsinteger(optional)

Returns SessionToken.

FieldTypeDescription
tokenstringchrm_st_…; send as Authorization: Bearer <token>.
token_type"Bearer"
customer_idstringShopify customer GID, e.g. gid://shopify/Customer/123. Accepted by every endpoint that takes a customer id, as is the numeric legacy_id.
scopesarray of string: read, write
api_scopesarray of string
expires_ininteger
expires_atstring
{
"token": "chrm_st_eyJ2IjoxLCJzaG9wIjoi…",
"token_type": "Bearer",
"customer_id": "gid://shopify/Customer/7712345",
"scopes": [
"read"
],
"api_scopes": [
"api scopes"
],
"expires_in": 1,
"expires_at": "2026-10-01T09:30:00.000Z"
}
Terminal window
curl -X POST "https://charm.appfleece.app/api/v1/session-tokens/exchange" \
-H "Authorization: Bearer chrm_live_..." \
-H "Content-Type: application/json" \
-d '{"shop":"your-store.myshopify.com","customer_access_token":"customer access token"}'

unauthorized · invalid_token · insufficient_scope · access_required · validation_error · rate_limited

Every error body carries code, message, request_id and a doc_url pointing at the matching entry in the error catalog.