Skip to content

Authentication and Project Access

Every protected request carries one credential: a project API key (for your backend) or a dashboard JWT (for a person signed in to the dashboard). An API key belongs to exactly one project; a JWT belongs to a user and works on every project that user is a member of.

CredentialLooks likeUse it forImportant distinction
Project API keysk_ + 40 hex charactersServer-to-server integration, agents, MCPLocked to one project; the project is derived from the key
Dashboard JWTStandard JWT from POST /auth/loginDashboard sessions, account and team managementExpires after 7 days; access depends on project membership

Use API keys for integrations. JWTs are for humans; creating projects and managing team members requires one.

Send the key as a Bearer token:

Terminal window
curl -s "https://api.storelayer.io/projects/prj_ab12cd34ef56/config" \
-H "Authorization: Bearer sk_your_key_here"

Event ingestion resolves the project from the key and also accepts X-API-Key:

RouteAuthorization: Bearer <key>X-API-Key: <key>Dashboard JWT
/projects/{projectId}/…yesnoyes (if member)
POST /ingest/eventsyesyesno

Prefer Authorization: Bearer everywhere — it is the one header every key-accepting route reads.

  • On /projects/{projectId}/…, the projectId in the path must equal the key’s project, otherwise 403 with code 1003 (“API key does not have access to this project”).
  • On /ingest/events, there is no project in the path; the request runs against the key’s project.
  • A project ID is an identifier, not a secret. A customer userId is not a credential either: your backend must decide which customer the signed-in shopper is before calling Storelayer.

API key management lives under API Keys in the API reference:

ActionEndpoint
CreatePOST /projects/{projectId}/api-keys with { "name": "checkout-backend", "expiresAt": "2027-01-01T00:00:00Z" } (expiresAt optional)
ListGET /projects/{projectId}/api-keys
RenamePATCH /projects/{projectId}/api-keys/{keyId} with { "name": "…" }
RevokeDELETE /projects/{projectId}/api-keys/{keyId}

A project can hold several keys. Create one per integration (checkout backend, CRM sync, agent) so you can revoke one without breaking the others. The create response returns id, name, prefix, key, expiresAt, createdAt; keys with expiresAt stop authenticating at that time.

Rotation without downtime: create a new key, deploy it, confirm traffic works, then revoke the old key by id. A revoked key stops authenticating.

The older single-key endpoints (GET /projects/{projectId}/api-key, POST /projects/{projectId}/api-key/generate, DELETE /projects/{projectId}/api-key) are kept for backward compatibility; generate replaces the previous single key. Use the multi-key endpoints for new work.

Some table-administration operations on custom storage (altering or dropping tables, raw SQL) require the storage:admin scope, which project API keys do not carry today — run those from the dashboard.

GET /health, GET /openapi.json, GET /docs, the /auth/* sign-in routes, and the public support-ticket and survey-response routes under /public/….

All auth failures use the standard error envelope:

{ "success": false, "error": { "code": 1002, "message": "Invalid or expired token" } }
StatusCodeMessageCause
4011002Missing or invalid Authorization headerNo Bearer header on a /projects/… route
4011002Invalid or expired tokenUnknown, revoked, or expired key/JWT
4011002Missing API key (X-API-Key header or Bearer token) / Invalid API keySame, on /ingest
4031003API key does not have access to this projectKey belongs to a different project
4031003Not a member of this projectJWT user is not a project member
4031004API key is missing the required scope: <scope>Admin-only operation
  • Never ship a key to a browser or mobile app. Proxy through your backend.
  • On a 403 1003, fix the project/key pairing; do not fall back to another project’s key.
  • Don’t treat every 401 as “token expired” — a revoked key or a missing Bearer prefix produce the same code.