Skip to content
Last updated

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 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:

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

FieldTypeRequiredDescription
grant_typestringYesMust be CLIENT_CREDENTIALS.
client_idstring (UUID)YesYour application's client ID.
client_secretstringYesYour application's client secret.

Response

Success (200 OK):

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkN2E4ZmJiMy0wN2Q0LTRlM2MtYjVmMi05YTZjOGIxZTBmMjMiLCJpYXQiOjE3MDY3MDAwMDAsImV4cCI6MTcwNjc4NjQwMH0.signature",
  "token_type": "BEARER",
  "expires_in": 86400
}

Response Fields

FieldTypeDescription
access_tokenstringBearer token for authenticating API requests.
token_typestringAlways BEARER.
expires_inintegerToken lifetime in seconds. Default is 86400 (24 hours).

Using the Token

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.

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:

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.

Tote APIYour ApplicationTote APIYour ApplicationApplication startsCache token + expiry timeReplace cached tokenReplace cached tokenalt[Token valid (< 23 hours old)][Token near expiry (> 23 hours old)][Token expired or 401 received]loop[Every API call]POST /auth/token{grant_type, client_id, client_secret}200 {access_token, expires_in: 86400}Check: is token valid?GET /locationsAuthorization: Bearer {token}200 {data: [...]}POST /auth/token{grant_type, client_id, client_secret}200 {access_token, expires_in: 86400}GET /locationsAuthorization: Bearer {new_token}200 {data: [...]}POST /auth/token{grant_type, client_id, client_secret}200 {access_token, expires_in: 86400}Retry original request200 {data}

Error Handling

Invalid Credentials (401)

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.

Invalid Request (400)

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.

Expired Token (401)

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.

Rate Limited (429)

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.

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