The Tote Online Ordering API lets third-party developers build online ordering experiences for convenience stores and fuel stations running Tote POS. With this API you can authenticate, browse store locations and menus, create carts, submit payments, and track order fulfillment.
This guide walks you through your first three API calls: authenticating, listing locations, and retrieving a menu.
Before you begin, make sure you have:
- Tote partner account -- Contact us to sign up as a partner.
- Base URL -- Your API base URL is provided during partner onboarding. Each partner receives a dedicated endpoint.
- Client ID and Client Secret -- Your API credentials are provided during partner onboarding alongside your base URL.
- curl (or any HTTP client) -- All examples in this guide use curl.
- HTTPS only -- All API requests must use HTTPS. HTTP requests are rejected.
Important: The URLs shown throughout these docs (e.g.,
https://sandbox.api.tote.ai/v1/online-ordering) are placeholders. Your actual base URL, client ID, and client secret will be provided when you complete partner onboarding. Replace the example URLs in all code samples with your assigned base URL.
Use the sandbox environment for development and testing. All examples in this guide use a sandbox-style URL.
Exchange your client credentials for an access token using the OAuth 2.0 client credentials flow.
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": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}'Response (200 OK):
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "BEARER",
"expires_in": 86400
}The access_token is valid for 24 hours (86,400 seconds). Save it -- you will include it in every subsequent request.
Tip: Cache your token and reuse it until it expires. Do not request a new token for every API call or you will trigger rate limiting.
Retrieve the locations accessible to your API credentials. Each location represents a physical store.
curl https://sandbox.api.tote.ai/v1/online-ordering/locations \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Response (200 OK):
{
"data": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "QuickStop #247",
"address": {
"street": "4521 Congress Ave",
"city": "Austin",
"state": "TX",
"postal_code": "78745",
"country": "US"
},
"phone": "+15125551234",
"timezone": "America/Chicago",
"online_ordering_enabled": true,
"business_hours": [
{ "day_of_week": "MONDAY", "open_time": "06:00", "close_time": "23:00", "is_closed": false },
{ "day_of_week": "TUESDAY", "open_time": "06:00", "close_time": "23:00", "is_closed": false }
]
}
],
"pagination": {
"has_more": true,
"next_cursor": "eyJpZCI6ImIyYzNkNGU1LWY2YTctODkwMS1iY2RlLWYxMjM0NTY3ODkwMSJ9"
}
}Use the online_ordering_enabled field to filter locations that accept online orders. To fetch the next page, pass the next_cursor value as a query parameter:
curl "https://sandbox.api.tote.ai/v1/online-ordering/locations?cursor=eyJpZCI6..." \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Retrieve the full menu for a specific location. The menu includes categories, items, and nested modifier groups.
curl https://sandbox.api.tote.ai/v1/online-ordering/locations/a1b2c3d4-e5f6-7890-abcd-ef1234567890/menu \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Response (200 OK):
{
"location_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"last_modified": "2026-01-15T14:30:00Z",
"version_hash": "sha256:a1b2c3d4e5f6",
"categories": [
{
"id": "c0000001-0000-0000-0000-000000000001",
"name": "Deli & Sandwiches",
"description": "Fresh-made sandwiches and deli items.",
"sort_order": 1,
"items": [
{
"id": "d0000001-0000-0000-0000-000000000001",
"name": "Build Your Own Sub Sandwich",
"description": "Choose your bread, protein, and toppings.",
"base_price": { "amount": 899, "currency": "USD" },
"available": true,
"modifier_groups": [ "..." ]
}
]
}
]
}Key fields in the menu response:
| Field | Description |
|---|---|
version_hash | Opaque hash for cache invalidation. Compare with your cached value to detect changes. |
categories[].items[] | Menu items within each category. |
base_price.amount | Price in cents. 899 means $8.99. |
modifier_groups | Customization options for each item (e.g., bread choice, toppings). |
The Tote Online Ordering API follows consistent conventions across all endpoints.
| Element | Convention | Example |
|---|---|---|
| JSON fields | snake_case | location_id, base_price, access_token |
| URL paths | kebab-case | /auth/token, /locations/{location_id}/menu |
| Enum values | SCREAMING_SNAKE_CASE | CLIENT_CREDENTIALS, MONDAY, BEARER |
| Schema names | PascalCase | TokenResponse, MenuItem, ModifierGroup |
| Format | Convention | Example |
|---|---|---|
| Resource IDs | UUID v4 | "a1b2c3d4-e5f6-7890-abcd-ef1234567890" |
| Timestamps | ISO 8601 UTC | "2026-01-15T14:30:00Z" |
| Monetary values | Integer cents (USD) | 899 = $8.99 |
| Phone numbers | E.164 | "+15125551234" |
| Timezones | IANA identifiers | "America/Chicago" |
List endpoints use cursor-based pagination:
# First page (default 20 results)
curl ".../locations"
# Next page
curl ".../locations?cursor=eyJpZCI6...&limit=50"The response includes a pagination object:
{
"pagination": {
"has_more": true,
"next_cursor": "eyJpZCI6..."
}
}When has_more is false, you have reached the last page.
All errors follow a consistent envelope format:
{
"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
}
}| Field | Description |
|---|---|
code | Machine-readable error category for programmatic handling. |
message | Human-readable description. |
detail | Additional context and resolution suggestions. |
request_id | Include this when contacting support. |
field | JSON pointer to the problematic field, or null for non-field errors. |
Error categories:
| Code | HTTP Status | When |
|---|---|---|
AUTHENTICATION_ERROR | 401, 403 | Invalid/expired token or insufficient permissions. |
INVALID_REQUEST_ERROR | 400 | Malformed request or validation failure. |
NOT_FOUND_ERROR | 404 | Resource does not exist. |
RATE_LIMIT_ERROR | 429 | Too many requests. Check the Retry-After header. |
CONFLICT_ERROR | 409 | Resource conflict (e.g., duplicate idempotency key). |
INTERNAL_ERROR | 500 | Server error. Retry with exponential backoff. |
- Authentication Guide -- Token lifecycle, caching strategy, and security best practices.
- Menu Synchronization Guide -- Strategies for keeping your menu data current.
- Modifiers Deep-Dive -- Understanding nested modifier groups with worked examples.