Async Image Processing
Manual invoicing / no self-serve creditsPOST /v1/jobsUse 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
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"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(){
"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
Workflow to run. Use `detect` for species output and `filter` for animal/human/vehicle screening. Urban variants use the urban model route.
Optional
Required for direct upload. Send only the original file name, such as `img_001.jpg`; do not send image bytes here.
Required for direct upload. Use the same value when uploading bytes to the returned upload URL.
Alternative input mode. Use only when the image is already available at a public URL.
Optional direct-upload validation. Maximum async input size is 10MB.
Optional direct-upload validation checksum for the image bytes.
Optional ID from your system, such as a camera ID, image ID, or import row ID.
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
Required. Work ID used for submit, status, and result calls.
Required. Current processing state.
Optional. Temporary direct-upload target. Present when you create a job with `filename` and `content_type`.
Optional. Call this after direct upload succeeds. Public-URL inputs are submitted automatically.
Required. Poll this URL until processing reaches `succeeded`, `failed`, or `expired`.
Required. Fetch this URL after status is `succeeded`. The response matches the selected sync endpoint shape.
Status Codes
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.