CanopyDocs

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.

Mint an access token
curl -X POST https://api.staging.canopywms.com/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials
Response
{
  "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.

StatuserrorWhen
400unsupported_grant_typegrant_type is anything other than client_credentials
400invalid_requestclient_id / client_secret missing, or sent both as Basic auth and as form fields
401invalid_clientUnknown client id, wrong secret, or a revoked credential — deliberately undistinguished; the body is { "error": "invalid_client" } with a WWW-Authenticate: Basic header
403ip_not_allowedThe secret verified, but the request came from outside the credential's IP allowlist
403access_deniedPartner-API access is not enabled for your tenant — contact Canopy

Calling the API

Send the token as a bearer token on every request:

Authenticated 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.

ScopeGrants
orders:readRead orders and order history
orders:writeCreate and update orders, holds, releases, cancellations, splits, and reships
receiving:readRead inbound shipments / purchase orders
receiving:writeCreate and update inbound shipments / purchase orders
shipping:readRead shipments, tracking, and label costs
shipping:writeCreate labels and manage shipments
inventory:readRead SKUs and stock levels
returns:readRead returns / RMAs
returns:writeCreate returns / RMAs
clients:readLook up the tenant's clients, to scope other calls by client
policy:readRead a client's agent operating policy
policy:writeReplace a client's agent operating policy (policy-editor surface, not for runtime credentials)
billing:readRead invoices, charges, adjustments and billing periods
rate-cards:readRead 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 403 This action is restricted to warehouse staff. It only ever sees its own client; X-Client-Id is 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-Id header.

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.

On this page