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

# Extract

> Wire-compatible twin of Extract, for callers that already speak the upstream model service's own contract

Wire-compatible twin of [Extract](/api-reference/judge/extract) — same auth, same billing, same request body, same limits — for a caller that already speaks the upstream model service's own contract directly.

Unlike [Evaluate](/api-reference/judge/systemone), this path does **not** mirror the upstream's own route: the upstream's real extract endpoint is unprefixed (`/extract`), but both routes here are grouped under `/v1/` for a consistent surface, rather than this one route breaking the pattern. The request/response *body* still matches the upstream exactly — only the path differs.

Three differences from `POST /judge/extract`:

* The response is the **raw upstream classification** — no `{success,data}` envelope, and no `id` / `saved` / `credits_charged` fields.
* Errors are `{error:{code,message}}`, not `{success:false,message,code}`.
* A run through this route is **never saved** to Judge history — there's no `save`, `label`, or `request_id`-as-storage-key concept that persists anything, and nothing you send changes that.

Billing still happens exactly as on `/judge/extract` — the same wallet, the same refund-on-failure — it rides the `x-credit-charged` / `x-credit-balance` / `x-billing-tx` response headers instead of the body.

<Info>
  Billed per input token, same rate as Extract. See [Judge pricing](/api-reference/judge/pricing).
</Info>

## Request

### Headers

<ParamField header="Authorization" type="string" required>
  Bearer token — **your standard 60db API key** (`sk_…`), or a user JWT. The same credential as every other Judge route.
</ParamField>

<ParamField header="Content-Type" type="string" required>
  application/json
</ParamField>

### Body

Identical to [Extract](/api-reference/judge/extract#body), minus the 60db-local `save` field:

<ParamField body="text" type="string" required>
  The turn to classify. Maximum **200,000 Unicode code points**.
</ParamField>

<ParamField body="schema" type="object" required>
  `intents`, `operations`, optional `entities`, optional `responsePaths` (supplying it selects the v2 model). See [Extract → Body](/api-reference/judge/extract#body) for the full shape — the required `unknown` label is added for you if you omit it.
</ParamField>

<ParamField body="profile" type="string" default="generic">
  `generic` or `medical`. v2 only.
</ParamField>

<ParamField body="budget_ms" type="integer" default="8000">
  Total request budget in milliseconds, 1–30000.
</ParamField>

<ParamField body="request_id" type="string">
  Your own correlation id, echoed back. Generated for you if omitted.
</ParamField>

<Note>
  No `save` field — a run through this route is never saved regardless. Use [Extract](/api-reference/judge/extract) if you need run history.
</Note>

## Response

The raw upstream object — no envelope, and **camelCase** field names (the upstream's own extract contract, unlike evaluate's response, does not use snake\_case):

<ResponseField name="version" type="integer">
  `1` or `2`, matching whether you supplied `schema.responsePaths`.
</ResponseField>

<ResponseField name="requestId" type="string">
  Your `request_id`, or a generated one. Note the camelCase — this is `request_id` on [Extract](/api-reference/judge/extract#response)'s wrapped response.
</ResponseField>

<ResponseField name="intent" type="object">
  `label` and `confidence`.
</ResponseField>

<ResponseField name="operation" type="object">
  `label` and `confidence`.
</ResponseField>

<ResponseField name="responsePath" type="object | null">
  Present only on v2. camelCase — `response_path` on the wrapped response.
</ResponseField>

<ResponseField name="entities" type="array">
  Each entity carries `label`, `text`, `start`, `end`, `confidence` — `start`/`end` are Unicode code point offsets, see [Extract → Entity offsets](/api-reference/judge/extract#entity-offsets).
</ResponseField>

<ResponseField name="offsetUnit" type="string">
  Always `unicode_code_points`. camelCase — `offset_unit` on the wrapped response.
</ResponseField>

<ResponseField name="modelRevision" type="string">
  camelCase — `model_revision` on the wrapped response.
</ResponseField>

<ResponseField name="timings" type="object">
  `queueMs`, `inferenceMs`, `totalMs`.
</ResponseField>

## Errors

| Status | Meaning |
| - | - |
| `400` | Text over 200,000 code points, a missing label map, or `version: 2` without `responsePaths`. |
| `402` | Insufficient credits. |
| `403` | Your role cannot run the judge, or the API key lacks the `judge` scope. |
| `429` | Inference queue full. |
| `504` | The request budget expired — shorten the text or raise `budget_ms`. |

Every error above is returned as `{"error": {"code": "...", "message": "..."}}`, not `{success:false,...}`.

## Example

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.60db.ai/v1/extract \
    -H "Authorization: Bearer your-api-key" \
    -H "Content-Type: application/json" \
    -d '{
        "text": "can you move my appointment to friday morning",
        "schema": {
            "intents": { "booking": "Wants to arrange or change a booking", "unknown": "Not clear" },
            "operations": { "reschedule": "Move an existing booking", "cancel": "Call it off" }
        }
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.60db.ai/v1/extract', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer your-api-key',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      text: 'can you move my appointment to friday morning',
      schema: {
        intents: { booking: 'Wants to arrange or change a booking', unknown: 'Not clear' },
        operations: { reschedule: 'Move an existing booking', cancel: 'Call it off' },
      },
    }),
  });
  const data = await response.json();
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.60db.ai/v1/extract",
      headers={
          "Authorization": "Bearer your-api-key",
          "Content-Type": "application/json",
      },
      json={
          "text": "can you move my appointment to friday morning",
          "schema": {
              "intents": {"booking": "Wants to arrange or change a booking", "unknown": "Not clear"},
              "operations": {"reschedule": "Move an existing booking", "cancel": "Call it off"},
          },
      },
  )
  data = response.json()
  ```
</RequestExample>

<ResponseExample>
  ```json Response theme={null}
  {
    "version": 1,
    "requestId": "b2c3d4e5-f6a7-4890-bcde-f01234567890",
    "intent": { "label": "booking", "confidence": 0.94 },
    "operation": { "label": "reschedule", "confidence": 0.61 },
    "responsePath": null,
    "entities": [
      { "label": "date", "text": "friday", "start": 36, "end": 42, "confidence": 0.8 }
    ],
    "offsetUnit": "unicode_code_points",
    "modelRevision": "a221b77a8baf4a613b8f8652661d41fa10a5641e",
    "timings": { "queueMs": 1.2, "inferenceMs": 18.7, "totalMs": 19.9 }
  }
  ```
</ResponseExample>
