# POST /v1/parallel/findall/runs

**Parallel FindAll and Entity Search.** [Parallel](https://parallel.ai)'s FindAll
builds a verified list of entities (companies, people, products, anything you can
name) that match conditions you write in plain language. For each candidate it finds,
it checks every condition against the web and answers with the evidence: citations,
reasoning and a confidence. A run takes from about 30 seconds to two hours, longer than
any request can stay open, so a run is a **job**:

1. `POST /v1/parallel/findall/runs` creates it and answers at once with a run object;
2. `GET /v1/parallel/findall/runs/{id}` reads it until its `status` is `completed`
   (or `failed`, or `cancelled`), or `GET .../{id}/events` streams candidates as they
   are found and checked;
3. `GET /v1/parallel/findall/runs/{id}/result` reads the candidates, at any time.

**Entity Search** (`POST /v1/parallel/findall/entity-search`) is the quick version: one
request, a ranked list of up to 100 people or companies with a name, a URL and a short
description, in a second or two, with no conditions checked.

The field names are Parallel's, so [Parallel's FindAll docs](https://docs.parallel.ai/findall-api/findall-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 FindAll needs an organization API key from a paid
organization: create one in API keys and add paid credit in Billing". Nothing is
reserved or sent. This applies to Entity Search too.

FindAll runs and Entity Searches 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
or an Entity Search 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 FindAll and Entity Search
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.

## Start with a preview

A `preview` run costs $0.10 and checks a handful of candidates, so you can see
whether your conditions find what you mean before you pay for a full run:

```bash
curl https://api.routerplus.com/v1/parallel/findall/runs \
  -H "Authorization: Bearer $TM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "objective": "Find companies that offer managed PostgreSQL hosting",
    "entity_type": "companies",
    "match_conditions": [
      {"name": "managed_postgres", "description": "The company offers a managed (hosted) PostgreSQL database service."}
    ],
    "generator": "preview",
    "match_limit": 5
  }'
```

```json
{"id": "pfrun_6f0c1e2d3a4b5c6d7e8f901a2b3c4d5e", "object": "parallel.findall_run", "status": "queued",
 "generator": "preview", "match_limit": 5, "enrichments": [], "termination_reason": null,
 "metrics": {"generated_candidates_count": null, "matched_candidates_count": null},
 "max_cost": 0.1, "cost": null, "created_at": "2026-11-06T01:47:13.980Z", "finished_at": null, "error": null}
```

Read it until it ends (our previews took 30 to 90 seconds):

```bash
curl https://api.routerplus.com/v1/parallel/findall/runs/pfrun_6f0c1e2d3a4b5c6d7e8f901a2b3c4d5e \
  -H "Authorization: Bearer $TM_API_KEY"
```

Then read the matches:

```bash
curl "https://api.routerplus.com/v1/parallel/findall/runs/pfrun_6f0c1e2d3a4b5c6d7e8f901a2b3c4d5e/result?match_status=matched" \
  -H "Authorization: Bearer $TM_API_KEY"
```

If the matches are what you wanted, create the same run with `"generator": "base"`
(or `core`, or `pro`) and a larger `match_limit`. A run cannot be extended: see
[Extend and enrich](#extend-and-enrich).

## Prices

Parallel's list price, with no markup. A run costs a **fixed fee** for its generator
plus a **fee for each match** it returns, plus, for each enrichment, the enrichment's
processor price for each match it enriched:

| Generator | Fixed fee | Per match | Largest `match_limit` |
|---|---|---|---|
| `preview` | $0.1 | $0 | 10 |
| `base` | $0.25 | $0.03 | 825 |
| `core` | $2 | $0.15 | 153 |
| `pro` | $10 | $1 | 15 |

| Enrichment processor | Per enriched match |
|---|---|
| `lite` | $0.005 |
| `base` | $0.01 |
| `core` | $0.025 |

A `preview` run's `match_limit` (5 to 10) counts the candidates it checks, not the
matches, and its matches are free; only its fixed fee is charged (and its
enrichments).

Two numbers on every run object, in USD:

- **`max_cost`**, set at create: the most the run can cost,
  `fixed + match_limit × (per match + each enrichment's price)`. The gateway holds
  this from your credit while the run is open, 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.
- **`cost`**, set when the run ends: what you were charged,
  `fixed + matches × per match + Σ (matches enriched × the enrichment's price)`, where
  matches is Parallel's `matched_candidates_count`, never more than `match_limit`. The
  rest of the hold is released. `cost` is `null` until the run ends; it is never more
  than `max_cost`.

For example, a `base` run with `match_limit` 20 and one `lite` enrichment has
`max_cost` $0.25 + 20 × ($0.03 + $0.005) = $0.95; if it ends with 12 matches, all
enriched, its `cost` is $0.25 + 12 × $0.03 + 12 × $0.005 = $0.67.

**Every run that Parallel accepted is charged by this formula, however it ends**:
`completed`, `cancelled` (the work done before it stopped) and `failed` (the work done
before it failed). If we can no longer read how a run ended (Parallel stops answering
about it, or no longer has it), the formula uses the last counts we read. A run Parallel
refuses at create is not charged.

**The one exception:** if Parallel never answered a single status read for the run after
it accepted it, we cannot know what it did, and the run is charged its `max_cost`. If
Parallel reports how the run ended within 24 hours after its time limit, the run takes
that end, its `cost` the formula, and the difference is credited back to your balance,
once. A run Parallel no longer has will never report its end, so it is not credited
back.

## The hold cap

A run may hold at most **$25**. A create whose `max_cost` would be more is a 400
`invalid_request` that says the largest `match_limit` that fits, for example
"match_limit: this run's worst case is $30.00, over the $25.00 a run may hold; the
largest match_limit that fits is 15". The largest `match_limit` per generator is in
the price table above (without enrichments; enrichments lower it). For more results,
run several searches with an `exclude_list` of what you already have.

## Create a run

The body, at most 256 KB:

| Field | Required | What it takes |
|---|---|---|
| `objective` | yes | What you are looking for, in plain language: 1 to 5,000 characters |
| `entity_type` | yes | What kind of thing, such as `companies` or `people`: 1 to 200 characters |
| `match_conditions` | yes | 1 to 10 conditions, each `{"name", "description"}`. A `name` is 1 to 64 letters, digits or underscores, unique in the run; a `description` is 1 to 2,000 characters and says what must be true |
| `generator` | yes | `preview`, `base`, `core` or `pro` |
| `match_limit` | yes | How many matches to find at most: 5 to 1,000 (5 to 10 for `preview`), within [the hold cap](#the-hold-cap) |
| `exclude_list` | no | Up to 1,000 entities to leave out, each `{"name", "url"}`: a name up to 300 characters, a URL up to 2,000 |
| `enrichments` | no | Up to 2 enrichments, each `{"processor": "lite" \| "base" \| "core", "output_schema": {"type": "json", "json_schema": {...}}}`. Each matched candidate is researched once more for the fields in the schema (at most 16 KB). The schema's property names must differ from the condition names and from the other enrichment's; at most 1,000 fields in all the enrichments' schemas together |

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` |
| `memory_scope_key` | RouterPlus does not use Parallel memory |
| `metadata` | RouterPlus sets the run's metadata itself |
| `mcp_servers` | A run cannot call MCP servers through RouterPlus |
| any other field | Unknown |

So is an enrichment schema no answer can satisfy, such as an empty `enum` or a
`required` field that `properties` does not define.

## Read a run

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

| Field | Meaning |
|---|---|
| `id` | Our id, `pfrun_` and 32 hex digits. Parallel's own run id is never shown |
| `object` | `parallel.findall_run` |
| `status` | `queued`, `running`, `completed`, `failed` or `cancelled` |
| `generator`, `match_limit` | As sent |
| `enrichments` | `[{"processor"}]`, one per enrichment Parallel accepted. If Parallel refused an enrichment right after the create, it is missing here (for example `[]`), is not charged, and the run goes on without it: check this field in the create's answer |
| `termination_reason` | Why the run ended, Parallel's word: `match_limit_met`, `low_match_rate`, `candidates_exhausted`, `error_occurred`, `timeout`, `user_cancelled` or `insufficient_funds`; `null` while it runs. `insufficient_funds` is about **our** account at Parallel, not your balance: the run is charged for the work done; contact support if you see it |
| `metrics` | `{"generated_candidates_count", "matched_candidates_count"}`: the candidates found so far and how many matched; `null` until we first read the run's status from Parallel |
| `max_cost`, `cost` | See [Prices](#prices) |
| `created_at`, `finished_at` | RFC 3339 times; `finished_at` is `null` until the run ends |
| `error` | `null`, or `{"code", "message"}` when the run did not end normally: `run_failed`; `run_cancelled` (Parallel stopped it for a reason of its own; **your own cancel shows `status` `cancelled` with `error` `null`**); `action_required`; `run_timeout`; or `run_expired` (Parallel no longer has the run: its results are gone, and it was charged for the matches we last saw, or its `max_cost` if we saw none) |

The run's `cost` is always what you were charged; `error.message` never says.

## Read the result

`GET /v1/parallel/findall/runs/{id}/result` works in **any** state: it is a snapshot of
the candidates so far. It answers `{"id", "status", "candidates": [...]}`, each
candidate:

| Field | Meaning |
|---|---|
| `candidate_id`, `name`, `url`, `description` | The entity |
| `match_status` | `generated` (found, not yet checked), `matched`, `unmatched` or `discarded` |
| `output` | Per condition and enrichment field: `{"value", "type", "is_matched"}` (`type` is `match_condition` or `enrichment`; `is_matched` only on conditions) |
| `basis` | Per field: `{"field", "citations": [{"url", "title", "excerpts"}], "reasoning", "confidence"}`. Not on `unmatched` candidates |

Add `?match_status=matched` for the matches only. The snapshot holds at most 5,000
candidates; a result over 10 MB is a 502 `result_too_large`.

**Save the matches soon after the run ends.** RouterPlus keeps no results: each call reads
them from Parallel again. Parallel does not publish how long it keeps a run's results (in
our tests they were all still there after an hour; we have not checked longer). If
Parallel has dropped a run, the run shows `failed` with `run_expired`, and a result read
answers 502 `upstream_error`.

You see only the matches you are charged for: at most `match_limit` while the run is
open, and once it has ended, the number of matches its `cost` counts. Parallel can go on
finding matches after a run ends or is cancelled; those are not charged and not shown.
Enrichment fields appear only on those matches, and once the run has ended, each
enrichment only on as many matches as its `cost` counts. While a run is being cancelled
you may see matches that are not charged in the end: **read the result again after the
run ends**, and keep that copy.

## Stream events

`GET /v1/parallel/findall/runs/{id}/events` is a server-sent event stream for up to
570 seconds; open it again to go on. Each event's `data` is JSON:

| `type` | `data` |
|---|---|
| `findall.status` | `{"type", "event_id", "timestamp", "run"}`: `run` is the run object above. Sent about every 10 seconds and when the status changes |
| `findall.candidate.generated`, `.matched`, `.unmatched`, `.enriched` | `{"type", "event_id", "timestamp", "candidate"}`: the candidate as in the result, without `basis` |
| `error` | `{"type": "error", "message": "the event stream failed"}` |

To resume, pass the last `event_id` you saw as `?last_event_id=...` (letters, digits,
`-` and `_`, up to 64). Events over 64 KB are dropped. At most 5 streams per
organization are open at once.

The stream follows the same rule as the result: matches past what you are charged for,
and enrichments past what the `cost` counts, are not sent.

## Cancel

`POST /v1/parallel/findall/runs/{id}/cancel` stops the run at Parallel and answers the
run object, at that moment usually still `running` with `cost` `null`. Work already under
way may still finish, so the run is settled about 30 seconds later: its `status` becomes
`cancelled` and its `cost` is **charged for the work done**, the fixed fee plus the
matches found before it stopped (a cancelled run is not free; one cancelled before it
started is charged its fixed fee). A second cancel in those 30 seconds answers 200 again
and changes nothing. Once the run has ended, a cancel answers 409 `run_finished`; read
the result again then (see [Read the result](#read-the-result)).

## Extend and enrich

Parallel lets a run be extended or enriched after it starts; through RouterPlus both
are refused, because they would raise the run's cost past the `max_cost` it was created
with:

- `POST .../{id}/extend` answers 409 `not_supported`: "extending a run is not
  supported: create a new run with a larger match_limit (each run's fixed fee is charged
  again)". Pass the first run's matches as its `exclude_list` to skip them.
- `POST .../{id}/enrich` answers 409 `not_supported`: "adding an enrichment to a
  created run is not supported: create a new run with the enrichment in its enrichments
  field".

## Entity Search

```bash
curl https://api.routerplus.com/v1/parallel/findall/entity-search \
  -H "Authorization: Bearer $TM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"entity_type": "companies", "objective": "Companies that develop open-source relational databases", "match_limit": 10}'
```

```json
{"object": "parallel.entity_search", "entities": [{"name": "...", "url": "https://...", "description": "..."}], "cost": 0.005}
```

| Field | Required | What it takes |
|---|---|---|
| `entity_type` | yes | `people` or `companies` |
| `objective` | yes | 1 to 5,000 characters |
| `match_limit` | no | 5 to 100; default 100. Parallel may return fewer (one of our 100-company searches returned 29) |

Each search costs **$0.005**, charged when Parallel answers; a search that fails is
not charged. Results are best-effort: nothing is verified, and there are no citations.

**Professional context only.** Parallel allows Entity Search for professional
purposes only, and **not for decisions about employment, credit or housing**. By using
it you accept the same limit.

## Limits

| Limit | Value | Over it |
|---|---|---|
| Creates per organization per minute | 2 | 429 `rate_limit`: wait `retry-after` |
| Active runs (`queued` or `running`) per organization | 3 | 429 `rate_limit`: "3 Parallel FindAll runs already active: wait for one to end, or cancel one". The three can hold up to 3 × $25 = $75 of your credit between them |
| Result reads and event streams opened per organization per minute | 60 | 429 `rate_limit`: wait `retry-after` |
| Entity Searches per organization per minute | 30 | 429 `rate_limit`: wait `retry-after` |
| Open event streams per organization | 5 | 429 `rate_limit` |
| A run's hold (`max_cost`) | $25 | 400 `invalid_request` naming the largest `match_limit` that fits |
| Request body | 256 KB | 400 `invalid_request` |
| Result | 10 MB, 5,000 candidates | 502 `result_too_large` |
| Event stream | 570 seconds | The stream ends; open it again |
| A run's life | 2 hours | `run_timeout`: the run is cancelled at Parallel and charged for the work done |

Busy times can also bring a 429 `rate_limit` that says the service is running as many
FindAll runs as it may, or a 503 `provider_unavailable` that says Parallel is limiting
new runs: wait `retry-after` and try again. Neither is charged.

## Errors

| Status | `error_type` | When |
|---|---|---|
| 400 | `invalid_request` | A field refused or over its limit, or a `max_cost` over the hold cap (the message names the field) |
| 400 | `upstream_rejected` | Parallel refused the run or the search: "Parallel refused the run (HTTP 422)". Not charged |
| 403 | `forbidden` | Not an organization key of a paid organization: create one in API keys and add paid credit. Or, on a create or an Entity Search, 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 |
| 409 | `run_finished` | `/cancel` on a run that has already ended |
| 409 | `not_supported` | `/extend` or `/enrich`: create a new run instead |
| 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 `max_cost` (or a search's $0.005) |
| 502 | `upstream_error` | Parallel did not accept the run, return the result, cancel, or answer the search. A failed create is not charged |
| 502 | `result_too_large` | The result is over 10 MB |
| 503 | `provider_unavailable` | Parallel is limiting new runs, or the service is not configured (see below). Not charged |
| 503 | `gateway_error` | Our ledger or run store is briefly unavailable: nothing was held or sent; retry after `retry-after` |

Parallel's own error text is never passed on.

## Troubleshooting

- **Every FindAll path answers 404 `not_found`, as an unknown route does.** FindAll
  is not available on the gateway you are calling. Contact support to ask for it.
- **503 `provider_unavailable` with "Parallel FindAll is turned on but not configured
  on this gateway yet".** The routes exist but our Parallel key or its address is not
  in place yet. Ask support; retrying will not help until it is configured.
- **403 `forbidden`.** Use an organization API key from the console, and add paid
  credit to the organization.
- **400 naming `match_limit` and the hold.** Lower `match_limit` to the number the
  message gives, or drop an enrichment. If no `match_limit` fits, remove an enrichment, use
  a cheaper enrichment processor or a cheaper generator.
- **429 `insufficient_quota` on a create.** Your balance (or a monthly spend cap) cannot
  hold the run's `max_cost`: lower `match_limit`, or add credit.
- **A run's `cost` is more than its fixed fee though you cancelled it at once.** Work
  under way when you cancelled still counts; see [Cancel](#cancel).

## Data

- What you send in a run (the objective, the entity type, the conditions, the exclude
  list and the enrichment schemas) and in an Entity Search 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 and searches 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 generator, the match limit, the enrichment
  processors, a one-way hash of each enrichment field name, the hold, the status,
  Parallel's termination reason and counts, the enriched counts charged, an error code,
  the cost and times: no objective, condition, candidate, 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, events and
  Entity Search answers are never logged or kept.
- RouterPlus never sends `webhook`, `memory_scope_key` or `mcp_servers`.
