---
title: "create_upload"
method: POST
path: "/v1/uploads"
tags: ["document-operations"]
---

# create_upload

`POST /v1/uploads`

## Request

- Base URL: `https://ndi-api.nace.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://ndi-api.nace.ai/v1/uploads`
- Auth: API key in header `X-API-Key`
- TypeScript: <https://skmtc.dev/nace/apis/ndi-service/docs/v1/uploads/post.ts> (a function with typed inputs; its response is checked against the schema)

Stage bytes for document operations and return a handle.

Cost class: **fast** (upload I/O only; no processing units charged here).

The returned ``upload_id`` is passed as ``{"type": "upload", "upload_id":
"<id>"}`` in any subsequent document-operation request.  Uploads are
retained until explicit deletion of the upload, consuming jobs or tenant.
Zero retention refuses standalone uploads with ``422 invalid_request``;
use a workspace bound to your own S3 or Azure storage.

Uploads are workspace-free: this is the entry point for callers that want
to use granular operations without onboarding to the full ingestion model.

Args:
    caller: Authenticated tenant (resolved from ``X-API-Key``).
    session: Database session.
    file: The document bytes.

Returns:
    An :class:`UploadResponse` with the opaque handle, file name, byte
    count, SHA-256 content hash, and expiry timestamp.

Raises:
    PlatformError: ``file_too_large`` when the upload exceeds
        ``NDI_MAX_UPLOAD_BYTES``; ``rate_limited`` when a service key's
        account or tenant is over its request budget.

## Request body `multipart/form-data`

- BodyCreateUploadV1UploadsPost
  - `file` string, binary, required: The document to stage for a granular operation.

## Response `200`

Successful Response

- NdiServiceAppPlatformApiDocumentOperationsSchemasUploadResponse: Returned by ``POST /v1/uploads`` on success. ``upload_id`` is the handle passed to :class:`~ndi_service.app.platform_api.contracts.UploadSource`. Ordinary bytes stay until explicit deletion; Zero retention remains separate.
  - `upload_id` string, uuid, required: Handle to pass as source.upload_id on a later document operation.
  - `file_name` string, required: Original filename as received.
  - `size_bytes` integer, required: Byte length of the staged file, after upload completes.
  - `content_hash` string, required: SHA-256 hex digest of the staged bytes. Confirms the upload is the file you sent, not a workspace file id.
  - `expires_at` string, date-time, nullable, required: Zero retention cleanup deadline; null for ordinary uploads kept until explicit deletion.

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `402`: Payment Required
- `403`: Forbidden
- `404`: Not Found
- `409`: Conflict
- `410`: Gone
- `413`: Request Entity Too Large
- `422`: Unprocessable Entity
- `424`: Failed Dependency
- `429`: Too Many Requests
- `500`: Internal Server Error
- `501`: Not Implemented
- `503`: Service Unavailable
- `504`: Gateway Timeout

---

[API](https://skmtc.dev/nace/apis/ndi-service.md) - [All operations](https://skmtc.dev/nace/apis/ndi-service/llms.txt) - [OpenAPI document](https://skmtc.dev/nace/apis/ndi-service/revisions/d9d1f8ec2207?raw)
