# 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.