> For the complete documentation index, see [llms.txt](https://docs.uptiq.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.uptiq.ai/document-ai/developer-documentation/getting-started/conventions.md).

# Conventions

The endpoints are consistent about most things and inconsistent about a few. This page covers both, because the inconsistencies are where integrations break.

### Requests are JSON

Every request body is `application/json`. There is no multipart upload endpoint — documents travel either as base64 inside the JSON body or as a URL the platform fetches.

```bash
-H "Content-Type: application/json"
```

### Sending a document

Two mutually exclusive ways:

| Property   | Use when                                          | Trade-off                                                                      |
| ---------- | ------------------------------------------------- | ------------------------------------------------------------------------------ |
| `content`  | The file is local to your service                 | Base64 inflates the payload by about a third                                   |
| `file_url` | The file already has a URL the platform can reach | No inflation, but the URL must be publicly reachable for the length of the job |

Send one or the other, never both.

### Naming the document type

Also mutually exclusive:

| Property        | Use when                                                                     |
| --------------- | ---------------------------------------------------------------------------- |
| `documentType`  | The file is one document of one known type                                   |
| `documentTypes` | One file contains several documents to extract separately (multi-extraction) |

Values come from the registry — see [Document Types](/document-ai/guides-1/document-types.md), or call `GET /listSupportedDocuments` to read the current set at build time.

{% hint style="warning" %}
Neither either/or rule is expressed in the spec's `required` list. Every property on the extraction request is formally optional, so a request can satisfy the schema and still be rejected. The obligations exist only in the property descriptions, and they are real.
{% endhint %}

### Response envelopes differ by operation

This is the single most common source of integration bugs on this API, so it is worth being precise. **Submitting** a job and **retrieving** one wrap their payloads differently *and* name the job status differently.

```jsonc
// POST /extract  — submit
{
  "data":   { "_id": "425f…", "documentStatus": "Pending" },
  "message": "Document queued for extraction",
  "status": "success"
}

// GET /document-extractions/{id}  — retrieve
{
  "extraction": { "_id": "425f…", "status": "Processed" },
  "status": "success"
}
```

Three things to notice:

* The wrapper is `data` on submit and `extraction` on retrieve.
* The job status is `documentStatus` on submit and `status` on retrieve.
* **The retrieve response has two `status` fields at different levels.**

{% hint style="danger" %}
The top-level `status` is the *envelope* status. It reads `"success"` whenever the HTTP call worked, and it says nothing about the job. A poller that checks the top-level `status` sees `"success"` on the very first attempt and never stops polling. The job state you want is `extraction.status`.
{% endhint %}

And it is not one pattern with one exception — the wrapper differs per operation. The full set:

| Operation                                    | Wrapper                                     | Job status at           |
| -------------------------------------------- | ------------------------------------------- | ----------------------- |
| `POST /extract`, `/classify/sync` … *(sync)* | none — result at top level                  | `documentStatus`        |
| `POST /extract` *(async)*                    | `data`                                      | `data.documentStatus`   |
| `GET /document-extractions/{id}`             | `extraction`                                | `extraction.status`     |
| `POST /classify` *(async)*                   | `requestInfo`                               | — (queued only)         |
| `GET /classifications/{id}`                  | `classification`                            | `classification.status` |
| `POST /extract/bulk`, `/classify/bulk`       | `results[]` — one entry per file            | per entry               |
| `GET /document-extractions/group/{id}`       | `extractions` — **keyed by `documentType`** | per child               |
| `POST /generate/sync`                        | none — **binary file, not JSON**            | —                       |

{% hint style="warning" %}
Write the unwrapping once, per operation, in one place. Four different wrapper names across eight shapes is the kind of thing that looks fine in a prototype and produces a subtle bug six months later when someone adds a ninth call.
{% endhint %}

### Success codes vary with the request, not just the endpoint

`POST /extract` returns **`200`** for a single-type extraction and **`202`** for a multi-extraction request using `documentTypes`. `POST /generate` returns `201`. Everything else returns `200`.

Treat any `2xx` as accepted rather than asserting one specific code.

### The processing lifecycle

| Status       | Meaning                                   |
| ------------ | ----------------------------------------- |
| `Pending`    | Accepted and queued; work has not started |
| `Processing` | In flight                                 |
| `Processed`  | Finished successfully — terminal          |
| `Failed`     | Finished unsuccessfully — terminal        |

Poll until `Processed` or `Failed`. Everything else is transient. Alongside the status, `processingStage` carries a human-readable progress signal that is useful if you are showing something to a user:

```json
{ "stage": "completed", "message": "Extraction complete", "updatedAt": "2026-07-29T03:23:15.820000" }
```

{% hint style="warning" %}
`Processed` means *extraction* finished. It does not mean fraud analysis finished — that continues afterwards, and `fraudResults.fraudStatus` can still read `processing` at the moment the extraction itself goes terminal. See [Reading fraud and control checks](/document-ai/cookbooks/fraud-and-control-checks.md).
{% endhint %}

### How long things take

Measured on UAT against a one-page PDF:

| Operation                        | Observed |
| -------------------------------- | -------- |
| `POST /extract` (accept the job) | \~0.6s   |
| `POST /classify/sync`            | \~13s    |
| `POST /extract/sync`             | \~93s    |

Extraction is slow because it is doing OCR and model inference, and a longer document takes longer. Set client timeouts well above these figures if you use the synchronous endpoints, and poll no more often than every few seconds — a tight loop will not make the job finish sooner.

### Identifiers

| Field               | What it identifies                                                                                                                     |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `_id`               | The extraction, classification or generation record. This is what you poll with                                                        |
| `requestId`         | The individual API call. Useful when raising a support issue                                                                           |
| `extractionGroupId` | The set, when one file produced several extractions                                                                                    |
| `eventId`           | A webhook delivery, for idempotency — see [Webhook payloads](/document-ai/developer-documentation/getting-started/webhook-payloads.md) |

All are UUIDs.

### Usage and cost are on the record

A completed extraction reports what it consumed, which is useful for chargeback or for catching a runaway job:

```json
{
  "creditReservation": { "cost": 0.03, "pages": 1, "operation": "extraction" },
  "tokenUsage":        { "total_tokens": 29375, "model": "gemini-3", "provider": "gemini" },
  "ocrUsage":          { "estimated_cost": 0.01, "page_count": 1, "provider": "azure" }
}
```

These fields are not described in any response schema, so treat their exact shape as less stable than the documented fields.

### Related pages

* [Errors](/document-ai/developer-documentation/getting-started/errors.md) — status codes and which failures are worth retrying.
* [Authentication](/document-ai/developer-documentation/getting-started/authentication.md) — the header every request needs.
* [Extraction API](/document-ai/developer-documentation/integration-guides/extraction.md) — the full parameter set these conventions apply to.
* [Extract asynchronously and poll](/document-ai/cookbooks/async-extraction-and-polling.md) — the envelope trap, in a working example.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.uptiq.ai/document-ai/developer-documentation/getting-started/conventions.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
