> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.cloudraker.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.cloudraker.com/_mcp/server.

# Extraction schema dialect

The `schema` you send to [extract](/paperwork/capabilities/extract) is JSON Schema with five constraints. The gateway checks them before the run starts. A bad schema returns in milliseconds as a `400 invalid_schema`. The error includes the offending path and a link to the rule it broke:

```json
{
  "code": "invalid_schema",
  "message": "schema.properties.a.oneOf: `oneOf` is not supported by the extraction schema dialect",
  "retryable": false,
  "requestId": "req_01KYCZWFZW7WX1JM0S0M0VJ6G8",
  "docUrl": "https://docs.cloudraker.com/capabilities/extract/schema#unsupported-keywords"
}
```

The `message` path (`schema.properties.a.oneOf`) points at the exact node to fix.

## Root object

**The root schema must be `{"type": "object"}`.**

Extraction returns one record per document. A record is a named set of fields. A root array or a bare scalar has no field names. Grounding then has nothing to attach to.

For repeating data, put the array on a property. Or use `"unit": "rows_per_document"` to get a row array from an object schema.

```json title="Rejected"
{
  "type": "array",
  "items": { "type": "object", "properties": { "amount": { "type": "number" } } }
}
```

```json title="Accepted"
{
  "type": "object",
  "properties": {
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": { "amount": { "type": ["number", "null"] } }
      }
    }
  }
}
```

## Max depth

**Nesting may not exceed 5 levels.** The root counts as level 1. Each step into `properties`, `items`, `prefixItems`, `patternProperties`, `definitions`, or `additionalProperties` counts as one more.

Deeply nested shapes reduce accuracy. The model must hold the whole path to place a value. A wrong turn high up invalidates everything under it. Flat, named fields extract better. They also produce citations you can check.

```json title="Rejected — the innermost field sits at level 6"
{
  "type": "object",
  "properties": {
    "a": {
      "type": "object",
      "properties": {
        "b": {
          "type": "object",
          "properties": {
            "c": {
              "type": "object",
              "properties": {
                "d": {
                  "type": "object",
                  "properties": { "e": { "type": "string" } }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

```json title="Accepted — flattened"
{
  "type": "object",
  "properties": {
    "a_b_c_d_e": { "type": ["string", "null"] }
  }
}
```

If you need the deep shape, extract it flat. Then reassemble it in your own code.

## Unsupported keywords

**These keywords are rejected:** `$ref`, `$defs`, `oneOf`, `anyOf`, `allOf`, `const`, and `pattern`. The validator checks every property, array item, and nested object. Treat them as unsupported everywhere. The extractor does not honour a keyword that slips past validation.

Write every field out in full. With `$ref` rejected, nothing can point at a `definitions` block.

The first five keywords make the output shape conditional. The schema then no longer says what the result looks like. Neither the extractor nor your code can rely on it.

`const` and `pattern` assert values that were read off a page. A failed assertion tells you nothing useful about the document. It turns a legible field into an error.

Constrain values with `enum` instead. `enum` also encodes a [choice field](/paperwork/capabilities/extract/field-types). Describe formats in `description` or `instructions`.

```json title="Rejected"
{
  "type": "object",
  "properties": {
    "party": {
      "oneOf": [
        { "type": "object", "properties": { "person": { "type": "string" } } },
        { "type": "object", "properties": { "company": { "type": "string" } } }
      ]
    },
    "invoice_number": { "type": "string", "pattern": "^INV-[0-9]{6}$" }
  }
}
```

```json title="Accepted"
{
  "type": "object",
  "properties": {
    "party_type": { "type": ["string", "null"], "enum": ["person", "company", null] },
    "party_name": { "type": ["string", "null"] },
    "invoice_number": {
      "type": ["string", "null"],
      "description": "Invoice number, formatted INV-000000."
    }
  }
}
```

A **property named** `pattern` is valid. The validator checks only keyword positions. Anything under `properties` is a field name, not a keyword.

## Nullable primitives

**Recommended, not enforced:** declare every field that can be absent as nullable. Use `{"type": ["string", "null"]}`, not `{"type": "string"}`.

A non-nullable field tells the extractor the value exists. When the document does not contain the value, that pressure produces a plausible guess. A nullable field lets the extractor report absence. A `null` result is unambiguous: the field was not in the document.

This rule never rejects a request. It is the highest-impact change you can make to extraction quality. With [`citations`](/paperwork/capabilities/extract#configuration) enabled, an absent value goes further than `null`. The field is marked `notFound` against the document. Nothing is left ambiguous.

```json title="Works, but invites invented values"
{
  "type": "object",
  "properties": {
    "po_number": { "type": "string" },
    "due_date": { "type": "string" }
  }
}
```

```json title="Better"
{
  "type": "object",
  "properties": {
    "po_number": { "type": ["string", "null"] },
    "due_date": { "type": ["string", "null"], "description": "ISO 8601 date, or null if absent." }
  }
}
```

## Size limit

**The serialized schema must be 64 KB or smaller.**

The schema travels with the run through internal dispatch. Dispatch has a hard message ceiling. The gateway checks the limit up front. You get a clear `400`, not a failure part-way through a run.

In practice, 64 KB holds hundreds of fields. A schema that hits the limit usually does several jobs at once.

```json title="Rejected"
{ "code": "invalid_schema", "message": "schema: serialized schema is 91204 bytes; the limit is 65536" }
```

Split the work. Run several narrower extractions over the same file. Reuse the file id so the file is parsed once. Drop fields you do not consume. Put long prose in `instructions`, not in a per-field `description` repeated across hundreds of properties.

## Next steps

#### [Extract](/capabilities/extract)

The endpoint, its configuration, and the response shape.

#### [Field types](/capabilities/extract/field-types)

Optional annotations that mark a field as money, a date, or a phone number.

#### [Errors](/developers/errors)

The full error envelope and every code the API returns.