Skip to main content

Authentication

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

Staging Environment


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.

  • 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

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.

Did this answer your question?