# Authentication 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. ## Obtaining Credentials [Contact us](mailto:developer@totepos.com) 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. ## Token Request Flow ### Request Send a `POST` request to the token endpoint with your credentials: ```bash 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..." }' ``` ### Request Fields | 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. | ### Response **Success (200 OK):** ```json { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkN2E4ZmJiMy0wN2Q0LTRlM2MtYjVmMi05YTZjOGIxZTBmMjMiLCJpYXQiOjE3MDY3MDAwMDAsImV4cCI6MTcwNjc4NjQwMH0.signature", "token_type": "BEARER", "expires_in": 86400 } ``` ### Response Fields | 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). | ## Using the Token Include the `access_token` in the `Authorization` header of every API request: ```bash 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. ## Token Caching Strategy 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:** 1. Request a token on application startup. 2. Cache the token in memory alongside its expiration time. 3. Reuse the cached token for all API calls. 4. Proactively refresh the token **1 hour before expiry** (at the 23-hour mark) to avoid failed requests during the refresh window. 5. If a request returns `401 AUTHENTICATION_ERROR`, treat the token as expired and request a new one immediately. **Pseudocode:** ```python 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_token ``` There is no refresh token flow. When a token expires, simply re-authenticate with your client credentials. ## Sequence Diagram The following diagram shows the complete authentication flow, from initial token request through token reuse and proactive refresh. ```mermaid sequenceDiagram participant App as Your Application participant API as Tote API Note over App: Application starts App->>API: POST /auth/token
{grant_type, client_id, client_secret} API-->>App: 200 {access_token, expires_in: 86400} Note over App: Cache token + expiry time loop Every API call App->>App: Check: is token valid? alt Token valid (< 23 hours old) App->>API: GET /locations
Authorization: Bearer {token} API-->>App: 200 {data: [...]} else Token near expiry (> 23 hours old) App->>API: POST /auth/token
{grant_type, client_id, client_secret} API-->>App: 200 {access_token, expires_in: 86400} Note over App: Replace cached token App->>API: GET /locations
Authorization: Bearer {new_token} API-->>App: 200 {data: [...]} else Token expired or 401 received App->>API: POST /auth/token
{grant_type, client_id, client_secret} API-->>App: 200 {access_token, expires_in: 86400} Note over App: Replace cached token App->>API: Retry original request API-->>App: 200 {data} end end ``` ## Error Handling ### Invalid Credentials (401) Returned when the `client_id` or `client_secret` is wrong. ```bash 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" }' ``` ```json { "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. ### Invalid Request (400) Returned when the request body is malformed or missing required fields. ```json { "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`. ### Expired Token (401) Returned when you use an expired token on any authenticated endpoint. ```json { "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. ### Rate Limited (429) Returned when you make too many token requests in a short period. ```json { "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. ## Security Best Practices ### Store secrets securely - Store `client_secret` in 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). ### Use server-to-server only 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) ``` ### Protect your tokens - Store tokens in memory, not in databases or log files. - Never log the full `access_token` value. Log only the last 4 characters for debugging. - Use HTTPS for all requests (the API rejects HTTP). ### Rotate credentials periodically - Rotate your `client_secret` through 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. ### Handle tokens in distributed systems 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. ## Next Steps - [Getting Started Guide](/online-ordering/guides/01-getting-started) -- Quick start with your first three API calls. - [Menu Synchronization Guide](/online-ordering/guides/03-menu-sync) -- Strategies for keeping menu data current. - [Modifiers Deep-Dive](/online-ordering/guides/05-modifiers) -- Understanding nested modifier groups.