Extraction Schemas

A schema describes the fields you want back. Create it once, version it, label the version your callers get, and test it before you rely on it.

Snippet setup

The Python and TypeScript snippets on this page set BASE_URL to the host of the base URL and read your key from AI_2KW_API_KEY. Python: import os, requests, BASE_URL = "https://api.2kw.ai", api_key = os.environ["AI_2KW_API_KEY"] and headers = {"Authorization": f"Bearer {api_key}"}. TypeScript: const BASE_URL = "https://api.2kw.ai" and const apiKey = process.env.AI_2KW_API_KEY.

Schema Management

Before you can extract anything, you need a schema. Schemas are organization-scoped and support full version control.

Creating a Schema

A schema takes two calls: create the schema with a name and an optional description, then commit its JSON Schema definition as the first version.

POST /v1/schemas

curl -X POST https://api.2kw.ai/v1/schemas \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk_your_api_key" \
  -d '{
    "name": "invoice",
    "description": "Supplier invoices"
  }'

The response carries the schema's id. The JSON Schema is not part of this call: the API reference lists a content field on the schema, but a value sent there is not stored.

POST /v1/schemas/{schemaId}/versions

{
  "jsonSchema": {
    "type": "object",
    "properties": {
      "company": { "type": "string" },
      "invoice_number": { "type": "string" },
      "total": { "type": "number" },
      "line_items": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "description": { "type": "string" },
            "quantity": { "type": "integer" },
            "price": { "type": "number" }
          }
        }
      }
    },
    "required": ["company", "invoice_number"]
  },
  "changeDescription": "First version"
}

jsonSchema is the JSON Schema definition; one that is not a valid JSON Schema is refused with 400. changeDescription says what changed. Every field: API reference.

The response (201) is the new version with its id, versionNumber and jsonSchema, and the latest label now points to it. Every later change is another call to this endpoint.

Version Control

Every time you change your schema, you create a new version. Versions are immutable —this gives you a full audit trail of every change.

You can:

  • Commit new versions with a change description
  • Deactivate versions you don't want used anymore
  • Re-activate a historical version (creates a new version as a copy, preserving the audit trail)
  • Pin extractions to a specific version for reproducibility

Each new version auto-increments the version number and updates the latest label.

Re-activation creates a copy

Re-activating version 3 doesn't revert —it creates version N+1 with the same content. The original stays untouched.

Labels

Labels are named pointers to specific schema versions. They decouple your application code from version numbers, enabling controlled rollouts.

How Labels Work

Every schema gets a latest label automatically, updated on each new version. You create custom labels for deployment stages:

LabelPoints toPurpose
latestv5 (auto)Always the newest version
productionv3What your API consumers use
stagingv5What you're testing

Your application resolves schemas by ID + label —when you're ready to promote, just move the label pointer.

Managing Labels

POST /v1/schemas/{schemaId}/labels

Create a label:

{
  "name": "production",
  "schemaVersionId": "version-uuid"
}

Update a label to point to a different version:

PUT /v1/schemas/{schemaId}/labels/{labelName}

{
  "schemaVersionId": "new-version-uuid"
}

Label names must be lowercase alphanumeric with hyphens (e.g., production, staging, v2-rollback). The system latest label cannot be deleted.

Resolving Schemas

GET /v1/schemas/{schemaId}/resolve

Resolve a schema by ID and optional label to get the JSON Schema definition for a specific version:

Request

curl "https://api.2kw.ai/v1/schemas/{schemaId}/resolve?label=production" \
  -H "Authorization: Bearer sk_your_api_key"

The label parameter names the label to resolve and defaults to latest.

Response:

{
  "schemaId": "uuid",
  "schemaName": "invoice-schema",
  "versionId": "uuid",
  "versionNumber": 3,
  "jsonSchema": {
    "type": "object",
    "properties": {
      "company": { "type": "string" },
      "total": { "type": "number" }
    }
  },
  "label": "production"
}

Validate Before Committing

POST /v1/schemas/{schemaId}/validate

Check if a schema is valid before you commit it. Returns structured errors and warnings:

{
  "jsonSchema": {
    "type": "object",
    "properties": {
      "name": { "type": "string" }
    }
  }
}

The response has three fields: valid, errors and warnings. Errors mean the schema document itself cannot be used. The same checks run when you commit a version, so POST /v1/schemas/{schemaId}/versions answers 400 for a document that fails them. The typical errors are:

  • JSON Schema must be a JSON object
  • JSON Schema must contain at least one schema keyword (e.g., type, properties, $schema)
  • Invalid JSON Schema: <detail from the validator>

Check the JSON syntax, make sure the document is an object with at least one JSON Schema keyword, and validate it against the JSON Schema 2020-12 specification.

Errors are about the schema, never about extraction output. An extraction whose output does not match its schema does not fail: there is no failed status and no retry. The mismatches are recorded on the result instead, see Schema Validation.

Schema-safety warnings

Warnings are advisory and never block saving a version: valid stays true. The validate endpoint returns them on request, and the schema editor in the console lints live while you type. Each warning names a stable rule id, a JSON pointer to the exact location in your schema, a message and, where one applies, a suggested fix. The editor offers a one-click fix for the first three rules.

RuleTriggerWhy it is riskyFix
required-non-nullableA required field declares a type that does not include nullThe model must emit some value even when the document has noneAllow null ("type": ["string", "null"]) or remove the field from required
required-enumA required field has an enum that contains neither "insufficient_evidence" nor nullThe model must pick a listed value even when the document contains noneAdd "insufficient_evidence" or null to the enum, or remove the field from required
min-itemsminItems greater than 0 on any arrayForces the model to invent entries for documents that have noneRemove minItems and check cardinality after extraction instead, for example with a business rule
provider-rewriteThe schema uses constraints your provider does not enforceConstraints look enforced but are ignored at generation time, or the schema is rejectedInformational, no automatic fix. See Provider enforcement below

A required field that carries an enum is reported as required-enum, not required-non-nullable. A field without a type keyword, or one whose anyOf/oneOf includes a {"type": "null"} branch, is already nullable and is not flagged.

The pointer targets the property for required-non-nullable and required-enum, and the offending keyword for min-items and provider-rewrite. A provider-rewrite warning about prompt-only providers uses the empty pointer, which means the whole schema.

Example. This schema requires two fields, one of them an enum without an escape value:

{
  "jsonSchema": {
    "type": "object",
    "properties": {
      "invoice_number": { "type": "string" },
      "status": { "type": "string", "enum": ["paid", "unpaid"] }
    },
    "required": ["invoice_number", "status"]
  }
}

With only the platform's default provider in play, the response is:

{
  "valid": true,
  "errors": [],
  "warnings": [
    {
      "rule": "required-non-nullable",
      "pointer": "/properties/invoice_number",
      "message": "Required field 'invoice_number' cannot be null. When the document does not contain this value, models fabricate one rather than fail.",
      "suggestion": "Allow null via \"type\": [\"string\", \"null\"], or remove 'invoice_number' from \"required\"."
    },
    {
      "rule": "required-enum",
      "pointer": "/properties/status",
      "message": "Required enum field 'status' forces the model to pick a listed value even when the document contains none — the highest-risk fabrication construct.",
      "suggestion": "Add an escape value \"insufficient_evidence\" to the enum, or remove 'status' from \"required\"."
    }
  ]
}

An organization that has an Anthropic or Mistral provider configured also gets a provider-rewrite warning at the empty pointer.

Escape values

Make fields optional and nullable by default. Require a field only when a document without it should count as unprocessable. For enums, include the escape value insufficient_evidence so the model has a legal way to say "not in the document". A null entry in the enum counts as an escape too. An escape value reads as a deliberate absence; a fabricated value looks exactly like a real one.

Provider enforcement

Extraction runs on the provider behind the model you choose, and providers differ in what they enforce while generating:

  • Anthropic and Mistral receive the schema as prompt text, without constrained decoding. Required fields, enums and every other constraint are advisory.
  • OpenAI, Azure OpenAI and xAI receive the schema as a structured-output json_schema. Constraint keywords outside the enforced subset are ignored, and Azure OpenAI may reject a schema that uses them. The flagged keywords are pattern, format, minLength, maxLength, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, maxItems, uniqueItems, minProperties and maxProperties. minItems is not enforced either; it is reported by the min-items rule, which carries the fix.
  • Vertex AI and Ollama are not checked, so they produce no provider-rewrite warnings.

The linter checks your schema against every provider configured for your organization, plus Azure OpenAI, which serves the platform's built-in models.

2kw.ai does not enforce these keywords after extraction either. It does check every output against its schema and records the violations, for enforced and advisory keywords alike, see Schema Validation.

Test Against Sample Text

POST /v1/schemas/{schemaId}/test

Run an extraction against sample text without persisting the result. Great for iterating on your schema:

Request

curl -X POST https://api.2kw.ai/v1/schemas/{schemaId}/test \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk_your_api_key" \
  -d '{
    "jsonSchema": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "email": { "type": "string" }
      }
    },
    "sampleText": "Contact Jane Smith at jane@acme.com",
    "model": "gpt-5.1"
  }'

The response includes token usage and processing time so you can estimate costs:

{
  "success": true,
  "extractedData": { "name": "Jane Smith", "email": "jane@acme.com" },
  "inputTokens": 85,
  "outputTokens": 22,
  "processingDurationMs": 890,
  "modelUsed": "gpt-5.1"
}

All Schema Endpoints

The endpoints in one place. Every field: API reference.

MethodEndpointWhat it does
GET/v1/schemas?search=invoice&page=0&size=20List schemas, paginated, optionally filtered by search
POST/v1/schemasCreate a schema (name, description)
GET/v1/schemas/{id}Get a schema
PUT/v1/schemas/{id}Update a schema's name or description
DELETE/v1/schemas/{id}Delete a schema and all its versions. Needs the admin role
POST/v1/schemas/{schemaId}/versionsCommit a new version (jsonSchema, changeDescription)
GET/v1/schemas/{schemaId}/versions?page=0&size=20List versions, paginated
GET/v1/schemas/{schemaId}/versions/{versionId}Get one version
GET/v1/schemas/{schemaId}/versions/latestThe latest version; 404 while there is none. Deprecated: use resolve
PUT/v1/schemas/{schemaId}/versions/{versionId}/activateRe-activate a historical version as a new copy
DELETE/v1/schemas/{schemaId}/versions/{versionId}Deactivate a version (soft delete); answers 204. Needs the admin role
GET/v1/schemas/{schemaId}/labelsList labels
POST/v1/schemas/{schemaId}/labelsCreate a label (name, schemaVersionId)
PUT/v1/schemas/{schemaId}/labels/{labelName}Point a label at another version (schemaVersionId)
DELETE/v1/schemas/{schemaId}/labels/{labelName}Delete a label; latest cannot be deleted. Needs the admin role
GET/v1/schemas/{schemaId}/resolve?label=productionResolve a label (default latest) to its version
POST/v1/schemas/{schemaId}/validateValidate a JSON Schema without saving it
POST/v1/schemas/{schemaId}/testExtract from sample text without saving the result

Was this page helpful?

A request failed? See Errors for every status and code.