Developer Docs

← Back to Console

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

  1. Create an API key in the Equos admin console
  2. Exchange the API key for a JWT token from EQUOS ID using Client Credentials grant
  3. Include the JWT token in the Authorization header of API requests

Getting a Token

POST to the EQUOS token endpoint with the following parameters:

ParameterValueDescription
client_idYour application IDFrom the EQUOS admin console
client_secretYour API key secretShown once during API key creation
grant_typeclient_credentialsFixed value
resourcehttps://api.equos.oneResource indicator (API identifier). A logical identifier, not a URL that is fetched.
scopeorders:writeRequired 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: clientSecret is shown only once at creation
  • Use: Exchange clientId and clientSecret for 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_demo

Omit businessId to use your Firebase claim default. Override requires owning that business (or super admin).

Response fields:

  • id — Unique key identifier
  • businessId — On a list response, the business you requested, not the key's home business
  • businessIds — 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 label
  • prefix — First 8 chars of clientId (for identification)
  • scopes — Array of granted permissions
  • createdAt — 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 key
  • scopes (array, required) — One or more scopes (e.g., ["orders:write"])
  • expiresAt (string, optional) — Expiration date in RFC3339 format
  • businessId (string, optional) — Home business to bind; defaults to your claim businessId
  • businessIds (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

ScopeDescription
orders:readRead order data
orders:writeCreate and manage orders
orders:fulfilFinalize, 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:readRead inventory data
inventory:writeDeclare on-hand quantities and upsert/archive catalogue items
contacts:readRead contact data; export a shopper's personal data
contacts:writeErase a shopper's personal data (/e9v3/contacts/erase, /erase/shop)
warehouse:readReserved — not yet enforced by any endpoint
warehouse:writeReserved — not yet enforced by any endpoint
webhooks:manageManage 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).