Auth
Customer token exchange — turns proven identity into a short-lived JWT
Exchange proven identity for a short-lived customer JWT
POST/auth/token
Two grant_types issue the same JWT — everything downstream is client-agnostic:
hmac— web / Weaverse. The merchant server (Oxygen worker) knows the session and holds the shop's Headless secret (Joy admin > Settings > Manage keys) in a private env var. It signsHMAC_SHA256("{customerId}-{lowercased email}-{ts}", headlessSecret)(hex) and sends it here withts(Unix seconds; rejected if more than 300s off server time).customerIdis the numeric Shopify customer id, not thegid://shopify/Customer/...GID. The headless secret never reaches the browser. The shop's REST APIsecretKeyis not accepted.shopify_customer_token— mobile / native. The client sends a Shopify Customer Account API access token; Joy verifies it with Shopify and issues the same JWT. The client never holds the headless secret, and this grant works even before the merchant generates one (Joy creates the shop's headless secret on the first mint if it does not exist yet).
Fail-closed: any invalid/missing proof returns 401 before any work. The customer must already exist in Joy, and for hmac the signed email must match the customer's Joy email (case-insensitive).
The shop must be on the Ultimate plan, otherwise 403 PLAN_NOT_ELIGIBLE.
Request body
Responses
200JWT issued
successbooleanOptional
Example:
truedataTokenDataOptional
Show properties
tokenstringOptional
Signed customer JWT (HS256).
Example:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."tokenTypestringOptional
Example:
"Bearer"expiresInintegerOptional
Seconds until expiry.
Example:
3600metaobjectOptional
Optional metadata (pagination, counts).
timestampstring · date-timeOptional
Example:
"2026-07-31T07:27:54.123Z"400`BAD_REQUEST` - a required field is missing. `UNSUPPORTED_GRANT_TYPE` - `grant_type` is not `hmac` or `shopify_customer_token`.
successbooleanOptional
Example:
falseerrorobjectOptional
Show properties
messagestringOptional
Example:
"Unauthorized"codestringOptional
Example:
"UNAUTHORIZED"statusCodeintegerOptional
Example:
401timestampstring · date-timeOptional
Example:
"2026-07-31T07:27:54.123Z"401`UNAUTHORIZED` - identity proof invalid or expired (`ts` more than 300s off), shop not found, customer not found in Joy, or the signed email does not match the customer's Joy email.
successbooleanOptional
Example:
falseerrorobjectOptional
Show properties
messagestringOptional
Example:
"Unauthorized"codestringOptional
Example:
"UNAUTHORIZED"statusCodeintegerOptional
Example:
401timestampstring · date-timeOptional
Example:
"2026-07-31T07:27:54.123Z"403`EXCLUDED` - the customer is excluded from the loyalty program (excluded tag / segment), so no token is minted. Do not retry. `PLAN_NOT_ELIGIBLE` - the shop is not on the Ultimate plan.
successbooleanOptional
Example:
falseerrorobjectOptional
Show properties
messagestringOptional
Example:
"Unauthorized"codestringOptional
Example:
"UNAUTHORIZED"statusCodeintegerOptional
Example:
401timestampstring · date-timeOptional
Example:
"2026-07-31T07:27:54.123Z"429
No body
500`INTERNAL_ERROR` - any unexpected server failure (every thrown error on the headless surface maps here). Safe to retry with backoff.
successbooleanOptional
Example:
falseerrorobjectOptional
Show properties
messagestringOptional
Example:
"Unauthorized"codestringOptional
Example:
"UNAUTHORIZED"statusCodeintegerOptional
Example:
401timestampstring · date-timeOptional
Example:
"2026-07-31T07:27:54.123Z"503`SERVICE_UNAVAILABLE` - the token could not be issued; retry. `RATE_LIMITER_UNAVAILABLE` - the token rate limiter is temporarily unavailable (this endpoint fails closed); retry after `Retry-After` seconds.
successbooleanOptional
Example:
falseerrorobjectOptional
Show properties
messagestringOptional
Example:
"Unauthorized"codestringOptional
Example:
"UNAUTHORIZED"statusCodeintegerOptional
Example:
401timestampstring · date-timeOptional
Example:
"2026-07-31T07:27:54.123Z"POST/auth/token
POST /app/api/v1/headless/auth/token HTTP/1.1Host: api.joy.soContent-Type: application/jsonAccept: */*Content-Length: 173{"grant_type": "hmac","shopDomain": "acme.myshopify.com","customerId": "6543210987654","email": "[email protected]","ts": 1753939674,"hmac": "9f1c2b...e7"}
200JWT issued
{"success": true,"data": {"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","tokenType": "Bearer","expiresIn": 3600},"meta": {},"timestamp": "2026-07-31T07:27:54.123Z"}