> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runpulse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Pipelines

> Parse a document and run up to 10 more steps on the parse from one request.

A pipeline parses one document, then runs up to 10 more Extract, Split, Tables or Charts steps on the parse, from one request. Use a pipeline when every document of a kind gets the same steps, and always when your organization deletes results on their first read.

The steps start once the parse completes and run side by side, and the parse decides how the pipeline ends:

```mermaid theme={null}
%%{init: {"themeVariables": {"fontSize": "14px"}, "flowchart": {"nodeSpacing": 24, "rankSpacing": 32, "padding": 8}}}%%
flowchart TD
    run["POST /pipeline"] -->|pipeline_id| parse["Parse step"]
    parse -->|completed| extract["Extract step"]
    parse -->|completed| tables["Tables step"]
    parse -->|"failed or canceled"| skipped["Steps skipped"]
    extract --> ends["Ends with parse's status"]
    tables --> ends
    skipped --> ends
```

Each step is still a job of its own endpoint, with its own id, status and result.

## Run a pipeline

This example parses a 10-K, extracts the company's name and reads its tables, then reads the Extract step's result once the pipeline ends:

```python Python theme={null}
import os
from pulse import Pulse

client = Pulse(api_key=os.environ["PULSE_API_KEY"])

pipeline = client.pipeline.run(
    file_url="https://example.com/10-k.pdf",
    config={
        "step": {"type": "parse", "config": {"model": "ultra-2"}},
        "children": [
            {"type": "extract", "config": {"input_schema": {"type": "object", "properties": {"company_name": {"type": "string"}}}}},
            {"type": "tables", "config": {"format": ["json"]}},
        ],
    },
)
wait(client.pipeline.get, pipeline.pipeline_id)

for child in client.pipeline.get_result(pipeline.pipeline_id).children or []:
    if child.type == "extract" and child.status == "completed":
        fields = client.extract.get_result(child.extract_id)
```

```typescript TypeScript theme={null}
import { PulseClient } from "@pulse-software/sdk";

const client = new PulseClient({ apiKey: process.env.PULSE_API_KEY! });

const { pipelineId } = await client.pipeline.run({
  fileUrl: "https://example.com/10-k.pdf",
  config: {
    step: { type: "parse", config: { model: "ultra-2" } },
    children: [
      { type: "extract", config: { inputSchema: { type: "object", properties: { company_name: { type: "string" } } } } },
      { type: "tables", config: { format: ["json"] } },
    ],
  },
});
await wait(() => client.pipeline.get({ pipelineId }));

for (const child of (await client.pipeline.getResult({ pipelineId })).children ?? []) {
  if (child.type === "extract" && child.status === "completed") {
    const fields = await client.extract.getResult({ extractId: child.extractId });
  }
}
```

`wait` is the polling helper from [Jobs and results](https://docs.runpulse.com/v2/jobs.md). A pipeline always runs async: `POST /pipeline` returns `202` with the `pipeline_id` at once, and takes no `async` field.

***

## Write the steps

`config.step` is the parse, always `{"type": "parse", "config": {...}}`, with `POST /parse`'s options in `config`. `config.children` lists up to 10 steps that read it, each with a `type` of `extract`, `split`, `tables` or `charts` and that endpoint's options in `config`. Leave `children` out to run the parse alone. Each step takes its `config` inline, never a `config_id`.

Steps don't feed each other: each one reads only the parse. To extract each topic that a Split step finds, run [Extract](https://docs.runpulse.com/v2/extract.md) on the pipeline's `parse_id` with the topic's pages as `page_range` once the Split step ends.

To reuse a pipeline, save its whole `config` as a [pipeline config](https://docs.runpulse.com/v2/saved-configs.md) and send its `config_id` instead. The [API reference](https://docs.runpulse.com/api-reference/v2/pipeline/run-pipeline.md) lists every field.

## When a pipeline is refused

`POST /pipeline` checks the whole pipeline before anything runs. If any part is refused, no step runs and nothing is billed. For example:

* a step's `config` is invalid, there are more than 10 children, or a step needs an endpoint your organization hasn't turned on, such as Tables or Charts: `400`;
* the parse itself is refused, for example because the file can't be read: the status `POST /parse` would return;
* your API key's credit limit doesn't cover the parse together with its extract and split steps: `402`.

***

## Follow a pipeline

`GET /pipeline/{pipeline_id}` (`client.pipeline.get`) returns the pipeline's status, which takes the same values as a [job's](https://docs.runpulse.com/v2/jobs.md#statuses).

`GET /pipeline/{pipeline_id}/result` (`client.pipeline.get_result`) lists each step's id and status, but no step output. Partway through, a pipeline's result looks like this:

```json theme={null}
{
  "pipeline_id": "pipeline-2f0b7c1e-9a4d-4e6b-8c3f-1d5a7e9b0c24",
  "step": {
    "type": "parse",
    "parse_id": "parse-9b1e4c7a-3d2f-4a8b-b6e0-5c7d1f2a9e36",
    "created_at": "2026-10-29T17:02:11Z",
    "status": "completed",
    "completed_at": "2026-10-29T17:03:40Z"
  },
  "children": [
    {
      "type": "extract",
      "extract_id": "extract-4d8a2f6b-1c9e-4b7d-a3f5-8e0b6c2d7a19",
      "created_at": "2026-10-29T17:02:11Z",
      "status": "completed",
      "completed_at": "2026-10-29T17:04:05Z"
    },
    {
      "type": "tables",
      "tables_id": "tables-c1f7e3a9-6b2d-4e8f-9a0c-3d5b7e1f4a62",
      "created_at": "2026-10-29T17:02:11Z",
      "status": "processing"
    }
  ],
  "usage": {
    "request": { "pages_processed": 160, "credits_used": 160.0 }
  },
  "history": { "file_name": "10-k.pdf" }
}
```

Here `usage` adds up the two steps that have completed, the parse and the Extract step, at 80 pages each.

## How a pipeline ends

Once every step has ended, the pipeline ends as its parse did:

| The parse | The other steps | The pipeline |
| - | - | - |
| `completed` | Run, and each ends on its own. | `completed`, even if some steps failed. |
| `failed` | `skipped`, never run. | `failed`, with the parse's `error`. |
| `canceled` | `skipped`, never run. | `canceled`. |

A step that fails doesn't stop the other steps, so check each step's `status` in the result.

`DELETE /pipeline/{pipeline_id}` (`client.pipeline.cancel`) cancels a pipeline that is still `queued` or `processing`, with its unfinished steps. Those steps never complete and aren't billed. A step that already completed keeps its result.

***

## Use a pipeline when results are deleted on first read

Some organizations have each result deleted the first time it's read, which makes the [order of your calls](https://docs.runpulse.com/v2/jobs.md#where-results-are-deleted-on-first-read) matter outside a pipeline. In a pipeline, the parse stays available to the pipeline's steps until every one of them has ended, even if you read the parse's result first. Reading `GET /pipeline/{pipeline_id}/result` doesn't count as reading any step's result.

> [!WARNING]
>
> Each step's result, the parse's included, can still be read only once. A second read returns `410`, so save what you read the first time.

## Billing and retention

Each step costs what its endpoint costs once it completes, and the pipeline adds no charge of its own. See [Pricing](https://docs.runpulse.com/v2/pricing.md).

The pipeline and its steps [expire](https://docs.runpulse.com/v2/jobs.md#retention) together, counted from when the pipeline ends. If the parse step sends `storage.save: false`, for example, the pipeline and every step expire 24 hours after the pipeline ends.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.