---
title: "Upload documents as files"
summary: "Upload up to 200 PDF, Word, PowerPoint, text or Markdown files into a batch, parsed on the server, each with the hash that proves which bytes were checked."
category: "developer"
tags: ["attest", "reference", "batch", "upload", "files", "file hash"]
updated: "2026-09-14T04:25:12.056Z"
url: "https://support.quorumtech.ch/articles/attest-upload-batch"
markdown: "https://support.quorumtech.ch/articles/attest-upload-batch.md"
---

# Upload documents as files

`POST https://api.quorumtech.ch/attest/v1/batches`

Uploads up to 200 files (PDF, Word, PowerPoint, plain text, Markdown) and parses them on the
server into a batch. Each document comes back with two hashes: `fileHash` is the SHA-256 of the
bytes as uploaded, which you can recompute from the file you hold; `contentHash` is the hash of
the normalised text actually searched. Only `fileHash` proves which document a verdict was
produced against.

Files that could not be parsed are listed in `failed`; the batch is still created from the
rest. A single file is capped at 25 MB.

This is the preferred way to load evidence. The server parses each file into citable spans in reading order, so a citation points at something the server actually read, and returns two hashes per document. `fileHash` is the SHA-256 of the bytes as uploaded, which you can recompute from the file you hold with `shasum -a 256`. `contentHash` is the hash of the normalised text searched, which depends on the parser's version and cannot be recomputed from the file. Only `fileHash` proves which document a verdict was produced against.

Files that could not be parsed are listed in `failed` with a reason, and the batch is created from the rest. When no file could be parsed the answer is 422 `no_files_parsed`. A single file is capped at 25 MB.

`name` labels the batch on listings. `scope` describes what the evidence covers, in a sentence, so the review can tell an out-of-scope claim from an unsupported one.

The batch lives in memory on one server and expires after six hours. Delete it when the report is written.

```json
{
  "name": "Board pack, September 2026",
  "scope": "The September board pack: the CFO report, the minutes of the July meeting and the risk register."
}
```

## Request

A `multipart/form-data` body with these parts:

| Part | Type | Required | Description |
| --- | --- | --- | --- |
| `files` | one or more files | yes | One or more documents. |
| `name` | text | no | A name for the batch, for example "Board Pack FY2024". |
| `scope` | text | no | What this evidence set covers. Describes the evidence, never the claims. |

## Example request

```bash
curl -X POST "https://api.quorumtech.ch/attest/v1/batches" \
  -H "Authorization: Bearer $QUORUM_API_KEY" \
  -F "files=@board-pack.pdf" -F "files=@minutes.docx" \
  -F "name=Board pack, September 2026" \
  -F "scope=The September board pack: the CFO report, the minutes of the July meeting and the risk register."
```

## Responses

### 201

The batch is loaded. Documents that failed to parse are named in `failed`.

| Field | Type | Description |
| --- | --- | --- |
| `batchId` | string |  |
| `documents` | UploadedBatchDocument[] |  |
| `spans` | integer |  |
| `totalChars` | integer |  |
| `batchHash` | string |  |
| `createdAt` | string (date-time) |  |
| `failed` | FailedFile[] | Files that could not be parsed. The batch was created from the rest. |
| `failed[].file` | string |  |
| `failed[].reason` | string |  |

## Errors

The body is `{ "error": { "code", "message" } }`; the full catalogue with what to do about each code is on the Errors page.

| Status | Codes | When |
| --- | --- | --- |
| 400 | `invalid_upload`, `invalid_request` | The multipart body is malformed or uses an unexpected field (`invalid_upload`), or no file part was sent at all (`invalid_request`). On a draft upload, a file that could not be read is also `invalid_upload`. |
| 401 |  | The key is missing, not recognised, or revoked. |
| 402 | `payment_required`, `credits_exhausted`, `remaining`, `needed` | The workspace has no active API subscription (`payment_required`), or is prepaid and has no credits left for this request (`credits_exhausted`, with `remaining` and `needed`). Manage billing and add credits at https://developer.quorumtech.ch/billing. |
| 403 |  | The key is not scoped to Attest. |
| 413 |  | The batch exceeds the size the server holds in memory. |
| 422 |  | No file could be parsed. |
| 429 | `rate_limited`, `claims_rate_limited`, `claim_budget_exhausted`, `monthly_cap_reached`, `cap` | Over the key's requests-per-minute limit (`rate_limited`), its claims-per-minute ceiling (`claims_rate_limited`), its claims-per-day budget (`claim_budget_exhausted`), or, for a postpaid workspace, its monthly credit cap (`monthly_cap_reached`, with `cap` and `resetsAt`). `Retry-After` says when to try again; the daily budget resets at midnight UTC and the cap on the first of the month. |

## Cost

Free. Uploading costs no credits; the claims checked against the batch are charged when they are checked.
