---
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`
- 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-platform-api/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.

## 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.
  - `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.
  - `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.
  - `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.
  - `expires_at` string, date-time, required: When the grant stops verifying.
  - `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.

## Other responses

- `504`: Gateway Timeout
- `default`: Typed error envelope used by every 4xx and 5xx. ``error.code`` distinguishes the failure; HTTP status is a consequence of the code.

---

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