Extract

Extraction schema dialect

The subset of JSON Schema that POST /v1/extract accepts, and the meaning of each rejection.

View as Markdown

The schema you send to 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:

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

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.

Rejected
1{
2 "type": "array",
3 "items": { "type": "object", "properties": { "amount": { "type": "number" } } }
4}
Accepted
1{
2 "type": "object",
3 "properties": {
4 "line_items": {
5 "type": "array",
6 "items": {
7 "type": "object",
8 "properties": { "amount": { "type": ["number", "null"] } }
9 }
10 }
11 }
12}

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.

Rejected — the innermost field sits at level 6
1{
2 "type": "object",
3 "properties": {
4 "a": {
5 "type": "object",
6 "properties": {
7 "b": {
8 "type": "object",
9 "properties": {
10 "c": {
11 "type": "object",
12 "properties": {
13 "d": {
14 "type": "object",
15 "properties": { "e": { "type": "string" } }
16 }
17 }
18 }
19 }
20 }
21 }
22 }
23 }
24}
Accepted — flattened
1{
2 "type": "object",
3 "properties": {
4 "a_b_c_d_e": { "type": ["string", "null"] }
5 }
6}

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. Describe formats in description or instructions.

Rejected
1{
2 "type": "object",
3 "properties": {
4 "party": {
5 "oneOf": [
6 { "type": "object", "properties": { "person": { "type": "string" } } },
7 { "type": "object", "properties": { "company": { "type": "string" } } }
8 ]
9 },
10 "invoice_number": { "type": "string", "pattern": "^INV-[0-9]{6}$" }
11 }
12}
Accepted
1{
2 "type": "object",
3 "properties": {
4 "party_type": { "type": ["string", "null"], "enum": ["person", "company", null] },
5 "party_name": { "type": ["string", "null"] },
6 "invoice_number": {
7 "type": ["string", "null"],
8 "description": "Invoice number, formatted INV-000000."
9 }
10 }
11}

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 enabled, an absent value goes further than null. The field is marked notFound against the document. Nothing is left ambiguous.

Works, but invites invented values
1{
2 "type": "object",
3 "properties": {
4 "po_number": { "type": "string" },
5 "due_date": { "type": "string" }
6 }
7}
Better
1{
2 "type": "object",
3 "properties": {
4 "po_number": { "type": ["string", "null"] },
5 "due_date": { "type": ["string", "null"], "description": "ISO 8601 date, or null if absent." }
6 }
7}

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.

Rejected
1{ "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