The Tote Online Ordering API uses OAuth 2.0 client credentials for authentication. This is a machine-to-machine flow designed for server-side integrations -- your backend exchanges a client ID and secret for a Bearer token, then includes that token on every API request.
This guide covers the complete token lifecycle: obtaining credentials, requesting tokens, caching, error handling, and security best practices.
Contact us to sign up as a Tote partner. During onboarding you will receive:
- Base URL -- Your dedicated API endpoint. Replace the placeholder URLs in these docs with your assigned base URL.
- Client ID -- A UUID that identifies your application. Safe to store in configuration files.
- Client Secret -- A secret string that authenticates your application. Treat this like a password.
You will receive separate credentials for sandbox and production environments.
Note: The URLs shown in the examples below (e.g.,
https://sandbox.api.tote.ai/...) are placeholders. Use the base URL provided during your onboarding.
Send a POST request to the token endpoint with your credentials:
curl -X POST https://sandbox.api.tote.ai/v1/online-ordering/auth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "CLIENT_CREDENTIALS",
"client_id": "d7a8fbb3-07d4-4e3c-b5f2-9a6c8b1e0f23",
"client_secret": "sk_live_a1b2c3d4e5f6..."
}'| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | Yes | Must be CLIENT_CREDENTIALS. |
client_id | string (UUID) | Yes | Your application's client ID. |
client_secret | string | Yes | Your application's client secret. |
Success (200 OK):
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkN2E4ZmJiMy0wN2Q0LTRlM2MtYjVmMi05YTZjOGIxZTBmMjMiLCJpYXQiOjE3MDY3MDAwMDAsImV4cCI6MTcwNjc4NjQwMH0.signature",
"token_type": "BEARER",
"expires_in": 86400
}| Field | Type | Description |
|---|---|---|
access_token | string | Bearer token for authenticating API requests. |
token_type | string | Always BEARER. |
expires_in | integer | Token lifetime in seconds. Default is 86400 (24 hours). |
Include the access_token in the Authorization header of every API request:
curl https://sandbox.api.tote.ai/v1/online-ordering/locations \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."The token grants access to all locations associated with your developer account. The token endpoint itself (POST /auth/token) does not require authentication.
Tokens are valid for 24 hours (86,400 seconds). Requesting a new token for every API call wastes resources and will trigger rate limiting.
Recommended approach:
- Request a token on application startup.
- Cache the token in memory alongside its expiration time.
- Reuse the cached token for all API calls.
- Proactively refresh the token 1 hour before expiry (at the 23-hour mark) to avoid failed requests during the refresh window.
- If a request returns
401 AUTHENTICATION_ERROR, treat the token as expired and request a new one immediately.
Pseudocode:
import time
import requests
class ToteAuth:
def __init__(self, client_id, client_secret, base_url):
self.client_id = client_id
self.client_secret = client_secret
self.base_url = base_url
self.access_token = None
self.token_expires_at = 0
def get_token(self):
# Proactive refresh: renew 1 hour before expiry
if self.access_token and time.time() < (self.token_expires_at - 3600):
return self.access_token
response = requests.post(
f"{self.base_url}/auth/token",
json={
"grant_type": "CLIENT_CREDENTIALS",
"client_id": self.client_id,
"client_secret": self.client_secret,
},
)
response.raise_for_status()
data = response.json()
self.access_token = data["access_token"]
self.token_expires_at = time.time() + data["expires_in"]
return self.access_tokenThere is no refresh token flow. When a token expires, simply re-authenticate with your client credentials.
The following diagram shows the complete authentication flow, from initial token request through token reuse and proactive refresh.
Returned when the client_id or client_secret is wrong.
curl -X POST https://sandbox.api.tote.ai/v1/online-ordering/auth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "CLIENT_CREDENTIALS",
"client_id": "invalid-id",
"client_secret": "invalid-secret"
}'{
"error": {
"code": "AUTHENTICATION_ERROR",
"message": "Invalid client credentials.",
"detail": "The client_id or client_secret is incorrect. Verify your credentials in the Tote Developer Portal.",
"request_id": "f1e2d3c4-b5a6-7890-cdef-123456789abc",
"field": null
}
}Resolution: Verify your credentials in the Developer Portal. Confirm you are using sandbox credentials against the sandbox URL and production credentials against the production URL.
Returned when the request body is malformed or missing required fields.
{
"error": {
"code": "INVALID_REQUEST_ERROR",
"message": "Missing required field: grant_type.",
"detail": "The grant_type field is required and must be set to CLIENT_CREDENTIALS.",
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"field": "grant_type"
}
}Resolution: Ensure all three fields (grant_type, client_id, client_secret) are present and grant_type is CLIENT_CREDENTIALS.
Returned when you use an expired token on any authenticated endpoint.
{
"error": {
"code": "AUTHENTICATION_ERROR",
"message": "Invalid or expired access token.",
"detail": "The provided Bearer token has expired. Request a new token via POST /auth/token.",
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"field": null
}
}Resolution: Request a new token. Implement proactive refresh (see Token Caching Strategy above) to avoid this error.
Returned when you make too many token requests in a short period.
{
"error": {
"code": "RATE_LIMIT_ERROR",
"message": "Too many requests.",
"detail": "Rate limit exceeded. Retry after the number of seconds specified in the Retry-After header.",
"request_id": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"field": null
}
}The response includes a Retry-After header with the number of seconds to wait before retrying. Use exponential backoff with jitter for retry logic.
- Store
client_secretin environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.). - Never commit credentials to version control.
- Never expose credentials in client-side code (browser JavaScript, mobile apps).
The client credentials flow is designed for backend services. Never make token requests from end-user browsers or mobile apps -- the client secret would be exposed.
CORRECT: User browser --> Your backend --> Tote API
WRONG: User browser --> Tote API (exposes client_secret)- Store tokens in memory, not in databases or log files.
- Never log the full
access_tokenvalue. Log only the last 4 characters for debugging. - Use HTTPS for all requests (the API rejects HTTP).
- Rotate your
client_secretthrough the Developer Portal at least every 90 days. - After rotation, update your secrets store and request a new token with the new secret.
- The old secret is invalidated immediately upon rotation.
If your application runs on multiple servers:
- Use a shared cache (Redis, Memcached) to store the token so all instances share one token.
- Implement a lock or leader election for token refresh to avoid thundering herd on expiry.
- Ensure your shared cache is encrypted at rest and in transit.
- Getting Started Guide -- Quick start with your first three API calls.
- Menu Synchronization Guide -- Strategies for keeping menu data current.
- Modifiers Deep-Dive -- Understanding nested modifier groups.