---
title: "Upload a file"
method: POST
path: "/files"
tags: ["Research Agent", "Library"]
---

# Upload a file

`POST /files`

## Request

- Base URL: `https://elicit.com/api/v2`
- URL: `https://elicit.com/api/v2/files`
- Auth: HTTP bearer

Stage a file so it can be attached to a Research Agent turn or imported into your library. This is a two-step, presigned upload:

1. **POST /api/v2/files** with the filename, MIME type, and exact byte size. The response returns a `file_id` and a short-lived presigned `upload_url`.
2. **PUT** the raw file bytes to `upload_url` with the same `Content-Type` and a `Content-Length` matching `size_bytes`. Do not send an `Authorization` header on the PUT — the URL is already signed.

Then either pass `{ "file_id": "..." }` in the `attachments` array of a create-session or send-message request — attached files are made available to the agent exactly as uploads made in the web interface are — or pass the `file_id` in `fileIds` to `POST /api/v2/library/imports` to parse the PDF into your library. A staged file can be claimed by one session or one library import. Once attached to a session, the same `file_id` can be referenced again in later turns of that session, including after the staging expiry; it cannot be reused in another session or import.

The request/response fields for this endpoint are deliberately `snake_case` (`content_type`, `size_bytes`, `file_id`, `upload_url`, `expires_at`), unlike the camelCase used elsewhere in the v2 surface. Files are capped at 30 MB, and a session accepts at most 100 distinct files totaling 10GB across all turns. Upload the bytes and make the first claim within 30 minutes: `upload_url` and an **unclaimed** `file_id` expire at `expires_at`. A claimed attachment is not subject to that staging expiry.

### Example

```bash
# 1. Register the upload
curl -X POST https://elicit.com/api/v2/files \
  -H "Authorization: Bearer elk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"filename":"trial-results.pdf","content_type":"application/pdf","size_bytes":245678}'

# 2. Upload the bytes to the returned upload_url
curl -X PUT "{upload_url}" \
  -H "Content-Type: application/pdf" \
  --data-binary @trial-results.pdf

# 3. Attach {"file_id": "..."} to a create-session or /messages request,
#    or import it with POST /library/imports {"fileIds": ["..."]}
```

## Request body

- object
  - `filename` string, required: Original filename of the upload. Used as the display name in the session.
  - `content_type` string, required: MIME type of the file.
  - `size_bytes` integer, required: Exact size of the file in bytes. Must match the uploaded object exactly. Maximum 31457280 bytes (30 MB).

## Response `200`

Upload registered. PUT the file bytes to `upload_url` before it expires.

- CreateFileResponse
  - `file_id` string, required: Opaque identifier for the staged upload. After uploading the bytes, pass it in `attachments` on a Research Agent create/message request, or in `fileIds` on a library import. It can be claimed by one session or import.
  - `upload_url` string, required: Short-lived presigned S3 PUT URL. Upload the file bytes directly to it with the same Content-Type and Content-Length declared here.
  - `expires_at` string, required: ISO 8601 expiry for the upload URL and unclaimed file_id (30 minutes after staging). A file already attached to a session remains usable in later turns of that same session.

## Other responses

- `400`: Invalid request. The filename, content type, or size is missing or invalid, or the size exceeds the 30 MB limit.
- `401`: Authentication failed. The API key is missing, invalid, revoked, or expired.
- `403`: API access is unavailable, or uploads are disabled for this account.
- `429`: Rate limit exceeded. More than 100 requests per minute were received from your IP address; further requests are blocked for 5 minutes.
- `500`: An unexpected error occurred. Retry reads with backoff; before retrying a write, check whether it took effect. See Error Handling for retry guidance.

## Changes

- **2026-10-16** `22847bf7e65d`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/elicitlabs/apis/elicit-labs-api/changes/files/post.md)

---

[API](https://skmtc.dev/elicitlabs/apis/elicit-labs-api.md) - [All operations](https://skmtc.dev/elicitlabs/apis/elicit-labs-api/llms.txt) - [OpenAPI document](https://skmtc.dev/elicitlabs/apis/elicit-labs-api/revisions/cbbe29509d04?raw)
