---
title: "Batches and drafts"
summary: "Upload documents once as a batch, upload a draft, and let Attest find and check every claim in it, with the hashes that prove which bytes were reviewed."
category: "developer"
tags: ["batches", "drafts", "upload", "review", "file hash", "jobs", "polling"]
updated: "2026-09-14T04:25:02.906Z"
url: "https://support.quorumtech.ch/articles/api-batches-and-drafts"
markdown: "https://support.quorumtech.ch/articles/api-batches-and-drafts.md"
---

# Batches and drafts

Checking one claim against inline evidence is the simplest call. Reviewing a whole document against a pack of sources is the common job, and it takes three calls.

## The flow

1. **Load the sources once** as a batch: `POST /attest/v1/batches` with the files, or `POST /attest/v1/batch` with their text. You get a `batchId` and, per document, the hashes below.
2. **Upload the draft** with `POST /attest/v1/drafts`. You get a `draftId` and the draft's `fileHash`.
3. **Review it** with `POST /attest/v1/attest/draft`, naming both ids. Attest extracts the claims from the draft, checks each against the batch, and returns one result per claim in draft order, each carrying the passage it came from.
4. **Delete the batch and the draft** when the report is written. Both expire on their own after six hours, but "it will age out" is a poor answer to anyone asking where a client's board pack currently is.

## Why the server extracts the claims

Passing a claim list makes coverage a function of what the caller thought to submit. In real use that was the binding constraint: over a thirteen-document review a careful manual pass found thirteen errors, and a checker fed a hand-written list surfaced two, because the rest were never sent. The claims that go unsent are the ones not perceived as claims at all. "Marketing deferred three times" reads as narrative, which is precisely why it was wrong and precisely why it was skipped.

Extraction targets what gets missed: counts and tallies, elapsed durations, sequence and firsts, comparisons, aggregations that assert something about every member of a set, attributions, negations and exclusivity, and characterisations of what a document does or does not say. Compound sentences are split, so each assertion gets its own verdict.

## Why files rather than text

Passing a draft as a string means the calling program, often a model, retypes its own work into a request. That is a generation step, and a generation step can drift: a figure lands a digit out, a hedge disappears. The review then runs against text nobody will read. Uploading the bytes removes the step, and `fileHash` proves which bytes were reviewed:

```bash
shasum -a 256 briefing.md
```

The same applies to evidence. `fileHash` is the SHA-256 of the file as uploaded and can be recomputed from the copy you hold. `contentHash` is the hash of the normalised text actually searched, which parsing makes version-dependent, so only `fileHash` proves what a verdict was produced against. A document supplied as text has no `fileHash`, because there was no file.

## Coverage is reported, not assumed

```json
{ "summary": { "extracted": 47, "chunks": 6, "failedChunks": 1, "truncated": false } }
```

A long draft is read in passages. `failedChunks` names stretches that could not be read and `failedPassages` quotes them, so a passage nothing looked at is never reported as a passage with nothing wrong. `truncated: true` means `maxClaims` was reached and later claims were not checked.

Each claim also reports `coverage`: `in_scope` when the batch could speak to it, `out_of_scope` with `missingTerms` when nothing in the batch mentions what the claim is about. Out-of-scope attention is not a finding against the draft.

## Long reviews and polling

A short review finishes inside the request and returns 200. A long one returns 202 with a `jobId` and a `poll` address. `config.waitMs` (up to 120,000) says how long the request is held before that happens. Poll `GET /attest/v1/attest/draft/jobs/{jobId}` until `state` is `done`. The finished result stays for five minutes after it is first collected, so a dropped poll can be repeated, and is then forgotten.

## What it costs

A draft review reserves `config.maxClaims` (default 200, at most 500) at two credits each up front, and settles to two credits per claim it actually checked once the result is returned or collected. Set `maxClaims` to reserve less. The response headers carry `X-Credits-Reserved` on the 202 and `X-Credits-Charged` on the result. See Credits and billing.

## Where the files live

Batches, drafts and jobs are held in memory only, on one server. Nothing is written to disk or to a database, a restart loses every batch, and the stores are capped and evict oldest-first. The not-found errors (`batch_not_found`, `draft_not_found`, `job_not_found`) all have the same recovery: upload again. See Privacy.

## Limits

| What | Limit |
| --- | --- |
| Files per batch | 200, each up to 25 MB, as PDF, Word, PowerPoint, plain text or Markdown |
| Documents per text batch | 200 |
| Draft upload | 2 MB and 500,000 characters of text |
| Draft sent as text | 200,000 characters |
| `maxClaims` | 1 to 500, default 200 |
| `waitMs` | 0 to 120,000 |
