Authentication
OAuth2 client-credentials authentication — minting tokens, scopes, IP allowlisting, secret rotation, and environment isolation.
The API uses the OAuth2 client-credentials grant — the standard flow for backend, server-to-server integrations. There is no browser or session login: your service exchanges a client id and secret for a short-lived bearer token, then sends that token on each request.
Credentials
Your CanopyWMS administrator issues you an API credential, which carries:
- a client id and client secret (the secret is shown once at creation);
- a set of scopes (least-privilege; see below);
- an optional IP allowlist (CIDR ranges that may use the credential);
- a rate limit (requests per minute);
- a tenant, and optionally a single pinned client (brand).
Credentials are per environment — sandbox credentials only work against
api.staging.canopywms.com, production credentials only against
api.canopywms.com.
Getting a token
POST /oauth/token with a client_credentials grant. Send the client id and
secret either as HTTP Basic auth or as form fields.
curl -X POST https://api.staging.canopywms.com/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=client_credentials{
"access_token": "eyJhbGciOiJIUzI1Ni..._jwt_...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "orders:read orders:write shipping:read"
}Tokens are valid for 1 hour (expires_in: 3600). A well-behaved
integration mints roughly one token per hour and caches it; the token endpoint
is rate-limited to protect against secret brute-forcing.
The scope in the response is what the token actually carries. Scopes and
the rate limit are clamped at issuance to what your tenant's partner-API
entitlement currently allows: a granted scope the entitlement no longer permits
is dropped from the token, and the per-minute rate is the lower of the
credential's limit and the entitlement's ceiling.
Token errors
The token endpoint answers in the RFC 6749 shape —
{ "error": "...", "error_description": "..." } — not the
{ "success": false, "error": "..." } envelope the rest of the API uses.
| Status | error | When |
|---|---|---|
400 | unsupported_grant_type | grant_type is anything other than client_credentials |
400 | invalid_request | client_id / client_secret missing, or sent both as Basic auth and as form fields |
401 | invalid_client | Unknown client id, wrong secret, or a revoked credential — deliberately undistinguished; the body is { "error": "invalid_client" } with a WWW-Authenticate: Basic header |
403 | ip_not_allowed | The secret verified, but the request came from outside the credential's IP allowlist |
403 | access_denied | Partner-API access is not enabled for your tenant — contact Canopy |
Calling the API
Send the token as a bearer token on every request:
curl https://api.staging.canopywms.com/api/orders \
-H "Authorization: Bearer $ACCESS_TOKEN"Token claims (tenant, client, scopes, rate limit, allowed IPs) are baked in at issuance, so each request is a fast signature check. Revoking a credential stops new tokens immediately; any token already minted stays valid until it expires (≤ 1 hour).
Scopes
Tokens are least-privilege: a credential only carries the scopes it was granted, and an endpoint rejects a token that lacks the required scope.
| Scope | Grants |
|---|---|
orders:read | Read orders and order history |
orders:write | Create and update orders, holds, releases, cancellations, splits, and reships |
receiving:read | Read inbound shipments / purchase orders |
receiving:write | Create and update inbound shipments / purchase orders |
shipping:read | Read shipments, tracking, and label costs |
shipping:write | Create labels and manage shipments |
inventory:read | Read SKUs and stock levels |
returns:read | Read returns / RMAs |
returns:write | Create returns / RMAs |
clients:read | Look up the tenant's clients, to scope other calls by client |
policy:read | Read a client's agent operating policy |
policy:write | Replace a client's agent operating policy (policy-editor surface, not for runtime credentials) |
billing:read | Read invoices, charges, adjustments and billing periods |
rate-cards:read | Read rate cards, their rules and pricing history (tenant-wide credentials only; client-pinned credentials are refused) |
A token may only call endpoints that explicitly accept API credentials and that match one of its scopes; everything else returns 403. This is fail-closed by design — management surfaces (issuing credentials, configuring channels, etc.) are never reachable with an API token.
Least privilege
Use the narrowest credential for the job: grant only the scopes you actually call, pin a brand's own integration to that brand (see below), and add an IP allowlist wherever your integration has stable egress addresses.
Client-pinned and tenant-wide credentials
A credential is either pinned to one client (a brand) or tenant-wide.
- A client-pinned credential is that brand. It can call the operations the
brand's own portal users can, and it reads each record the way they do: the
warehouse's own costs (label
shippingCost,rate,totalPaid, …), staff names, and bin, zone and tote locations are left out, and warehouse staff on notes and history read as "Warehouse team". Fields it does not receive are marked Omitted for a client-pinned credential in the Reference. Operations marked Tenant-wide credentials only refuse it with 403This action is restricted to warehouse staff. It only ever sees its own client;X-Client-Idis ignored. - A tenant-wide credential is the 3PL's own. It can act across every
client in the tenant, receives every documented field, and narrows a call to
one client with the
X-Client-Idheader.
So a brand's own integration — its storefront, ERP or returns tool — gets a
credential pinned to that brand. An integration the 3PL runs uses a tenant-wide
credential with X-Client-Id, even when it only ever works on one brand.
Tenant-wide credentials only: mark shipped / delivered, allocate, re-run
automation rules, who handled an order, carrier override, reship, create label,
update tracking, void label, stock by location, stock changes, the putaway
queue, create a return, the agent policy, and rate cards. A brand reads its
stock levels from GET /api/inventory/skus (totalStock, allocated,
available).
IP allowlisting
A credential can be restricted to a set of source IPs / CIDR ranges. The allowlist is enforced twice:
- at token issuance — a request from a non-allowlisted IP is refused with
403
ip_not_allowed(only after the secret verifies, so the allowlist never leaks whether a credential exists); and - on every request — the allowed IPs travel inside the token, and a call
from outside them is refused with 403
Request IP not allowed for this credential, even with a valid token.
An empty allowlist means unrestricted.
Secret rotation & revocation
- Rotate a secret (or revoke a credential) from the CanopyWMS admin settings. Revocation is immediate for new tokens.
- Because tokens are self-contained and live up to an hour, plan rotations with that overlap in mind.
Environment isolation
Sandbox and production are fully separate — separate base URLs, separate credentials, separate data. The sandbox mirrors production for the purchase-order lifecycle, order ingestion, label creation, and webhooks, so you can validate an integration end-to-end before switching the base URL and credential to production.
Next
With a token in hand, see Conventions for idempotency, errors, rate limits, and pagination — then Endpoints.