> ## 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.

# Moving from V1

> Map your version 1 calls to version 2, and move them one at a time.

Version 2 keeps the base URL and your API keys. You choose the version on each request with the `Pulse-Version` header, so you can move one call at a time.

> [!WARNING]
>
> Two endpoints change names. Version 1's `/extract` is version 2's `/parse`, and version 1's `/schema` is version 2's `/extract`, which reads a `parse_id` instead of a document. Rename each call when you add the header.

> [!WARNING]
>
> You can't create a version 1 saved pipeline on the hosted API anymore. A version 1 `POST /pipeline` is refused, because that path now runs [version 2 pipelines](https://docs.runpulse.com/v2/pipelines.md). Saved pipelines you already have still run on version 1.

## Move your calls

**1. Send the header**

Use a [2.x SDK](#sdks), which sends `Pulse-Version: 2` for you, or add the header to each raw HTTP call you move.

**2. Rename the call**

Rename the endpoint, move its options into `config`, and read the result from the job's `/result` route. The tables below map each name.

**3. Change the default, if you want**

Once every call sends the header, an admin can [make version 2 the default](#change-the-default-version).

***

## Which version serves a request

A request with `Pulse-Version: 2` or `Pulse-Version: 1` reaches that version, and any other value is refused with `400`. A request without the header reaches your organization's default version, its `default_api_version`. When the default is 2 but version 2 has no such route, version 1 serves it.

A few requests don't follow these rules:

* **A job's routes follow its id.** A `parse-…` id reaches version 2 with or without the header, and a version 1 job id, a bare UUID, reaches version 1.
* **Ids don't cross versions.** A version 2 id sent to version 1 is refused, and so is a version 1 `extraction_id` sent to version 2 as a `parse_id`.
* **Saved configs and `/settings/org` exist only in version 2.** Without the header they reach version 2; with `Pulse-Version: 1` they're refused.

### Change the default version

New organizations start with version 1 as their default. An organization admin can change it in the [dashboard](https://platform.runpulse.com), under **Organization Settings** → **Default API Version**, or with a key whose creator is an organization admin:

```bash theme={null}
curl -X PATCH https://api.runpulse.com/settings/org \
  -H "x-api-key: $PULSE_API_KEY" \
  -H "Pulse-Version: 2" \
  -H "Content-Type: application/json" \
  -d '{"default_api_version": "2"}'
```

The new default applies to requests within a minute.

> [!WARNING]
>
> Keep the default at 1 while you still use the 1.x SDKs, the MCP server or the CLI. They send no `Pulse-Version`, so with the default at 2, their calls to `/extract`, `/split`, `/tables`, `/charts`, `/classify`, `POST /webhook` and `/usage` reach version 2 and fail. In the 1.x SDKs, those calls are `extract`, `split`, `tables`, `charts`, `classify`, `webhooks.create_webhook_link` and `usage.get`.

***

## Endpoints

| Version 1 | Version 2 |
| - | - |
| `POST /extract` | `POST /parse` |
| `POST /extract_async` | `POST /parse` with `async: true` |
| `POST /schema` | `POST /extract`, which reads a `parse_id` |
| `POST /split`, `POST /tables`, `POST /charts` | The same paths, reading a `parse_id` |
| `POST /classify` | `POST /classify`, which still reads the document |
| `POST /pipeline/execute` | `POST /pipeline`, which always runs async |
| `POST /batch/extract` | `POST /parse/batch` |
| `POST /batch/schema`, `/batch/tables`, `/batch/split` | None: run the step on each document's parse |
| `GET /job/{jobId}` | `GET /{endpoint}/{id}`, such as `GET /parse/{parse_id}` |
| `DELETE /job/{jobId}`, `POST /cancel/{job_id}` | `DELETE /{endpoint}/{id}` |
| `POST /webhook`, which returns a portal link | `POST /webhook`, which creates [your own webhook](https://docs.runpulse.com/v2/webhooks.md) |
| `GET /usage` | `GET /usage`, with `from` and `to` but no `group_by` |
| `/form/*`, `/results/{jobId}/…`, `POST /convert` | None: these stay on version 1 |

Pay-per-request (MPP) keys also stay on version 1.

## What changed on every endpoint

| What | Version 1 | Version 2 |
| - | - | - |
| A call without `async` | Returns the result | Returns the job's id; you read the result at `GET /{endpoint}/{id}/result` |
| Large results | A one-time URL | Always the result route, which streams |
| Unknown fields | Often ignored | Refused with `400`, naming the field |
| Errors | `{"error": {"code", "message", "details"}}`, with codes such as `REQ_002` | A [problem details](https://docs.runpulse.com/v2/errors.md) body, with named codes such as `REQUEST_INVALID` and a `request_id`. [Error codes](https://docs.runpulse.com/errors.md#from-api-version-1) maps each version 1 code |
| An element's `page` with a `page_range` | The document's page number | Counted from 1 within the range: with `"5-7"`, the document's page 5 is page 1 |
| Pages of a long document that fail | `failed_pages: [{page, error}]` | Page numbers in `failed_pages`, and a `PAGES_FAILED` warning |
| A refine or figure pass that fails | Fails the job | Completes with a `REFINE_SKIPPED` or `FIGURE_DESCRIPTIONS_SKIPPED` warning |

## /extract becomes /parse

Every version 2 option below sits inside `config`. [Parse](https://docs.runpulse.com/v2/parse.md) describes the rest, such as spreadsheet options and extensions.

| Version 1 | Version 2 |
| - | - |
| `model` (optional) | `model` (required): `ultra-2` |
| `pages`, `additional_prompt` | `page_range`, `prompt` |
| `refine`, `refine_prompt` | `enhancements.refine.modes: [{"mode": "tables", "prompt": "..."}]` |
| `detect_selections` (on by default) | `enhancements.detect: ["selections"]` (off by default) |
| `figure_processing.description`, `figure_processing.show_images`, `custom_image_prompt` | `enhancements.figures.describe: ["all"]`, `include: true` (images inline, base64-encoded), `prompt` |
| `storage.enabled`, `storage.folder_name` | `storage.save`, and `storage.folder_id`: create a folder in the dashboard, then copy its ID from the folder's menu |
| `bounding_boxes`, `markdown_with_ids` | `elements` in the result, in reading order; `extensions.additional_formats: ["tagged_md"]` for Markdown tagged with element ids |

## /schema becomes /extract

| Version 1 | Version 2 |
| - | - |
| `extraction_id` | `parse_id` |
| `extraction_ids` | Gone: an extraction reads one parse |
| `schema_config.input_schema`, `schema_config.schema_prompt` | `config.input_schema`, `config.prompt` |
| `schema_config.effort: true`, `agentic_confidence: true` | `config.effort: "high"`, `config.confidence: "agentic"` |
| `split_id`, `split_schema_config` | Gone: extract each topic's pages with `config.page_range` |
| `schema_output.values`, `citations`, `confidence` | `extraction.enriched`, where each value is `{value, citation, confidence}`. For the values alone, send `config.format: ["raw"]` (or `"all"` for both) and read `extraction.raw` |

## Split, Tables and Charts

| Version 1 | Version 2 |
| - | - |
| `/split`: `split_config.split_input`, `split_config.effort: true` | `config.topics`, `config.effort: "high"` |
| `/tables`: `tables_config.merge` | Gone: tables are always merged across pages |
| `/tables`: `tables_config.charts_to_tables` | Gone: send charts to `/charts` |
| `/charts`: `charts_config.agentic_zoom` | `config.enhancements.zoom` |
| `/charts`: export files | Inside the result, base64-encoded |

***

## SDKs

The 2.x Python SDK, `pulse-ai`, and the 1.x `pulse-python-sdk` both install the `pulse` module, so uninstall the 1.x package first:

```bash theme={null}
pip uninstall pulse-python-sdk
pip install pulse-ai
```

| 1.x call | 2.x call |
| - | - |
| `client.extract(...)` | `client.parse.run(...)`, then `client.parse.get_result(parse_id)` |
| `client.extract_async(...)` | `client.parse.run(..., async_=True)` |
| `client.jobs.get_job(...)` | `client.parse.get(parse_id)`, or the `get` of the job's own endpoint |
| `client.schema(...)` | `client.extract.run(parse_id=..., config=...)` |
| `client.split(...)`, `client.tables(...)`, `client.charts(...)` | `client.split.run(parse_id=..., config=...)`, and the same for `tables` and `charts` |
| `client.classify(...)` | `client.classify.run(...)` |
| `client.batch.extract(...)` | `client.parse.batch.run(...)` |
| `client.pipeline.execute(...)` | `client.pipeline.run(...)` |
| `client.webhooks.create_webhook_link()` | `client.webhook.create(...)` |

TypeScript has the same calls in camelCase. The 2.x SDKs never retry a request on their own: [SDKs](https://docs.runpulse.com/v2/sdks.md#retries) shows how to retry safely.


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