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

# Extraction schemas

> Immutable Extract schema registry — 6 endpoints

Reusable JSON Schemas for `POST /v1/extract`, versioned immutably under a stable
`sch_...` id. Create a family, append new versions, and pin a version from Extract
with `schema_version` (omit it to use latest).

Tenant keys create and append only their own schemas. Reads include tenant and
platform schemas; when both share a `schema_id`, the tenant row wins.

All list routes use **keyset cursor pagination** (`cursor` / `next_cursor`).

Out of scope for this surface: drafts, in-place updates, archive/delete.

***

## List extraction schemas

```
GET /v1/extraction-schemas
```

Return one **latest** summary per visible schema family.

**Query parameters:**

* **`cursor`** (optional) — Opaque cursor from a previous page's `next_cursor`.
* **`limit`** (optional, default 50, max 200) — Rows per page.

**Response:**

```json theme={"dark"}
{
  "items": [
    {
      "schema_id": "sch_abc123",
      "name": "Invoice",
      "description": "Core invoice fields",
      "version": 2,
      "owner": "tenant",
      "created_at": "2026-08-16T12:00:00Z",
      "updated_at": "2026-08-16T13:00:00Z"
    }
  ],
  "next_cursor": null,
  "total_count": 1
}
```

**Response fields:**

* **`schema_id`** — Stable family id (`sch_...`).
* **`name` / `description`** — Snapshotted on the latest version.
* **`version`** — Highest integer version in the winning ownership scope.
* **`owner`** — `tenant` or `platform`.

***

## Create extraction schema

```
POST /v1/extraction-schemas
```

Create version 1 of a new tenant-owned family. The server generates `schema_id`.

**Request:**

```json theme={"dark"}
{
  "name": "Invoice",
  "description": "Core invoice fields",
  "schema": {
    "type": "object",
    "properties": {
      "invoice_number": {"type": "string"}
    },
    "required": ["invoice_number"]
  }
}
```

**Body fields:**

* **`name`** (required, max 128) — Human-readable label for this version.
* **`description`** (optional, max 2000) — Longer explanation.
* **`schema`** (required) — JSON Schema. Validated with the same compiler Extract uses; invalid schemas are refused with `invalid_schema` and not written.

**Response (201 Created):** full version object including `schema`, `version=1`, and generated `schema_id`.

***

## Get extraction schema

```
GET /v1/extraction-schemas/{schema_id}
```

Return the latest visible version (tenant preferred over platform).

**Errors:**

* **`schema_not_found`** (404) — Unknown id, or a schema owned only by another tenant.

***

## List extraction schema versions

```
GET /v1/extraction-schemas/{schema_id}/versions
```

Page the immutable history of one visible family (oldest → newest).

**Query parameters:** same `cursor` / `limit` as list.

***

## Create extraction schema version

```
POST /v1/extraction-schemas/{schema_id}/versions
```

Validate and append the next immutable integer version. Only the owning tenant
may append; platform-only ids and foreign tenant ids return `schema_not_found`.

**Request:**

```json theme={"dark"}
{
  "name": "Invoice with totals",
  "schema": {
    "type": "object",
    "properties": {
      "invoice_number": {"type": "string"},
      "total": {"type": "number"}
    },
    "required": ["invoice_number"]
  }
}
```

**Body fields:**

* **`schema`** (required) — New JSON Schema body (compiler-validated before write).
* **`name`** (optional) — Defaults to the previous version's name.
* **`description`** (optional) — Defaults to the previous version's description.

**Response (201 Created):** the new version object (`version = previous + 1`).

***

## Get extraction schema version

```
GET /v1/extraction-schemas/{schema_id}/versions/{version}
```

Return one immutable version (`version` ≥ 1).

**Errors:**

* **`schema_not_found`** (404) — Family or version not visible to the caller.

***

## Using a registry schema with Extract

```
POST /v1/extract
```

```json theme={"dark"}
{
  "source": {"type": "upload", "upload_id": "..."},
  "schema_id": "sch_abc123",
  "schema_version": 1
}
```

* Omit **`schema_version`** to resolve **latest**.
* Exactly one of `schema_id` / `schema` on the wire.
* Accept-time freeze stores the effective JSON Schema on the job and records the
  resolved id/version for `ExtractResult.schema_id` / `schema_version`.
