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

# Uploads

> POST /v1/uploads — Stage a file for a granular operation

Stage bytes for a single granular document operation and receive an opaque handle.
Uploads are workspace-free: a caller can parse, classify, extract, or ground a file
without a workspace.

## Endpoint

```
POST /v1/uploads
```

## Request

`multipart/form-data` with a `file` part. Optional `ttl_seconds` form field (integer)
overrides the default retention window; capped at the server maximum.

```bash theme={"dark"}
curl -X POST "$NDI_BASE_URL/v1/uploads" \
  -H "X-API-Key: $NDI_API_KEY" \
  -F "file=@annual-report.pdf"
```

With a custom TTL (seconds):

```bash theme={"dark"}
curl -X POST "$NDI_BASE_URL/v1/uploads" \
  -H "X-API-Key: $NDI_API_KEY" \
  -F "file=@annual-report.pdf" \
  -F "ttl_seconds=3600"
```

**Form fields:**

* **`file`** (required) — The document bytes.
* **`ttl_seconds`** (optional) — How long the upload is retained, in seconds. Defaults to 86400 (24 hours); capped at 604800 (7 days). Bytes are deleted at job completion, so the TTL is only the ceiling for an upload that was never consumed.

## Response

**200 OK**

```json theme={"dark"}
{
  "upload_id": "550e8400-e29b-41d4-a716-446655440002",
  "file_name": "annual-report.pdf",
  "size_bytes": 2097152,
  "content_hash": "a3f2b1c0d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
  "expires_at": "2026-08-10T12:00:00Z"
}
```

**Fields:**

* **`upload_id`** — Opaque UUID handle; pass this in any document-operation source.
* **`file_name`** — The original filename from the upload.
* **`size_bytes`** — Exact byte count of the staged file.
* **`content_hash`** — SHA-256 hex digest of the file bytes (bare 64-character hex string, no prefix).
* **`expires_at`** — When the upload is deleted if not consumed. Bytes are also deleted at job completion.

## Using the upload in an operation

Pass the `upload_id` as the source discriminator in any document-operation:

```bash theme={"dark"}
curl -X POST "$NDI_BASE_URL/v1/parse" \
  -H "X-API-Key: $NDI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "type": "upload",
      "upload_id": "550e8400-e29b-41d4-a716-446655440002"
    }
  }'
```

Uploads are **single-use**: bytes are deleted once the job that consumes them finishes.

## Error responses

**413 — `file_too_large`**

```json theme={"dark"}
{
  "error": {
    "code": "file_too_large",
    "message": "Upload exceeds the maximum allowed size of N bytes.",
    "retryable": false,
    "request_id": "..."
  }
}
```

## Upload vs workspace file

|           | `POST /v1/uploads`        | `POST /v1/workspaces/{id}/files` |
| --------- | ------------------------- | -------------------------------- |
| Storage   | Temporary (TTL-bound)     | Persistent in workspace          |
| Purpose   | Single granular operation | Corpus building and querying     |
| After use | Deleted at job completion | Stays until explicitly deleted   |

See [Sources and uploads](/concepts/sources-and-uploads) for all three source discriminator types.
