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

# Extract File

## Overview

<Info>
  **Pipeline Step 1** — Extract is where document processing begins: every downstream step consumes its `extraction_id`. After extraction, you can optionally [split](/current/api-reference/endpoint/split) the document into topics, apply [schema extraction](/current/api-reference/endpoint/schema) to get structured data, or use [tables](/current/api-reference/endpoint/tables) for span-aware table extraction.

  Handling mixed document types? [`/classify`](/current/api-reference/endpoint/classify) can run before Extract to route each raw document to the right pipeline — it only chooses *which* pipeline runs, so extraction still happens here.
</Info>

Extract document content as markdown, layout elements, table cells, words, figures, and optional extension output.

When a response reaches the configured inline-size threshold (5 MB by default), the API returns a download URL instead of embedding the payload. Fetching that URL returns the same complete extraction result JSON you would receive inline. See [Large Result Response](#large-result-response) below.

<Note>
  For large documents or batch processing workflows, set `async: true` to process asynchronously and poll for results via [GET /job/{'{'}jobId{'}'}](/current/api-reference/endpoint/poll).
</Note>

<Note>
  To process many files at once, use [Batch Extract](/current/api-reference/endpoint/batch-overview#batch-extract). It accepts an S3 prefix, local directory, or list of URLs and runs `/extract` on each file in parallel.
</Note>

### Async Mode

Set `async: true` to return immediately with a job ID for polling:

```json theme={null}
{
  "file_url": "https://example.com/document.pdf",
  "async": true
}
```

**Async Response (200):**

```json theme={null}
{
  "job_id": "abc123-def456",
  "status": "pending",
  "message": "Document processing started"
}
```

Use `GET /job/{job_id}` to poll for completion.

## Request

### Document Source

Provide the document using one of these methods:

| Field      | Type   | Description                                                                                                                                                                                       |
| ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `file`     | binary | Document file to upload directly (multipart/form-data). Limited to 100 MB — larger uploads return HTTP `413` (`FILE_TOO_LARGE_USE_URL`).                                                          |
| `file_url` | string | Public or pre-signed URL that Pulse will download and extract. Accepted at any size. Files over 100 MB must be submitted this way, via a `file_url` pointing at your own hosted or presigned URL. |

### Extraction Options

| Field                 | Type                           | Default   | Description                                                                                                                                                                                                                |
| --------------------- | ------------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pages`               | string                         | all pages | 1-indexed page ranges such as `"1-2,5"`. All page numbers in the response (markdown page breaks, bounding boxes, tables, words) always reference **original document page numbers**, even when only a subset is requested. |
| `refine`              | boolean \| string\[] \| object | `false`   | Post-extraction correction pass. See [Refinement](#refinement) below.                                                                                                                                                      |
| `refine_prompt`       | string                         | `""`      | Additional refinement guidance. Alias for `refine.prompt`, convenient with the boolean or array form of `refine`.                                                                                                          |
| `additional_prompt`   | string                         | `""`      | Additional document or domain context for extraction.                                                                                                                                                                      |
| `detect_selections`   | boolean                        | `true`    | Detect selected and unselected checkboxes, radio buttons, checkmarks, and similar controls.                                                                                                                                |
| `custom_image_prompt` | string                         | `""`      | Additional context for figure and chart interpretation.                                                                                                                                                                    |
| `figure_processing`   | object                         | `{}`      | Controls visual descriptions and image delivery.                                                                                                                                                                           |
| `extensions`          | object                         | `{}`      | Adds optional derived outputs without changing the core result contract.                                                                                                                                                   |
| `spreadsheet`         | object                         | `{}`      | Controls workbook parsing and spreadsheet cell output.                                                                                                                                                                     |
| `storage`             | object                         | enabled   | Controls persistence of extraction artifacts.                                                                                                                                                                              |
| `async`               | boolean                        | `false`   | Return a job ID immediately and poll `GET /job/{jobId}`.                                                                                                                                                                   |
| `force_url`           | boolean                        | `false`   | Deliver the complete result through a one-time result URL. Large and spreadsheet results may use URL delivery automatically.                                                                                               |

### Refinement

`refine` is off unless you enable it. It accepts three forms:

```json theme={null}
// 1. Boolean — run the general refinement pass
{ "refine": true }

// 2. Array — run only the listed modes
{ "refine": ["tables", "text"] }

// 3. Object — modes plus an optional prompt in one place
{ "refine": { "modes": ["tables", "layout"], "prompt": "Keep the source row and column order." } }
```

Valid modes:

| Mode         | What it corrects                                                          |
| ------------ | ------------------------------------------------------------------------- |
| `tables`     | Table structure, headers, and cell content                                |
| `text`       | OCR text, missing content, and numerical accuracy                         |
| `formatting` | Bold, italic, strikethrough, super/subscript, and LaTeX formatting        |
| `layout`     | Layout correctness pass (fixed behavior; does not accept a custom prompt) |

`refine.prompt` accepts a string that applies to the whole refinement pass, or an object keyed by mode (e.g. `{"tables": "...", "text": "..."}`) to scope guidance per mode. Prompt keys must be a subset of the requested modes, and `layout` never accepts a custom prompt. When you use the boolean or array form, pass the guidance through the sibling `refine_prompt` field instead; `refine.prompt` takes precedence over `refine_prompt` when both are present.

Refinement changes the normal `markdown` and `bounding_boxes` fields in place; it does not create a parallel response object.

### Figure Processing

| Field                           | Type                 | Default | Description                                                                                                                                                 |
| ------------------------------- | -------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `figure_processing.description` | boolean \| string\[] | `false` | Generate descriptions for detected visuals. Pass `true` for all visual classes, or a target list such as `["chart"]`, `["image"]`, or `["chart", "image"]`. |
| `figure_processing.show_images` | boolean \| string\[] | `false` | Return authenticated image URLs under `bounding_boxes.Images[]`. Accepts the same boolean or target-list forms as `description`.                            |
| `custom_image_prompt`           | string               | `""`    | Top-level field: additional context for figure and chart interpretation.                                                                                    |

For spreadsheets, `show_images: true` returns embedded charts and images with workbook-specific metadata such as `sheet_name`, `excel_range`, `chart_type`, and `source_ranges`.

### Extensions

Extensions add derived outputs while preserving the core response fields.

| Field                                | Type      | Default          | Output                                     |
| ------------------------------------ | --------- | ---------------- | ------------------------------------------ |
| `extensions.document_metadata`       | boolean   | `false`          | `extensions.document_metadata`             |
| `extensions.footnote_references`     | boolean   | `false`          | `extensions.footnoteReferences`            |
| `extensions.chunking.chunk_types`    | string\[] | none             | `extensions.chunking`                      |
| `extensions.chunking.chunk_size`     | integer   | strategy default | `extensions.chunking`                      |
| `extensions.alt_outputs.wlbb`        | boolean   | `false`          | `extensions.altOutputs.wlbb`               |
| `extensions.alt_outputs.return_html` | boolean   | `false`          | `extensions.altOutputs.html`               |
| `extensions.alt_outputs.return_xml`  | boolean   | `false`          | `extensions.altOutputs.xml` when available |

The standard `bounding_boxes.Words` collection is always part of the extraction response. `extensions.alt_outputs.wlbb` (equivalently, the top-level `word_level_bounding_boxes` parameter) requests the additional `extensions.altOutputs.wlbb` word-box output used by existing WLBB workflows. Organizations can also have word-level output enabled by default for every extraction — contact support to configure an org-wide default.

### Spreadsheet Options

| Field                                 | Type                   | Default  | Description                                                                             |
| ------------------------------------- | ---------------------- | -------- | --------------------------------------------------------------------------------------- |
| `spreadsheet.include_hidden_rows`     | boolean                | `false`  | Include hidden workbook rows.                                                           |
| `spreadsheet.include_hidden_cols`     | boolean                | `false`  | Include hidden workbook columns.                                                        |
| `spreadsheet.include_hidden_sheets`   | boolean                | `false`  | Include hidden sheets.                                                                  |
| `spreadsheet.use_raw_values`          | boolean                | `false`  | Return underlying numeric values instead of display-formatted text where supported.     |
| `spreadsheet.only_data_rows`          | boolean                | `false`  | Trim trailing empty rows after the last data-bearing cell.                              |
| `spreadsheet.only_data_cols`          | boolean                | `false`  | Trim trailing empty columns after the last data-bearing cell.                           |
| `spreadsheet.include_cell_formatting` | boolean                | `false`  | Include captured formatting and formula metadata in spreadsheet table cells.            |
| `spreadsheet.cell_data_mode`          | `inline` \| `external` | `inline` | Return full `cell_data` arrays inline, or per-table references to JSONL cell artifacts. |

### Complete Processing Example

```json theme={null}
{
  "file_url": "https://example.com/document.pdf",
  "async": true,
  "additional_prompt": "Preserve account labels and signed amounts.",
  "refine": {
    "modes": ["tables", "text"],
    "prompt": "Keep the source row and column order."
  },
  "detect_selections": true,
  "figure_processing": {
    "description": true,
    "show_images": true
  },
  "custom_image_prompt": "Describe axes, units, and legends.",
  "extensions": {
    "chunking": {
      "chunk_types": ["semantic", "page"],
      "chunk_size": 1000
    },
    "document_metadata": true
  }
}
```

### Storage Options

Control whether extractions are saved to your extraction library:

| Field                 | Type          | Default | Description                                                                           |
| --------------------- | ------------- | ------- | ------------------------------------------------------------------------------------- |
| `storage.enabled`     | boolean       | `true`  | Whether to persist extraction artifacts. Set to `false` for temporary extractions.    |
| `storage.folder_name` | string        | -       | Target folder name to save the extraction to. Creates the folder if it doesn't exist. |
| `storage.folder_id`   | string (uuid) | -       | Target folder ID to save the extraction to. Takes precedence over `folder_name`.      |

## Response

The response structure varies based on document size to optimize for different use cases.

### Standard Inline Response

When the response payload stays below the inline threshold, results are returned directly in the response body:

```json theme={null}
{
  "markdown": "# Document Title\n\nExtracted content...",
  "page_count": 15,
  "extraction_id": "abc123-def456-ghi789",
  "extraction_url": "https://platform.runpulse.com/dashboard/extractions/abc123",
  "credits_used": 15.0,
  "plan_info": {
    "tier": "growth",
    "pages_used": 240,
    "total_credits_used": 240.0
  },
  "plan-info": {
    "tier": "growth",
    "pages_used": 240,
    "total_credits_used": 240.0
  },
  "bounding_boxes": {
    "Title": [
      {
        "id": "txt-1",
        "content": "0a-Document Title",
        "original_content": "Document Title",
        "bounding_box": [0.1, 0.08, 0.7, 0.08, 0.7, 0.12, 0.1, 0.12],
        "page_number": 1,
        "average_word_confidence": 0.99
      }
    ],
    "Tables": [
      {
        "table_info": {
          "id": "tbl-1",
          "dimensions": {"rows": 3, "columns": 2},
          "location": {
            "coordinates": [0.1, 0.3, 0.9, 0.3, 0.9, 0.6, 0.1, 0.6],
            "page": 1
          },
          "confidence": 0.96
        },
        "cell_data": [
          {
            "id": "tbl-1-r0c0",
            "position": {"row": 0, "column": 0},
            "text": "0t-Account",
            "location": {
              "coordinates": [0.1, 0.3, 0.5, 0.3, 0.5, 0.4, 0.1, 0.4],
              "page": 1
            },
            "confidence": 0.97,
            "properties": {"type": "header"}
          }
        ]
      }
    ],
    "Words": [
      {
        "content": "Document",
        "page_number": 1,
        "bounding_box": [
          {"x": 0.1, "y": 0.08},
          {"x": 0.3, "y": 0.08},
          {"x": 0.3, "y": 0.12},
          {"x": 0.1, "y": 0.12}
        ],
        "confidence": 0.99
      }
    ],
    "markdown_with_ids": "<p data-bb-text-id=\"txt-1\">..."
  },
  "extensions": {
    "chunking": {
      "semantic": ["chunk 1...", "chunk 2..."],
      "header": ["section 1...", "section 2..."]
    },
    "altOutputs": {
      "html": "<html>...</html>"
    }
  }
}
```

#### Response Fields

| Field                           | Type          | Description                                                                                                                                                                                                                                      |
| ------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `markdown`                      | string        | Clean markdown content extracted from the document. Always present.                                                                                                                                                                              |
| `page_count`                    | integer       | Total number of pages processed.                                                                                                                                                                                                                 |
| `extraction_id`                 | string (uuid) | Persisted extraction ID. Present when storage is enabled (default). Use with `/split` and `/schema`.                                                                                                                                             |
| `extraction_url`                | string        | URL to view the extraction in the Pulse Platform. Present when storage is enabled.                                                                                                                                                               |
| `credits_used`                  | number        | Credits consumed by **this request**. Only present when the org has the credit billing system enabled.                                                                                                                                           |
| `plan_info`                     | object        | Billing tier and cumulative usage information for the calling org, including `tier`, `total_credits_used`, `pages_used`, and an optional `note`.                                                                                                 |
| `plan-info`                     | object        | Alias of `plan_info` with the same value.                                                                                                                                                                                                        |
| `bounding_boxes`                | object        | Typed layout data including text categories, `Tables[].table_info`, `Tables[].cell_data`, `Words`, visual items, and `markdown_with_ids`. Empty detected categories may be omitted. See [Bounding Boxes](/current/api-reference/bounding-boxes). |
| `extensions`                    | object        | Output from enabled extensions. Only keys for enabled extensions are present. See below.                                                                                                                                                         |
| `extensions.document_metadata`  | object        | Native properties and deterministic structure from the original file (when `extensions.document_metadata` is enabled). See [Document Metadata](#document-metadata) below.                                                                        |
| `extensions.chunking`           | object        | Chunk results by strategy (when `extensions.chunking` is enabled).                                                                                                                                                                               |
| `extensions.footnoteReferences` | array         | List of detected footnotes with their in-text references (when `extensions.footnote_references` is enabled). See [Footnote References](#footnote-references) below.                                                                              |
| `extensions.altOutputs.wlbb`    | object        | Word-level bounding boxes (when `extensions.alt_outputs.wlbb` is enabled).                                                                                                                                                                       |
| `extensions.altOutputs.html`    | string        | HTML representation (when `extensions.alt_outputs.return_html` is enabled).                                                                                                                                                                      |
| `extensions.altOutputs.xml`     | string        | XML representation (when `extensions.alt_outputs.return_xml` is enabled, WIP).                                                                                                                                                                   |
| `warnings`                      | array         | Optional non-fatal warnings generated during extraction.                                                                                                                                                                                         |
| `failed_pages`                  | array         | Only present on partial results. Each entry is `{"page": number, "error": string}` describing a page that could not be extracted. See [Partial Results](#partial-results-failed_pages) below.                                                    |

### Partial Results (`failed_pages`)

Extraction completes with a partial result whenever at least one page succeeds. A job fails outright only when **every** page is unextractable. On a partial result:

* `failed_pages` at the top level lists each unrecoverable page with a short reason.
* A matching entry is appended to `warnings`.
* The markdown carries a visible `<!-- PAGE N FAILED EXTRACTION: ... -->` placeholder at each hole, so page-oriented consumers keep their alignment.
* Only successfully extracted pages are billed.

```json theme={null}
{
  "markdown": "...\n<!-- PAGE 7153 FAILED EXTRACTION: content could not be certified -->\n...",
  "page_count": 11622,
  "failed_pages": [
    {"page": 7153, "error": "content could not be certified"}
  ],
  "warnings": [
    "1 of 11622 pages could not be certified; see failed_pages"
  ]
}
```

Treat `failed_pages` as the authoritative list of holes: if it is absent, every requested page was extracted.

<Note>
  If your organization was migrated from the default engine, responses keep the familiar shape documented on this page — including `bounding_boxes` with grouped `Words`, `Tables[].cell_data` with ids, spans, and confidences, and chunking output — and extraction bills at a flat 1 credit per page. Callers that explicitly request `model="pulse-ultra-2"` continue to receive today's Ultra response format unchanged.
</Note>

### Large Result Response

When a response reaches the configured inline-size threshold (5 MB by default), or when `force_url: true` is supplied, the API returns a download URL instead of inlining the payload. Some rollback deployments may also offload documents over 70 pages. The downloaded JSON is the complete extraction result with the normal response shape.

```json theme={null}
{
  "is_url": true,
  "url": "https://api.runpulse.com/results/abc123-def456-ghi789",
  "extraction_id": "abc123-def456-ghi789"
}
```

#### Large Result Response Fields

| Field           | Type    | Description                                                                                                                                                                                                                                                                                                    |
| --------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `is_url`        | boolean | Always `true` for URL-backed responses. Use this to detect URL-based responses.                                                                                                                                                                                                                                |
| `url`           | string  | Download URL for the complete JSON result. It may be a Pulse `/results/{job_id}` URL or a presigned storage URL, depending on organization settings. Anonymous Pulse result links are single-use and expire after 1 hour; same-org authenticated callers can replay them while the artifact remains available. |
| `extraction_id` | string  | Extraction/job identifier. The downloaded result contains the normal response fields, including metadata such as `page_count`, `credits_used`, and `plan_info` when available.                                                                                                                                 |

<Warning>
  Download and persist URL-backed results promptly. Anonymous Pulse result links are single-use and expire 1 hour after completion. Same-org authenticated requests can replay the Pulse URL while retention keeps the artifact available; presigned storage URLs follow their own expiry.
</Warning>

#### Handling Large Document Responses

<CodeGroup>
  ```python Python theme={null}
  import requests
  from pulse import Pulse

  API_KEY = "YOUR_API_KEY"
  client = Pulse(api_key=API_KEY)

  response = client.extract(
      file_url="https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf"
  )

  if hasattr(response, "is_url") and response.is_url:
      full_result = requests.get(
          response.url,
          headers={"x-api-key": API_KEY},
      ).json()
      print(full_result["markdown"])
  else:
      print(response.markdown)
  ```

  ```typescript TypeScript theme={null}
  import { PulseClient } from 'pulse-ts-sdk';

  const API_KEY = "YOUR_API_KEY";
  const client = new PulseClient({ apiKey: API_KEY });

  const response = await client.extract({
      fileUrl: "https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf"
  });

  if ((response as any).is_url) {
      const fullResult = await fetch((response as any).url, {
          headers: { "x-api-key": API_KEY },
      }).then(r => r.json());
      console.log(fullResult.markdown);
  } else {
      console.log(response.markdown);
  }
  ```

  ```bash curl theme={null}
  curl -X POST https://api.runpulse.com/extract \
    -H "x-api-key: YOUR_API_KEY" \
    -F "file=@large_document.pdf"

  # Response: {"is_url": true, "url": "https://api.runpulse.com/results/abc123-..."}

  # Fetch the complete result
  curl -H "x-api-key: YOUR_API_KEY" \
    "https://api.runpulse.com/results/abc123-..."
  ```
</CodeGroup>

<Note>
  Persist URL-backed results in your own storage after download. You can also keep `storage.enabled` and retrieve saved extractions from the Pulse Platform.
</Note>

## Example Usage

### Core Processing

Use `refine`, `refine_prompt`, `additional_prompt`, and `detect_selections` to steer or refine the primary extraction result. These are separate from `extensions`, which add derived outputs.

```bash theme={null}
curl -X POST https://api.runpulse.com/extract \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "file_url": "https://example.com/bank-statement.pdf",
    "additional_prompt": "Preserve account labels and signed amounts.",
    "refine": {
      "modes": ["tables", "text"],
      "prompt": "Keep source row and column order."
    },
    "detect_selections": true,
    "figure_processing": {
      "description": true
    },
    "custom_image_prompt": "Describe axes, units, and legends."
  }'
```

`refine` also accepts a boolean (`"refine": true` runs the general pass) or a bare array of modes (`"refine": ["tables", "formatting", "text", "layout"]`); with those forms, pass refinement guidance through the sibling `refine_prompt` field. See [Core Processing](/current/concepts/processing-parameters-core) for precedence and defaults.

### Basic Extraction

<CodeGroup>
  ```python Python theme={null}
  from pulse import Pulse
  from pulse.types import (
      ExtractRequestFigureProcessing,
      ExtractRequestExtensions,
      ExtractRequestExtensionsAltOutputs,
  )

  client = Pulse(api_key="YOUR_API_KEY")

  # Extract from URL with figure processing and HTML output
  response = client.extract(
      file_url="https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf",
      figure_processing=ExtractRequestFigureProcessing(
          description=True,
      ),
      extensions=ExtractRequestExtensions(
          alt_outputs=ExtractRequestExtensionsAltOutputs(
              return_html=True,
          ),
      ),
  )

  print(f"Markdown: {response.markdown}")
  print(f"HTML: {response.extensions.alt_outputs.html}")
  print(f"Extraction ID: {response.extraction_id}")
  ```

  ```typescript TypeScript theme={null}
  import { PulseClient } from 'pulse-ts-sdk';

  const client = new PulseClient({ apiKey: "YOUR_API_KEY" });

  const response = await client.extract({
      fileUrl: "https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf",
      figureProcessing: { description: true },
      extensions: { altOutputs: { returnHtml: true } }
  });

  console.log(`Markdown: ${response.markdown}`);
  console.log(`HTML: ${response.extensions?.altOutputs?.html}`);
  console.log(`Extraction ID: ${response.extraction_id}`);
  ```

  ```bash curl theme={null}
  # Extract from URL with figure processing
  curl -X POST https://api.runpulse.com/extract \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "file_url": "https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf",
      "figure_processing": {"description": true},
      "extensions": {"alt_outputs": {"return_html": true}}
    }'
  ```
</CodeGroup>

### File Upload

<CodeGroup>
  ```python Python theme={null}
  from pulse.types import ExtractRequestFigureProcessing

  # Upload and extract a local file
  with open("document.pdf", "rb") as f:
      response = client.extract(
          file=f,
      figure_processing=ExtractRequestFigureProcessing(
          description=True,
      ),
      )
  ```

  ```typescript TypeScript theme={null}
  import * as fs from 'fs';

  const fileBuffer = fs.readFileSync("document.pdf");
  const blob = new Blob([fileBuffer], { type: 'application/pdf' });

  const response = await client.extract({
      file: blob,
  });
  ```

  ```bash curl theme={null}
  curl -X POST https://api.runpulse.com/extract \
    -H "x-api-key: YOUR_API_KEY" \
    -F "file=@document.pdf"
  ```
</CodeGroup>

<Warning>
  Direct multipart uploads (`file=`) are limited to **100 MB**. Larger uploads fail fast with HTTP `413` and error code `FILE_TOO_LARGE_USE_URL`. Files over 100 MB must be submitted via `file_url` pointing at your own hosted or presigned URL — `file_url` submissions are accepted at **any size**. Very large documents are sharded and processed transparently under a single job ID; see [Working with Large Documents](/current/api-reference/large-documents).
</Warning>

### Structured Data (Extract → Schema)

<Info>
  Apply structured output with [`/schema`](/current/api-reference/endpoint/schema) after extraction. The resulting `extraction_id` lets you rerun or change schemas without processing the source document again.
</Info>

**Recommended two-step approach:**

<CodeGroup>
  ```python Python theme={null}
  # Step 1: Extract the document
  response = client.extract(
      file_url="https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf"
  )

  extraction_id = response.extraction_id

  # Step 2: Apply schema separately
  schema_result = client.schema(
      extraction_id=extraction_id,
      schema_config={
          "input_schema": {
              "type": "object",
              "properties": {
                  "total": {"type": "number"},
                  "vendor": {"type": "string"}
              }
          },
          "schema_prompt": "Extract invoice total and vendor"
      }
  )

  print(schema_result.schema_output)
  ```

  ```typescript TypeScript theme={null}
  // Step 1: Extract the document
  const response = await client.extract({
      fileUrl: "https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf"
  });

  const extractionId = response.extraction_id;

  // Step 2: Apply schema separately
  const schemaResult = await client.schema({
      extraction_id: extractionId,
      schema_config: {
          input_schema: {
              type: "object",
              properties: {
                  total: { type: "number" },
                  vendor: { type: "string" }
              }
          },
          schema_prompt: "Extract invoice total and vendor"
      }
  });

  console.log(schemaResult.schema_output);
  ```

  ```bash curl theme={null}
  # Step 1: Extract the document
  curl -X POST https://api.runpulse.com/extract \
    -H "x-api-key: YOUR_API_KEY" \
    -F "file=@invoice.pdf"

  # Response includes extraction_id: "abc123-..."

  # Step 2: Apply schema
  curl -X POST https://api.runpulse.com/schema \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "extraction_id": "abc123-...",
      "schema_config": {
        "input_schema": {"type": "object", "properties": {"total": {"type": "number"}, "vendor": {"type": "string"}}},
        "schema_prompt": "Extract invoice total and vendor"
      }
    }'
  ```
</CodeGroup>

### Document Metadata

Enable `extensions.document_metadata` to read native properties from the original
file before conversion, rendering, or OCR. The option is a single boolean; Pulse
returns every safely recoverable field for the detected format.

<CodeGroup>
  ```python Python SDK theme={null}
  from pulse.types import ExtractRequestExtensions

  response = client.extract(
      file_url="https://example.com/report.pdf",
      extensions=ExtractRequestExtensions(
          document_metadata=True,
      ),
  )

  metadata = response.extensions.document_metadata
  print(metadata.properties.title)
  print(metadata.structure.page_count)
  ```

  ```bash curl theme={null}
  curl -X POST https://api.runpulse.com/extract \
    -H "x-api-key: YOUR_API_KEY" \
    -F "file=@report.pdf" \
    -F 'extensions={"document_metadata":true};type=application/json'
  ```
</CodeGroup>

```json theme={null}
{
  "extensions": {
    "document_metadata": {
      "file": {
        "name": "pulse-complex-metadata-10-page.pdf",
        "extension": ".pdf",
        "media_type": "application/pdf",
        "size_bytes": 32506
      },
      "properties": {
        "title": "Pulse Complex Metadata Validation Report",
        "authors": ["Ritvik Pandey", "Pulse Document Intelligence"],
        "created_at": "2026-01-15T09:30:00-08:00"
      },
      "structure": {
        "page_count": 10,
        "outline_count": 10,
        "attachment_count": 1,
        "annotation_count": 5,
        "form_field_count": 3
      },
      "warnings": []
    }
  }
}
```

Absent metadata fields are omitted rather than returned as `null`. Metadata is
evidence declared by the source file and is not independently verified. Original
camera files may contain sensitive capture timestamps or GPS coordinates.

See [Document Metadata](/current/concepts/processing-parameters-document-metadata) for
format-specific behavior and implementation guidance.

### Page Range and Chunking

<CodeGroup>
  ```python Python theme={null}
  from pulse.types import (
      ExtractRequestExtensions,
      ExtractRequestExtensionsChunking,
  )

  response = client.extract(
      file_url="https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf",
      pages="1-5,10",  # 1-indexed
      extensions=ExtractRequestExtensions(
          chunking=ExtractRequestExtensionsChunking(
              chunk_types=["semantic", "page"],
              chunk_size=1000,
          ),
      ),
  )

  # Chunk data is in extensions.chunking
  print(response.extensions.chunking.semantic)
  print(response.extensions.chunking.page)
  ```

  ```typescript TypeScript theme={null}
  const response = await client.extract({
      fileUrl: "https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf",
      pages: "1-5,10",  // 1-indexed
      extensions: {
          chunking: {
              chunkTypes: ["semantic", "page"],
              chunkSize: 1000
          }
      }
  });

  // Chunk data is in extensions.chunking
  console.log(response.extensions?.chunking?.semantic);
  console.log(response.extensions?.chunking?.page);
  ```

  ```bash curl theme={null}
  curl -X POST https://api.runpulse.com/extract \
    -H "x-api-key: YOUR_API_KEY" \
    -F "file=@document.pdf" \
    -F "pages=1-5,10" \
    -F 'extensions={"chunking": {"chunk_types": ["semantic", "page"], "chunk_size": 1000}}'
  ```
</CodeGroup>

<Note>
  With a `pages=` subset, every page number in the response — page-break markers, bounding boxes, tables, `Words`, and extension output — refers to the **original document's** page numbers. Requesting `pages="10-20"` returns items labeled pages 10 through 20.
</Note>

### Footnote References

Enable `extensions.footnote_references` to detect footnote markers (e.g. `*`, `†`, `1`) in body text and link them to the footnote explanation paragraphs at the bottom of the page. Each result item includes the marker symbol, the bounding-box text ID of the footnote, and the bounding-box text IDs of all body-text paragraphs that reference it.

<CodeGroup>
  ```python Python theme={null}
  from pulse.types import ExtractRequestExtensions

  response = client.extract(
      file_url="https://example.com/research-paper.pdf",
      extensions=ExtractRequestExtensions(
          footnote_references=True,
      ),
  )

  # Footnote links are in extensions.footnote_references
  for ref in response.extensions.footnote_references:
      print(f"Marker: {ref.symbol}")
      print(f"  Footnote: {ref.footnote_text_id}")
      print(f"  Referenced by: {ref.reference_text_ids}")
  ```

  ```typescript TypeScript theme={null}
  const response = await client.extract({
      fileUrl: "https://example.com/research-paper.pdf",
      extensions: {
          footnoteReferences: true
      }
  });

  // Footnote links are in extensions.footnoteReferences
  for (const ref of response.extensions?.footnoteReferences ?? []) {
      console.log(`Marker: ${ref.symbol}`);
      console.log(`  Footnote: ${ref.footnoteTextId}`);
      console.log(`  Referenced by: ${ref.referenceTextIds}`);
  }
  ```

  ```bash curl theme={null}
  curl -X POST https://api.runpulse.com/extract \
    -H "x-api-key: YOUR_API_KEY" \
    -F "file=@research-paper.pdf" \
    -F 'extensions={"footnote_references": true}'
  ```
</CodeGroup>

#### Example Response

```json theme={null}
{
  "markdown": "...",
  "bounding_boxes": { ... },
  "extensions": {
    "footnoteReferences": [
      {
        "symbol": "*",
        "footnoteTextId": "txt-11",
        "referenceTextIds": ["txt-4", "txt-5", "txt-6", "txt-7", "txt-8"]
      },
      {
        "symbol": "†",
        "footnoteTextId": "txt-12",
        "referenceTextIds": ["txt-8"]
      },
      {
        "symbol": "4",
        "footnoteTextId": "txt-48",
        "referenceTextIds": ["txt-45"]
      }
    ]
  }
}
```

#### Footnote Reference Fields

| Field              | Type      | Description                                                                                                                                                                       |
| ------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `symbol`           | string    | The footnote marker symbol as detected in the document (e.g. `*`, `†`, `‡`, `1`, `#`).                                                                                            |
| `footnoteTextId`   | string    | The bounding-box text ID (e.g. `txt-11`) of the footnote explanation paragraph. Cross-reference with `bounding_boxes.Footer` to get the footnote's content and position.          |
| `referenceTextIds` | string\[] | Bounding-box text IDs of body-text paragraphs that contain a reference to this footnote. Cross-reference with `bounding_boxes.Text` to get each paragraph's content and position. |

<Info>
  Footnote reference detection combines layout categories with native PDF text evidence for accurate symbol identification. This handles common OCR confusion between visually similar symbols like `†`/`+` and `‡`/`#`. Supported markers include numbered (`1`, `2`, `3`), symbolic (`*`, `†`, `‡`, `§`, `#`), and lettered (`a`, `b`, `c`) footnotes.
</Info>

### Excel Spreadsheet Options

<CodeGroup>
  ```python Python theme={null}
  from pulse import Pulse
  from pulse.types import ExtractRequestSpreadsheet

  client = Pulse(api_key="YOUR_API_KEY")

  # Extract from Excel with hidden content included
  response = client.extract(
      file=open("financials.xlsx", "rb"),
      spreadsheet=ExtractRequestSpreadsheet(
          include_hidden_rows=True,
          include_hidden_cols=True,
          include_hidden_sheets=False,
      ),
  )

  print(response.markdown)
  ```

  ```typescript TypeScript theme={null}
  import { PulseClient } from 'pulse-ts-sdk';

  const client = new PulseClient({
      headers: { 'x-api-key': 'YOUR_API_KEY' }
  });

  const response = await client.extract({
      file: fs.createReadStream("financials.xlsx"),
      spreadsheet: {
          includeHiddenRows: true,
          includeHiddenCols: true,
          includeHiddenSheets: false
      }
  });

  console.log(response.markdown);
  ```

  ```bash curl theme={null}
  curl -X POST https://api.runpulse.com/extract \
    -H "x-api-key: YOUR_API_KEY" \
    -F "file=@financials.xlsx" \
    -F 'spreadsheet={"include_hidden_rows": true, "include_hidden_cols": true, "include_hidden_sheets": false, "cell_data_mode": "inline"}'
  ```
</CodeGroup>

<Note>
  Spreadsheet table cells are returned under `bounding_boxes.Tables[].cell_data`. The default `spreadsheet.cell_data_mode: "inline"` returns each full cell array. Set it to `"external"` to return per-table JSONL artifact references when very large workbooks make inline cell arrays impractical.
</Note>

<Note>
  Workbooks exported from claims systems, ERPs, and other automated pipelines often declare a "used range" that extends hundreds of thousands of rows past where the data actually ends. Set `spreadsheet.only_data_rows: true` and `spreadsheet.only_data_cols: true` to have Pulse trim those trailing empty "phantom" rows and columns before parsing. Surviving cells keep their original A1 coordinates, so any citation or bounding box that references a specific cell remains stable. Both flags default to `false`. See the extraction options above for the full reference.
</Note>

### Excel Charts and Embedded Images

When you set `figure_processing.show_images: true` on an Excel workbook, every embedded chart and image is collected from the workbook directly and returned under `bounding_boxes.Images[]`. Each entry carries a Pulse-hosted `image_url` you can fetch via [`results.getImage`](/current/api-reference/endpoint/results-image) (or any HTTP client with your API key) to get the raw PNG/JPEG bytes.

<CodeGroup>
  ```python Python theme={null}
  import re
  from pulse import Pulse
  from pulse.types import ExtractRequestFigureProcessing

  client = Pulse(api_key="YOUR_API_KEY")

  # 1) Extract the workbook with show_images enabled.
  response = client.extract(
      file=open("financials.xlsx", "rb"),
      figure_processing=ExtractRequestFigureProcessing(
          show_images=True,
          description=False,
      ),
  )

  # 2) Walk the typed Images array.
  for img in response.bounding_boxes.images or []:
      print(f"{img.id}: {img.visual_type} '{img.chart_title}' @ {img.excel_range}")
      print(f"    url: {img.image_url}")

  # 3) Fetch the bytes for one chart.
  img = response.bounding_boxes.images[0]
  m = re.search(r"/results/([^/]+)/images/([^/?#]+)", img.image_url)
  job_id, filename = m.group(1), m.group(2)

  chunks = list(client.results.get_image(job_id=job_id, filename=filename))
  with open("chart.png", "wb") as f:
      f.write(b"".join(chunks))
  ```

  ```typescript TypeScript theme={null}
  import { PulseClient } from "pulse-ts-sdk";
  import * as fs from "node:fs";

  const client = new PulseClient({ apiKey: "YOUR_API_KEY" });

  // 1) Extract the workbook with show_images enabled.
  const response = await client.extract({
      file: fs.createReadStream("financials.xlsx"),
      figureProcessing: { showImages: true, description: false },
  });

  // 2) Walk the typed Images array.
  for (const img of response.boundingBoxes?.Images ?? []) {
      console.log(
          `${img.id}: ${img.visualType} '${img.chartTitle}' @ ${img.excelRange}`,
      );
      console.log(`    url: ${img.imageUrl}`);
  }

  // 3) Fetch the bytes for one chart.
  const url = response.boundingBoxes?.Images?.[0]?.imageUrl;
  const m = url?.match(/\/results\/([^/]+)\/images\/([^/?#]+)/);
  const [, jobId, filename] = m!;
  const image = await client.results.getImage({ jobId, filename });
  // Persist `image` per your runtime (e.g. `await image.bytes()`).
  ```

  ```bash curl theme={null}
  # Step 1: extract and capture an image_url from the response.
  curl -sS -X POST https://api.runpulse.com/extract \
    -H "x-api-key: YOUR_API_KEY" \
    -F "file=@financials.xlsx" \
    -F 'figure_processing={"show_images": true}' \
    | jq -r '.bounding_boxes.Images[0].image_url'

  # Step 2: fetch the PNG bytes.
  curl -sS -X GET "https://api.runpulse.com/results/$JOB_ID/images/excel_image_1_1.png" \
    -H "x-api-key: YOUR_API_KEY" \
    -o chart.png
  ```
</CodeGroup>

#### Example `bounding_boxes.Images` Entry

```json theme={null}
{
  "id": "excel_image_1_1",
  "visual_type": "chart",
  "page_number": 1,
  "bounding_box": [],
  "image_url": "https://api.runpulse.com/results/13e3e75f-.../images/excel_image_1_1.png",
  "sheet_name": "Charts",
  "excel_range": "D2",
  "chart_type": "BarChart",
  "chart_title": "Revenue",
  "source_ranges": ["'Charts'!$A$2:$A$5", "'Charts'!$B$2:$B$5"],
  "description": "Bar chart showing revenue by quarter."
}
```

See [Bounding Boxes — Images Array](/current/api-reference/bounding-boxes#images-array) for the full field reference and [Get Result Image](/current/api-reference/endpoint/results-image) for the auth requirement on `image_url`.

### Disable Storage

<CodeGroup>
  ```python Python theme={null}
  response = client.extract(
      file_url="https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf",
      storage={"enabled": False}
  )
  ```

  ```typescript TypeScript theme={null}
  const response = await client.extract({
      fileUrl: "https://platform.runpulse.com/api/examples/637e5678-30b1-45fa-acc4-877f2d636419/pdf",
      storage: { enabled: false }
  });
  ```

  ```bash curl theme={null}
  curl -X POST https://api.runpulse.com/extract \
    -H "x-api-key: YOUR_API_KEY" \
    -F "file=@document.pdf" \
    -F 'storage={"enabled": false}'
  ```
</CodeGroup>
