Skip to content

Authentication

The KitchenClick Ecommerce API uses two authentication modes depending on the endpoint.

Public vs Protected Endpoints

Endpoint TypeAuthenticationUse Case
Menu BrowsingNoneDisplay menus to customers
Concept LookupNoneLook up concepts by slug
Order TrackingNoneLet customers track orders
Order SchedulingNoneGet scheduling config and time slots
Item AvailabilityOAuth TokenCheck stock before ordering
Customer HistoryOAuth TokenRetrieve past orders by email
Order OperationsOAuth TokenCreate and manage orders
PaymentOAuth TokenProcess payments
Kiosk OperationsOAuth TokenManage self-service kiosks

OAuth 2.0 Authentication

Protected endpoints require a JWT token from the Identity service using the Client Credentials grant.

Step 1: Get Your Credentials

Contact your ShopHero administrator to create a service account with KitchenClick scopes:

json
{
  "client_id": "sa_00000x1Y2z3A4b5",
  "client_secret": "sk_live_abcdefghijklmnopqrstuvwxyz123456",
  "scopes": ["kitchenclick:orders.create", "kitchenclick:payments.create"]
}

Store Secrets Securely

The client secret is only shown once. Store it in environment variables or a secrets manager. Never expose it in frontend code.

Step 2: Request an Access Token

bash
curl -X POST https://identity.retailsuccessplatform.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=${CLIENT_ID}" \
  -d "client_secret=${CLIENT_SECRET}" \
  -d "scope=kitchenclick:orders.create kitchenclick:payments.create"

Response:

json
{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 86400
}

Step 3: Use the Token

Include the token in the Authorization header:

bash
curl -X POST https://api.kitchenclick.retailsuccessplatform.com/api/v1/ecommerce/orders \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"location_id": "loc_xxxxx", ...}'

Available Scopes

Order Scopes

ScopeDescription
kitchenclick:orders.calculateCalculate order totals with tax
kitchenclick:orders.createCreate guest orders

Payment Scopes

ScopeDescription
kitchenclick:payments.readCheck payment status
kitchenclick:payments.createCreate payment requests
ScopeDescription
kitchenclick:menus.readCheck item availability (batch)

Kiosk Scopes

ScopeDescription
kitchenclick:kiosks.bootstrapInitialize kiosks via PIN
kitchenclick:kiosks.heartbeatSend kiosk heartbeats

Wildcard Scope

ScopeDescription
kitchenclick:*Full access to all ecommerce operations

Token Management

Token Caching

Tokens are valid for 24 hours. Cache them to avoid rate limiting:

javascript
class TokenManager {
  constructor(clientId, clientSecret) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.token = null;
    this.expiry = 0;
  }

  async getToken() {
    // Return cached token if valid (with 5 min buffer)
    if (this.token && Date.now() < this.expiry - 300000) {
      return this.token;
    }

    const response = await fetch('https://identity.retailsuccessplatform.com/oauth/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: this.clientId,
        client_secret: this.clientSecret,
        scope: 'kitchenclick:orders.create kitchenclick:payments.create',
      }),
    });

    const data = await response.json();
    this.token = data.access_token;
    this.expiry = Date.now() + (data.expires_in * 1000);

    return this.token;
  }
}

Handling 401 Errors

If you receive a 401 Unauthorized, your token may have expired:

javascript
async function makeRequest(url, options) {
  let token = await tokenManager.getToken();

  let response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`,
    },
  });

  if (response.status === 401) {
    // Token expired, clear cache and retry
    tokenManager.token = null;
    token = await tokenManager.getToken();

    response = await fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${token}`,
      },
    });
  }

  return response;
}

Rate Limiting

All endpoints are rate limited. Headers indicate your current status:

X-RateLimit-Limit: 30
X-RateLimit-Remaining: 28
X-RateLimit-Reset: 1705330800

Rate Limit Tiers

TierLimitApplied To
Public100/minPer IP address
Read200/minPer OAuth client
Write30/minPer OAuth client
Kiosk Heartbeat60/minPer kiosk
Kiosk Bootstrap200/minPer OAuth client

Handling Rate Limits

When rate limited (HTTP 429):

json
{
  "message": "Too many requests. Please try again later.",
  "error": "rate_limit_exceeded",
  "retry_after_seconds": 45
}

Implement exponential backoff:

javascript
async function withRetry(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (error.status === 429 && i < maxRetries - 1) {
        const retryAfter = error.retry_after_seconds || Math.pow(2, i);
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        continue;
      }
      throw error;
    }
  }
}

Security Best Practices

  1. Server-Side Only - Never expose credentials in frontend/mobile code
  2. Use Environment Variables - Store secrets in env vars or secret managers
  3. Request Minimal Scopes - Only request scopes you actually need
  4. Rotate Credentials - Rotate service account credentials every 90 days
  5. Monitor Usage - Check audit logs for unusual activity
  6. Use HTTPS - Always use encrypted connections

Changelog
DateChange
2026-03-14Added e-commerce API endpoints.
2026-01-15Initial publication.

ShopHero CommerceCore Platform