API errors
A failure carries an error object where a success would have carried data:
{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "customer.id is required for jobs. Look it up via GET /api/v1/customers.", "field": "customer.id" }, "request_id": "req_abc123", "timestamp": "2026-09-07T12:00:00Z"}Read error.code, not error.message. The code is the contract; the message is written for a human reading a log and can be reworded. On a validation failure, field names the offending field, which is usually enough to fix it without guessing.
HTTP statuses
Section titled “HTTP statuses”| Status | Meaning |
|---|---|
| 201 | Job or request created |
| 200 | A lookup succeeded, or an idempotent replay |
| 401 | Missing or invalid credentials, or a key that has expired or been revoked |
| 403 | The credential lacks permission — or the request came over HTTP |
| 409 | Duplicate external ID, or an Idempotency-Key whose original call is still running |
| 422 | Valid JSON, invalid contents — or an idempotency conflict |
| 429 | Rate limit exceeded, by the minute or by the hour |
| 500 | Something broke on the server |
| 501 | The endpoint is out of scope, reserved or not implemented |
200 on a create is not a mistake. It means your idempotency key has been seen before and you are getting the original answer back — the job exists, and it was not created twice. See idempotency.
Error codes
Section titled “Error codes”error.code | Status | Meaning |
|---|---|---|
| VALIDATION_ERROR | 422 | A field is missing or invalid — field says which |
| AUTH_ERROR | 401 | Missing, invalid, expired or revoked credential |
| PERMISSION_DENIED | 403 | The credential lacks permission |
| HTTPS_REQUIRED | 403 | The request came over HTTP — see if a key is exposed |
| DUPLICATE_EXTERNAL_ID | 409 | That external_id already exists for this resource |
| IDEMPOTENCY_CONFLICT | 422 / 409 | The key was reused with a different body (422), or while the first call was still running (409) |
| RATE_LIMITED | 429 | Too many requests — see Retry-After |
| NOT_IMPLEMENTED | 501 | Out of scope or not implemented |
| INTERNAL_ERROR | 500 | Server or database error |
This table is the whole list. Do not assume a code exists because it is a common convention elsewhere — handle these, and treat anything unexpected as a generic failure rather than matching on a code you hoped for.
501 is an answer, not a fault. It means you have called something this API does not do — reading a job back, updating one, deleting one. The scope is short on purpose.
Two examples
Section titled “Two examples”Rate limited:
{ "success": false, "error": { "code": "RATE_LIMITED", "message": "Minute rate limit exceeded. Retry after 42 seconds." }, "request_id": "req_abc123", "timestamp": "2026-09-07T12:00:00Z"}Sent over HTTP:
{ "success": false, "error": { "code": "HTTPS_REQUIRED", "message": "This API is available over HTTPS only" }, "request_id": "req_e9fb04dd8728f7c6", "timestamp": "2026-09-09T09:19:05Z"}Getting help
Section titled “Getting help”Always send the request_id. It identifies the exact call in the server’s logs, and without it a support conversation starts with trying to find the request rather than fixing it. That is why it is worth storing alongside whatever your integration created.
- Email support@eyeontask.com, or
- raise a ticket from inside the EyeOnTask Super Admin account.
Never put an API key or secret in a ticket. Support does not need it, and a credential in a ticket thread is a credential to revoke.
Contact support too when you need something the API does not do — the OpenAPI 3.0 specification, or a status-visibility workflow. Ask before you design around the gap rather than after.