Errors
Every Partner API error comes back with an HTTP status code, and most carry a JSON body.
The status code is the reliable part. Body shapes vary across the API, and some server
errors are not JSON at all, so branch your error handling on the status code and treat the
body as diagnostic detail for your logs.
Status codes
| Code | Cause | What to do |
|---|---|---|
400 Bad Request | The request was malformed or failed validation — a missing or wrong Content-Type, unparseable JSON, a field that failed a schema or business rule, or a pagination value out of range. This is the most common error on the API. | Fix the request and send it again. Retrying it unchanged will fail the same way. |
401 Unauthorized | A failed login on POST /v1/users/login. No other endpoint returns 401 — everywhere else, authentication failures are 403. | Check the credentials. This endpoint is deprecated; new integrations should authenticate with x-api-key instead. |
403 Forbidden | Every other authentication and authorization failure, and requests to paths that do not exist. There are four distinct cases — see Telling the 403s apart. | Read the response body to work out which case you have hit. |
404 Not Found | The record you referenced does not exist. A record that exists but sits outside your key's scope generally returns 403 rather than 404, though this varies by endpoint — check the endpoint's reference page. | Check the id. Do not retry. |
405 Method Not Allowed | The endpoint exists but does not accept that HTTP method. | Check the method on the endpoint's reference page. A path that does not exist at all returns 403, not 405. |
409 Conflict | The same job seeker has already applied to that job, matched on email address. On POST /easy-apply this only happens when repeat applications are turned off for the team. | Treat this as "already applied" rather than a failure. Do not retry. |
412 Precondition Failed | On POST /easy-apply with "enqueue": false, the job does not exist or is not accepting applications. | Refresh your list of open jobs. Do not retry. |
422 Unprocessable Entity | The request was well-formed but semantically invalid. Returned by PUT /jobs/ai_chat/subscription/{team_id}; most other endpoints report validation problems as 400. | Fix the request and send it again. Do not retry unchanged. |
424 Failed Dependency | A service we depend on refused the request. In practice this is the address lookup provider hitting its own rate limit while you create or update a job. | Retry with backoff. |
429 Too Many Requests | You have exceeded the request rate allowed for your API key. | Back off exponentially with jitter, then retry. We do not send a Retry-After header. |
500 Internal Server Error | An unhandled error on our side. | Retry once with backoff. If it keeps happening, contact us with the timestamp and the request path. |
502 Bad Gateway | A service we depend on failed — document templates and résumé parsing are the usual sources. | Retry with backoff. |
503 Service Unavailable | A service we depend on was unreachable. | Retry with backoff. |
504 Gateway Timeout | The request took too long. Requests are cut off after 29 seconds, and the underlying database query has its own 30-second ceiling. This is the status to handle for any timeout. | Ask for less data. On GET /applicants, lower limit and page through with offset. |
Telling the 403s apart
403 covers more ground than you might expect, including requests that never reach your
data at all. The response body tells you which case you have.
| Response body | What happened | What to do |
|---|---|---|
{"message": "Forbidden"} | Your x-api-key header is missing, invalid, or not enabled for this environment. The request was rejected before it reached your data. | Check the header name and the key value. |
{"message": "Missing Authentication Token"} | The path or HTTP method does not exist. An unrecognised route returns 403, not 404 — this catches most typos. | Check the path spelling, any trailing slash, and the HTTP method against the reference. |
{"developer_message": "invalid permission for api!", "extra": null} | Your key is valid but is not permitted to call that endpoint. | Ask your Apploi contact to widen the key's permissions. |
{"developer_message": "requested team_id 42 doesn't belong to logged in user membership [7, 9]", "extra": null} | Your key is valid, but the team, job, or applicant you asked for is outside its scope. | Use an id your key can reach, or ask your Apploi contact to widen its scope. |
Error response shapes
The API does not use a single error envelope. You may receive any of these:
{ "message": "Forbidden" }{ "developer_message": "Object Not Found", "extra": null }{
"developer_message": "There were one or more field errors",
"errors": { "email": ["Not a valid email address."] }
}{
"developerMessage": "This application has already been submitted.",
"errors": { "application_form_id": 918273 }
}{
"error": "Validation Error",
"message": "Missing required parameters in request body",
"details": { "validationErrors": ["interviewer_id is required"] }
}Two things to guard against:
- A
500is not always JSON. Some server errors return an HTML page instead. Do not
assume a500body will parse — attempt it if you want the detail, but fall back to
treating the response as text rather than letting the parse failure mask the error. - The presence of a message field does not mean failure. A successful
POST /easy-applyreturns{"developer_message": "Submit Successful", ...}. Decide
success or failure from the status code, not from the body.
Retrying safely
Worth retrying — 424, 429, 500, 502, 503 and 504. Use exponential backoff
with jitter rather than a fixed delay.
Not worth retrying — 400, 401, 403, 404, 405, 409, 412 and 422. These
describe something about the request itself, so an identical retry fails identically.
There is no Retry-After header. We do not send one on 429 or on any other
response, so choose your own backoff schedule rather than waiting for a header that will
not arrive.
Is a retry safe?
GETandPUTrequests are safe to retry.POST /easy-applyis not idempotent, but duplicates are caught: a repeat submission for
the same email address and job returns409instead of creating a second application,
as long as repeat applications are turned off for that team.POST /jobsis not idempotent. If a request may have partially succeeded, check with
GET /jobsbefore sending it again rather than risking a duplicate posting.
Timeouts
Requests are cut off after 29 seconds. The most common cause is asking for too large a
page of applicants, and GET /applicants returns a specific hint when that happens:
{
"message": "Request timed out. Try requesting a smaller page by setting limit to 50 or less."
}limit on GET /applicants defaults to 30. Request pages of 50 or fewer and use
offset to page through large result sets — increment offset by limit on each request
until a page comes back with fewer than limit results.
Updated about 9 hours ago

