Skip to main content
Customei’s GraphQL API is gated by Shopify session tokens — the same short-lived JWTs that the Shopify embedded admin uses. There are no separate API keys to manage. If your caller can prove it’s acting on behalf of a specific shop and member, Customei trusts it.

The two modes

There are two ways to authenticate against the API, and which one you use depends on where your code runs:

Production — Shopify session token

For real integrations. Your caller obtains a session token from Shopify App Bridge and sends it on every request. Customei verifies it against Shopify’s public keys and resolves the shop and member from the token claims.

Local dev — standalone mode

For local development and scripts. Set two environment variables on your own machine and Customei skips the Shopify JWT step entirely, pretending every request comes from the Owner of a specific shop.

Production: Shopify session token

Get a token

The session token comes from Shopify App Bridge inside your embedded admin app:
import { getSessionToken } from '@shopify/app-bridge/utilities';

const token = await getSessionToken(app);
// token is a JWT with your shop's claims
Session tokens are short-lived (about 60 seconds) and bound to the current session. Fetch a fresh one for every API call rather than caching — the App Bridge helper is fast and caches internally.

Send it

Put the token in an Authorization: Bearer header:
POST /api/graphql HTTP/1.1
Host: your-app.example.com
Authorization: Bearer <shopify-session-token>
Content-Type: application/json

{"query": "{ shop { id name } }"}
Customei verifies the token, pulls out the shop domain and the acting user, and resolves:
  • accountId — the top-level owner of all resources.
  • shopID — the specific shop you’re operating on.
  • currentMember — the person whose permissions apply to this request.
You don’t pass any of those fields yourself — they’re derived from the token.

From outside the embedded admin

If you need to call the API from outside an embedded-admin page (for example, from a standalone web app or a server-side cron), you have two options:
  1. Generate a session token server-side using your Shopify app credentials and the target shop’s install. This is Shopify’s officially sanctioned path — it’s more involved but gives you a real, verifiable token.
  2. Store a long-lived token in your backend, obtained once from an admin session. This works but requires carefully rotating the token if the install is reauthorized.
For most server-to-server cases, option 1 is the right answer. See Shopify’s session token authentication docs for the issuing flow.

Local dev: standalone mode

When you’re developing locally, the Shopify session-token dance is often more ceremony than you need. Customei supports standalone mode — a dev-only bypass that treats every request as coming from a specific shop’s Owner.
Standalone mode only activates when NODE_ENV=development. It’s impossible to accidentally enable it in production.

Enable it

Set two environment variables on the server you’re running Customei against:
STANDALONE_MODE=true
STANDALONE_SHOP_DOMAIN=your-shop.myshopify.com
With both set, requests to /api/graphql without an Authorization header resolve to the Owner of your-shop.myshopify.com. No JWT is required.

Use it

In standalone mode you can call the API with plain curl:
curl -X POST http://localhost:3000/api/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query": "{ shop { id name } }"}'
Or any GraphQL client:
import { GraphQLClient } from 'graphql-request';

const client = new GraphQLClient('http://localhost:3000/api/graphql');
const data = await client.request('{ shop { id name } }');

When not to use standalone mode

  • In production. It’s gated by NODE_ENV, but belt and suspenders: don’t bake it into anything customer-facing.
  • When testing permission boundaries. Standalone mode always assumes the Owner role — you can’t exercise Staff-level permission checks from it. For permission testing, switch to a real session token from a Staff user.

What the context looks like

Every authenticated request gets a GraphQL context with:
  • accountId — the account that owns the resources you’ll see.
  • shopID — the shop being operated on.
  • currentMember — the acting member, with their role and permission overrides.
  • prisma — a database client wired up for the request.
  • loaders — DataLoader instances for efficient relation loading.
Resolvers use accountId almost everywhere — because resources belong to the account, not to a user or a shop. Use shopID only for shop-scoped operations like reading orders or publishing products.

Common auth failures

SymptomLikely cause
401 Unauthorized with no messageMissing or empty Authorization header in production.
401 Unauthorized with “Invalid signature”Token was signed by the wrong app; double-check your Shopify app credentials.
401 Unauthorized with “Expired”Session token older than 60s — fetch a fresh one.
403 Forbidden on a mutationAuthentication worked, but your member doesn’t have the required permission.
Works in dev, fails in prodStandalone mode is dev-only; production needs a real session token.

Next