Getting started
Errors & rate limits
Every error is JSON with an error string.
{
"error": "Validation failed.",
"details": [
{ "path": ["orderAmountCents"], "message": "Expected number, received string" }
]
}Status codes
| Code | Meaning | What to do |
|---|---|---|
| 400 | Malformed body or failed validation. | Fix the request. Retrying won't help. |
| 401 | Missing or invalid API key. | Check the Authorization header. |
| 403 | Key lacks the required scope. | Issue a key with the scope. |
| 404 | Not found — or not yours. | Verify the id came from this key's program. |
| 409 | Conflict: duplicate order, email, coupon, or slug. | Usually safe to treat as success. |
| 422 | Valid request, but nothing to act on. | Not a failure. Don't alert on it. |
| 429 | Rate limited. | Back off and retry. |
| 5xx | Something broke on our side. | Retry with backoff. |
Why 422 matters
A payout dispatch with nothing matured returns 422, not 200 or 500. That distinction is deliberate: “there was nothing to pay” is an ordinary Tuesday, and paging someone for it trains them to ignore the alert that matters.
Idempotency
POST /v1/conversions is idempotent on your orderId. Reporting the same order twice returns 409 instead of crediting the affiliate twice — enforced by a database constraint, not a check-then-insert, so concurrent retries cannot both win.
paid and are excluded from the next batch, so a retry pays only what genuinely didn't go out — but two concurrent dispatches can create two batches. Don't call it from a fan-out or an at-least-once queue.Retries
Retry 429 and 5xx with exponential backoff. Never retry 400, 401, or 403 — the same request will fail identically. Treat 409 on a conversion as success: it means the data is already recorded.
Rate limits
Authenticated endpoints are generously limited and you are unlikely to hit them from normal integration traffic. POST /v1/clicks is unauthenticated and therefore limited per IP by each program's own fraud settings — a rate-limited click still redirects the visitor normally, it just isn't recorded, so a bot cannot use the limit to break real links.