Authentication
OAuth 2.0 Machine-to-Machine (M2M) Flow
Overview
The EQUOS API uses OAuth 2.0 machine-to-machine authentication for server-to-server communication. This guide shows how to authenticate your integration and manage API keys.
OAuth 2.0 Client Credentials Flow
- Create an API key in the Equos admin console
- Exchange the API key for a JWT token from EQUOS ID using Client Credentials grant
- Include the JWT token in the
Authorizationheader of API requests
Getting a Token
POST to the EQUOS token endpoint with the following parameters:
| Parameter | Value | Description |
|---|---|---|
client_id | Your application ID | From the EQUOS admin console |
client_secret | Your API key secret | Shown once during API key creation |
grant_type | client_credentials | Fixed value |
resource | https://api.equos.one | Resource indicator (API identifier). A logical identifier, not a URL that is fetched. |
scope | orders:write | Required scope for order creation |
Token Request Examples
import requests
# Get access token
response = requests.post(
"https://id.equos.one/oidc/token",
data={
"client_id": "your_client_id",
"client_secret": "your_client_secret",
"grant_type": "client_credentials",
"resource": "https://api.equos.one",
"scope": "orders:write"
}
)
token = response.json()["access_token"]
print(f"Token: {token}")const axios = require('axios');
const response = await axios.post(
'https://id.equos.one/oidc/token',
new URLSearchParams({
client_id: 'your_client_id',
client_secret: 'your_client_secret',
grant_type: 'client_credentials',
resource: 'https://api.equos.one',
scope: 'orders:write'
})
);
const token = response.data.access_token;
console.log('Token:', token);curl -X POST https://id.equos.one/oidc/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=your_client_id" \
-d "client_secret=your_client_secret" \
-d "grant_type=client_credentials" \
-d "resource=https://api.equos.one" \
-d "scope=orders:write"Token Response
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 3600,
"token_type": "Bearer"
}Using the Token
Include the token in the Authorization header of your API requests:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...Token Caching
Tokens expire after 1 hour (as shown in the expires_in response field). To avoid unnecessary token requests:
- Cache the token and check its expiration time before each request
- Only request a new token when the cached one is about to expire
- Do NOT request a new token for every API call
API Key Management
API keys are created, rotated, and revoked in the subscriber console under Integrations → API Keys. Use the business picker at the top of Integrations to choose which business the key belongs to. Each key is an OAuth 2.0 machine-to-machine application: it gives you the clientId / clientSecretpair you exchange for JWT tokens. See API Keys for the full management reference.
One key → one business by default. After token exchange, /e9v3 resolves the tenant from the key in Postgres. Existing single-business keys ignore X-Business-ID on public API calls. Account admins who own several businesses may mint a multi-business key; those keys requireX-Business-ID on each /e9v3 request, and the header must be one of the businesses bound at create time.
API Key Lifecycle
- Create: Generate a new API key with specific scopes (bound to the selected business)
- Obtain secret:
clientSecretis shown only once at creation - Use: Exchange
clientIdandclientSecretfor JWT tokens - Revoke: Delete keys that are no longer needed
- Expire: Keys can have optional expiration dates; expired keys return HTTP 403
GET /api/keys
List API keys for a business from the subscriber console (Integrations → API Keys), or via the console admin API:
GET /api/keys
GET /api/keys?businessId=biz_equos_demoOmit businessId to use your Firebase claim default. Override requires owning that business (or super admin).
Response fields:
id— Unique key identifierbusinessId— On a list response, the business you requested, not the key's home businessbusinessIds— Missing means a single-business key. Present means the key is shared. On a list response the array is only the business you requested.name— Human-readable labelprefix— First 8 chars of clientId (for identification)scopes— Array of granted permissionscreatedAt— Creation timestamp (RFC3339)expiresAt— Expiration timestamp (null = never expires)isActive— Whether the key is active
POST /api/keys
Create a new API key. The response includes clientSecret which is shown only once.
Request body:
name(string, required) — Display name for the keyscopes(array, required) — One or more scopes (e.g., ["orders:write"])expiresAt(string, optional) — Expiration date in RFC3339 formatbusinessId(string, optional) — Home business to bind; defaults to your claimbusinessIdbusinessIds(array, optional) — Two or more owned business ids to bind. Omit or send a single id for a classic one-business key.
Response includes:
clientId— Application ID (use in token requests)clientSecret— API secret (save securely, shown only now!)businessId— Business the key is bound to- All other fields from GET /api/keys response
DELETE /api/keys/:id
Revoke an API key immediately (owner only). A key can be bound to several businesses, and revoking it from any one of them stops it working for all of them. All requests with this key will return HTTP 403 api key revoked. Pass ?businessId= when revoking a key that is not under your claim default business. A missing businessIds means a single-business key and a present one means the key is shared. On a list response, businessId is the business you requested, not the key's home business.
DELETE /api/keys/:id?businessId=biz_equos_demo⚠️ ClientSecret Security
- Store in a secure secrets manager (AWS Secrets Manager, HashiCorp Vault, environment variables)
- Never commit to version control
- Never display in logs or UI
- If exposed, delete the key immediately and create a new one
- You cannot retrieve a lost clientSecret — create a new key
Available Scopes
| Scope | Description |
|---|---|
orders:read | Read order data |
orders:write | Create and manage orders |
orders:fulfil | Finalize, fulfil (create warehouse tasks) and commit orders. Higher risk; requires orders:write. Used on create (advance) and on POST /e9v3/orders/{type}/{orderId}/finalize|fulfil|commit. |
inventory:read | Read inventory data |
inventory:write | Declare on-hand quantities and upsert/archive catalogue items |
contacts:read | Read contact data; export a shopper's personal data |
contacts:write | Erase a shopper's personal data (/e9v3/contacts/erase, /erase/shop) |
warehouse:read | Reserved — not yet enforced by any endpoint |
warehouse:write | Reserved — not yet enforced by any endpoint |
webhooks:manage | Manage webhook subscriptions (order, shipment and inventory events) |
Security Best Practices
- Store API keys securely: Use environment variables, secrets managers, or secure vaults
- Rotate keys periodically: Create new keys and revoke old ones on a regular schedule
- Use different keys per environment: Separate keys for development, staging, and production
- Revoke unused keys: Remove API keys that are no longer in use
- Monitor token errors: Alert on repeated 401/403 errors which may indicate compromised credentials
Common Issues
401 Unauthorized
Cause: Token has expired or is invalid.
Fix: Request a new token and retry the request.
403 Forbidden
Possible causes:
- API key is revoked
- API key has expired
- Token scope is insufficient (must include
orders:write)
Fix: Check the error message in the response and create a new API key if needed.
Invalid Token Format
Cause: Missing or incorrect Authorization header format.
Fix: Ensure the header format is exactly: Authorization: Bearer <token> (with a space between "Bearer" and the token).