Loading...
Animal Detect logo

API endpoint update

New integrations should use . Existing URLs will keep working for the next few months while teams migrate.

Async Image Processing

Manual invoicing / no self-serve credits
POST /v1/jobs

Use async jobs for approved high-volume workflows such as camera fleets, scheduled imports, partner integrations, or monthly volumes in the tens of thousands. Create a job, upload or provide the image, submit it, then fetch the result when processing finishes.

Manual onboarding required

Async jobs are enabled only for approved API customers and are billed outside the self-serve credit system. Use the synchronous endpoints for first tests, debugging, and low-volume immediate responses.

Examples

cURL
export AD_API_KEY="sk_..."
export AD_API_BASE_URL="https://api.animaldetect.com/v1"
export IMAGE_PATH="/path/to/img_001.jpg"

curl -sS -X POST "$AD_API_BASE_URL/jobs" \
  -H "Authorization: Bearer $AD_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: camera-a-img-001" \
  -d '{
    "operation": "detect",
    "filename": "img_001.jpg",
    "content_type": "image/jpeg",
    "client_reference_id": "camera-a/2026-06-17/img_001",
    "options": { "threshold": 0.2, "classify": true }
  }' > job.json

UPLOAD_URL="$(jq -r '.upload.url' job.json)"
SUBMIT_URL="$(jq -r '.submit_url' job.json)"
STATUS_URL="$(jq -r '.status_url' job.json)"
RESULT_URL="$(jq -r '.result_url' job.json)"

curl -sS -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/jpeg" \
  --upload-file "$IMAGE_PATH"

curl -sS -X POST "$SUBMIT_URL" \
  -H "Authorization: Bearer $AD_API_KEY"

curl -sS "$STATUS_URL" \
  -H "Authorization: Bearer $AD_API_KEY"

curl -sS "$RESULT_URL" \
  -H "Authorization: Bearer $AD_API_KEY"
Node.js / Express
const apiKey = process.env.ANIMAL_DETECT_API_KEY
const baseUrl = 'https://api.animaldetect.com/v1'
const imageFile = new File([imageBytes], 'img_001.jpg', { type: 'image/jpeg' })

const createResponse = await fetch(baseUrl + '/jobs', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + apiKey,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'camera-a-img-001',
  },
  body: JSON.stringify({
    operation: 'detect',
    filename: imageFile.name,
    content_type: imageFile.type,
    expected_size_bytes: imageFile.size,
    client_reference_id: 'camera-a/2026-06-17/img_001',
    options: {
      threshold: 0.2,
      classify: true,
    },
  }),
})

const job = await createResponse.json()

await fetch(job.upload.url, {
  method: job.upload.method,
  headers: {
    ...job.upload.headers,
    'Content-Type': imageFile.type,
  },
  body: imageFile,
})

await fetch(job.submit_url, {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + apiKey,
  },
})

let status
do {
  await new Promise((resolve) => setTimeout(resolve, 2000))
  const statusResponse = await fetch(job.status_url, {
    headers: { Authorization: 'Bearer ' + apiKey },
  })
  status = await statusResponse.json()
} while (status.status === 'queued' || status.status === 'running')

if (status.status !== 'succeeded') {
  throw new Error(status.error?.message ?? 'Async job failed')
}

const resultResponse = await fetch(job.result_url, {
  headers: { Authorization: 'Bearer ' + apiKey },
})
const result = await resultResponse.json()
Example Response
{
  "job_id": "5fd3825f-46a3-4892-8600-44a319e2d18f",
  "status": "awaiting_upload",
  "operation": "detect",
  "client_reference_id": "camera-a/2026-06-17/img_001",
  "upload": {
    "method": "PUT",
    "url": "https://storage.googleapis.com/animal_detect_prod/users/...",
    "headers": {
      "Content-Type": "image/jpeg"
    },
    "expires_at": "2026-06-17T13:15:00.000Z"
  },
  "submit_url": "https://api.animaldetect.com/v1/jobs/5fd3825f-46a3-4892-8600-44a319e2d18f/submit",
  "status_url": "https://api.animaldetect.com/v1/jobs/5fd3825f-46a3-4892-8600-44a319e2d18f",
  "result_url": "https://api.animaldetect.com/v1/jobs/5fd3825f-46a3-4892-8600-44a319e2d18f/result",
  "expires_at": "2026-07-17T13:00:00.000Z"
}

Parameters

Required

detect | detect_urban | filter | filter_urban

Workflow to run. Use `detect` for species output and `filter` for animal/human/vehicle screening. Urban variants use the urban model route.

Optional

string

Required for direct upload. Send only the original file name, such as `img_001.jpg`; do not send image bytes here.

image/jpeg | image/png | image/webp | image/heic | image/heif

Required for direct upload. Use the same value when uploading bytes to the returned upload URL.

URL string

Alternative input mode. Use only when the image is already available at a public URL.

integer

Optional direct-upload validation. Maximum async input size is 10MB.

64-character hex string

Optional direct-upload validation checksum for the image bytes.

string

Optional ID from your system, such as a camera ID, image ID, or import row ID.

object

Processing options from sync detect/filter endpoints: threshold, country, classify, metadata, smooth_herd, top_candidate, latitude, and longitude.

Request Notes

Recommended flow: call `POST /v1/jobs` with `filename` and `content_type`, upload image bytes to the returned `upload.url` with `PUT`, then call `submit_url`. If the image is already public, send `source_url` instead and the job is queued immediately.

Response Fields

uuid

Required. Work ID used for submit, status, and result calls.

awaiting_upload | queued | running | succeeded | failed | expired

Required. Current processing state.

object

Optional. Temporary direct-upload target. Present when you create a job with `filename` and `content_type`.

URL string

Optional. Call this after direct upload succeeds. Public-URL inputs are submitted automatically.

URL string

Required. Poll this URL until processing reaches `succeeded`, `failed`, or `expired`.

URL string

Required. Fetch this URL after status is `succeeded`. The response matches the selected sync endpoint shape.

Status Codes

201Job created.
200Idempotent replay returned an existing job.
400Validation error.
401Invalid, missing, or revoked API key.
409Idempotency key was reused with a different payload.
429Rate limit exceeded.
503Database, storage, queue, or worker dependency temporarily unavailable.

Notes

  • Direct upload is preferred for high-volume processing.
  • `filename` is metadata only. Send a file name string such as `img_001.jpg`; do not send file bytes in `POST /v1/jobs`.
  • Use `source_url` only for images that are already publicly reachable.
  • For retries, send one stable `Idempotency-Key` per image/job and reuse it only with the same payload.
  • Async jobs use the agreed manual billing arrangement, not self-serve credits.
  • Batch async, OCR async, and webhooks are later-phase additions.