Developer Docs
Storefront API
Authentication

Authentication

The Joy Subscription Storefront REST API uses two-layer authentication. Every business endpoint (everything except the unauthenticated health probe GET /__health) requires both layers on the same request:

  • Layer 2 — App credentials identify the calling integration and its owning shop (machine identity).
  • Layer 1 — Customer session identifies the end customer (user identity).

The shopId is always derived from the customer session — the API never reads a shop id from a request header. If the shop that owns the app credentials does not match the shop on the customer session, the request fails with 403 SHOP_MISMATCH.

Layer 2 — App credentials (server-side only)

App credentials are a pair of headers sent on every request:

HeaderDescription
X-Joy-App-IdPublic, URL-safe identifier for your integration. Safe to expose.
X-Joy-Secret-KeySecret key, prefixed sk_live_. Server-side only.

Getting credentials

  1. Open your app's Developers tab in the Shopify admin.
  2. Generate (or rotate) an App ID + Secret Key pair.
  3. The App ID is a public, URL-safe identifier.
  4. The Secret Key is shown once at creation/rotation time, prefixed with sk_live_. It is stored only as a salted HMAC-SHA256 hash and cannot be recovered later — store it in your server-side secret manager.

Note: The secret key is server-side only. Never embed it in browser JavaScript, mobile apps, or any client the customer can inspect. All Storefront API calls must be proxied through your own backend that attaches these headers. Rotating credentials in the Developers tab revokes the previous active key.

Send on every request:

X-Joy-App-Id: <your-app-id>
X-Joy-Secret-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Layer 1 — Customer session (OAuth)

A customer session is established through Shopify's Customer Account API OAuth (PKCE) flow. These OAuth endpoints live on the client API surface (/clientApi/...), not on /storefront-api/v1. The flow yields a sessionId that you then pass as the X-Session-Id header.

OAuth flow

1. Initiate — redirect the customer's browser to the auth endpoint. Substitute your shop domain and return URL:

GET /clientApi/customer-account-api/auth?shopDomain=<shop>.myshopify.com&returnUrl=<your-return-url>

This builds the PKCE authorization URL and redirects the customer to Shopify's Customer Account login.

2. Callback — Shopify redirects back to the callback endpoint:

GET /clientApi/customer-account-api/callback?code=...&state=...

The server exchanges the code, creates a session, and redirects to your returnUrl with a one-time authToken query parameter appended (...?authToken=<one-time-token>).

3. Exchange — exchange the one-time token for a durable session id:

curl -sS -X POST \
  "https://<your-app-domain>/clientApi/customer-account-api/exchange-token" \
  -H "Content-Type: application/json" \
  -d '{"authToken":"<one-time-token>"}'

Response:

{
  "success": true,
  "data": {
    "sessionId": "...",
    "customerId": 123456789,
    "shopId": "...",
    "customer": null
  }
}

4. Use — send the sessionId as the X-Session-Id header on every Storefront API call.

Supporting OAuth endpoints

These share the same /clientApi/customer-account-api/ prefix:

MethodPathPurpose
POST/verifyVerify a session is still valid and fetch customer data. Body: { "sessionId": "..." }
POST/logoutInvalidate a session. Body: { "sessionId": "..." }

Note: These OAuth endpoints use the legacy client-API envelope ({ success, data } on success, { success, error: "<message string>" } on failure), not the structured error: { code, message } envelope of the Storefront API. They are documented here only because they produce the sessionId the Storefront API consumes.

Combined request shape

Every business endpoint requires all three headers together:

X-Joy-App-Id: <app-id>
X-Joy-Secret-Key: sk_live_xxx        # server-side only
X-Session-Id: <session-id>

Full cURL example

List the authenticated customer's subscriptions:

curl -sS "https://<your-app-domain>/storefront-api/v1/subscriptions" \
  -H "X-Joy-App-Id: $JOY_APP_ID" \
  -H "X-Joy-Secret-Key: $JOY_SECRET_KEY" \
  -H "X-Session-Id: $SESSION_ID"

Authentication errors

When authentication fails, the API returns the structured error envelope { success: false, error: { code, message } } with one of the following codes:

ConditionStatuserror.code
Missing/invalid X-Joy-App-Id or X-Joy-Secret-Key401INVALID_APP_CREDENTIALS
Missing X-Session-Id (or session context not resolved)401AUTH_REQUIRED
Unknown or expired session401SESSION_EXPIRED
App credentials' shop does not match session shop403SHOP_MISMATCH

Example error response:

{
  "success": false,
  "error": {
    "code": "SESSION_EXPIRED",
    "message": "Session not found or expired."
  }
}
Product
Install on ShopifyWebsitePricing
Resources
DocumentationGetting StartedFAQsIntegrations
Company
Avada GroupContact Support
© 2026 Avada Group. All rights reserved.