For: Developers
Overview
The Delivery Management API uses a secure OAuth 2.0 authentication flow. To ensure the highest level of security, we utilize Private Key JWTs (RFC 7523) for client authentication.
Your application will use an RSA key pair to sign a JSON Web Token (JWT) locally. This signed JWT is exchanged for a short-lived Access Token, which is then used to authorize your API requests.
Key Specifications
Feature | Specification |
Protocol | OAuth 2.0 (RFC 7523) |
Grant Type | client_credentials |
Auth Method | |
Algorithm | RS256 (RSA Signature with SHA-256) |
Token Validity | 24 hours (typically) |
Rate Limit | 3 token requests per hour (Strictly enforced) |
Environment Configuration
You will be provided with credentials for both Staging (for development/testing) and Production environment.
Production Environment
OAuth 2 Domain (Issuer): https://account.ezcater.com/
OAuth 2 Token URL: https://account.ezcater.com/oauth/token
Grant Type: client_credentials
Client Assertion Type: urn:ietf:params:oauth:client-assertion-type:jwt-bearer
Staging Environment
OAuth 2 Domain (Issuer): https://account.staging-ezcater.com/
OAuth 2 Token URL: https://account.staging-ezcater.com/oauth/token
Grant Type: client_credentials
Client Assertion Type: urn:ietf:params:oauth:client-assertion-type:jwt-bearer
Setup & Registration
Before writing any code, you must generate your credentials and register them with ezCater.
We require that you generate separate key pairs for Staging and Production environments.
Step 1: Generate an RSA Key Pair
You must create a 2048-bit RSA key pair.
Private Key: Used by your application to sign requests. Never share this.
Public Key: Shared with ezCater to verify your signature.
Generate the RSA Key Pair in a programming language of your choice or by running the following commands in your terminal:
A. Generate Private Key
openssl genrsa -out private_key.pem 2048
B. Extract Public Key
openssl rsa -in private_key.pem -pubout -out public_key.pem
Step 2: Register Public Key
Send the Public Key(s) to your ezCater Representative and specify if the key is for Staging or Production.
Do NOT share your Private Key. Store it securely in your own infrastructure.
You may send both the Staging and Production Public Keys at the same time to your ezCater Representative.
Your ezCater Representative will notify you when the Public Key is successfully registered to your account and can be used for requests.
Authentication Flow Implementation
To authenticate, your application must perform a "Client Credentials Exchange" using a signed JWT.
Generate the Client Assertion (Signed JWT)
Construct a JWT with the following claims and sign it using your Private Key (RS256).
Required Claims:
iss (Issuer): Your Client ID (provided by your ezCater Representative).
sub (Subject): Your Client ID.
aud (Audience): https://account.ezcater.com/oauth/token
iat (Issued At): Current timestamp.
exp (Expiration): Timestamp (max 5 minutes from iat).
jti (JWT ID): A unique UUID for this assertion.
Request Access Token
Make a POST request to the Token Endpoint
Production: https://account.ezcater.com/oauth/token
Example Request
curl --request POST \
--url https://account.ezcater.com/oauth/token \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data client_id=YOUR_CLIENT_ID \
--data client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
--data client_assertion=YOUR_SIGNED_JWT \
--data audience=https://delivery-management.ezcater.com/api/graphql
Success Response
{
"access_token": "eyJhbGciOiJSUzI1Ni...",
"token_type": "Bearer",
"expires_in": 86400
}Critical Requirement: Token Caching
You must cache the access_token for its entire validity period
Generating a signed JWT and exchanging it for a token is computationally expensive. We strictly enforce rate limits to ensure platform stability.
Rate Limit: You are allowed a maximum of 3 token requests per hour.
Do NOT request a new token for every API call.
Do NOT generate a new Client Assertion for every API call.
Implementation Logic:
Check if you have a valid cached access_token.
If yes, use it in the API request Authorization header.
If no (or if it is expired), request a new access token [link → Authentication § request-access-token].
Cache the new token for expires_in seconds (typically 24 hours).
Failure to implement caching will result in rate limiting and increased latency for your requests
Code Examples
The examples are not production-ready and are only meant to give a general idea of the implementation.
Node.js
const jwt = require('jsonwebtoken');
const fs = require('fs');
const uuid = require('uuid');
// Configuration Details
const PRIVATE_KEY = fs.readFileSync('private_key.pem');
const CLIENT_ID = 'YOUR_EZCATER_CLIENT_ID'; // Provided by ezCater
const AUTH0_DOMAIN = 'https://account.ezcater.com/';
const TOKEN_URL = 'https://account.ezcater.com/oauth/token';
const API_AUDIENCE = 'https://delivery-management.ezcater.com/api/graphql';
let cachedToken = null;
let tokenExpiration = 0;
async function getAccessToken() {
const now = Math.floor(Date.now() / 1000);
// 1. Check Cache
// We add a 60-second buffer to ensure we don't use a token about to expire
if (cachedToken && now < tokenExpiration - 60) {
return cachedToken;
}
// 2. Generate Client Assertion (Signed JWT)
const assertion = jwt.sign(
{
jti: uuid.v4(),
},
PRIVATE_KEY,
{
algorithm: 'RS256',
expiresIn: '5m', // Assertion expires quickly
issuer: CLIENT_ID,
subject: CLIENT_ID,
audience: AUTH0_DOMAIN,
}
);
// 3. Exchange Assertion for Access Token
try {
const params = new URLSearchParams();
params.append('grant_type', 'client_credentials');
params.append('client_id', CLIENT_ID);
params.append('client_assertion_type', 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer');
params.append('client_assertion', assertion);
params.append('audience', API_AUDIENCE); // Note: Audience for token is the API Identifier
const response = await fetch(TOKEN_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`HTTP error! status: ${response.status}, body: ${errorBody}`);
}
const data = await response.json();
// 4. Cache Token
cachedToken = data.access_token;
tokenExpiration = now + data.expires_in;
console.log("Fetched new Access Token. Caching until:", new Date(tokenExpiration * 1000));
return cachedToken;
} catch (error) {
console.error("Error fetching token:", error.message);
throw error;
}
}
// Usage Example
async function makeApiCall() {
try {
const token = await getAccessToken();
// Example API Request using fetch
const apiResponse = await fetch(API_AUDIENCE, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ query: '{ ... }' })
});
// Handle 429 Rate Limiting specifically if needed
if (apiResponse.status === 429) {
console.warn("Rate limit exceeded.");
}
const result = await apiResponse.json();
} catch (err) {
console.error("API Call failed", err);
}
}
Python
import time
import uuid
import jwt # PyJWT
import requests
# Configuration Details
PRIVATE_KEY = open('private_key.pem', 'rb').read()
CLIENT_ID = 'YOUR_EZCATER_CLIENT_ID'
AUTH0_DOMAIN = 'https://account.ezcater.com/'
TOKEN_URL = 'https://account.ezcater.com/oauth/token'
API_AUDIENCE = 'https://delivery-management.ezcater.com/api/graphql'
_cached_token = None
_token_expiry = 0
def get_access_token():
global _cached_token, _token_expiry
now = int(time.time())
# 1. Check Cache (with 60s buffer)
if _cached_token and now < (_token_expiry - 60):
return _cached_token
# 2. Generate Client Assertion
payload = {
'iss': CLIENT_ID,
'sub': CLIENT_ID,
'aud': AUTH0_DOMAIN,
'iat': now,
'exp': now + 300,
'jti': str(uuid.uuid4())
}
assertion = jwt.encode(payload, PRIVATE_KEY, algorithm='RS256')
# 3. Exchange for Access Token
data = {
'grant_type': 'client_credentials',
'client_id': CLIENT_ID,
'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'client_assertion': assertion,
'audience': API_AUDIENCE
}
resp = requests.post(TOKEN_URL, data=data)
resp.raise_for_status()
token_data = resp.json()
# 4. Update Cache
_cached_token = token_data['access_token']
_token_expiry = now + token_data['expires_in']
return _cached_token
Go
package main
import (
"crypto/rsa"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
// Configuration Details
const (
ClientID = "YOUR_EZCATER_CLIENT_ID"
Auth0Domain = "https://account.ezcater.com/"
TokenURL = "https://account.ezcater.com/oauth/token"
APIAudience = "https://delivery-management.ezcater.com/api/graphql"
KeyFile = "private_key.pem"
)
var (
cachedToken string
tokenExpiry int64
privateKey *rsa.PrivateKey
)
func init() {
// Load Private Key once at startup
keyData, _ := ioutil.ReadFile(KeyFile)
privateKey, _ = jwt.ParseRSAPrivateKeyFromPEM(keyData)
}
func GetAccessToken() (string, error) {
now := time.Now().Unix()
// 1. Check Cache
if cachedToken != "" && now < (tokenExpiry-60) {
return cachedToken, nil
}
// 2. Generate Client Assertion
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
"iss": ClientID,
"sub": ClientID,
"aud": Auth0Domain, // Assertion Audience
"iat": now,
"exp": now + 300,
"jti": uuid.New().String(),
})
assertion, _ := token.SignedString(privateKey)
// 3. Exchange for Access Token
data := url.Values{}
data.Set("grant_type", "client_credentials")
data.Set("client_id", ClientID)
data.Set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
data.Set("client_assertion", assertion)
data.Set("audience", APIAudience)
resp, _ := http.PostForm(TokenURL, data)
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
// 4. Update Cache
cachedToken = result["access_token"].(string)
expiresIn := int64(result["expires_in"].(float64))
tokenExpiry = now + expiresIn
return cachedToken, nil
}
Legacy Authentication Migration Guide (2026)
Starting January 16th, 2026 we will begin onboarding for the new authentication implementation. If you are migrating from our legacy authentication then follow these specific steps.
Key Changes
Feature | Legacy Authentication | New Authentication |
Credential Type | Static API ID & Token | RSA Key Pair (Private/Public) |
Authorization Rate Limits | None | 3 requests / hour |
Authorization Header | Bearer Base64(ID:TOKEN) | Bearer \ |
Token Lifespan | Indefinite (Static) | 30 minutes (Requires Caching) |
Caching Requirements | None | Access token must be cached in-memory across processes |
Security Standard | Custom | OAuth 2.0 (RFC 7523) Private Key JWT |
Migration Steps
Step 1: Generate & Register Keys
Follow the steps in Setup & Registration [link → Authentication § setup--registration].
Step 2: Update Your Integration
Refactor your authentication logic to implement the "Sign JWT -> Exchange for Token -> Cache" flow described here [link → Authentication § authentication-flow-implementation].
Step 3: Staging
We recommend verifying your new authentication flow in Staging before implementing it in Production. If you’re receiving an unexpected 403 response status code then it means the implementation wasn’t successful.
Step 4: Production
Once Staging is verified, then update your implementation for the Production environment with the Production Client ID (sent earlier by your ezCater Representative) and endpoints.
