Errors and request ids

Every failed request answers with an HTTP status code and a JSON body. Every body carries the same top-level fields, whichever endpoint produced it, so one error handler covers the whole API.

Request ids

Every response, success or error, carries an X-Request-Id header. Error bodies repeat it as request_id. Log it with your own records and include it when writing to support@animaldetect.com: it lets us find the exact request, its parameters and its result. On a successful detection the request id is the same value as the result id.

Fields on every error

code
string

Machine-readable reason, always at the top level. One of the codes in the table below.

type
string

Error family: invalid_request_error, authentication_error, permission_error, billing_error, not_found_error, idempotency_error, rate_limit_error, processing_error or api_error. Branch on it when you do not need the exact code.

request_id
uuid

Identifies this HTTP exchange. It is also sent as the X-Request-Id header on every response, and on a successful detection it equals the result id. Quote it when writing to support@animaldetect.com.

doc_url
URL

Link to the row for this code on this page.

param
string

Present only when one request field is at fault, for example threshold or Idempotency-Key.

Two body shapes, same fields

Detection, filter, OCR and job endpoints return a flat body where error is the message string. The reference endpoints (species, countries, models) and GET /detect/{id} return a nested body where error is an object. Both shapes stay as they are for existing integrations; both carry the top-level fields above. Read code from the top level and you never need to know which shape you got.

Flat shape (detect, filter, OCR, jobs)
{
  "error": "Request validation failed",
  "code": "VALIDATION_ERROR",
  "details": {
    "errors": [
      { "field": "threshold", "message": "Threshold must be between 0.01 and 0.99" }
    ]
  },
  "type": "invalid_request_error",
  "request_id": "1d6492ab-f29e-480e-ae56-2133f3fb5429",
  "doc_url": "https://www.animaldetect.com/doc/api/errors#VALIDATION_ERROR",
  "param": "threshold"
}
Nested shape (reference routes, detect by id)
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Detection result with ID \"...\" was not found"
  },
  "code": "NOT_FOUND",
  "type": "not_found_error",
  "request_id": "7c1b0d2e-5e5a-4d0b-9f0e-3f9a4c1e8b21",
  "doc_url": "https://www.animaldetect.com/doc/api/errors#NOT_FOUND"
}
Python handling
import requests

response = requests.post(
    "https://api.animaldetect.com/v1/detect",
    headers={
        "Authorization": "Bearer " + API_KEY,
        "User-Agent": "MyApp/1.0 (ops@example.com)",
    },
    files={"image": open("camera-trap.jpg", "rb")},
)

if response.status_code == 429:
    wait_seconds = int(response.headers.get("Retry-After", "30"))
elif not response.ok:
    error = response.json()
    # error["type"] is the family, error["code"] the exact reason,
    # error["param"] the field at fault when there is one.
    print(error["code"], error["request_id"], error["doc_url"])

Error types

invalid_request_error400, 413Fix the request; retrying the same payload will not help.
authentication_error401Check the key and the Authorization header.
permission_error403The key is valid but may not do this.
billing_error402Free quota used up, keys paused, or prepaid credit exhausted. Resolve in the dashboard.
not_found_error404Unknown or expired id.
idempotency_error409Idempotency-Key reused with a different payload. Use a new key.
rate_limit_error429Wait for Retry-After seconds, then retry.
processing_error422The image could not be processed.
api_error500, 503Our side. Retry with backoff and quote the request_id if it persists.

Error codes

The doc_url in an error body points at the matching row here.

400VALIDATION_ERRORInvalid input, query, or path parameters. `param` names the field when one is at fault.
400USER_AGENT_REQUIREDPOST requests must send a `User-Agent` header that identifies your application.
400INVALID_FILE_TYPEUnsupported image type. Send JPEG, PNG, WebP or BMP.
400INVALID_BASE64The `image` field is not valid base64 (raw or data URI).
400FILE_TOO_SMALLImage is smaller than 100 bytes.
400INVALID_URLThe URL could not be fetched as an image.
400SSRF_BLOCKEDThe URL points at a private or internal target. Only public URLs are fetched.
401MISSING_AUTH_HEADERNo `Authorization: Bearer sk_...` header.
401INVALID_KEY_FORMATAuthorization header malformed; keys start with `sk_`.
401KEY_NOT_FOUNDAPI key does not exist.
401KEY_REVOKEDAPI key has been revoked.
402FREE_QUOTA_EXHAUSTEDFree monthly quota used up and no pay-per-use billing enabled.
402PREPAID_CREDIT_EXHAUSTEDPrepaid credit used up; top up to resume.
402QUOTA_EXCEEDEDUsage quota exceeded for this request.
402API_KEYS_PAUSEDAPI keys paused for the organisation (unpaid invoice or spend limit reached).
403FORBIDDENThe key may not access this resource.
404NOT_FOUNDRequested resource was not found or has expired.
409IDEMPOTENCY_CONFLICT`Idempotency-Key` reused with a different payload, or it refers to an expired request.
413FILE_TOO_LARGEImage or request body exceeded the endpoint limit.
422IMAGE_PROCESSING_ERRORThe file could not be decoded as an image (corrupt or truncated). Do not retry with the same file.
429RATE_LIMIT_EXCEEDEDToo many requests for the current key, organisation throttle, or concurrency limit. Honour `Retry-After`.
500INTERNAL_ERRORUnexpected server failure. Quote the `request_id` when writing to support.
503DATABASE_ERRORDatabase temporarily unavailable. Retry with backoff.
503EXTERNAL_SERVICE_ERRORUpstream AI, storage, or rate-limiter service failure. Retry with backoff.

Retries

Retry 429 after the number of seconds in Retry-After, and 503 with exponential backoff. Never retry a 400, 401, 402 or 409 with the same payload. When you retry a detection, send the same Idempotency-Key so a request that did succeed is never billed twice.