# POST /v1/parallel/tasks/runs

**Parallel Task runs.** [Parallel](https://parallel.ai)'s Task API does deep research and enrichment: you
give it a question or a JSON object and, if you like, the shape of the answer, and
it searches and reads the web and answers with citations. A run takes from about
10 seconds (`lite`) to about 10 minutes (`pro`), longer than any request can stay
open. So a run is a **job**:

1. `POST /v1/parallel/tasks/runs` creates it and answers at once with a run object;
2. `GET /v1/parallel/tasks/runs/{id}` reads it until its `status` is `completed`
   (or `failed`, or `cancelled`), or `GET .../{id}/events` streams its progress;
3. `GET /v1/parallel/tasks/runs/{id}/result` fetches the answer.

The field names are Parallel's, so [Parallel's Task API docs](https://docs.parallel.ai/task-api/task-quickstart)
apply to the body you send, within the limits below.

> [!NOTE]
> What you send goes to Parallel, a third party, which may use it and the answer to
> improve its services and stores data in the US. Do not send what you would not
> share with Parallel. See [Data](#data).

## Who can use it

An **organization API key of a paid organization**: a key you created in
[API keys](https://app.routerplus.com/console/keys) for an organization that has added paid
credit. Playground and evaluation keys, and organizations still on the free trial,
get 403 `forbidden`: "Parallel Task runs need an organization API key from a paid
organization: create one in API keys and add paid credit in Billing". Nothing is
reserved or sent.

Task runs are billed to your organization's credit. A key limited to your own
provider keys or to a routing policy cannot spend it, so a create with such a key is
a 403 `forbidden` too: "This API key is limited to its own provider keys or a routing
policy, and Parallel Task runs are billed to marketplace credit: use an organization
API key without that limit.
A routing policy set on the organization or workspace applies to every key, so an owner must change it in the console". Nothing is reserved or sent. Reading runs is unchanged.

## Prices

Parallel's list price for the processor, per run, with no markup. A run is billed
**when Parallel completes it**; see the rules below for runs that never end or that a
restart interrupts, and read the run's `cost` for what was charged:

| Processor | Price per run | Per 1,000 runs | Typical time |
|---|---|---|---|
| `lite` | $0.005 | $5 | 10 s to 2 min |
| `base` | $0.01 | $10 | 15 s to 3 min |
| `core` | $0.025 | $25 | 1 to 5 min |
| `core2x` | $0.05 | $50 | 1 to 10 min |
| `pro` | $0.1 | $100 | 2 to 10 min |

The `ultra` processors are not offered. The price of one output field or twenty is
the same: Parallel prices the run, not the fields.

How the money moves:

- On create, the gateway **holds the run's price** from your credit, as it holds
  any request's maximum cost (see [Pricing](/docs/pricing)). Without that much
  credit the create is the usual 429 `insufficient_quota`, and nothing runs.
- When Parallel says the run is `completed`, the hold becomes the charge: the run's
  price, to the micro-dollar.
- When Parallel says the run `failed` or was `cancelled`, or that it needs an action
  (`action_required`), the charge is **$0**.
- If no end arrives within 2 hours, the run is given up on with the error
  `run_timeout`. If Parallel still reports it `queued` or `running`, Parallel has
  not completed it and the charge is **$0**. Only if we cannot get an answer from
  Parallel at that point is it charged its price (the hold), since it may have
  completed.
- If the gateway that follows your run restarts while it is running, the run is
  charged its price (the hold) when the gateway comes back, as any request a
  restart cuts short is. If Parallel then completes it, that is its price anyway.
  If Parallel still reports it running after 2 hours, the charge is credited back,
  but only once you read the run after those 2 hours: it then shows as an
  `adjustment` credit in [Billing](https://app.routerplus.com/console/billing), and the run's
  `cost` reads 0. If Parallel fails it, the charge stands: contact support with the
  run's `id` to have it reconciled.
- The run's `cost` is always what you were charged; `error.message` never says.

The charge appears in [GET /v1/usage & /v1/generation](/docs/api-usage) as one
attempt: provider `parallel`, model `parallel:task:<processor>`, the price as
`server_tool_fee_usd`.

## Create a run

```bash
curl https://api.routerplus.com/v1/parallel/tasks/runs \
  -H "Authorization: Bearer $TM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"processor": "lite", "input": "Which RFC defines HTTP semantics, and when was it published?"}'
```

```json
{"id": "ptrun_6f0c1e2d3a4b5c6d7e8f901a2b3c4d5e", "object": "parallel.task_run", "status": "queued",
 "processor": "lite", "created_at": "2026-11-06T00:26:54.676Z", "finished_at": null, "cost": null, "error": null}
```

The body, at most 64 KB:

| Field | Required | What it takes |
|---|---|---|
| `processor` | yes | `lite`, `base`, `core`, `core2x` or `pro` |
| `input` | yes | A string, or a JSON object; at most 32,000 characters as JSON |
| `task_spec` | no | `{"output_schema": ..., "input_schema": ...}`, at most 16 KB as JSON. Each schema is a string (a text description), `{"type": "json", "json_schema": {...}}` or `{"type": "text", "description": "..."}`. `output_schema` may also be `{"type": "auto"}` (deep research), on `pro` only. `output_schema` is required when `task_spec` is set |
| `enable_events` | no | `true` or `false`: whether Parallel records progress events for `/events`. Parallel turns them on by default for `pro` |

Refused with a 400 `invalid_request` that names the field, before anything is held
or sent:

| Field | Why |
|---|---|
| `webhook` | Not supported: read the run, or stream its `/events` |
| `mcp_servers` | A run cannot call MCP servers through RouterPlus |
| `previous_interaction_id` | Runs cannot be chained |
| `source_policy` | Not supported yet |
| `advanced_settings` | Not supported |
| `memory_scope_key` | RouterPlus does not use Parallel memory |
| `metadata` | RouterPlus sets the run's metadata itself |
| any other field | Unknown |

A `processor` of `ultra`, `ultra2x`, `ultra4x` or `ultra8x` is a 400 that says it
is not offered. So is a JSON schema no answer can satisfy, such as an empty `enum`
or a `required` field that `properties` does not define: Parallel accepts such a
run and it never ends (in our test one was still queued after an hour).

## Read a run

`GET /v1/parallel/tasks/runs/{id}` answers the run object:

| Field | Meaning |
|---|---|
| `id` | Our id, `ptrun_` and 32 hex digits. Parallel's own run id is never shown |
| `status` | `queued`, `running`, `completed`, `failed` or `cancelled` |
| `processor` | As sent |
| `created_at`, `finished_at` | RFC 3339 times; `finished_at` is `null` until the run ends |
| `cost` | USD, once the run is settled: what you were charged (the price, or 0); `null` before. The one source for the charge |
| `error` | `null`, or `{"code", "message"}` once a run ends without completing: `run_failed`, `run_cancelled`, `action_required`, `run_timeout` or `run_expired`. Read `cost` for what it was charged |

A run is shown finished only once its charge is recorded, so a `completed` run
always has its `cost`. Poll every 5 to 10 seconds. A run slower than its typical
time is still working; nothing speeds it up, and after 2 hours it ends with
`run_timeout`.

An unknown id, a malformed id and a run of another organization all get the same
404 `not_found`: `run "<id>" not found`.

## Fetch the result

`GET /v1/parallel/tasks/runs/{id}/result`, for a `completed` run:

```json
{"id": "ptrun_6f0c...", "status": "completed",
 "output": {"type": "json", "content": {"output": "RFC 9110, published in June 2022."},
  "basis": [{"field": "output", "citations": [{"url": "https://www.rfc-editor.org/rfc/rfc9110.html", "title": "RFC 9110: HTTP Semantics", "excerpts": ["..."]}],
             "reasoning": "...", "confidence": "high"}]}}
```

- `output.type` is `text` or `json`; `content` is a string or the JSON your
  `output_schema` describes.
- `basis` keeps, per field, its `citations` (`url`, `title`, `excerpts`), the
  `reasoning` and the `confidence`. Parallel's other fields are not passed on.
- The result is fetched from Parallel each time you ask: RouterPlus keeps no output.
  We have seen Parallel serve a result unchanged for an hour, not longer: save the
  result when you fetch it. The `pro` results we tested were 180 to 280 KB; a result over 2 MB is a 502 `result_too_large`.
- For a run that is not `completed`, the answer is a 409 `run_not_complete` with
  the status. It does not wait: read the run until it is `completed`, then fetch
  the result.

## Stream progress

`GET /v1/parallel/tasks/runs/{id}/events` streams Server-Sent Events for up to 570
seconds, then closes; open it again to follow a long run. The stream starts with the
run's state and, while the run is active, replays the progress recorded so far. Only
these events are passed on:

| Event | Data |
|---|---|
| `task_run.state` | `{"type", "run"}`: our run object, as a read shows it |
| `task_run.progress_msg.*` (`plan`, `search`, `tool_call`, `result`, `exec_status`, ...) | `{"type", "message", "timestamp"}` |
| `task_run.progress_stats` | `{"type", "source_stats": {"num_sources_considered", "num_sources_read"}}` |

Other events and progress lines over 16 KB are dropped. The final state event
never carries the output: fetch it from `/result`. At most 5 streams per
organization are open at once (a sixth is a 429 `rate_limit`). When you disconnect,
the gateway stops reading from Parallel.

## Cancel

`POST /v1/parallel/tasks/runs/{id}/cancel` answers 409 `cancel_unsupported`:
"Parallel Task runs cannot be stopped once created; this run continues and is
billed when Parallel completes it; see Limits for runs that never end". Parallel offers no way to stop a Task run (we tried
its `cancel` and `DELETE` routes on queued and running runs: neither exists, and
the runs completed). Nothing changes; the run goes on and is billed when Parallel
completes it (see [Limits](#limits) for runs that never end).
A run that is still `queued` long after its typical time will not be stopped
either: it keeps one of your 5 active slots until it ends or reaches 2 hours, and
then ends with `run_timeout` at $0 if Parallel still reports it active.

## Limits

| Limit | Value | Over it |
|---|---|---|
| Creates per organization per minute | 10 | 429 `rate_limit`: wait `retry-after` |
| Result fetches and event streams opened per organization per minute | 60 | 429 `rate_limit`: wait `retry-after` |
| Active runs (`queued` or `running`) per organization | 5 | 429 `rate_limit`: "5 Parallel Task runs already active". A run keeps its slot until it ends or reaches 2 hours |
| Open event streams per organization | 5 | 429 `rate_limit` |
| Request body | 64 KB | 400 `invalid_request` |
| `input` as JSON | 32,000 characters | 400 `invalid_request` |
| `task_spec` as JSON | 16 KB | 400 `invalid_request` |
| Result | 2 MB | 502 `result_too_large` |
| Event stream | 570 seconds | The stream ends; open it again |
| A run's life | 2 hours | `run_timeout`: $0 if Parallel still reports it active, its price if Parallel could not be reached |

## Errors

| Status | `error_type` | When |
|---|---|---|
| 400 | `invalid_request` | A field refused or over its limit (the message names it) |
| 400 | `upstream_rejected` | Parallel refused the run: "Parallel refused the run (HTTP 422)". Not billed |
| 403 | `forbidden` | Not an organization key of a paid organization: create one in API keys and add paid credit. Or, on a create, a key limited to its own provider keys or a routing policy: use one without that limit (a policy on the organization or workspace applies to every key: an owner changes it in the console) |
| 404 | `not_found` | No such run for your organization. On every `/v1/parallel/tasks/runs` path, even a create, it means the gateway you called does not serve Task runs (the same 404 as any unknown route): ask support to turn them on |
| 409 | `run_not_complete` | `/result` for a run that is not `completed`: read the run, then retry |
| 409 | `cancel_unsupported` | `/cancel`: runs cannot be stopped |
| 429 | `rate_limit` | A per-minute or active-run limit, or 5 open streams: wait `retry-after` |
| 429 | `insufficient_quota` | Not enough credit to hold the run's price |
| 502 | `upstream_error` | Parallel did not accept the run or return the result. A failed create is not billed |
| 502 | `result_too_large` | The result is over 2 MB |
| 503 | `provider_unavailable` | "turned on but not configured": Task runs are turned on for the gateway you called, but our Parallel key or its address is not in place yet; ask support, retrying will not help until it is configured. Or Parallel is refusing our key or limiting new runs. Not billed |
| 503 | `gateway_error` | Our ledger or run store is briefly unavailable: nothing was held or sent; retry after `retry-after`. If the message says the runs have no price, retrying will not help: contact support |

Parallel's own error text is never passed on.

## Data

- What you send in `input` and `task_spec` goes to Parallel, a third party, with
  our Parallel key. Under Parallel's standard terms it may use inputs and outputs
  to improve its services; until our account terms with Parallel are settled, treat
  runs as retained and possibly used for training by Parallel. Parallel stores data
  in the US. See [Data policy](/docs/data-policy).
- RouterPlus keeps, per run, ids, the processor, the status, an error code, the cost
  and times: no input, schema, output, event or Parallel text. The create body is
  logged only if your organization has [request logging](/docs/data-policy) on, as
  any request is. Results and events are never logged or kept.
- RouterPlus never sends `memory_scope_key`, `webhook`, `mcp_servers` or
  `previous_interaction_id`.
