Appearance
Authentication
The KitchenClick Ecommerce API uses two authentication modes depending on the endpoint.
Public vs Protected Endpoints
| Endpoint Type | Authentication | Use Case |
|---|---|---|
| Menu Browsing | None | Display menus to customers |
| Concept Lookup | None | Look up concepts by slug |
| Order Tracking | None | Let customers track orders |
| Order Scheduling | None | Get scheduling config and time slots |
| Item Availability | OAuth Token | Check stock before ordering |
| Customer History | OAuth Token | Retrieve past orders by email |
| Order Operations | OAuth Token | Create and manage orders |
| Payment | OAuth Token | Process payments |
| Kiosk Operations | OAuth Token | Manage 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
| Scope | Description |
|---|---|
kitchenclick:orders.calculate | Calculate order totals with tax |
kitchenclick:orders.create | Create guest orders |
Payment Scopes
| Scope | Description |
|---|---|
kitchenclick:payments.read | Check payment status |
kitchenclick:payments.create | Create payment requests |
Menu Scopes
| Scope | Description |
|---|---|
kitchenclick:menus.read | Check item availability (batch) |
Kiosk Scopes
| Scope | Description |
|---|---|
kitchenclick:kiosks.bootstrap | Initialize kiosks via PIN |
kitchenclick:kiosks.heartbeat | Send kiosk heartbeats |
Wildcard Scope
| Scope | Description |
|---|---|
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: 1705330800Rate Limit Tiers
| Tier | Limit | Applied To |
|---|---|---|
| Public | 100/min | Per IP address |
| Read | 200/min | Per OAuth client |
| Write | 30/min | Per OAuth client |
| Kiosk Heartbeat | 60/min | Per kiosk |
| Kiosk Bootstrap | 200/min | Per 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
- Server-Side Only - Never expose credentials in frontend/mobile code
- Use Environment Variables - Store secrets in env vars or secret managers
- Request Minimal Scopes - Only request scopes you actually need
- Rotate Credentials - Rotate service account credentials every 90 days
- Monitor Usage - Check audit logs for unusual activity
- Use HTTPS - Always use encrypted connections
Related
- Identity API Documentation - Full OAuth documentation
- Service Accounts - Managing service accounts
Changelog
| Date | Change |
|---|---|
| 2026-03-14 | Added e-commerce API endpoints. |
| 2026-01-15 | Initial publication. |