---
title: "Open an upload session, or upload small objects inline in one call"
method: POST
path: "/vaults/v1/{vault_id}/upload-sessions"
tags: ["vault"]
---

# Open an upload session, or upload small objects inline in one call

`POST /vaults/v1/{vault_id}/upload-sessions`

## Request

- Base URL: `https://api.run402.com`
- URL: `https://api.run402.com/vaults/v1/{vault_id}/upload-sessions`
- Auth: one of:
  - API key in header `SIGN-IN-WITH-X`
  - HTTP bearer
  - HTTP bearer

Declares the expected manifest (kinds, ids, FULL_OBJECT SHA-256, sizes) and returns one **create-only** presigned PUT per object. Each URL signs `If-None-Match: *`, the exact `Content-Length`, and the exact `x-amz-checksum-sha256` — send `headers` verbatim or storage rejects the write, so an upload can neither overwrite an existing object nor store bytes that do not hash to what you declared. URLs are returned **once**: re-reading the session redacts them. The session reserves its accounted bytes (64 KiB per-object floor) against the ORGANIZATION's pooled storage. An object id already present in any state is refused — the row is the tombstone.

**Inline upload** (vault-composite-state-read design D2): when EVERY entry also carries `bytes` (base64url) at or under 256 KiB, and the request's decoded total is at or under 1 MiB, the gateway verifies each declared checksum against the received bytes BEFORE any write and performs the storage writes itself — create-only, same checksum binding, same accounting — and the response IS the finalize response (`receipts`, `charged_bytes`, `finalized_at`, `deduplicated`) instead of presigned PUTs, replacing session + N PUTs + finalize with one call. Mixing inline and presigned entries in one request is refused `VALIDATION_FAILED`; an object over either cap uses the presigned shape unchanged.

## Path parameters

- `vault_id` string, required

## Headers

- `SIGN-IN-WITH-X` string, required

## Request body

- object
  - `objects` object[], required
    - `object_kind` 'wal_pack' | 'ref_state' | 'retention_roots' | 'checkpoint_manifest' | 'checkpoint_pack' | 'checkpoint_claim_set' | 'maintenance_stage_claim_set' | 'maintenance_stage_page' | 'verifier_receipt' | 'key_envelope', required
    - `object_id` string: Id-addressed kinds only (`<prefix>_<32 hex>`).
    - `epoch` string: key_envelope only: 16 lowercase hex.
    - `recipient_fingerprint` string: key_envelope only: `ek_<32 hex>`.
    - `sha256` string, required: FULL_OBJECT SHA-256 of the bytes as stored, 64 lowercase hex.
    - `size_bytes` string, required: Decimal byte count as a string (values exceed 2^53).
    - `base_generation` string: wal_pack only: 16 lowercase hex.
    - `bytes` string: Inline upload only (design D2): base64url, at or under 256 KiB, and the declared sha256/size_bytes of THIS object. Every entry in the request must carry bytes, or none may — mixing is refused.
  - `resource_binding` object: Which resource lane the session draws on. Defaults to `ordinary_push`.
    - `kind` 'ordinary_push' | 'maintenance_cycle' | 'repair_attempt', required
    - `maintenance_lease_id` string
    - `repair_attempt_id` string

## Response `201`

Session opened; presigned PUTs returned once

- object
  - `upload_session_id` string, uuid
  - `repo_id` string
  - `resource_binding` object: Which resource lane the session draws on. Defaults to `ordinary_push`.
    - `kind` 'ordinary_push' | 'maintenance_cycle' | 'repair_attempt', required
    - `maintenance_lease_id` string
    - `repair_attempt_id` string
  - `lease_expires_at` string, date-time
  - `reserved_bytes` string: Decimal byte count as a string (values exceed 2^53).
  - `objects` object[]
    - `object_kind` string
    - `object_id` string
    - `sha256` string
    - `size_bytes` string: Decimal byte count as a string (values exceed 2^53).
    - `put` object
      - `url` string
      - `method` 'PUT'
      - `headers` object: Send these VERBATIM. Always carries `If-None-Match: *` (create-only), `x-amz-checksum-sha256`, and `Content-Length` — all inside the signature.
      - `expires_at` string, date-time
  - `next_actions` object[]
  - `receipts` object[]: Inline upload only: present INSTEAD of the fields above — this response IS the finalize response (design D2). See POST .../finalize for the receipt shape.
  - `charged_bytes` string: Inline upload only. Decimal byte count as a string.
  - `finalized_at` string, date-time: Inline upload only.
  - `deduplicated` boolean: Inline upload only — always false for a freshly created session's own first (and only) finalize.

## Other responses

- `400`: VALIDATION_FAILED — manifest shape, duplicate id, a reused object id, mixed inline/presigned entries, an inline object over a cap, or inline bytes that do not hash to the declared sha256
- `403`: VAULT_ACCESS_DENIED / QUOTA_EXCEEDED (org-pooled storage) / REPAIR_NOT_AUTHORIZED
- `409`: MAINTENANCE_LEASE_HELD — the named lease is not active on this vault
- `429`: RATE_LIMITED — the per-principal or per-project session cap
- `503`: STORAGE_UNAVAILABLE

---

[API](https://skmtc.dev/run402/apis/run402-api.md) · [All operations](https://skmtc.dev/run402/apis/run402-api/llms.txt) · [OpenAPI document](https://skmtc.dev/run402/apis/run402-api/revisions/893607557f50?raw)
