---
title: "create_upload_grant"
method: POST
path: "/v1/workspaces/{workspace_id}/upload-grants"
tags: ["files"]
---

# create_upload_grant

`POST /v1/workspaces/{workspace_id}/upload-grants`

## 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/workspaces/{workspace_id}/upload-grants`
- Auth: API key in header `X-API-Key`
- TypeScript: <https://skmtc.dev/nace/apis/ndi-service/docs/v1/workspaces/:workspace_id/upload-grants/post.ts> (a function with typed inputs; its response is checked against the schema)

Mint a short-lived grant an end client redeems to upload directly. Cost class: **fast**.

The customer's backend holds the API key; the end user's browser must
never see it. This mints an HMAC-signed token that authorizes exactly one
thing — ``upload_file`` into this workspace, optionally pinned to one
destination path and a byte cap — until ``expires_at``. The holder sends
it in the ``X-Upload-Token`` header of a multipart ``POST`` to
``upload_url``, so the bytes travel end-client → NDI once instead of
end-client → customer → NDI twice.

The grant is stateless (no DB row); its short TTL stands in for
revocation, except that revoking the minting API key kills its
outstanding grants immediately. Where the deployment runs Redis the grant
is additionally single-use.

Args:
    context: Resolved workspace plus the caller's access predicate.
    body: Optional path pin, byte cap, and TTL.

Returns:
    ``UploadGrantResponse`` with the token, the upload URL, and expiry.

Raises:
    PlatformError: ``invalid_request`` when ``max_bytes`` exceeds the
        service-wide upload cap.

## Path parameters

- `workspace_id` string, uuid, required: Workspace identifier.

## Request body

- CreateUploadGrantRequest: Body for ``POST /workspaces/{workspace_id}/upload-grants``. Every field narrows what the grant's holder may do; an empty body mints a grant for one upload anywhere in the workspace with the default TTL.
  - `path` string, nullable: Pin the upload to exactly this destination path. The platform-reserved '.workspace' subtree is not allowed. Omit to let the holder choose.
  - `max_bytes` integer, nullable: Per-grant byte cap, tighter than the service-wide cap for the pinned path. Omit to freeze the grant at that cap; an unpinned grant freezes at NDI_MAX_UPLOAD_BYTES.
  - `total_size_bytes` integer, nullable: Exact expected upload size frozen into the grant. Distinct from max_bytes: a short body is rejected even when it fits under the cap.
  - `ttl_seconds` integer: How long the grant verifies, in seconds.

## Response `201`

Successful Response

- UploadGrantResponse: A minted upload grant, ready to hand to an end client. The holder sends ``token`` in the ``X-Upload-Token`` header of a multipart ``POST`` to ``upload_url`` (the workspace's ``upload_file`` route). The grant authorizes that one method only, and is single-use where the deployment runs Redis.
  - `token` string, required: Send in the X-Upload-Token header of the upload request.
  - `upload_url` string, required: The upload_file route the token is valid against.
  - `expires_at` string, date-time, required: When the grant stops verifying.

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