Skip to content

Reference

Errors

Failures are part of the API surface, so they have a documented shape. Every error carries a status you can branch on, a machine-readable code you can switch on, a message meant for a human, and a request id that makes a support conversation short.

Stable codesRequest id on every response

Error shape

Branch on code, not on message. Codes are part of the contract and will not change under you; messages are written for people and get rewritten when they are unclear.

1HTTP 409
2{
3 "code": "email_already_exists",
4 "message": "A user with the email alan@foo-corp.example already exists.",
5 "request_id": "req_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
6 "documentation_url": "https://paycux.com/docs/errors#error-codes"
7}

Status codes

Standard HTTP semantics. A 4xx means the request will not succeed unchanged; a 5xx means it might succeed on a retry.

StatusMeaning
400Bad request
401Unauthorized
403Forbidden
404Not found
409Conflict
422Unprocessable entity
429Too many requests
500Server error
503Service unavailable

Error codes

The codes you are most likely to handle explicitly. New codes are added over time and announced in the changelog, so treat an unknown code as a generic failure of its status class rather than a crash.

CodeStatus
authentication_failed401
api_key_revoked401
environment_mismatch403
insufficient_permissions403
entity_not_found404
email_already_exists409
organization_domain_conflict409
role_slug_taken409
invalid_parameters422
connection_not_active422
directory_not_linked422
mfa_enrollment_required422
rate_limit_exceeded429
internal_error500

Validation errors

A 422 with invalid_parameters lists every field that failed, not just the first one, so a form can be corrected in a single pass.

1HTTP 422
2{
3 "code": "invalid_parameters",
4 "message": "The request could not be processed.",
5 "request_id": "req_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
6 "errors": [
7 { "field": "email", "code": "format", "message": "Must be a valid email address." },
8 { "field": "domain_data[0].domain", "code": "format", "message": "Must be a bare domain." },
9 { "field": "permissions", "code": "too_long", "message": "At most 100 permissions per role." }
10 ]
11}

Rate limits

Limits are applied per environment, which means your staging load testing cannot throttle production. The current budget is on every response, so you can back off before you are refused rather than afterwards.

ScopeLimit
Read endpoints (GET)600 requests
Write endpoints (POST, PUT, DELETE)180 requests
Authentication endpoints120 requests
Audit log exports10 requests
Bulk directory reads60 requests

Higher budgets are available on annual plans — talk to us before you build a queue you do not need.

rate limit headers
1HTTP 429
2Paycux-RateLimit-Limit: 600
3Paycux-RateLimit-Remaining: 0
4Paycux-RateLimit-Reset: 1781683260
5Retry-After: 17
6
7{
8 "code": "rate_limit_exceeded",
9 "message": "Too many requests. Retry after 17 seconds.",
10 "request_id": "req_01HQ8ZK3M4N5P6R7S8T9V0W1X2"
11}

Retrying safely

The official SDKs already retry 429 and 5xx responses with exponential backoff and jitter. If you are calling the API directly, four rules keep a retry from becoming an outage of your own making.

  • Never retry a 4xx other than 429 — the same request will fail the same way
  • Honour Retry-After when it is present instead of guessing an interval
  • Add jitter, so a shared failure does not turn into a synchronised stampede
  • Cap total attempts and surface the failure; an infinite retry hides a real problem
retry.ts
1async function withRetry(call, attempts = 5) {
2 for (let attempt = 1; attempt <= attempts; attempt++) {
3 try {
4 return await call();
5 } catch (error) {
6 const retryable = error.status === 429 || error.status >= 500;
7 if (!retryable || attempt === attempts) throw error;
8
9 const hinted = Number(error.headers['retry-after']) * 1000;
10 const backoff = Math.min(2 ** attempt * 250, 20000);
11 const jitter = Math.random() * 250;
12
13 await sleep(hinted || backoff + jitter);
14 }
15 }
16}

Request ids

Every response, successful or not, includes Paycux-Request-Id. Log it next to your own correlation id. When something goes wrong at three in the morning, that pair is the difference between a diagnosis and a guess.

Still stuck?

Send the request id, the endpoint and the timestamp. We can trace the call end to end from those three things alone.