Ordeliya Docs

Authentication

Three auth realms, API Keys, roles & permissions — everything you need to secure your Ordeliya API integration.

Overview

The Ordeliya API uses three completely isolated authentication realms. Tokens from one realm cannot be used in another. This separation ensures that a customer storefront token can never access restaurant admin endpoints, and vice versa.

RealmAudience ClaimToken LifetimeEndpoint Prefix
Tenanttenant15 min access / 7 day refresh/auth/*
Customercustomer15 min access / 7 day refresh/customer-auth/*
API KeyNo expiry (until revoked)Any tenant endpoint

Realm 1 — Tenant Authentication

Tenant auth is used by restaurant owners, admins, and staff who manage the business through the dashboard or API.

POST /auth/login

Authenticate with email and password. Returns an access token (JWT) and sets a refresh token as an httpOnly cookie.

curl -X POST https://api.ordeliya.com/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "owner@myrestaurant.dk",
    "password": "Str0ng!P@ssw0rd"
  }'

Response 200 OK

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfNGs3bTJuOHYiLCJlbWFpbCI6Im93bmVyQG15cmVzdGF1cmFudC5kayIsInN0b3JlSWQiOiJzdG9yZV9yNGs3Iiwicm9sZSI6Ik9XTkVSIiwiYXVkIjoidGVuYW50IiwiaWF0IjoxNzEwNTEwMTgwLCJleHAiOjE3MTA1MTEwODB9.Hk7x9Qm2nR4vB1cT",
    "user": {
      "id": "usr_4k7m2n8v",
      "email": "owner@myrestaurant.dk",
      "name": "Anders Jensen",
      "platformRole": null
    },
    "stores": [
      {
        "storeId": "store_r4k7",
        "storeName": "Pizza Roma — Copenhagen",
        "websiteId": "web_m3x9",
        "websiteName": "Pizza Roma",
        "websiteDomain": "pizzaroma.dk",
        "role": "OWNER",
        "countryCode": "DK",
        "storeViews": [
          { "id": "sv_d1a2", "code": "da_dk", "locale": "da-DK", "currency": "DKK", "isDefault": true },
          { "id": "sv_e3n4", "code": "en_dk", "locale": "en-GB", "currency": "DKK", "isDefault": false }
        ]
      },
      {
        "storeId": "store_h8n2",
        "storeName": "Pizza Roma — Aarhus",
        "websiteId": "web_m3x9",
        "websiteName": "Pizza Roma",
        "websiteDomain": "pizzaroma.dk",
        "role": "OWNER",
        "countryCode": "DK",
        "storeViews": [
          { "id": "sv_f5g6", "code": "da_dk", "locale": "da-DK", "currency": "DKK", "isDefault": true }
        ]
      }
    ]
  }
}

The response also sets an httpOnly cookie:

Set-Cookie: refreshToken=rt_a1b2c3...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=604800

Error 401 Unauthorized

{
  "success": false,
  "error": {
    "statusCode": 401,
    "message": "Invalid email or password"
  }
}

POST /auth/refresh

Exchange a refresh token for a new access token. The refresh token cookie is sent automatically by the browser.

curl -X POST https://api.ordeliya.com/auth/refresh \
  -b "refreshToken=rt_a1b2c3..."

Response 200 OK

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs..."
  }
}

The old refresh token is invalidated and a new one is set via cookie (rotation). If a revoked refresh token is reused, all sessions for that user are terminated (reuse detection).

GET /auth/me

Get the currently authenticated user and active store context.

curl https://api.ordeliya.com/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response 200 OK

{
  "success": true,
  "data": {
    "user": {
      "id": "usr_4k7m2n8v",
      "email": "owner@myrestaurant.dk",
      "name": "Anders Jensen"
    },
    "store": {
      "storeId": "store_r4k7",
      "storeName": "Pizza Roma — Copenhagen",
      "websiteId": "web_m3x9",
      "role": "OWNER",
      "countryCode": "DK",
      "locale": "da-DK",
      "currency": "DKK",
      "timezone": "Europe/Copenhagen"
    }
  }
}

POST /auth/switch-store

Switch to a different store within the same Website. Returns a new access token scoped to the target store.

curl -X POST https://api.ordeliya.com/auth/switch-store \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "storeId": "store_h8n2",
    "storeViewId": "sv_f5g6"
  }'

Response 200 OK

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "store": {
      "storeId": "store_h8n2",
      "storeName": "Pizza Roma — Aarhus",
      "role": "OWNER"
    }
  }
}

POST /auth/logout

Revoke the current session. The refresh token is invalidated immediately.

curl -X POST https://api.ordeliya.com/auth/logout \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response 200 OK

{
  "success": true,
  "data": {
    "message": "Logged out successfully"
  }
}

Realm 2 — Customer Authentication

Customer auth is used by end users on the storefront. Customers can only access their own data (orders, profile, addresses).

POST /customer-auth/login

curl -X POST https://api.ordeliya.com/customer-auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "storeId": "store_r4k7",
    "email": "maria@example.dk",
    "password": "customer-password"
  }'

Response 200 OK

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "customer": {
      "id": "cust_n3k7m2",
      "email": "maria@example.dk",
      "firstName": "Maria",
      "lastName": "Nielsen",
      "phone": "+4520123456",
      "loyaltyPoints": 2450,
      "totalOrders": 12
    }
  }
}

POST /customer-auth/register

curl -X POST https://api.ordeliya.com/customer-auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "storeId": "store_r4k7",
    "email": "new.customer@example.dk",
    "password": "SecurePass123!",
    "firstName": "Lars",
    "lastName": "Andersen",
    "phone": "+4531234567"
  }'

Response 201 Created

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "customer": {
      "id": "cust_p8q9r0",
      "email": "new.customer@example.dk",
      "firstName": "Lars",
      "lastName": "Andersen",
      "phone": "+4531234567",
      "loyaltyPoints": 0,
      "totalOrders": 0
    }
  }
}

POST /customer-auth/social

Authenticate via Google, Apple, or Facebook. The token is the OAuth ID token from the provider.

curl -X POST https://api.ordeliya.com/customer-auth/social \
  -H "Content-Type: application/json" \
  -d '{
    "storeId": "store_r4k7",
    "provider": "google",
    "token": "eyJhbGciOiJSUzI1NiIs..."
  }'

Supported providers: google, apple, facebook

If the email is not registered, a new customer account is created automatically (JIT provisioning).

POST /customer-auth/refresh

Exchange a customer refresh token for a new access token.

curl -X POST https://api.ordeliya.com/customer-auth/refresh \
  -b "customerRefreshToken=crt_x1y2z3..."

Response 200 OK

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs..."
  }
}

POST /customer-auth/logout

Revoke the current customer session.

curl -X POST https://api.ordeliya.com/customer-auth/logout \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response 200 OK

{
  "success": true,
  "data": {
    "message": "Logged out successfully"
  }
}

Realm 3 — API Keys

API Keys provide long-lived, scoped access to tenant endpoints. They are ideal for server-to-server integrations, POS systems, and third-party apps.

Key Format

ord_live_sk_7f3a9b2c1d4e5f6a7b8c9d0e1f2a3b4c
│    │    │   └── 32-character hex secret
│    │    └────── "sk" = secret key
│    └─────────── "live" = production (vs "test" for sandbox)
└──────────────── "ord" = Ordeliya prefix

Creating API Keys

  1. Navigate to Settings → API Keys in the dashboard
  2. Click Create API Key
  3. Enter a descriptive name (e.g., "POS Integration — Main Branch")
  4. Select the required scopes (see table below)
  5. Click Create — the full key is displayed once
  6. Copy and store it securely (e.g., environment variable, secrets manager)

Using API Keys

API Keys are passed as Bearer tokens, just like JWTs:

curl https://api.ordeliya.com/orders \
  -H "Authorization: Bearer ord_live_sk_7f3a9b2c1d4e5f6a7b8c9d0e1f2a3b4c"

API Key Scopes

ScopeAllowsEndpoints
read:ordersList and view ordersGET /orders, GET /orders/:id
write:ordersCreate and update ordersPOST /orders, PATCH /orders/:id/status
read:productsList and view productsGET /products, GET /products/:id
write:productsCreate, update, delete productsPOST /products, PATCH /products/:id
read:customersList and view customersGET /customers, GET /customers/:id
read:analyticsView revenue and order analyticsGET /analytics/*
read:reservationsList and view reservationsGET /reservations
write:reservationsCreate and update reservationsPOST /reservations
webhooks:manageCreate and manage webhooksPOST /webhooks, DELETE /webhooks/:id

A key with read:orders cannot create orders. A key with write:products can read and write products (write implies read for the same resource).

Revoking API Keys

Revoke a compromised key immediately from Settings → API Keys → Revoke. Revocation is instant — all subsequent requests with that key return 401.


Roles & Permissions

Tenant users have one of four roles. Each role inherits all permissions of the roles below it.

PermissionOwnerAdminManagerStaffAPI KeyCustomer
View ordersyesyesyesyesscopedown only
Create/update ordersyesyesyesyesscoped
Cancel ordersyesyesyesscoped
View productsyesyesyesyesscoped
Create/update productsyesyesyesscoped
Delete productsyesyesscoped
View customersyesyesyesscopedown only
View analyticsyesyesscoped
Manage settingsyesyes
Manage users & rolesyes
Manage API keysyes
Billing & subscriptionyes

"scoped" means the API Key can access the resource only if it has the corresponding scope.


JWT Structure

The access token is a standard JWT with the following claims:

{
  "sub": "usr_4k7m2n8v",
  "email": "owner@myrestaurant.dk",
  "storeId": "store_r4k7",
  "role": "OWNER",
  "aud": "tenant",
  "iat": 1710510180,
  "exp": 1710511080
}
ClaimDescription
subUser ID
emailUser email
storeIdActive store ID (token is scoped to this store)
roleUser role: OWNER, ADMIN, MANAGER, STAFF
audAudience: tenant, platform-admin, or customer
iatIssued at (Unix timestamp)
expExpires at (Unix timestamp, 15 minutes after iat)

Do not decode and trust the JWT on the client side for authorization decisions. The server validates the token on every request.


Security Best Practices

  • HTTPS only — All API communication must use HTTPS. HTTP requests are rejected.
  • Never expose tokens in URLs — Use Authorization header, never query parameters.
  • Rotate refresh tokens — Each refresh call invalidates the old token. Reuse of old tokens terminates all sessions.
  • Use API Keys for server-to-server — Don't use JWTs for cron jobs or background processes.
  • Minimum scopes — Only request the scopes your integration needs.
  • Store secrets securely — Use environment variables or a secrets manager, never commit to source control.
  • Monitor API Key usage — Check Settings → API Keys → Usage for anomalies.

Error Reference

StatusCodeMeaningResolution
401TOKEN_EXPIREDJWT has expiredCall POST /auth/refresh to get a new token
401TOKEN_INVALIDMalformed or tampered tokenRe-authenticate with POST /auth/login
401TOKEN_REVOKEDRefresh token was revokedRe-authenticate with POST /auth/login
401API_KEY_REVOKEDAPI key has been revokedCreate a new key in Settings → API Keys
403INSUFFICIENT_ROLERole doesn't have permissionUse an account with a higher role
403INSUFFICIENT_SCOPEAPI key lacks required scopeCreate a new key with the needed scope
403STORE_MISMATCHToken is scoped to a different storeSwitch store with POST /auth/switch-store
429RATE_LIMITEDToo many auth requests (max 10/min)Wait for Retry-After seconds