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

# Get Job

GET https://acme.captain.dev/v1/jobs/{job_id}

Get a job's status and stages, and a build's progress. A write key reads the jobs it started and the automatic builds of its indexes, the ones Get Update Status names. Any other job answers 404 JOB_NOT_FOUND.

Reference: https://docs.captain.dev/reference/product-search/indexes/get-job

## Authentication

- `Authorization` header (bearer token, required) — A write key, issued by Captain. Server side only. Sent as Authorization: Bearer \<key>.

## Request

### Path parameters

- `job_id` (string, required)

## Response

### 200

job

- `id` (string, optional)
- `type` (enum, optional) — index.write: record updates and deletes through the ordered update log, one job per burst
  - Allowed values: `index.build`, `index.delete`, `index.import`, `index.write`
- `index` (string, optional)
- `seq_from` (integer, optional) — index.write: the first update this job carries
- `seq_to` (integer, optional) — index.write: the last update this job carries
- `records` (integer, optional) — index.write: records written
- `status` (enum, optional)
  - Allowed values: `queued`, `running`, `importing`, `succeeded`, `failed`
- `created_at` (integer, optional)
- `started_at` (integer, optional, nullable)
- `finished_at` (integer, optional, nullable)
- `stages` (list of JobStagesItems, optional)
- `progress` (BuildProgress, optional) — index.build: how far the build has got. records_done counts the records the running step has done; a step that doesn't count records (tables, verify, publish) reports every record done
- `build_id` (string, optional)
- `error` (string, optional, nullable)
- `error_code` (string, optional) — why a failed job failed; error says it in words to show. A build: BAD_VALUES (more than half of a field's values don't fit its declared type; see bad_values), BUILD_QUALITY (the build's checks refused it; the previous build keeps serving), NO_RECORDS (every record was deleted or cleared before the build read them), CANCELLED (the index is being deleted), ENGINE_ERROR (a fault on Captain's side, in the build or in running the job; the index keeps serving its previous build; retry). An index delete: DELETE_FAILED (Captain couldn't delete the index; retry). An Algolia import: the Algolia code (ALGOLIA_APP_NOT_FOUND, ALGOLIA_UNREACHABLE, ALGOLIA_INDEX_NOT_FOUND, ALGOLIA_FORBIDDEN, ALGOLIA_TIMEOUT, ALGOLIA_ERROR, INDEX_TOO_LARGE, VALIDATION_ERROR) or IMPORT_FAILED (a fault on Captain's side; nothing was changed; retry)
- `bad_values` (list of JobBadValuesItems, optional) — with error_code BAD_VALUES: per field, count of bad values, of how many, and up to 3 examples
- `warnings` (list of JobWarningsItems, optional) — \{code, message}. Settings resolved by a rule (the same warnings GET settings shows). Any build: SETTLE\_TIMEOUT (the build is live, but its search results are still settling and may shift for a few minutes; no action needed)
- `facet_report` (list of JobFacetReportItems, optional) — per number or date facet, whether it has value counts and why
- `result` (JobResult, optional, nullable)

## Types

### JobStagesItems

- `name` (enum, optional)
  - Allowed values: `schema`, `dense`, `splade`, `upload`, `tables`, `verify`, `publish`, `replicate`, `drop`, `pull`, `read`, `map`, `add`, `validate`, `embed`, `upsert`, `refresh`
- `status` (enum, optional)
  - Allowed values: `queued`, `pending`, `running`, `done`, `failed`, `skipped`
- `count` (integer, optional)
- `ms` (double, optional)
- `started_at` (double, optional)
- `finished_at` (double, optional)
- `reason` (string, optional) — why this step was skipped, in words
- `items` (integer, optional)
- `of` (integer, optional)
- `seconds` (integer, optional)

### BuildProgress

index.build: how far the build has got. records_done counts the records the running step has done; a step that doesn't count records (tables, verify, publish) reports every record done

- `step` (string, optional) — the step running now, as named in stages; queued before the first, done after the last
- `step_number` (integer, optional) — its place in the build, from 1
- `steps` (integer, optional) — steps in this build
- `records_done` (integer, optional)
- `records_total` (integer, optional, nullable)

### JobBadValuesItems

### JobWarningsItems

### JobFacetReportItems

- `field` (string, optional)
- `type` (string, optional)
- `distinct` (integer, optional, nullable)
- `facet_counts` (boolean, optional)
- `reason` (string, optional)

### JobResult

- `alias` (string, optional)
- `build` (string, optional)
- `physical` (string, optional)
- `previous` (string, optional, nullable)
- `records` (integer, optional)
- `facets` (list of string, optional)
- `seconds` (integer, optional)
- `checks` (JobResultChecks, optional)
- `constraints` (JobResultConstraints, optional) — index.build: whether measurement queries ('under 30 inches') are scored on the new build, and why not
- `report` (string, optional) — index.import: where the import report is
- `build_job` (string, optional, nullable) — index.import: the build job it queued

### JobResultChecks

### JobResultConstraints

index.build: whether measurement queries ('under 30 inches') are scored on the new build, and why not

- `ranking` (boolean, required)
- `reason` (string, required, nullable)

## Examples

### Get an import job

**Response**

```json
{
  "id": "job_20260928213040_9b27e1",
  "type": "index.import",
  "index": "products",
  "status": "succeeded",
  "created_at": 1759095040,
  "started_at": 1759095040,
  "finished_at": 1759095161,
  "stages": [
    {
      "name": "pull",
      "status": "done",
      "records": 12840,
      "synonyms": 46,
      "rules": 6,
      "notes": []
    },
    {
      "name": "map",
      "status": "done",
      "fields": 14,
      "facets": [
        "brand",
        "category",
        "color"
      ],
      "counts": {
        "settings": {
          "mapped": 9,
          "approximated": 2,
          "not_mapped": 1
        },
        "synonyms": {
          "mapped": 46,
          "approximated": 0,
          "not_mapped": 0
        },
        "rules": {
          "mapped": 5,
          "approximated": 0,
          "not_mapped": 1
        },
        "records": 12840
      }
    },
    {
      "name": "add",
      "status": "done",
      "documents": 12840,
      "synonyms_saved": 46,
      "rules_saved": 5
    }
  ],
  "error": null,
  "result": {
    "records": 12840,
    "build_job": "job_20260928213301_4c8d2e",
    "documents": 12840,
    "counts": {
      "settings": {
        "mapped": 9,
        "approximated": 2,
        "not_mapped": 1
      },
      "synonyms": {
        "mapped": 46,
        "approximated": 0,
        "not_mapped": 0
      },
      "rules": {
        "mapped": 5,
        "approximated": 0,
        "not_mapped": 1
      },
      "records": 12840
    }
  }
}
```

**SDK Code**

```python Python REST
import json
import requests

BASE_URL = "https://acme.captain.dev"
API_KEY = "your_write_key"
JOB_ID = "job_20260928213040_9b27e1"

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

response = requests.get(
    f"{BASE_URL}/v1/jobs/{JOB_ID}",
    headers=headers,
    timeout=30.0,
)

print(json.dumps(response.json(), indent=2))
```

```typescript TypeScript REST
const BASE_URL = "https://acme.captain.dev";
const API_KEY = "your_write_key";
const jobId = "job_20260928213040_9b27e1";

const response = await fetch(`${BASE_URL}/v1/jobs/${jobId}`, {
  method: "GET",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
  },
});

const result = await response.json();
console.log(JSON.stringify(result, null, 2));
```

### Get a running build job

**Response**

```json
{
  "id": "job_20260929231500_1a2b3c",
  "type": "index.build",
  "index": "products",
  "status": "running",
  "created_at": 1759187700,
  "started_at": 1759187702,
  "finished_at": null,
  "stages": [
    {
      "name": "schema",
      "status": "done",
      "items": 250000,
      "seconds": 41
    },
    {
      "name": "dense",
      "status": "running",
      "items": 118000,
      "of": 250000
    },
    {
      "name": "splade",
      "status": "queued"
    },
    {
      "name": "upload",
      "status": "queued"
    },
    {
      "name": "tables",
      "status": "queued"
    },
    {
      "name": "verify",
      "status": "queued"
    },
    {
      "name": "publish",
      "status": "queued"
    },
    {
      "name": "replicate",
      "status": "queued"
    }
  ],
  "progress": {
    "step": "dense",
    "step_number": 2,
    "steps": 8,
    "records_done": 118000,
    "records_total": 250000
  },
  "error": null,
  "result": null
}
```

**SDK Code**

```python Python REST
import json
import requests

BASE_URL = "https://acme.captain.dev"
API_KEY = "your_write_key"
JOB_ID = "job_20260929231500_1a2b3c"

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

response = requests.get(
    f"{BASE_URL}/v1/jobs/{JOB_ID}",
    headers=headers,
    timeout=30.0,
)

print(json.dumps(response.json(), indent=2))
```

```typescript TypeScript REST
const BASE_URL = "https://acme.captain.dev";
const API_KEY = "your_write_key";
const jobId = "job_20260929231500_1a2b3c";

const response = await fetch(`${BASE_URL}/v1/jobs/${jobId}`, {
  method: "GET",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
  },
});

const result = await response.json();
console.log(JSON.stringify(result, null, 2));
```