Authentication

Ebda Store has three ways of identifying a caller. Which one applies depends on the surface you call.

SurfaceCredentialUsed for
Public storefront (/public/*)None for reads; store named by headerCatalogue, categories, cart
Merchant dashboard APIJWT access token (cookie or Authorization: Bearer)Managing a store as a signed-in user
Merchant admin MCPx-merchant-key merchant API keyScripts, CI jobs and AI assistants acting for one store

Access tokens

POST /api/v1/auth/login signs a user in. The access token is a short-lived JWT (about 30 minutes) set as an HTTP-only cookie; send it back either way:

Authorization: Bearer <access-token>

POST /api/v1/auth/refresh exchanges the refresh cookie for a new access token. Browsers handle this transparently. A call with no refresh token at all (no cookie, no body) answers 401 with code: "NO_REFRESH_TOKEN". Sign-in is rate limited and an account is locked for 15 minutes after 5 failed attempts (see Rate limits).

Multi-factor authentication (TOTP) is optional for merchants and enforced for platform super-admin accounts, which get 403 MFA_SETUP_REQUIRED on protected routes until they enrol.

How public endpoints find the store

Storefront requests carry no user. They name the store with one of:

  • x-store-domain: the storefront host, for example amelia.ebda.app or a verified custom domain. <slug>.ebda.app resolves by slug; any other host resolves by custom domain. Reserved and malformed hosts resolve to nothing, never to a default store.
  • x-store-id: the store's UUID.

If neither resolves to an active store the API answers 400 STORE_NOT_RESOLVED.

Merchant API keys

A merchant API key authenticates one store's admin MCP endpoint, POST /api/v1/mcp/admin/:storeSlug. It is a device credential: one per laptop, CI job or teammate, so that revoking one never signs out the others.

  • Send it in the x-merchant-key header. Keys look like ebdam_ followed by 43 URL-safe characters.
  • It is shown once, when minted. Only a SHA-256 hash is stored, so a lost key is rotated, never recovered.
  • Keys carry scopes. The default is read only.
ScopeAllows
merchant:readReporting and listing tools
merchant:writeCreating and updating records
merchant:destructiveDeleting records
  • A store may hold at most 20 active keys. A key can have an expiry of 1 to 730 days.
  • The surface is off by default (merchant_mcp feature flag). A missing store, a disabled feature and a wrong or revoked key all return the same 404 NOT_FOUND, so a prober learns nothing.

Managing keys

Keys are managed by a signed-in store admin (or platform admin) with access to the store:

MethodPathPurpose
GET/api/v1/stores/:id/integrations/merchant-keysList keys (metadata only, never the secret)
POST/api/v1/stores/:id/integrations/merchant-keysMint a key; body { name, scopes?, expiresInDays? }. The plaintext is in this response only
DELETE/api/v1/stores/:id/integrations/merchant-keys/:keyIdRevoke
curl -X POST https://api.ebda.app/api/v1/stores/$STORE_ID/integrations/merchant-keys \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "ci-reporting", "scopes": ["merchant:read"], "expiresInDays": 90 }'