---
title: "Reserve signed uploads in bulk"
method: POST
path: "/assets/reserve"
tags: ["Assets"]
---

# Reserve signed uploads in bulk

`POST /assets/reserve`

## Request

- Base URL: `https://app.flora.ai/api/v1`
- URL: `https://app.flora.ai/api/v1/assets/reserve`
- Auth: HTTP bearer

Reserves up to 25 signed uploads in one request — the batch counterpart of POST /assets with source="signed-url", counted once against the workspace's rate limit instead of once per file. workspace_id, project_id and folder apply to every upload; an upload's own folder overrides the shared one. Each entry in assets echoes its index and carries either the single reservation's fields (asset_id, upload, upload_url, expires_at, url) or ok false with an error; a failed entry, such as an unsupported content_type, never stops the rest. GLB entries require project_id and exact size_bytes. A workspace this credential cannot write, or a project_id outside the workspace, fails the whole request. Upload each file's bytes using the returned descriptor: existing formats use multipart POST, while GLB uses raw-byte PUT to its resumable session. Then finish them in one call with POST /assets/complete. As on the single reservation, project_id associates each asset with a project; supported static media are surfaced on its canvas, while GLB Model3D assets are not auto-placed. Mutating public API requests support an optional Idempotency-Key header for client retries; duplicate keys within two hours return idempotency_duplicate, and a batch whose every entry was rejected releases its key (see the Idempotency guide).

## Request body

- object
  - `workspace_id` string, required: Workspace identifier. Use the public API ID returned by list workspaces; it must start with ws_.
  - `project_id` string: Project identifier. Use the public API ID returned by list projects; it must start with prj_. Every upload is associated with this project. Supported static media are surfaced on its canvas after completion; GLB Model3D assets are not auto-placed.
  - `folder` string: Destination folder for uploads that set none
  - `uploads` object[], required: Uploads to reserve, 1 to 25 per call
    - `file_name` string: Asset file name
    - `content_type` string: Asset content type, pinned in this upload's policy
    - `size_bytes` integer
    - `folder` string: Destination folder for this upload

## Response `200`

Uploads reserved; each entry reports its own outcome.

- object
  - `assets` union[], required: One entry per requested upload, in request order: ok true with the single reservation's fields, or ok false with error
    - union
      - object
        - `asset_id` string, required: Asset identifier
        - `uploaded_via` 'signed_url', required
        - `url` string, uri, required: Asset URL
        - `workspace_id` string, required: Workspace identifier
        - `visibility` 'workspace', required
        - `status` 'pending_upload' | 'ready' | 'failed', required
        - `upload` union, required
          - object
            - `url` string, uri, required: Upload URL
            - `method` 'POST', required
            - `content_type` 'multipart/form-data', required
            - `file_field` 'file', required
            - `form_fields` object, required: Upload form fields
          - object
            - `url` string, uri, required: Resumable upload session URL
            - `method` 'PUT', required
            - `content_type` 'model/gltf-binary', required
            - `headers` object, required: Required upload headers
        - `upload_url` string, required: Upload URL (serialized)
        - `expires_at` string, date-time, required: Expiration time for the upload URL
        - `index` integer, required: Position of this entry in the request's list
        - `ok` true, required: This upload was reserved
      - object
        - `index` integer, required: Position of this entry in the request's list
        - `ok` false, required: This entry failed; error says why
        - `error` object, required: Why this entry failed
          - `code` string, required: Stable public API error code, as the single-asset operation would return it
          - `message` string, required: Human-readable reason this entry failed

## Other responses

- `400`: 400
- `401`: 401
- `402`: 402
- `403`: 403
- `404`: 404
- `409`: 409
- `429`: 429
- `500`: 500
- `503`: 503

## Changes

- **2026-10-28** `a8351619f225`: 9 warning
  - added the new `not_configured` enum value to the `error/code` response property for the response status `400`
  - added the new `not_configured` enum value to the `error/code` response property for the response status `401`
  - added the new `not_configured` enum value to the `error/code` response property for the response status `402`
  - added the new `not_configured` enum value to the `error/code` response property for the response status `403`
  - …5 more
- **2026-10-20** `d7653e24b083`: 6 breaking, 2 info
  - the `assets/items/anyOf[subschema #1]/upload` response's property type changed from `object` to no type for status `200`
  - removed the required property `assets/items/anyOf[subschema #1]/upload/content_type` from the response with the `200` status
  - removed the required property `assets/items/anyOf[subschema #1]/upload/file_field` from the response with the `200` status
  - removed the required property `assets/items/anyOf[subschema #1]/upload/form_fields` from the response with the `200` status
  - …4 more
- **2026-10-16** `e54130373b10`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/florafauna-ai/apis/flora-ai-api/changes/assets/reserve/post.md)

---

[API](https://skmtc.dev/florafauna-ai/apis/flora-ai-api.md) - [All operations](https://skmtc.dev/florafauna-ai/apis/flora-ai-api/llms.txt) - [OpenAPI document](https://skmtc.dev/florafauna-ai/apis/flora-ai-api/revisions/87cfe2c4ac29?raw)
