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

# File uploads

> Upload a video, a voice note, a PDF or an image as an input, in three calls.

API bodies are JSON (100 KB at most), so a file goes in steps: you ask for an upload, PUT the file to the URL you get, then complete it. CaptureFlow checks the file and creates the input, as an upload in the app does.

**1. Create the upload**

```bash theme={null}
curl https://api.captureflow.ai/v1/inputs/uploads?workspace_id=$WORKSPACE_ID \
  -H "Authorization: Bearer $CAPTUREFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "filename": "talk.mp4", "content_type": "video/mp4", "size_bytes": 48211234, "title": "Conference talk" }'
```

`size_bytes` is the exact size of the file. Optional: `idea_id` (one of your ideas), `orientation` (`horizontal` or `vertical`, for a video). The answer has an `upload` with `url`, `method` (`PUT`), `headers` and `expires_at`.

**2. PUT the file**

Send the whole file in one PUT to `upload.url`, with exactly `upload.headers` and **no** `Authorization` header:

```bash theme={null}
curl -X PUT "$UPLOAD_URL" -H "Content-Type: video/mp4" --upload-file ./talk.mp4
```

Any `2xx` means it arrived. The URL is private. For video and audio it only accepts this file's type and size; for every kind, the size and type are checked again when you complete.

**3. Complete it**

```bash theme={null}
curl -X POST https://api.captureflow.ai/v1/inputs/uploads/$UPLOAD_ID/complete?workspace_id=$WORKSPACE_ID \
  -H "Authorization: Bearer $CAPTUREFLOW_API_KEY"
```

CaptureFlow checks the size, the type and the file's first bytes, then creates the input. The answer's `input` has the input `id`. Completing again is safe: it answers with the same input.

**4. Wait for processing (audio and video)**

Call `GET /v1/inputs/{id}` until neither `processing.media` nor `processing.transcript` is `pending`. Each ends as `ready`, `failed` or `none` (nothing to expect: an image has no transcript, nor does a scanned PDF). Video and audio take a few minutes; PDFs and images are ready at once. Then draft from it, or start the [content agent](https://docs.captureflow.ai/guides/content-agent.md) with its `input_id`.

## Accepted files

| Kind | Types | Largest |
| - | - | - |
| Video | `video/mp4`, `video/quicktime`, `video/webm`, `video/x-matroska` | 2 GB |
| Audio | `audio/mpeg`, `audio/mp4`, `audio/x-m4a`, `audio/aac`, `audio/wav`, `audio/ogg`, `audio/webm`, `audio/flac`, `audio/opus` | 2 GB |
| PDF | `application/pdf` | 50 MB |
| Image | `image/jpeg`, `image/png`, `image/webp`, `image/gif` | 25 MB |

HEIC and SVG images are not accepted. Text goes through `POST /v1/inputs/text`, web pages through `POST /v1/inputs/url`. The text of a PDF is read from its first 200 pages (200,000 characters at most); a scanned PDF has no text.

## Timing and limits

* Complete an upload within **24 hours**. The upload URL lasts 6 hours (video, audio) or 2 hours (PDF, image); `POST /v1/inputs/uploads/{id}/url` gives a new one, or answers `uploaded: true` when the file is already there.
* Unfinished uploads at once: 20 per key or connected app, 50 per workspace and 10 GB in total (an agency's client workspaces share the agency's).
* Per day, per workspace (an agency's client workspaces share the agency's): 20 audio or video uploads, 100 PDFs or images, 20 GB. During a trial: 5, 25 and 2 GB. Over that: [`upload_budget_reached`](https://docs.captureflow.ai/api/errors#upload_budget_reached).
* An upload belongs to the key or app that created it.

## When something goes wrong

| Code | Meaning | Do |
| - | - | - |
| [`object_missing`](https://docs.captureflow.ai/api/errors#object_missing) | The file hasn't arrived. | PUT it, then complete again (`Retry-After`). |
| [`upload_in_progress`](https://docs.captureflow.ai/api/errors#upload_in_progress) | Another complete is working on it. | Retry after `Retry-After`. |
| [`object_changed`](https://docs.captureflow.ai/api/errors#object_changed) | The file was replaced while being checked. | Complete again. |
| [`size_mismatch`](https://docs.captureflow.ai/api/errors#size_mismatch), [`file_type_mismatch`](https://docs.captureflow.ai/api/errors#file_type_mismatch) | The file isn't what was declared. | Start a new upload. |
| [`file_unprocessable`](https://docs.captureflow.ai/api/errors#file_unprocessable) | The image can't be read. | Start a new upload with another file. |
| [`upload_expired`](https://docs.captureflow.ai/api/errors#upload_expired) | Not completed in time. | Start a new upload. |
| [`storage_unavailable`](https://docs.captureflow.ai/api/errors#storage_unavailable) | Storage is unavailable. | Retry after `Retry-After`. |

Retryable answers also carry `details.retry_after_seconds`.

> [!NOTE]
>
> Over MCP, the upload tools need an assistant that can run HTTP requests or a shell (Claude Code, Cursor, VS Code). Claude and ChatGPT in the browser can't PUT a file, so these tools are hidden there.


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