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

# Files

> Upload a file once, get a durable handle, and reference it from any product — extract, and soon message attachments.

Some products need bytes: a document to extract from, an image to send to an assistant, a file the assistant hands back. Instead of inlining bytes into every call, upload the file once and pass its handle (`file_…`) wherever bytes are needed.

Small files go up in one request. Large files go up in resumable segments. Either way you get back a durable handle you can reference until you are done with it.

<Note>
  Uploads are not billed — storage is on us. [Extract](/guides/extract) still charges per page.
</Note>

## Small files (≤ 10 MB)

`POST /v1/files` — send `multipart/form-data` with a `file` part, or JSON with base64 `content`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.usepatchwork.co/v1/files \
    -H "Authorization: Bearer $PATCHWORK_API_KEY" \
    -F "file=@invoice.pdf" \
    -F "media_type=application/pdf"
  ```

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

  r = requests.post(
      "https://api.usepatchwork.co/v1/files",
      headers={"Authorization": f"Bearer {os.environ['PATCHWORK_API_KEY']}"},
      files={"file": ("invoice.pdf", open("invoice.pdf", "rb"), "application/pdf")},
  )
  file = r.json()["data"]
  print(file["id"])  # file_9f3a...
  ```
</CodeGroup>

You get back a ready handle:

```json theme={null}
{
  "status": "success",
  "data": {
    "id": "file_9f3a...",
    "state": "ready",
    "media_type": "application/pdf",
    "filename": "invoice.pdf",
    "size": 48213,
    "sha256": "e3b0c442...",
    "expires_at": "2026-09-03T18:22:00Z",
    "created_at": "2026-09-02T18:22:00Z"
  }
}
```

Anything over 10 MB is rejected with `file_too_large` — use the chunked flow below.

## Large files (> 10 MB, up to 100 MB)

Three steps, one handle throughout: **initialize → append → finalize**.

<Steps>
  <Step title="Initialize">
    Declare the total size. You get a `file_…` handle in the `uploading` state and the `segment_size` to chunk by (5 MB).

    ```bash theme={null}
    curl -X POST https://api.usepatchwork.co/v1/files/initialize \
      -H "Authorization: Bearer $PATCHWORK_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "media_type": "application/pdf", "total_bytes": 41943040, "filename": "big-scan.pdf" }'
    ```
  </Step>

  <Step title="Append each segment">
    Send each 5 MB segment with its zero-based `segment_index`. The last segment may be smaller. Segments can go in any order and in parallel.

    ```bash theme={null}
    curl -X POST https://api.usepatchwork.co/v1/files/$FILE_ID/append \
      -H "Authorization: Bearer $PATCHWORK_API_KEY" \
      -F "segment_index=0" \
      -F "chunk=@segment-0.bin"
    ```
  </Step>

  <Step title="Finalize">
    Assemble the segments and get the ready handle, with the whole-file `sha256`.

    ```bash theme={null}
    curl -X POST https://api.usepatchwork.co/v1/files/$FILE_ID/finalize \
      -H "Authorization: Bearer $PATCHWORK_API_KEY"
    ```
  </Step>
</Steps>

### Resuming a dropped upload

`append` is idempotent per `segment_index` — re-sending an index overwrites it. If a client drops mid-upload, read the file's status and re-send only the segments that did not land:

```bash theme={null}
curl https://api.usepatchwork.co/v1/files/$FILE_ID \
  -H "Authorization: Bearer $PATCHWORK_API_KEY"
# data.received_segments -> [0, 1, 3]  (2 is missing — re-append it)
```

`finalize` refuses an incomplete upload with `incomplete_upload`, naming the gaps.

## Using a file

Pass the handle where a product accepts one. Extract takes it as `document.file_id` instead of inline `content` — which lets you extract the same file against several schema types without re-uploading:

```bash theme={null}
curl -X POST https://api.usepatchwork.co/v1/extract \
  -H "Authorization: Bearer $PATCHWORK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "document": { "file_id": "file_9f3a..." }, "template": "sch_8f2c..." }'
```

## Downloading

`GET /v1/files/{id}/content` streams the raw bytes back, proxied through the API (supports `Range`). This is also how you retrieve a file an assistant produced.

## Lifecycle

A file's clock starts at 24 hours. **Referencing a file pins it** — while any product references it (an extract job, a thread), its expiry is suspended, and it becomes eligible for cleanup only once the last reference drops. An upload session you never finish, or a file nothing ever uses, is swept after its 24 hours.

| State | Meaning |
| - | - |
| `uploading` | A chunked session is open; append segments, then finalize. |
| `ready` | Assembled and usable. `size` and `sha256` are set. |
| `referenced` | In use by a product; pinned from cleanup. |
| `expired` | Past its clock with no references; swept. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.