Reference
Error reference
How the API reports problems — and what to do about each one.
Errors return a standard JSON body with an HTTP status and a message. Authentication failures additionally record a machine errorCode in your API request logs (the Where column shows log). Importantly, duplicates are not errors: a repeated conversion or webhook returns 202 with status: "DUPLICATE" and never double-counts.
{
"statusCode": 400,
"message": ["orderAmount must be a number string"],
"error": "Bad Request"
}Error codes
| Code | HTTP | Where | Meaning | What to do |
|---|---|---|---|---|
INVALID_API_KEY | 401 | log | Missing X-API-Key, or the key id is unknown/disabled/revoked. | Check the X-API-Key header and that the key is active in your dashboard. |
API_KEY_DISABLED | 401 | log | The key exists but has been disabled. | Re-enable the key or create a new one. |
API_KEY_REVOKED | 401 | log | The key has been permanently revoked. | Create a new key. |
MERCHANT_NOT_APPROVED | 401 | log | Your merchant account is suspended or rejected (logged as MERCHANT_INACTIVE). Live keys also require an approved account. | Contact support / complete approval. Use a test key while pending. |
INVALID_SIGNATURE | 401 | log | HMAC signature did not match (logged as SIG_BAD_SIGNATURE). | Sign `${timestamp}.${rawBody}` with HMAC-SHA256 (hex) using the exact bytes you send. |
EXPIRED_TIMESTAMP | 401 | log | X-Timestamp is outside ±300s of server time (logged as SIG_STALE_TIMESTAMP). | Sync your clock (NTP) and send a fresh timestamp per request. |
REPLAY_DETECTED | 401 | log | The exact signature was already used inside the tolerance window (logged as SIG_REPLAY). | Generate a new timestamp + signature for every request; do not resend identical signed requests. |
IP_NOT_ALLOWED | 401 | log | Request IP is not in the key’s IP allowlist (when configured). | Add your server IP to the key allowlist, or clear the allowlist. |
RATE_LIMITED | 429 | response | The key exceeded its per-minute or per-hour limit. | Back off and retry; request higher limits via support if needed. (No Retry-After header is sent.) |
DUPLICATE_REQUEST | 409 | response | A request with the same Idempotency-Key is still processing or already completed differently. | Reuse the stored response; do not change the body for a given Idempotency-Key. |
VALIDATION_ERROR | 400 | response | Body failed validation (missing/invalid fields or unknown properties). | Fix the fields listed in the `message` array. |
PRODUCT_NOT_FOUND | 404 | response | No such product for your merchant. | Verify the product id; import the product first. |
CAMPAIGN_NOT_FOUND | 404 | response | No such campaign for your merchant. | Verify the campaign id. |
MERCHANT_DOMAIN_INVALID | 400 | response | A product URL is not on your approved domain. | Set your approved domain and use product URLs on it. |
DUPLICATE_ORDER | 202 | graceful | A conversion for (merchant + externalOrderId) already exists. | None — the response has status DUPLICATE and no second commission is created. |
WEBHOOK_DUPLICATE_EVENT | 202 | graceful | A webhook with the same (merchant + X-External-Event-Id) was already received. | None — the response has status DUPLICATE; the event is processed exactly once. |
ORDER_STATUS_INVALID | 400 | response | The `status` value is not one of the accepted order statuses. | Use one of the documented order-status values. |
ORDER_STATUS_TRANSITION_DENIED | 202 | graceful | The request is accepted, but a disallowed commission transition (e.g. editing a PAID commission) is skipped during async processing. | None at request time — review the resulting commission status in your dashboard. |
PERMISSION_DENIED | 403 | response | The authenticated principal lacks permission (dashboard/admin routes). | Use the correct role/key for the operation. |
INTERNAL_ERROR | 500 | response | Unexpected server error. | Retry with backoff; contact support if it persists (include X-Request-ID). |
Rate limits
Each key is limited per-minute and per-hour (defaults 600/min and 30000/hour). Over the limit returns 429 with a message like Rate limit exceeded (600/min). No Retry-After header is sent — implement exponential backoff and retry. To request higher limits, contact support with your expected volume.
X-Request-ID header on every call and include it when contacting support — it’s echoed into your logs and makes any issue far faster to trace.