Conventions
Idempotency, error schema, rate limits, pagination, filtering, and incremental sync — the rules that apply across every endpoint.
These rules apply across the whole API.
Idempotency
Every create endpoint (POST /api/orders, POST /api/receiving/shipments,
POST /api/shipping/labels) accepts an Idempotency-Key header. Send a
unique key (e.g. a UUID) per logical operation and you can safely retry on
timeouts or network errors without creating duplicates.
curl -X POST https://api.staging.canopywms.com/api/orders \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Idempotency-Key: 6f1a8c2e-..." \
-H "Content-Type: application/json" \
-d '{ "...": "..." }'Behavior:
| Situation | Result |
|---|---|
| First request with a key | Runs normally; the 2xx response is stored against the key. |
| Retry after success | Returns the original status and body verbatim, with an Idempotent-Replay: true header. |
| Concurrent duplicate (first still in flight) | 409 — A request with this Idempotency-Key is already in progress. |
| Same key reused for a different operation | 422 — Idempotency-Key was already used for a different operation. |
Only successful (2xx) responses are cached, so a transient validation error never "poisons" a key.
Domain-level idempotency for purchase orders
Purchase-order creates are also idempotent on your external PO number —
re-creating a PO with an externalPoNumber that already exists returns the
existing PO rather than a duplicate, independent of the Idempotency-Key
header. See Endpoints → Purchase orders.
Errors
Errors use a consistent JSON shape:
{
"success": false,
"error": "Human-readable message describing what went wrong"
}Conditions are distinguished by HTTP status:
| Status | Meaning |
|---|---|
400 | Validation error (malformed or missing fields) |
401 | Missing or invalid token |
403 | Token lacks the required scope, or the endpoint isn't credential-callable |
404 | Resource not found within your tenant/client scope |
409 | Idempotency conflict (duplicate in flight) |
422 | Idempotency key reused for a different operation |
429 | Rate limit exceeded |
5xx | Transient server error — safe to retry with the same Idempotency-Key |
Rate limits
Each credential has its own requests-per-minute limit. Responses carry the standard rate-limit headers so you can pace yourself:
x-ratelimit-limit: 600
x-ratelimit-remaining: 591
x-ratelimit-reset: 42When you exceed the limit you get 429 with a retry-after header. The token
endpoint (/oauth/token) is separately limited per source IP. Need a higher
limit? Ask your CanopyWMS administrator — the per-credential limit is
configurable.
Pagination
List endpoints are paginated with page and limit (max 500) query
parameters, and return a pagination block:
{
"success": true,
"data": [ /* ...rows... */ ],
"pagination": { "page": 1, "limit": 50, "total": 1284, "totalPages": 26 }
}Most lists also accept sortBy, sortOrder (asc/desc), and search.
Filtering & incremental sync
List endpoints expose resource-specific filters (status, date range, and exact external-reference lookups — see each resource under Endpoints).
For incremental sync / changed-since polling, orders, purchase orders, and
shipments accept an updatedSince parameter (ISO-8601). Combine it with
sortBy=updatedAt&sortOrder=asc to walk every change since your last poll:
curl "https://api.staging.canopywms.com/api/orders?updatedSince=2026-06-29T00:00:00Z&sortBy=updatedAt&sortOrder=asc&limit=200" \
-H "Authorization: Bearer $ACCESS_TOKEN"Versioning
All endpoints live under the /api path. Event-type names and response fields
are treated as a stable, additive contract — new fields and new webhook
event types are added without breaking existing consumers, so build your
integration to ignore unknown fields.