Jobs
Create, inspect, pause, complete, cancel, and retry labelling jobs.
All endpoints below are prefixed with https://api.trydatapoint.com/data-labelling/v1 and require an X-API-Key header.
Create a job
Creates a labelling job. Reserves credit from your account up front; the job runs asynchronously.
Request body
| Field | Type | Description |
|---|---|---|
name* | string | Unique per account. Re-using an existing name returns the original job (idempotent). |
task_type | "comparison" | "i2v_comparison" | "rating" | "ranking" | "multiple_choice" | Defaults to `comparison`. See [task types](/docs/task-types). |
instruction | string | Question shown to annotators. Mutually exclusive with `dimensions`. |
dimensions | { [id: string]: string } | Multiple questions against the same datapoints. Mutually exclusive with `instruction`. |
dimension_weights | { [id: string]: number } | Optional when `dimensions` is set — omitting it weights every dimension equally. When supplied, keys must match `dimensions` and values must sum to 1.0 (within 0.01). |
response_options | object | Task-type-specific config. Required for rating (`scale`) and multiple_choice (`mode`). See the task-type pages. |
max_responses_per_datapoint | integer (1–10000) | Response count per datapoint per dimension. Defaults to 10. |
serving_environment | "all" | "sandbox" | "prod" | Which environments serve this job's tasks. `sandbox` is a free test pool (no credits charged); `prod` is the live annotator pool; `all` serves both. Defaults to the environment the request is made against. |
datapoints* | array | Minimum 1. Each item has `media` (task-type-specific) and optional `context`. |
steps | array of step objects (1–5) | Setting `steps` switches the job into a [chain](/docs/chains): each annotator answers the steps in order, with optional `skip_if` rules to end the walk early. When `steps` is set, the standalone fields `instruction`, `task_type`, `dimensions`, `dimension_weights`, and `response_options` must be omitted. |
Response (200 OK)
{
"job_id": "job_abc123def456",
"status": "processing",
"total_datapoints": 50,
"estimated_cost_credits": 2500,
"credits_per_response": 5,
"pricing": {
"base_credits_per_response": 5,
"demographic_surcharge_credits": 0,
"geo_surcharge_credits": 0,
"credits_per_response": 5
},
"created_at": "None"
}credits_per_response is the effective per-response rate this job was priced at — your account rate plus any audience-targeting surcharge — fixed at creation for the life of the job. pricing breaks that rate down; it is returned on fresh creations only (idempotent replays return the stored rate without the breakdown).
Error cases
| Status | Cause |
|---|---|
400 | Unknown task_type; instruction and dimensions both set; malformed dimension_weights; or handler-level datapoint rejection (e.g. wrong candidate count). |
402 | Insufficient credit. detail is an object with needed_credits and available_credits. |
422 | Field-level request validation failed (detail is an array of per-field errors), or request text that did not pass content review (detail.code is content_blocked). |
503 | Task dispatch failed (reservation is released automatically — safe to retry with a new name), or content review is temporarily unavailable (a Retry-After header tells you when to retry). |
Media that couldn't be reviewed because it was unreadable doesn't fail this call: it surfaces asynchronously as blocked datapoints in errors[] on GET /jobs/{job_id}. The create-time invalid_media error belongs to POST /media.
See Errors for the full envelope format and the content-review outcomes.
List jobs
Returns every job for the authenticated client, most recent first.
Response (200 OK)
{
"jobs": [
{
"job_id": "job_abc123def456",
"name": "hello-datapoint",
"task_type": "comparison",
"status": "completed",
"priority": "basic",
"serving_environment": "prod",
"max_responses_per_datapoint": 10,
"api_key_label": "production-server",
"created_at": "2026-04-21T12:34:56.123456+00:00",
"is_paused": false
}
],
"total": 1
}priority is assigned by Datapoint and is not settable on job creation; it defaults to "basic".
Get job status
Live counters on a single job: how many datapoints are in flight, completed, or failed.
Response (200 OK)
{
"job_id": "job_abc123def456",
"name": "hello-datapoint",
"status": "active",
"task_type": "comparison",
"serving_environment": "prod",
"total_datapoints": 50,
"processing_datapoints": 12,
"ready_datapoints": 38,
"completed_datapoints": 30,
"failed_datapoints": 0,
"blocked_datapoints": 0,
"total_responses": 412,
"max_responses_per_datapoint": 10,
"cost_credits": 2060,
"credits_per_response": 5,
"refundable_credits": 440,
"created_at": "2026-04-21 12:34:56.123456+00:00",
"errors": [],
"response_options": null,
"annotator_filter": null,
"is_paused": false
}Key fields
| Field | Type | Description |
|---|---|---|
status | string | One of processing, active, completed, failed, blocked, cancelled. Starts as 'processing' on creation; transitions to 'active' once tasks are dispatched. completed / failed / blocked / cancelled are terminal — poll until the status is terminal to know the job is done. Pause/resume does not change `status` — see `is_paused`. |
is_paused | boolean | Whether task serving is currently paused. Toggled by `POST /jobs/{id}/pause` and `POST /jobs/{id}/resume`. Independent of `status`: a job can be paused while in any non-terminal lifecycle state (cancel and complete also set it true). |
processing_datapoints | int | Datapoints not yet dispatched to annotators. Computed as total_datapoints minus every datapoint that already has a status. |
ready_datapoints | int | active + completed. Datapoints that have been dispatched. Partial results are fetchable for every ready datapoint. |
completed_datapoints | int | Hit `max_responses_per_datapoint`. |
failed_datapoints | int | Permanently failed. Details in `errors[]`. |
blocked_datapoints | int | Datapoints whose media did not pass content review. Terminal, like failed; excluded from `/results`. |
total_responses | int | Count of completed, billable responses on this job. For chain jobs, only walks that complete every step are counted; walks ended early by a step's `skip_if` rule are free and are not counted here. |
cost_credits | int | Actual cost in credits accrued from total_responses at the job's per-response rate. Grows as responses arrive; not the full reservation. |
credits_per_response | int | null | The per-response rate this job was priced at (account rate plus any audience-targeting surcharge), fixed at creation. Null on jobs created before per-job rates. |
refundable_credits | int | Live preview of what you'd get back if you cancelled or completed the job right now: the reserved estimate minus what the work collected so far would settle at. 0 once the job is settled (terminal), and always 0 for billing-exempt accounts. |
response_options | object | null | The job's task-type options (e.g. a rating scale or the multiple-choice mode). Null for task types that don't use them. Always present in the response. |
annotator_filter | object | null | Audience gate set at creation, or null. Always present in the response. |
chain_progress | object | null | Chain jobs only. `{ target_responses, completed_responses, screened_out_responses }`: `target_responses` is `max_responses_per_datapoint × chain instances`; `completed_responses` counts walks that finished every step (the only walks that bill); `screened_out_responses` counts walks ended early by a `skip_if` rule (free — never billed and never counted toward the target). Omitted entirely for standalone jobs. |
created_at | string | Python-str datetime: 'YYYY-MM-DD HH:MM:SS.ffffff+00:00' (space separator, not ISO 'T'). |
errors | array of { datapoint_index, error } | Per-datapoint failure reasons for failed and blocked datapoints. |
response_options and annotator_filter are always present, returned as null when unset rather than omitted. chain_progress is the only field that is dropped entirely — it appears only on chain jobs.
Error cases
| Status | Cause |
|---|---|
404 | Job doesn't exist or belongs to another client. |
Preview a job as an annotator
Returns a read-only sample in the same task-bundle contract used by the annotator application. The Preview tab on a job in the client dashboard uses this endpoint and the live annotator renderer, so you can refine instructions, labels, media layout, rating scales, and reference media before evaluating the collected results.
The sample contains up to 10 resolved standalone tasks, or the first resolved
instance of a chain. Both active and completed datapoints are eligible, so the
preview remains available after a job finishes. Calling it does not assign
work, create a session or response, consume credits, or depend on the job's
serving_environment.
Response (200 OK)
{
"data": {
"bundle_size": 1,
"reward": { "reward_type": "coin", "reward_amount": 1 },
"tasks": [
{
"task_id": "11111111-1111-4111-8111-111111111111",
"task_type": "rating",
"reward": { "reward_type": "coin", "reward_amount": 0 },
"payload": {
"prompt": "How accurately does this description match the reference image? A red shoe with white laces.",
"scale": [1, 2, 3, 4, 5],
"labels": { "1": "Not at all", "5": "Completely" },
"references": [
{
"id": "image_1",
"image_url": "https://api.trydatapoint.com/data-labelling/v1/media/11111111-1111-4111-8111-111111111111"
}
]
},
"chain_step": null,
"custom_ui_html_url": null
}
],
"is_chain": null,
"chain_length": null
}
}The response is marked Cache-Control: private, no-store. Treat generated
task_id values as preview-only; they cannot be submitted as assignments.
Error cases
| Status | Cause |
|---|---|
404 | Job doesn't exist or belongs to another client. |
409 | The job exists, but no datapoint has finished preparation yet. Retry after the job becomes active. |
Get job results
Aggregated per-datapoint results. Safe to call while a job is still running. Returns one row per dispatched datapoint that has not failed or been blocked. Datapoints with no responses yet come back with total_responses: 0 and null consensus fields.
Query parameters
| Field | Type | Description |
|---|---|---|
page | integerdefault: 1 | 1-indexed page number. |
per_page | integerdefault: 100 | Results per page. No upper bound (unlike `/responses`, which caps `per_page` at 1000). |
labels | booleandefault: false | When `true`, translates aggregation keys and `consensus` from option/candidate ids into their display text, and echoes the job's `response_options` on each row. Default `false` keeps stable ids. |
Response (200 OK)
{
"job_id": "job_abc123def456",
"status": "active",
"task_type": "comparison",
"page": 1,
"per_page": 100,
"total_results": 50,
"results": [
{
"datapoint_index": 0,
"context": null,
"media": [
{
"media_id": "b1e2c3d4-5678-90ab-cdef-1234567890ab",
"type": "image",
"role": "candidates",
"url": "/media/v2/b1e2c3d4-5678-90ab-cdef-1234567890ab?sig=...",
"thumbnail_url": "/media/v2/b1e2c3d4-5678-90ab-cdef-1234567890ab?preview=1&sig=..."
}
],
"votes": { "A": 7, "B": 3 },
"total_responses": 10,
"consensus": "A",
"confidence": 0.7,
"agreement_rate": 0.7,
"weighted_votes": { "A": 6.1, "B": 2.4 },
"weighted_consensus": "A",
"weighted_confidence": 0.718
}
]
}Result shape varies by task_type. The response declares the fields for every task type; the ones that don't apply to this job come back null, and task-type-specific keys are passed through as-is. See the task types for the exact fields per type. Chain jobs return one entry per datapoint with a per-step steps array (and media: null); see the chain result shape.
media[]
Each standalone result carries a media array describing the assets that were labelled:
| Field | Type | Description |
|---|---|---|
media_id | string | Stable identifier of the media item. |
type | string | null | Media type — `image`, `video`, or `audio`. |
role | string | null | The item's role in the task: `candidates`, `reference`, or `subject`. Null for pure-context media. |
url | string | Signed proxy path for the full asset, scoped to your account and valid for roughly one hour. It's a relative path — prepend the API base URL to fetch it. |
thumbnail_url | string | Signed proxy path for a smaller preview of the same asset, same scope and lifetime as `url`. |
Chain results carry media: null.
The weighted_* fields carry a second verdict computed alongside the equal-vote fields (weighted_votes / weighted_consensus / weighted_confidence, and their per-task-type analogues): the same tally, weighted by a platform-computed reliability score for each response. For multi-dimension jobs there is also a composite (weighted_score / overall_consensus) that combines the per-dimension results by the dimension_weights you set at creation.
Get raw responses
Per-annotator responses, one row per submission. Use this for audits, custom aggregation, or reconstructing which submitted asset was labelled A vs B.
Query parameters
Same page / per_page as /results, except per_page is capped at 1000.
Response (200 OK)
{
"job_id": "job_abc123def456",
"task_type": "comparison",
"page": 1,
"per_page": 100,
"total_responses": 412,
"total_pages": 5,
"total_annotators": 96,
"responses": [
{
"datapoint_index": 0,
"response": "A",
"response_label": "A",
"response_time_ms": 4832,
"annotator_id": "anon_8f2cd1a3e9",
"annotator_country": "US",
"annotator_country_name": "United States",
"timestamp": "2026-04-21 12:37:18.452731+00:00"
}
]
}Pagination is grouped by annotator: each page holds whole annotator response sets, so a page can contain more than per_page rows. Iterate using total_pages rather than computing the page count from total_responses / per_page.
| Field | Type | Description |
|---|---|---|
datapoint_index | int | Zero-indexed position in the original `datapoints[]` array. |
response | string | null | Raw submission. Task-type-specific: `"A"` or `"B"` (comparison), the rating value as a string (rating), comma-separated item ids like `"video_2,video_1,video_3"` (ranking), option id or comma-separated ids (multiple choice). |
response_label | string | null | Human-readable form of `response` (e.g. an option's display text instead of its id). |
response_labels | array of string | null | Structured multi-value form, one entry per selection. Prefer this over splitting `response` on commas, since label text can itself contain a comma. |
response_time_ms | int | null | How long the annotator spent on the task. |
annotator_id | string | Format `anon_` + 10 lowercase hex chars. A stable, anonymized identifier — the same annotator keeps the same id across all your jobs, without revealing their real identity. |
annotator_country | string | null | ISO country code of the annotator, when available. |
annotator_country_name | string | null | Human-readable country name, when available. |
annotator_region | string | null | Sub-national region, when available. |
annotator_city | string | null | City, when available. |
timestamp | string | null | Python-str datetime of when the response completed (falls back to created_at if not yet completed): `'YYYY-MM-DD HH:MM:SS.ffffff+00:00'`. |
step_index | int | null | Chain jobs only. Zero-based step position within the chain. Null for standalone jobs. |
task_type | string | null | Chain jobs only. The step's task type (e.g. `"multiple_choice"`, `"rating"`). Null for standalone jobs; use the top-level `task_type` instead. |
Pause a job
Stops serving any new tasks for the job. In-flight responses continue to come in. Returns 400 if the job is already terminal (completed, failed, blocked, or cancelled) or already paused.
Response (200 OK)
{
"job_id": "job_abc123def456",
"is_paused": true,
"status": "active"
}is_paused is a separate flag from status. The job's lifecycle status stays the same across pause/resume; only is_paused toggles.
Resume a job
Re-activates a paused job. Task serving resumes immediately. Returns 400 if the job is already terminal (completed, failed, blocked, or cancelled) or is not paused.
Response (200 OK)
{
"job_id": "job_abc123def456",
"is_paused": false,
"status": "active"
}Complete a job early
Ends a running job early. The job flips to terminal completed with whatever responses it has collected so far: you're charged for those, the unused reserve is released back to your balance, and, if any responses were collected, its report is generated from the partial data. Unlike cancel, completing dispatches report generation. (A cancelled job stays visible in GET /jobs and the dashboard either way; it just never gets a report.)
Returns 400 if the job is already terminal, or if it's still processing (no datapoints have been dispatched yet). 404 if the job doesn't exist or belongs to another client.
Response (200 OK)
{
"job_id": "job_abc123def456",
"status": "completed",
"is_paused": true,
"cost_credits": 2060,
"released_credits": 440
}Cancel a job
Permanently cancels a job and settles its reserved credits. Serving stops immediately, you're charged only for the responses already collected, and the unused reserve is released back to your balance. Cancellation is irreversible, and unlike complete it does not generate a report.
Returns 400 if the job is already terminal (completed, failed, blocked, or cancelled). 404 if the job doesn't exist or belongs to another client.
Response (200 OK)
{
"job_id": "job_abc123def456",
"status": "cancelled",
"is_paused": true,
"cost_credits": 640,
"released_credits": 1860
}Retry failed datapoints
Re-queues failed datapoints. Pass datapoint_indices to target specific failures; omit the body to retry all. If the job itself had flipped to failed, retrying flips it back to active.
Request body
{
"datapoint_indices": [3, 7, 12]
}| Field | Type | Description |
|---|---|---|
datapoint_indices | array of int | Optional. Zero-indexed positions in the original `datapoints[]` array to retry. Omit to retry all failed. |
Response (200 OK)
{
"job_id": "job_abc123def456",
"retried": 3,
"datapoint_indices": [3, 7, 12]
}If no datapoints are failed (or none match the requested indices), the endpoint returns 200 with retried: 0 and datapoint_indices: []. That's not an error.
Error cases
| Status | Cause |
|---|---|
400 | Job was created before retry support was added (no stored request payload). |
400 | Job is cancelled, or a chain instance can't be retried because a sibling step was blocked during content review. |
400 | Job is completed or blocked and has failed datapoints to retry. |
404 | Job not found. |
Next
- Media: upload reference
- Billing: balance, checkout, and history
- Survey Planner: natural-language job generation