> ## 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.

# Sources and uploads

> Four ways to provide documents to granular operations

Granular document operations (parse, split, classify, extract, and ground) all
accept a `source` field in the request body. The source is a discriminated
union on `type`:

***

## URL source (`"type": "url"`)

Point an operation at any `https://` or `s3://` URL:

```json theme={"dark"}
{
  "source": {
    "type": "url",
    "url": "https://example.com/annual-report.pdf",
    "file_name": "annual-report.pdf"
  }
}
```

**Fields:**

* **`url`** — `https://` or `s3://` only. NDI verifies the resolved hostname.
* **`file_name`** — Used to choose the processing lane and appears in result metadata.

***

## Upload source (`"type": "upload"`)

Upload a file via `POST /v1/uploads`, then reference it by `upload_id`:

```bash theme={"dark"}
# 1. Upload
UPLOAD=$(curl -s -X POST "$NDI_BASE_URL/v1/uploads" \
  -H "X-API-Key: $NDI_API_KEY" \
  -F "file=@document.pdf")
UPLOAD_ID=$(echo "$UPLOAD" | jq -r '.upload_id')

# 2. Use in an operation
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\": \"$UPLOAD_ID\"}}"
```

**Upload response:**

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

**Upload lifecycle:**

* TTL is set at upload time via `ttl_seconds` form field. The default is 7 days; the maximum is also 7 days. The `expires_at` field in the response shows the exact expiry.
* Uploads are not single-use — the same `upload_id` can be used in multiple operations until it expires.
* Upload IDs are scoped to your API key.

**Source discriminator:**

```json theme={"dark"}
{
  "source": {
    "type": "upload",
    "upload_id": "550e8400-e29b-41d4-a716-446655440002"
  }
}
```

***

## Workspace file source (`"type": "workspace_file"`)

Reference a file already stored in a workspace. Granular operations read the
raw source bytes — the file does not need to be ingested first.

```json theme={"dark"}
{
  "source": {
    "type": "workspace_file",
    "workspace_id": "550e8400-e29b-41d4-a716-446655440001",
    "file_id": "550e8400-e29b-41d4-a716-446655440004"
  }
}
```

This is the bridge between the two integration patterns: you can use granular
operations (parse, extract, …) on files already in a workspace.

***

## Parse result source (`"type": "parse_result"`)

Reuse the retained result of a successful Parse job without downloading and
uploading the document again:

```json theme={"dark"}
{
  "source": {
    "type": "parse_result",
    "job_id": "550e8400-e29b-41d4-a716-446655440010"
  }
}
```

The referenced job must belong to the same API key, have `kind: "parse"` and
`status: "succeeded"`, and still have an available result.

Support varies by operation:

| Operation    | `parse_result` support                                                  |
| ------------ | ----------------------------------------------------------------------- |
| **Parse**    | Supported; page selection and `output.include_images` are not available |
| **Split**    | Supported                                                               |
| **Classify** | Not supported                                                           |
| **Extract**  | Supported; `page_ranges` is not available                               |
| **Ground**   | Supported                                                               |

***

## Workspace file uploads

Uploading files **into a workspace** is a separate endpoint — not `POST /v1/uploads`.

```bash theme={"dark"}
# Upload into a workspace (persistent storage)
curl -X POST "$NDI_BASE_URL/v1/workspaces/$WS/files" \
  -H "X-API-Key: $NDI_API_KEY" \
  -F "file=@document.pdf" \
  -F 'metadata={"path":"reports/document.pdf","access_labels":[]}'
```

This returns a `Job` with `kind: "upload_file"`, not an upload object. The file
persists in the workspace after the job completes. See [Files](/api-reference/files)
for full documentation.

***

## Which upload to use?

| Pattern                | Endpoint                         | Storage           | Lifecycle           |
| ---------------------- | -------------------------------- | ----------------- | ------------------- |
| **Granular operation** | `POST /v1/uploads`               | Temporary staging | Expires after TTL   |
| **Workspace storage**  | `POST /v1/workspaces/{id}/files` | Persistent        | Lives until deleted |
