Getting Started > Error Codes
Error Codes
Last Updated: 2026-07-02
Every error response has a machine-readable code. Branch your retry logic on the code — never the human-readable message.
Error shape
All error responses share this envelope:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "title is required"
},
"requestId": "01HXYZ..."
} The code is the contract. The message may change between versions — don't pattern-match on it. The requestId is your tracking ID when reporting bugs to our support team.
Error code catalog
| Code | HTTP | When you'll see it |
|---|---|---|
BAD_REQUEST | 400 | Malformed request (missing required header, invalid query param) |
INVALID_JSON | 400 | Body parse failed (malformed JSON) |
VALIDATION_ERROR | 400 | Body schema rejected (field missing, wrong type, out of bounds) |
PAYLOAD_TOO_LARGE | 413 | Body exceeded 1 MB (10 MB for /admin/uploads) |
UNAUTHORIZED | 401 | Missing or invalid credentials / signature |
SELLER_TOKEN_REQUIRED | 401 | Endpoint needs an access token (see OAuth flow) |
HMAC_VERSION_DEPRECATED | 401 | Using HMAC v1; migrate to v2 (binds method + body) |
SIGNATURE_REPLAYED | 401 | Same signature was used within the freshness window — re-sign with a fresh timestamp |
FORBIDDEN | 403 | Auth succeeded but the actor lacks the required scope |
NOT_FOUND | 404 | Resource does not exist — or exists but does not belong to your seller (we return 404 not 403 to avoid leaking existence) |
RATE_LIMITED | 429 | Per-minute quota tripped — see Retry-After header |
SHIP_FAILED | 400 | Upstream rejected the ship action (state transition not allowed) |
CANCEL_FAILED | 400 | Upstream rejected the cancel action |
CREATE_FAILED | 502 | Upstream rejected the return-creation (see safe message in response) |
APPROVE_FAILED | 400 | Upstream rejected the return-approval or cancellation-approval |
REJECT_FAILED | 400 | Upstream rejected the return-rejection or cancellation-rejection |
DISPUTE_FAILED | 400 | Upstream rejected the return dispute (POST /open/v1/returns/:id/dispute) |
INVALID_TRANSITION | 400 | Tried to ship an already-cancelled order, etc. |
ORDER_NOT_CANCELLABLE | 422 | Order has shipped or completed — seller cancellation refused (POST /orders/:id/cancel) |
CANCELLATION_ALREADY_PROCESSED | 422 | Cancellation request was already approved, rejected, or auto-approved |
CANCELLATION_DEADLINE_EXCEEDED | 422 | Cancellation response window expired — buyer is entitled to auto-approval |
SSRF_CHECK_FAILED | 400 | Webhook URL points to a private/internal address |
NOT_IMPLEMENTED | 501 | Endpoint reserved for a future version (e.g. /open/v1/payouts) |
MAINTENANCE | 503 | Platform is in maintenance mode — retry shortly |
INTERNAL_ERROR | 500 | Bug on our end — include the requestId from the response when reporting |
Cross-tenant 404 — why not 403
If you look up an order ID that exists but belongs to a different seller, we return 404 NOT_FOUND, not 403 FORBIDDEN. This is deliberate — 403 leaks that the resource exists. Partners might use that for enumeration.
If you're sure the ID is yours but getting 404, check:
- Are you using the right access token for the right seller?
- Did the order's seller revoke your authorization?
POST /open/v1/auth/revokeflips all subsequent reads to404. - Is the access token expired? Refresh via
POST /open/v1/auth/token/refresh.