Skip to content
Platform docsPlatform docsPlatform docs

Programmatic access (API)

Drive the labeling loop over HTTP — create a project and dataset, load items, bring a pipeline, run it, and export — with the beta Pipeline API, plus how to connect, the working model, and its sharp edges.

You can drive the full labeling loop over HTTP: create a project and a dataset, load items, bring a pipeline, run it, and export the results. The reference the API serves contains the endpoint schemas. To reconcile the same project from TypeScript source, use the CLI.

You need:

  • Access to a team in the Toloka platform.
  • An API key. You generate it yourself in the Business Console, the application the platform’s Top up and Billing history buttons open (see Manage your team and balance). Open your company there, switch to its API keys tab, and click Generate API key. The same tab lists your existing keys and deletes one.

The API is served at:

https://platform.toloka.ai/api/v2-beta

Authenticate every request with your API key in the Authorization header:

Authorization: ApiKey <your-api-key>

These two public references match the deployed version:

List your projects to check the connection. The call needs only a key and does not modify data:

curl -H 'Authorization: ApiKey <your-api-key>' \
https://platform.toloka.ai/api/v2-beta/projects

A working key returns your projects across your teams — an empty list if you have none. A 401 with Unauthenticated means the key or the header is wrong; see Troubleshooting.

  • Uploading items, running a pipeline, and exporting results are long-running jobs. Create the job, then poll the matching GET endpoint until it reports succeeded or failed.
  • Post rows directly to a dataset, up to 2 MB and 10,000 rows per call. For larger data, start an upload with a format of json or csv and declare the file size, which the upload holds you to. PUT the file to the returned link, finalize, and poll. Files run up to 1024 MB.
  • No endpoint assembles or edits a pipeline’s nodes. Import a .pipeline file — the API can produce one from an existing pipeline — or copy a pipeline outright. Then attach a dataset and start a run. The attach call takes the same itemLimit and itemFilter as the Start node’s controls. See Start and End nodes.
  • No endpoint reads dataset rows. Start an export job and fetch the result from the download link it returns.
  • The rate limit is 60 requests per minute, per endpoint. Every response carries the standard RateLimit-* headers. Going over returns 429.
  • After you cancel a run, GET /runs/:runId returns a failed status with the error Run canceled. Treat that as the expected cancellation outcome. Cancel works only once the run is deployed. Canceling during the deploy phase or canceling twice returns 409. The cancel body takes an optional pendingHumanQa: wait_for_review (the default, also for {}) or accept_without_review. With wait_for_review, items that wait for Human QA can end after the run reports failed. After 14 days on the node, accept_without_review applies to items that still wait, and you pay for the items that it accepts. With accept_without_review, they are accepted without review now, and you pay for them as for completed items. An unknown field or value returns 400. See Resume or cancel a run.
  • A run item returns state.output only when it completed. For a canceled item, state.partialOutput has the same shape and covers the fields the End node returns that had a value when the item was canceled, for example the outputs of an item accepted without review. It is null for other statuses and when no node wrote a field the End node returns.
  • An export job that fails currently stays in running. Poll it with your own timeout rather than waiting for a terminal status; the export status field carries only running and succeeded.
  • Deploying through the API passes the same lint gate as a UI deploy: a run is rejected until the built-in lints of every node on the path from Start to End have finished. Lints start after every graph change. POST /pipelines/:pipelineId/runs also starts the lints that have not run on the current graph and dataset items (for example after dataset items change) before it rejects the run, and runs the exam dataset lint again when it reported findings, so retry once they finish. It does not restart a lint that failed or timed out: start it again with POST /pipelines/:pipelineId/lints and poll the returned check jobs.
  • A dataset export over the API returns the dataset’s own fields, not the per-item run export documented in Export format.
  • State-changing requests (POST, PUT, PATCH, DELETE) must carry Content-Type: application/json and a JSON body, {} when the endpoint takes none. A request without a content type is treated as a cross-site form submission and rejected with 403 Forbidden: the request looks like a cross-site form submission. Non-browser clients must send Content-Type: application/json with a JSON body on state-changing requests.
  • Every response carries X-Request-Id: the value you sent when it is 1-128 characters of letters, digits, ., _ or -, otherwise a generated UUID. Error responses carry it too. Quote it when you report a problem; it is the key our logs are searched by.
  • The UI caps apply to the API too. A run processes at most 10,000 items. A dataset holds at most 150,000 items, and each field value can be up to 1 MiB. See labeling in batches and managing datasets.

A team that has not completed identity or business verification is on a trial: it may hold one project and one pipeline, and it cannot start a run. All three refusals come back as 403. Trial and verification explains how to lift them.

An API key acts as the user it belongs to. Access follows that user’s team membership: a key reads and writes only in the teams its user belongs to. A key whose every team is read-only gets 403 with You do not have access to this resource on a write. Otherwise a write against a read-only or foreign team is masked as 404 <Resource> not found — naming a foreign team explicitly, as when creating a project, is the one case that answers 403. A “not found” on an id you know exists usually means the wrong team.

Match the exact text your call returns:

ResponseCauseWhat to do
401 UnauthenticatedThe key is missing or invalid.Send Authorization: ApiKey <your-api-key>.
403 You do not have access to this resourceThe key’s user has no team with write access (a read-only key), or the call names a team the user is not on. Also returned when starting a run on a team that has not completed verification.Use a key for a user with write access on that team. If the team is on a trial, complete verification in the web app — see Trial and verification.
403 Your trial allows a single project. Verify your account to unlock full access.The team is on an unverified trial and already holds a project.Reuse the existing project, or complete verification to lift the limit.
403 Your trial allows a single pipeline. Verify your account to unlock full access.The team is on an unverified trial and already holds a pipeline.Build inside the existing pipeline, or complete verification to lift the limit.
429 Too many requests, please try again later.Over 60 requests in a minute on one endpoint.Wait RateLimit-Reset seconds, then retry.
400 Payload too large for an inline upload (max 2 MB); use the file upload endpoint for large data.An inline items body over 2 MB.Switch to the upload flow: start an upload, PUT the file, then finalize.
400 Cannot deploy pipeline (<n> validation error<s>): <messages>The pipeline failed validation when the run tried to deploy.Open it in the builder and fix the listed issues.
400 Pipeline graph validation failed (<n> errors): and one - node <name>, <setting path>: <reason> line per errorA graph write (PUT /pipelines/:pipelineId/graph) or pipeline import whose graph the platform rejects. At most 100 errors are listed, then - …and <n> more.Fix each listed setting of each listed node and send the graph again.
403 Forbidden: the request looks like a cross-site form submission. …A state-changing request without Content-Type: application/json.Send Content-Type: application/json and a JSON body, {} if the endpoint takes none.
400 deliveryOrderId is required for internal teams. …POST /projects for a team that is internal, without a Sunday delivery order.Pick an id from GET /delivery-orders and pass it as deliveryOrderId.
400 deliveryOrderId can only be set for internal teams.POST /projects passed a delivery order for a team that is not internal.Omit deliveryOrderId for that team.

For every other message, search Errors and how to fix them.