Detection jobs
Metered / image on completion/v1/jobsUse Detection jobs for 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.
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 job input size is 10 MB.
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 the Instant detection 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. This avoids sending large image files through the Detection API server. 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 Instant detection endpoint shape.
Status codes
Notes
- Direct upload is preferred for high-volume processing.
filenameis metadata only. Send a file name string such asimg_001.jpg; do not send file bytes inPOST /v1/jobs.- Use
source_urlonly for images that are already publicly reachable. - For retries, send one stable
Idempotency-Keyper image/job and reuse it only with the same payload. - Completed jobs are metered per processed image once the free monthly quota is used.
- Track job volume with
GET /v1/jobs/usage(organisation-wide today, this month, and all-time counts per operation, split into free-quota-covered and billable jobs, priced at your effective rate, plus the free quota left this month). Metered images from completed jobs also count inGET /v1/usage. - Multi-image jobs, OCR jobs, and webhooks are later-phase additions.
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 ?? 'Detection 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"
}