> For a complete page index of the Captain API documentation, fetch https://docs.captain.dev/llms.txt?excludeSpec=true

# Create Evaluation Upload

POST https://api.filesearch.dev/v3/collections/{collection_name}/evals/uploads
Content-Type: application/json

Mint a single-use upload URL for an evaluation set. PUT the raw `.ndjson` bytes to `upload_url` with exactly the returned `headers` (no `Authorization`); the URL accepts one PUT of exactly `byte_size` bytes, then expires. Files are at most 10,000 lines and 50 MB. The upload is bound to this collection, so a later [Create Eval](/reference/evals/create) cannot point the same `upload_id` at a different one. Line format and limits are on the [Evaluations](/guides/evaluations) guide.

Reference: https://docs.runcaptain.com/reference/evals/uploads/create

## Request

### Path parameters

- `collection_name` (string, required)

### Body (application/json)

This endpoint expects an EvalUploadRequestV3.

- `byte_size` (integer, required) — Exact number of bytes the PUT will send. Enforced on PUT.
- `filename` (string, required) — Must end in `.ndjson`.
- `content_type` (enum, optional, default: application/x-ndjson)
  - Allowed values: `application/x-ndjson`, `application/jsonl`, `application/octet-stream`

## Response

### 200

Upload minted. PUT the file to `upload_url` before `expires_at`.

- `byte_size` (integer, required)
- `collection_name` (string, required)
- `expires_at` (string, required)
- `filename` (string, required)
- `headers` (EvalUploadHeadersV3, required)
- `upload_id` (string, required)
- `upload_url` (string, required) — Signed, PUT-only, single-use URL on *.captainusercontent.com. Opaque.

## Errors

### 401 Unauthorized Error

Missing or invalid authentication.

- `error` (string, required) — Error kind. `HTTP_ERROR` for errors raised by the route.
- `message` (string, required) — What went wrong.
- `path` (string, required) — The request path.
- `status_code` (integer, optional) — The HTTP status code.

### 403 Forbidden Error

API key does not have the query permission for this collection.

- `error` (string, required) — Error kind. `HTTP_ERROR` for errors raised by the route.
- `message` (string, required) — What went wrong.
- `path` (string, required) — The request path.
- `status_code` (integer, optional) — The HTTP status code.

### 404 Not Found Error

Collection not found (or an eval / upload that belongs to another organization: no existence oracle).

- `error` (string, required) — Error kind. `HTTP_ERROR` for errors raised by the route.
- `message` (string, required) — What went wrong.
- `path` (string, required) — The request path.
- `status_code` (integer, optional) — The HTTP status code.

### 422 Unprocessable Entity Error

The body failed validation: `filename` does not end in `.ndjson` or is outside 8 to 512 characters, `byte_size` is outside 1 to 52,428,800, or `content_type` is not an accepted value.

- `error` (string, required) — `VALIDATION_ERROR`.
- `message` (string, required)
- `details` (list of V3CollectionsCollectionNameEvalsUploadsPostResponsesContentApplicationJsonSchemaDetailsItems, required) — One entry per invalid field, with `type`, `loc` and `msg`.
- `path` (string, required)

### 429 Too Many Requests Error

Too many unconsumed upload URLs for this collection (`TOO_MANY_PENDING_UPLOADS`); PUT to one or let it expire.

- `error` (string, required) — Error code, for example `INVALID_ID_PREFIX`. `HTTP_ERROR` when the route raised a plain message.
- `message` (string, required) — What went wrong.
- `path` (string, required) — The request path.
- `status_code` (integer, optional) — The HTTP status code. Present only on `HTTP_ERROR`.

## Types

### EvalUploadHeadersV3

- `Content-Length` (string, required)
- `Content-Type` (string, required)

### V3CollectionsCollectionNameEvalsUploadsPostResponsesContentApplicationJsonSchemaDetailsItems

## Examples

**Request**

```json
{
  "byte_size": 1842,
  "filename": "payments-eval-2026-09-12.ndjson",
  "content_type": "application/x-ndjson"
}
```

**Response**

```json
{
  "byte_size": 1842,
  "collection_name": "customer_forms",
  "expires_at": "2026-09-12T17:45:00Z",
  "filename": "payments-eval-2026-09-12.ndjson",
  "headers": {
    "Content-Length": "1842",
    "Content-Type": "application/x-ndjson"
  },
  "upload_id": "evu_019b4c2e7a3f70d2a91c4e8b6f1023aa",
  "upload_url": "https://uploads.captainusercontent.com/evals/org_example/evu_019b4c2e7a3f70d2a91c4e8b6f1023aa.ndjson?sig=example"
}
```

**SDK Code**

```python Python REST
import os
import requests

BASE_URL = "https://api.filesearch.dev"
API_KEY = "your_api_key"
COLLECTION_NAME = "customer_forms"
PATH = "payments-eval-2026-09-12.ndjson"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

response = requests.post(
    f"{BASE_URL}/v3/collections/{COLLECTION_NAME}/evals/uploads",
    headers=headers,
    json={
        "filename": PATH,
        "byte_size": os.path.getsize(PATH),
        "content_type": "application/x-ndjson",
    },
    timeout=60.0,
)
upload = response.json()

# Send the file to the minted URL with exactly the headers it returned.
with open(PATH, "rb") as f:
    requests.put(upload["upload_url"], headers=upload["headers"], data=f, timeout=300.0)

print(upload["upload_id"], upload["expires_at"])

```

```typescript TypeScript REST
import { readFileSync, statSync } from "fs";

const BASE_URL = "https://api.filesearch.dev";
const API_KEY = "your_api_key";
const COLLECTION_NAME = "customer_forms";
const PATH = "payments-eval-2026-09-12.ndjson";

const response = await fetch(
  `${BASE_URL}/v3/collections/${COLLECTION_NAME}/evals/uploads`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      filename: PATH,
      byte_size: statSync(PATH).size,
      content_type: "application/x-ndjson",
    }),
  },
);
const upload = await response.json();

// Send the file to the minted URL with exactly the headers it returned.
await fetch(upload.upload_url, {
  method: "PUT",
  headers: upload.headers,
  body: readFileSync(PATH),
});

console.log(upload.upload_id, upload.expires_at);

```