> For a complete page index of the Captain API documentation, fetch https://docs.captain.dev/llms.txt?excludeSpec=true

# Get Parse Job

GET https://api.captain.dev/v3/parse/jobs/{job_id}

Returns the status of a parse job and, once it completes, its chunks in order. Chunks are included in the response when the result is 1 MB or smaller. `result_url` always links to the full result, so large results and bulk loads into another database can read from it directly. There are two ways to wait for a job. Poll: call this endpoint every few seconds without `wait`, and each request returns the current status at once. Long poll: pass `wait` (up to 50 seconds), and each request returns as soon as the job ends or the wait runs out. Either way, repeat until `status` is `completed`, `failed` or `cancelled`. The `parse.completed`, `parse.failed` and `parse.cancelled` webhook events report the end without polling.

Reference: https://docs.captain.dev/reference/v3/parse/jobs/get

## Request

### Path parameters

- `job_id` (string, required)

### Query parameters

- `wait` (integer, optional, default: 0) — Seconds to hold the request while the job is still running, from 0 to 50. The response returns as soon as the job completes, fails or is cancelled, or when the wait runs out, whichever comes first. 0 returns the current status at once.

### Headers

- `authorization` (string, optional)

## Response

### 200

The job.

- `job_id` (string, required)
- `type` (enum, required)
  - Allowed values: `document`, `spreadsheet`
- `status` (enum, required)
  - Allowed values: `queued`, `processing`, `completed`, `failed`, `cancelled`
- `created_at` (string, required)
- `file` (ParseJobV3File, optional)
- `options` (ParseJobV3Options, optional) — The options the job ran with. Credentials are never returned.
- `chunker_version` (string, optional, nullable)
- `usage` (ParseJobV3Usage, optional) — What this job was billed, at the same rates as indexing. Documents bill per page. Spreadsheets bill no pages: their credits come from the sheet text and any described images.
- `chunk_count` (integer, optional, nullable)
- `chunks` (list of ParseChunkV3, optional, nullable) — The chunks, in order, when the result is 1 MB or smaller. Larger results are returned only through `result_url`.
- `result_url` (string, optional, nullable)
- `result_expires_at` (string, optional, nullable)
- `error` (ParseJobV3Error, optional, nullable) — Why the job failed. Present when `status` is `failed`. A failed job is not billed.
- `completed_at` (string, optional, nullable)

## Errors

### 401 Unauthorized Error

Missing or invalid API key.

- `error` (string, optional)
- `message` (string, optional)

### 404 Not Found Error

No parse job with this id belongs to the organization (`not_found`).

- `error` (string, required)
- `message` (string, required)
- `path` (string, optional)

## Types

### ParseJobV3File

- `name` (string, optional)
- `type` (string, optional)
- `pages` (integer, optional, nullable)

### ParseJobV3Options

The options the job ran with. Credentials are never returned.

### ParseJobV3Usage

What this job was billed, at the same rates as indexing. Documents bill per page. Spreadsheets bill no pages: their credits come from the sheet text and any described images.

- `pages_basic` (integer, optional) — Pages billed at the basic rate.
- `pages_advanced` (integer, optional) — Pages billed at the advanced rate.
- `credits` (double, optional) — Total credits billed for the job, including spreadsheet text and image credits.

### ParseChunkV3

- `chunk_id` (string, required) — Stable identifier of the chunk within this job.
- `chunk_index` (integer, required) — Position of the chunk within its parent section. Numbering restarts for each `parent_chunk_index`.
- `parent_chunk_index` (integer, required) — Index of the parent section the chunk was cut from. Order chunks by `parent_chunk_index`, then `chunk_index`.
- `text` (string, required) — Chunk text, the same text Captain embeds and stores when indexing this file.
- `tokens` (integer, required) — Token count of the chunk.
- `location` (ParseLocationV3, required) — Source position for a returned chunk. Fields that do not apply to the source type are returned as null.
- `chunk_type` (enum, optional, nullable) — Layout role of the chunk when the parser reports one. Can be null, for example on long documents parsed in page ranges.
  - Allowed values: `body`, `table`, `heading`, `page_header`, `page_footer`, `footnote`, `figure`
- `sheet` (ParseChunkSheetV3, optional, nullable) — Spreadsheet structure for a chunk. Present on spreadsheet chunks only.
- `tags` (list of string, optional, nullable) — Present when `include_tags_summary` is true.
- `summary` (string, optional, nullable)
- `verified_facts` (ParseChunkV3VerifiedFacts, optional, nullable) — Present on spreadsheet row-group chunks when `include_verified_facts` is true.

### ParseJobV3Error

Why the job failed. Present when `status` is `failed`. A failed job is not billed.

- `code` (string, required) — One of `unsupported_file_type`, `download_failed`, `file_too_large`, `parse_failed`, `chunk_failed`, `assemble_failed`, `timed_out` or `start_failed`.
- `message` (string, required)

### ParseLocationV3

Source position for a returned chunk. Fields that do not apply to the source type are returned as null.

- `page_start` (integer, optional, nullable) — First page covered by the chunk for paged documents.
- `page_end` (integer, optional, nullable) — Last page covered by the chunk for paged documents.
- `start_sec` (double, optional, nullable)
- `end_sec` (double, optional, nullable)
- `sheet_name` (string, optional, nullable) — Worksheet name for spreadsheet chunks.
- `section` (string, optional, nullable)
- `row_start` (integer, optional, nullable) — First spreadsheet row covered by the chunk.
- `row_end` (integer, optional, nullable) — Last spreadsheet row covered by the chunk.
- `col_start` (integer, optional, nullable) — First spreadsheet column covered by the chunk.
- `col_end` (integer, optional, nullable) — Last spreadsheet column covered by the chunk.
- `columns` (list of string, optional, nullable)
- `row_role` (string, optional, nullable)

### ParseChunkSheetV3

Spreadsheet structure for a chunk. Present on spreadsheet chunks only.

- `section` (string, optional, nullable)
- `columns` (list of string, optional, nullable) — Column headers exactly as they appear in the sheet. Multi-row headers are joined with ` / `.
- `row_role` (string, optional, nullable)
- `structured_fields` (map from string to string, optional, nullable) — Values of the chunk's first row, keyed by column header.

### ParseChunkV3VerifiedFacts

Present on spreadsheet row-group chunks when `include_verified_facts` is true.

## Examples

### Poll every 5 seconds

**Response**

```json
{
  "job_id": "prs_c9f0f895fb98ab9159f51fd0297e236d",
  "type": "spreadsheet",
  "status": "completed",
  "created_at": "2026-10-01T15:04:05Z",
  "file": {
    "name": "ledger.xlsx",
    "type": "xlsx",
    "pages": null
  },
  "options": {
    "include_images": false,
    "include_verified_facts": true,
    "include_tags_summary": false,
    "input": "captain://upl_5d41402abc4b2a76b9719d911017c592"
  },
  "chunker_version": "production:412",
  "usage": {
    "pages_basic": 0,
    "pages_advanced": 0,
    "credits": 0
  },
  "chunk_count": 2,
  "chunks": [
    {
      "chunk_id": "prs_c9f0f895fb98ab9159f51fd0297e236d:0:0",
      "chunk_index": 0,
      "parent_chunk_index": 0,
      "text": "# Sheet: Intake, sheet information\n| Field | Value |\n| --- | --- |\n| Prepared by | Operations |\n| Season | 2025 |",
      "tokens": 31,
      "location": {
        "sheet_name": "Intake",
        "row_start": 1,
        "row_end": 3,
        "col_start": 1,
        "col_end": 2
      },
      "chunk_type": null,
      "sheet": {
        "section": "sheet information",
        "columns": [
          "Field",
          "Value"
        ],
        "row_role": "sheet_header",
        "structured_fields": {
          "Field": "Prepared by",
          "Value": "Operations"
        }
      }
    },
    {
      "chunk_id": "prs_c9f0f895fb98ab9159f51fd0297e236d:1:0",
      "chunk_index": 0,
      "parent_chunk_index": 1,
      "text": "# Sheet: Intake, section 1 (L-1000)\n| Load | Farm | Wheat / Tonnes | Wheat / Price/t |\n| --- | --- | --- | --- |\n| L-1000 | Mill Farm | 16 | 250 |\n| L-1001 | Ridge Farm | 22 | 245 |",
      "tokens": 58,
      "location": {
        "sheet_name": "Intake",
        "row_start": 7,
        "row_end": 8,
        "col_start": 1,
        "col_end": 4
      },
      "chunk_type": null,
      "sheet": {
        "section": "section 1 (L-1000)",
        "columns": [
          "Load",
          "Farm",
          "Wheat / Tonnes",
          "Wheat / Price/t"
        ],
        "row_role": null,
        "structured_fields": {
          "Load": "L-1000",
          "Farm": "Mill Farm",
          "Wheat / Tonnes": "16",
          "Wheat / Price/t": "250"
        }
      },
      "verified_facts": {
        "row_count": 2,
        "columns": {
          "Wheat / Tonnes": {
            "min": 16,
            "max": 22
          },
          "Wheat / Price/t": {
            "min": 245,
            "max": 250
          }
        }
      }
    }
  ],
  "result_url": "https://results.captain.dev/parse/prs_c9f0f895fb98ab9159f51fd0297e236d/result.json?signature=abc123",
  "result_expires_at": "2026-10-08T15:04:40Z",
  "error": null,
  "completed_at": "2026-10-01T15:04:40Z"
}
```

**SDK Code**

```python Poll every 5 seconds
import time

import requests

BASE_URL = "https://api.captain.dev"
API_KEY = "your_api_key"
JOB_ID = "prs_c9f0f895fb98ab9159f51fd0297e236d"

headers = {
    "Authorization": f"Bearer {API_KEY}",
}

while True:
    response = requests.get(
        f"{BASE_URL}/v3/parse/jobs/{JOB_ID}",
        headers=headers,
        timeout=30.0
    )
    response.raise_for_status()
    job = response.json()
    if job["status"] in ("completed", "failed", "cancelled"):
        break
    time.sleep(5)

chunks = job["chunks"]
if chunks is None and job["status"] == "completed":
    chunks = requests.get(job["result_url"], timeout=120.0).json()["chunks"]
print(job["status"], len(chunks or []))
```

```typescript Poll every 5 seconds
const BASE_URL = "https://api.captain.dev";
const API_KEY = "your_api_key";
const JOB_ID = "prs_c9f0f895fb98ab9159f51fd0297e236d";

let job;
for (;;) {
    const response = await fetch(
        `${BASE_URL}/v3/parse/jobs/${JOB_ID}`,
        {
            headers: {
                "Authorization": `Bearer ${API_KEY}`,
            }
        }
    );
    if (!response.ok) throw new Error(`Error: ${response.status}`);
    job = await response.json();
    if (["completed", "failed", "cancelled"].includes(job.status)) break;
    await new Promise((resolve) => setTimeout(resolve, 5000));
}

let chunks = job.chunks;
if (chunks == null && job.status === "completed") {
    chunks = (await (await fetch(job.result_url)).json()).chunks;
}
console.log(job.status, chunks?.length ?? 0);
```

### Long poll with wait

**Response**

```json
{
  "job_id": "prs_c9f0f895fb98ab9159f51fd0297e236d",
  "type": "spreadsheet",
  "status": "completed",
  "created_at": "2026-10-01T15:04:05Z",
  "file": {
    "name": "ledger.xlsx",
    "type": "xlsx",
    "pages": null
  },
  "options": {
    "include_images": false,
    "include_verified_facts": true,
    "include_tags_summary": false,
    "input": "captain://upl_5d41402abc4b2a76b9719d911017c592"
  },
  "chunker_version": "production:412",
  "usage": {
    "pages_basic": 0,
    "pages_advanced": 0,
    "credits": 0
  },
  "chunk_count": 2,
  "chunks": [
    {
      "chunk_id": "prs_c9f0f895fb98ab9159f51fd0297e236d:0:0",
      "chunk_index": 0,
      "parent_chunk_index": 0,
      "text": "# Sheet: Intake, sheet information\n| Field | Value |\n| --- | --- |\n| Prepared by | Operations |\n| Season | 2025 |",
      "tokens": 31,
      "location": {
        "sheet_name": "Intake",
        "row_start": 1,
        "row_end": 3,
        "col_start": 1,
        "col_end": 2
      },
      "chunk_type": null,
      "sheet": {
        "section": "sheet information",
        "columns": [
          "Field",
          "Value"
        ],
        "row_role": "sheet_header",
        "structured_fields": {
          "Field": "Prepared by",
          "Value": "Operations"
        }
      }
    },
    {
      "chunk_id": "prs_c9f0f895fb98ab9159f51fd0297e236d:1:0",
      "chunk_index": 0,
      "parent_chunk_index": 1,
      "text": "# Sheet: Intake, section 1 (L-1000)\n| Load | Farm | Wheat / Tonnes | Wheat / Price/t |\n| --- | --- | --- | --- |\n| L-1000 | Mill Farm | 16 | 250 |\n| L-1001 | Ridge Farm | 22 | 245 |",
      "tokens": 58,
      "location": {
        "sheet_name": "Intake",
        "row_start": 7,
        "row_end": 8,
        "col_start": 1,
        "col_end": 4
      },
      "chunk_type": null,
      "sheet": {
        "section": "section 1 (L-1000)",
        "columns": [
          "Load",
          "Farm",
          "Wheat / Tonnes",
          "Wheat / Price/t"
        ],
        "row_role": null,
        "structured_fields": {
          "Load": "L-1000",
          "Farm": "Mill Farm",
          "Wheat / Tonnes": "16",
          "Wheat / Price/t": "250"
        }
      },
      "verified_facts": {
        "row_count": 2,
        "columns": {
          "Wheat / Tonnes": {
            "min": 16,
            "max": 22
          },
          "Wheat / Price/t": {
            "min": 245,
            "max": 250
          }
        }
      }
    }
  ],
  "result_url": "https://results.captain.dev/parse/prs_c9f0f895fb98ab9159f51fd0297e236d/result.json?signature=abc123",
  "result_expires_at": "2026-10-08T15:04:40Z",
  "error": null,
  "completed_at": "2026-10-01T15:04:40Z"
}
```

**SDK Code**

```python Long poll with wait
import requests

BASE_URL = "https://api.captain.dev"
API_KEY = "your_api_key"
JOB_ID = "prs_c9f0f895fb98ab9159f51fd0297e236d"

headers = {
    "Authorization": f"Bearer {API_KEY}",
}
params = {
    "wait": 30,
}

while True:
    response = requests.get(
        f"{BASE_URL}/v3/parse/jobs/{JOB_ID}",
        headers=headers,
        params=params,
        timeout=60.0
    )
    response.raise_for_status()
    job = response.json()
    if job["status"] in ("completed", "failed", "cancelled"):
        break

chunks = job["chunks"]
if chunks is None and job["status"] == "completed":
    chunks = requests.get(job["result_url"], timeout=120.0).json()["chunks"]
print(job["status"], len(chunks or []))
```

```typescript Long poll with wait
const BASE_URL = "https://api.captain.dev";
const API_KEY = "your_api_key";
const JOB_ID = "prs_c9f0f895fb98ab9159f51fd0297e236d";

const params = new URLSearchParams({
    wait: "30",
});

let job;
for (;;) {
    const response = await fetch(
        `${BASE_URL}/v3/parse/jobs/${JOB_ID}?${params}`,
        {
            headers: {
                "Authorization": `Bearer ${API_KEY}`,
            }
        }
    );
    if (!response.ok) throw new Error(`Error: ${response.status}`);
    job = await response.json();
    if (["completed", "failed", "cancelled"].includes(job.status)) break;
}

let chunks = job.chunks;
if (chunks == null && job.status === "completed") {
    chunks = (await (await fetch(job.result_url)).json()).chunks;
}
console.log(job.status, chunks?.length ?? 0);
```