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

POST/jobsAuth: API key

Creates a labelling job. Reserves credit from your account up front; the job runs asynchronously.

Request body

FieldTypeDescription
name*stringUnique 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).
instructionstringQuestion 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_optionsobjectTask-type-specific config. Required for rating (`scale`) and multiple_choice (`mode`). See the task-type pages.
max_responses_per_datapointinteger (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*arrayMinimum 1. Each item has `media` (task-type-specific) and optional `context`.
stepsarray 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

StatusCause
400Unknown task_type; instruction and dimensions both set; malformed dimension_weights; or handler-level datapoint rejection (e.g. wrong candidate count).
402Insufficient credit. detail is an object with needed_credits and available_credits.
422Field-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).
503Task 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

GET/jobsAuth: API key

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

GET/jobs/{job_id}Auth: API key

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

FieldTypeDescription
statusstringOne 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_pausedbooleanWhether 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_datapointsintDatapoints not yet dispatched to annotators. Computed as total_datapoints minus every datapoint that already has a status.
ready_datapointsintactive + completed. Datapoints that have been dispatched. Partial results are fetchable for every ready datapoint.
completed_datapointsintHit `max_responses_per_datapoint`.
failed_datapointsintPermanently failed. Details in `errors[]`.
blocked_datapointsintDatapoints whose media did not pass content review. Terminal, like failed; excluded from `/results`.
total_responsesintCount 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_creditsintActual cost in credits accrued from total_responses at the job's per-response rate. Grows as responses arrive; not the full reservation.
credits_per_responseint | nullThe 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_creditsintLive 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_optionsobject | nullThe 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_filterobject | nullAudience gate set at creation, or null. Always present in the response.
chain_progressobject | nullChain 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_atstringPython-str datetime: 'YYYY-MM-DD HH:MM:SS.ffffff+00:00' (space separator, not ISO 'T').
errorsarray 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

StatusCause
404Job doesn't exist or belongs to another client.

Preview a job as an annotator

GET/jobs/{job_id}/previewAuth: API key

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

StatusCause
404Job doesn't exist or belongs to another client.
409The job exists, but no datapoint has finished preparation yet. Retry after the job becomes active.

Get job results

GET/jobs/{job_id}/resultsAuth: API key

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

FieldTypeDescription
pageinteger
default: 1
1-indexed page number.
per_pageinteger
default: 100
Results per page. No upper bound (unlike `/responses`, which caps `per_page` at 1000).
labelsboolean
default: 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:

FieldTypeDescription
media_idstringStable identifier of the media item.
typestring | nullMedia type — `image`, `video`, or `audio`.
rolestring | nullThe item's role in the task: `candidates`, `reference`, or `subject`. Null for pure-context media.
urlstringSigned 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_urlstringSigned 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

GET/jobs/{job_id}/responsesAuth: API key

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.

FieldTypeDescription
datapoint_indexintZero-indexed position in the original `datapoints[]` array.
responsestring | nullRaw 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_labelstring | nullHuman-readable form of `response` (e.g. an option's display text instead of its id).
response_labelsarray of string | nullStructured multi-value form, one entry per selection. Prefer this over splitting `response` on commas, since label text can itself contain a comma.
response_time_msint | nullHow long the annotator spent on the task.
annotator_idstringFormat `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_countrystring | nullISO country code of the annotator, when available.
annotator_country_namestring | nullHuman-readable country name, when available.
annotator_regionstring | nullSub-national region, when available.
annotator_citystring | nullCity, when available.
timestampstring | nullPython-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_indexint | nullChain jobs only. Zero-based step position within the chain. Null for standalone jobs.
task_typestring | nullChain 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

POST/jobs/{job_id}/pauseAuth: API key

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

POST/jobs/{job_id}/resumeAuth: API key

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

POST/jobs/{job_id}/completeAuth: API key

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

POST/jobs/{job_id}/cancelAuth: API key

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

POST/jobs/{job_id}/retryAuth: API key

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]
}
FieldTypeDescription
datapoint_indicesarray of intOptional. 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

StatusCause
400Job was created before retry support was added (no stored request payload).
400Job is cancelled, or a chain instance can't be retried because a sibling step was blocked during content review.
400Job is completed or blocked and has failed datapoints to retry.
404Job not found.

Next