> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.cloudraker.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.cloudraker.com/_mcp/server.

# Pipelines

`POST /v1/pipeline` takes one file set and a list of steps. Every file is parsed **once**, then each step runs over the parsed set. It is the JSON, inline-config successor to the multipart `/process` API.

Steps run **in parallel over the same files, not chained**. A step never consumes another step's output — "redact the form that fill produced" is not expressible today. Each step reads the original file set.

## How it works

1. You send **files** (URLs or file ids) and **steps**.
2. Each step is one of `{"extract": {…}}`, `{"redact": {…}}`, `{"fill": {…}}`, `{"sign": {…}}` — the same inline config the matching verb takes — or `{"action": "<id or slug>", "params": {…}}` for a [saved action](/capabilities/actions). There is no `parse` step: parsing is automatic.
3. The response is `202` with the pipeline id (`plr_…`) and a typed id per step, in the order you sent them.
4. `GET /v1/runs/plr_…` carries `steps[]` — each with its own `id`, `capability`, `status`, and result.

## Quickstart

```bash title="curl"
curl -X POST https://api.cloudraker.com/v1/pipeline \
  -H "Authorization: Bearer $CLOUDRAKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "files": [{ "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf", "name": "w9.pdf" }],
    "steps": [
      { "extract": { "schema": { "type": "object", "properties": { "form_number": { "type": ["string", "null"] } } } } },
      { "redact": { "categories": ["ein"], "mode": "targeted" } }
    ],
    "ttl": 86400
  }'
```

```ts title="TypeScript"
const res = await fetch("https://api.cloudraker.com/v1/pipeline", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CLOUDRAKER_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    files: [{ url: "https://www.irs.gov/pub/irs-pdf/fw9.pdf", name: "w9.pdf" }],
    steps: [
      { extract: { schema: { type: "object", properties: { form_number: { type: ["string", "null"] } } } } },
      { redact: { categories: ["ein"], mode: "targeted" } },
    ],
  }),
});

const { id, steps } = await res.json(); // 202
console.log(id, steps.map((s) => `${s.capability} ${s.id}`));
```

```python title="Python"
import os, requests

res = requests.post(
    "https://api.cloudraker.com/v1/pipeline",
    headers={"Authorization": f"Bearer {os.environ['CLOUDRAKER_API_KEY']}"},
    json={
        "files": [{"url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf", "name": "w9.pdf"}],
        "steps": [
            {"extract": {"schema": {"type": "object", "properties": {"form_number": {"type": ["string", "null"]}}}}},
            {"redact": {"categories": ["ein"], "mode": "targeted"}},
        ],
    },
)

run = res.json()  # 202
print(run["id"], [s["capability"] for s in run["steps"]])
```

## Example response

The create call returns the handle and the step ids:

```json
{
  "object": "pipeline_run",
  "id": "plr_01KYD7F40BGQA7WXB50ENSQEK1",
  "status": "queued",
  "statusUrl": "/v1/runs/plr_01KYD7F40BGQA7WXB50ENSQEK1",
  "steps": [
    { "id": "exr_01KYD7F40B35TT7XR1KPMFZXJ6", "capability": "extract" },
    { "id": "rdr_01KYD7F40BDN07JRKXRERJYTRE", "capability": "redact" }
  ]
}
```

Polling `GET /v1/runs/plr_…` returns the same step ids with results attached:

```jsonc
{
  "object": "pipeline_run",
  "id": "plr_01KYD7F40BGQA7WXB50ENSQEK1",
  "status": "processed",
  "expiresAt": "2026-07-26T18:09:16.204Z",
  "statusUrl": "/v1/runs/plr_01KYD7F40BGQA7WXB50ENSQEK1",
  "files": [
    { "id": "39c1759c-5f47-425a-9ef2-6b9efe51fb7a", "name": "w9.pdf", "status": "processed" }
  ],
  "steps": [
    {
      "id": "exr_01KYD7F40B35TT7XR1KPMFZXJ6",
      "capability": "extract",
      "status": "processed",
      "output": {
        "value": { "form_number": "W-9" },
        "citations": {
          "form_number": [
            {
              "fileId": "39c1759c-5f47-425a-9ef2-6b9efe51fb7a",
              "page": 0,
              "bbox": { "x": 0.0918, "y": 0.0376, "width": 0.0653, "height": 0.0364 },
              "text": "Form W-9",
              "confidence": 5
            }
          ]
        },
        "documents": [ /* one entry per input document */ ]
      }
    },
    {
      "id": "rdr_01KYD7F40BDN07JRKXRERJYTRE",
      "capability": "redact",
      "status": "processed",
      "output": {
        "file": { "id": "e130ad59-6309-44c7-bb27-1106a1c32621", "name": "w9 (redacted).pdf", "url": "https://cdn.cloudraker.com/…/latest?token=…" },
        "files": [ /* one redacted file per input document */ ],
        "entities": { "ein": 11 },
        "skipped": 0
      }
    }
  ]
}
```

## Key fields

| Field                | What it is                                                                                                           |
| -------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `object`             | Always `pipeline_run`.                                                                                               |
| `id`                 | The pipeline id (`plr_…`).                                                                                           |
| `status`             | The pipeline's own status — `queued`, `processing`, `needs_input`, `processed`, `failed`, `cancelled`, or `expired`. |
| `files[]`            | The shared, parsed-once input set.                                                                                   |
| `steps[].id`         | The typed step id — `exr_`, `rdr_`, `flr_`, `sgr_`. `null` for a bare `{"action": …}` step.                          |
| `steps[].capability` | `extract`, `redact`, `fill`, `sign`, or `action`.                                                                    |
| `steps[].status`     | That step's own status. One step failing does not erase the others' results.                                         |
| `steps[].output`     | Exactly the `output` the matching verb returns.                                                                      |

Sub-routes that address a single step — `/task`, `/envelope`, `/void`, `/audit`, `/signers/:id/resend` — resolve against the run's matching step: `/task*` goes to the `fill` step, the rest to the `sign` step. A pipeline without that step answers `404 not_found`.

## Configuration

| Field      | Type                                                  | What it does                                                                                                                                      |
| ---------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `files[]`  | array of `{url, name?, processing?}` or `{id}`, 1–100 | **Required.** Parsed once, shared by every step.                                                                                                  |
| `steps[]`  | array, 1–20                                           | **Required.** The capabilities to run.                                                                                                            |
| `metadata` | object                                                | Your own key/values, echoed back on the run body (not on webhook deliveries). Max 10 KB.                                                          |
| `webhook`  | `{url}` or `{id}`                                     | Where to deliver events. See [Webhooks](/developers/webhooks).                                                                                    |
| `ttl`      | integer seconds, 1–604800                             | How long the pipeline and its files live. Default 24 hours, max 7 days. A pipeline containing a `sign` step is exempt while the envelope is open. |

Step configs are validated exactly like the standalone verbs: an `extract` step with neither `schema` nor `action` fails with [`400 invalid_request`](/developers/errors#invalid_request), and a `schema` that breaks the [schema dialect](/capabilities/extract/schema) fails with `400 invalid_schema` — same codes, same messages, as `POST /v1/extract`.

## Sync vs async

Always asynchronous — `POST /v1/pipeline` returns `202` and takes no `?wait=`. Poll `GET /v1/runs/plr_…?wait=<seconds>` (0–120) to long-poll for a terminal state, or subscribe a webhook and wait for `processing.completed`.

Sending an `idempotency-key` makes retries safe. A replay returns the **original** pipeline — its id, its current status, and its original step ids — with an `idempotent-replay: true` response header, not a second run.

## Save as an action

Each step accepts a saved action instead of inline config, and the two can be combined — inline fields win:

```json
{
  "files": [{ "id": "a04d6597-4e34-4a99-94ea-964c289a4c68" }],
  "steps": [
    { "extract": { "action": "medical-intake" } },
    { "action": "hr-offboarding" }
  ]
}
```

`{"extract": {"action": …}}` keeps the step typed (its id is `exr_…`) and lets you override fields inline. A bare `{"action": …}` step runs the saved action as-is and its step `id` is `null`. See [Saved actions](/capabilities/actions).

## Next steps

#### [Saved actions](/capabilities/actions)

The catalog, saving a configuration, and referencing it by id or slug.

#### [Runs](/developers/runs)

Statuses, downloading outputs, TTL, and keeping a result.

#### [Files](/developers/files)

Register a file once and reuse it across pipelines.

#### [Webhooks](/developers/webhooks)

Per-step and per-pipeline events, signed and verifiable.