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.
Which credential to use
Section titled “Which credential to use”| Credential | Looks like | Use it for | Important distinction |
|---|---|---|---|
| Project API key | sk_ + 40 hex characters | Server-to-server integration, agents, MCP | Locked to one project; the project is derived from the key |
| Dashboard JWT | Standard JWT from POST /auth/login | Dashboard sessions, account and team management | Expires 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.
Sending the credential
Section titled “Sending the credential”Send the key as a Bearer token:
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:
| Route | Authorization: Bearer <key> | X-API-Key: <key> | Dashboard JWT |
|---|---|---|---|
/projects/{projectId}/… | yes | no | yes (if member) |
POST /ingest/events | yes | yes | no |
Prefer Authorization: Bearer everywhere — it is the one header every key-accepting route reads.
Project scoping
Section titled “Project scoping”- On
/projects/{projectId}/…, theprojectIdin the path must equal the key’s project, otherwise403with code1003(“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
userIdis not a credential either: your backend must decide which customer the signed-in shopper is before calling Storelayer.
Managing keys
Section titled “Managing keys”API key management lives under API Keys in the API reference:
| Action | Endpoint |
|---|---|
| Create | POST /projects/{projectId}/api-keys with { "name": "checkout-backend", "expiresAt": "2027-01-01T00:00:00Z" } (expiresAt optional) |
| List | GET /projects/{projectId}/api-keys |
| Rename | PATCH /projects/{projectId}/api-keys/{keyId} with { "name": "…" } |
| Revoke | DELETE /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.
Public routes (no credential)
Section titled “Public routes (no credential)”GET /health, GET /openapi.json, GET /docs, the /auth/* sign-in routes, and the public support-ticket and survey-response routes under /public/….
Errors
Section titled “Errors”All auth failures use the standard error envelope:
{ "success": false, "error": { "code": 1002, "message": "Invalid or expired token" } }| Status | Code | Message | Cause |
|---|---|---|---|
| 401 | 1002 | Missing or invalid Authorization header | No Bearer header on a /projects/… route |
| 401 | 1002 | Invalid or expired token | Unknown, revoked, or expired key/JWT |
| 401 | 1002 | Missing API key (X-API-Key header or Bearer token) / Invalid API key | Same, on /ingest |
| 403 | 1003 | API key does not have access to this project | Key belongs to a different project |
| 403 | 1003 | Not a member of this project | JWT user is not a project member |
| 403 | 1004 | API key is missing the required scope: <scope> | Admin-only operation |
Pitfalls
Section titled “Pitfalls”- 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
401as “token expired” — a revoked key or a missingBearerprefix produce the same code.