API Reference
Programmatic access to Pipelines via the External API v1.
Pipelines exposes a small, focused REST API — the External API v1 — for programmatic task creation and status polling.
If anything in this page disagrees with the live OpenAPI spec at /api/v1/openapi.json, treat the spec as the source of truth.
What the External API can do
| Capability | Endpoint |
|---|---|
| Health check (no auth) | GET /health |
| Fetch workflow metadata | GET /workflows/{workflow_id} |
| Fetch the field schema required to create tasks | GET /workflows/{workflow_id}/schema |
| Create 1–5,000 tasks from JSON | POST /workflows/{workflow_id}/tasks |
| List / paginate tasks in a workflow | GET /workflows/{workflow_id}/tasks |
| Get a single task's status and node progress | GET /workflows/{workflow_id}/tasks/{task_id} |
| Start a ZIP-based task creation upload | POST /workflows/{workflow_id}/tasks/upload |
| Confirm a ZIP upload and start processing | POST /workflows/{workflow_id}/tasks/upload/{upload_id}/confirm |
| Poll ZIP-upload processing status | GET /workflows/{workflow_id}/tasks/upload/{upload_id} |
The following are not in the External API: creating/editing/activating pipelines, creating projects or organizations, editing or deleting tasks, querying datasets, and triggering evaluation runs. Use the web app for those.
Base URL
https://api.pipelines.tech/api/v1Authentication
Every endpoint except GET /health requires an organization-scoped API key. Keys are the literal prefix pk_live_ followed by 40 hex characters (48 characters total) and are scoped to a single organization — they can only access workflows inside the org they were created in.
Pass the key as either Authorization: Bearer <key> or X-API-Key: <key> — both schemes are accepted.
curl -H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
https://api.pipelines.tech/api/v1/workflows/42Missing, malformed, expired, or revoked keys return 401 with error code missing_api_key (no key in the request) or invalid_api_key (wrong prefix, not found, expired, or revoked). Revocation takes effect immediately.
Creating an API key
Only Org Admin users can manage API keys. Create Key under Organization Keys (in the admin API Keys page) takes:
- Key name (required) — used to identify and revoke the integration later.
- Expiry — one of No expiry, 30 days, 90 days, or 1 year, measured from creation.
The full pk_live_... value is shown once on creation and cannot be retrieved afterward. If lost, revoke and create a new key.
After creation, only the last 4 characters of the key are ever shown again. Treat the full pk_live_... value the same way you would any other production secret.
After a key exists
From the Organization Keys table you can:
- Update expiry — pick a new duration (30 days, 90 days, 1 year, or No expiry). The new expiration is always computed from now, so picking "30 days" on a key that already has a year left will shorten it to 30 days from today. Updating expiry on a revoked key returns
400. - Revoke — permanently disables the key. Revocation is immediate and cannot be undone. Revoked keys remain in the list for audit and sort below active keys; re-revoking is a no-op.
- See Last Used, Expires, Created, and Created By columns.
Last Usedis debounced to roughly once per minute per key, so a freshly-used key may take a moment to refresh.
There is no rename or rotate action today. To rotate a key, revoke the old one and create a new one.
API keys carry the identity of the user who created them. If that user is later removed, the three ZIP-upload endpoints (init, confirm, and status) start returning 422 because they can no longer resolve an uploader identity; in that state, revoke the key and have an active Org Admin create a new one. JSON task creation and status endpoints do not depend on the creator identity and continue to work.
Rate limiting
Each API key has its own rate-limit bucket. /health and other unauthenticated paths are bucketed by API key when a key is supplied, and by client IP otherwise. Repeated failed auth attempts against protected paths are rate-limited per client IP to mitigate brute-forcing.
On 429 Too Many Requests, the Retry-After header and body field retry_after both return the wait in integer seconds. The exact per-key limit is configured per deployment; treat Retry-After as authoritative.
Request size limits
POST /workflows/{workflow_id}/tasks— up to 5,000 tasks per request.- All JSON endpoints — request body capped at 32 MB. Exceeding the cap returns
413 payload_too_large. - ZIP archives do not count against this limit; they upload directly to cloud storage via a presigned URL (see the upload flow below).
Error responses
All errors use this shape:
{
"error": "validation_error",
"message": "tasks: ensure this value has at most 5000 items",
"retry_after": 30
}erroris a machine-readable code. Stable values:missing_api_key,invalid_api_key,invalid_request,unauthorized,forbidden,not_found,payload_too_large,validation_error,rate_limit_exceeded,internal_server_error,service_unavailable,api_error.messageis a human-readable description.retry_afteris present only on429responses.
Status code mapping:
| Code | error | Typical cause |
|---|---|---|
400 | invalid_request | Malformed pagination cursor, invalid status filter value |
401 | missing_api_key / invalid_api_key | No key, wrong prefix, revoked, expired, or unknown key |
404 | not_found | Workflow, task, or upload doesn't exist in the key's org |
409 | api_error | ZIP upload already confirmed |
413 | payload_too_large | Request body > 32 MB |
422 | validation_error | Body failed schema validation, or workflow is in the wrong state |
429 | rate_limit_exceeded | Rate limit hit (per-key, per-IP, or brute-force bucket) |
500 | internal_server_error | Server-side error |
503 | service_unavailable | Downstream worker unavailable — safe to retry with backoff |
Successful responses are 200 OK for GETs and POST /tasks, and 202 Accepted for POST /tasks/upload and POST /tasks/upload/{upload_id}/confirm (both kick off async work).
Pagination
Only GET /workflows/{workflow_id}/tasks is paginated. It uses cursor-based pagination — there is no offset or total count.
Query parameters:
limit— integer,1–200, default50.cursor— opaque string; omit on the first page. Pass thenext_cursorfrom the previous response to get the next page.status— optional filter. Valid values:pending,in_progress,finished,failed,paused,escalated,quarantined. Any other value returns400 invalid_requestwith the accepted values listed in the message.
Response shape:
{
"tasks": [
/* TaskStatusResponse[] */
],
"next_cursor": "eyJsYXN0X2lkIjogMTAwMX0=",
"has_more": true
}Results are sorted by task ID descending (newest first). next_cursor is null when has_more is false. An invalid cursor returns 400 invalid_request.
Interactive documentation
- Swagger UI:
https://api.pipelines.tech/api/v1/docs— authorize withExternalApiBearerAuthorExternalApiKeyHeaderAuth. - ReDoc:
https://api.pipelines.tech/api/v1/redoc - OpenAPI JSON:
https://api.pipelines.tech/api/v1/openapi.json
Typical flow: create tasks from JSON
- Discover field names. Call
GET /workflows/{workflow_id}/schemato get the exact fieldnamevalues you need to send. Thenameis the API key infields;titleis the human label shown in the UI. Forselectfields,format_hintlists valid options. - Check prerequisites.
- The workflow must be ACTIVE or PAUSED. Any other workflow status (
DRAFT,COMPLETED,ARCHIVED,PENDING_ACTIVATION) rejects task creation with422and the messageWorkflow must be ACTIVE or PAUSED to create tasks. - If the schema response has
has_file_upload_fields: true,POST /tasksis not the right endpoint — use the ZIP-upload flow below instead;/taskswill return422.
- The workflow must be ACTIVE or PAUSED. Any other workflow status (
- POST the batch.
curl -X POST https://api.pipelines.tech/api/v1/workflows/42/tasks \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"tasks": [
{
"fields": {
"input_text": "Translate this to French: Hello world",
"target_language": "French"
}
}
]
}'- Handle the partial-success response. A
200does not mean every task succeeded — only that at least one did. Always inspect both counts:
{
"success_count": 1,
"error_count": 1,
"errors": [
{
"index": 1,
"field_name": "target_language",
"message": "Missing required columns: target_language"
}
],
"task_ids": [1001]
}errors[].index is the zero-based position in the tasks array you sent. errors can contain both pre-validation failures (unknown or missing fields) and downstream seeding failures, mixed in the same array. Only when every task fails does the endpoint return 422 instead of 200.
Common mistakes:
- Sending field titles (what you see in the UI) instead of field names (what
/schemareturns) — yields "Unknown field" errors. - Forgetting that the workflow must be in
ACTIVEorPAUSEDstate — DRAFT / COMPLETED / ARCHIVED workflows always reject task creation. - Sending tasks to a workflow that has predefined
file_uploadfields — use the upload flow instead.
Typical flow: create tasks with files (ZIP upload)
When the workflow's schema reports has_file_upload_fields: true, the only way to create tasks is the three-step upload flow:
-
Init.
POST /workflows/{workflow_id}/tasks/uploadwith an optionaltasksarray (1–5,000 items) and optionalfile_size_bytes. Returns202 Accepted. The response contains:upload_id— UUID used for the rest of the flow.upload_url— a presigned cloud-storagePUTURL.expires_at— whenupload_urlstops working. Expiration scales withfile_size_bytesand is clamped between 60 minutes (the default whenfile_size_bytesis omitted) and 12 hours. Send a realisticfile_size_bytesfor large ZIPs so the URL doesn't expire mid-upload.
If
has_only_file_upload_fields: trueon the schema, thetasksarray may be omitted — in that case every task row is derived from the ZIP's top-level contents:- When there is a single
file_uploadfield, each top-level file in the ZIP becomes one task and that file is assigned to the field. - When there are multiple
file_uploadfields, each top-level folder becomes one task; files inside the folder are matched to fields by name.
Otherwise
tasksis required. -
Upload the ZIP directly to
upload_urlusing an HTTPPUTwithContent-Type: application/zip. This does not go through the API server, so the 32 MB limit does not apply. -
Confirm.
POST /workflows/{workflow_id}/tasks/upload/{upload_id}/confirmstarts background processing. Returns202 Acceptedwith{ "upload_id": "...", "status": "processing" }.- Calling
/confirmbefore the ZIP is actually in storage returns422("ZIP archive has not been uploaded yet"). - Calling
/confirmtwice returns409("Upload has already been confirmed or is no longer pending"). The endpoint is effectively single-use on success. - If the background worker is unreachable,
/confirmreturns503and leaves the upload inpendingso a retry is safe.
- Calling
-
Poll status.
GET /workflows/{workflow_id}/tasks/upload/{upload_id}untilstatusiscompleted,failed, orcancelled. A terminalcompletedresponse includessuccess_count,error_count, per-rowerrors, andtask_ids.
Task status and node progress
GET /workflows/{workflow_id}/tasks/{task_id} returns:
{
"id": 1001,
"status": "in_progress",
"node_summary": { "total": 5, "finished": 2, "in_progress": 1, "pending": 2 },
"created_at": "2026-03-30T12:00:00Z",
"updated_at": "2026-03-30T12:05:00Z"
}statusis one ofpending,in_progress,finished,failed,paused,escalated,quarantined.node_summarycollapses the many internal node statuses into three buckets:finished—finishednodes.in_progress—available,claimed,submitted,escalated,quarantined.pending—pending,deferred.
- Nodes with status
naare excluded from all counts, includingtotal. - Tasks are always scoped to the
workflow_idin the path. Atask_idthat exists in a different workflow (even in the same org) returns404.
This same payload (per task) is what populates the tasks[] array in the list endpoint.
Verifying your integration
GET /healthwithout a key — expect{"status": "ok"}.GET /workflows/{workflow_id}with your key — expect the workflow name and status. A404here means the key is not scoped to that workflow's org, or the workflow ID is wrong.GET /workflows/{workflow_id}/schema— confirm the fieldnamevalues match what your integration sends.- Create a single task with
POST /tasksand thenGET /tasks/{task_id}with the returned ID. The task should appear in the Data Explorer tab for the workflow in the UI.