Upload
StableGenerated from capsule-server/openapi.json, the OpenAPI 3.2 document
capsule-server emits and mise run openapi-check-kynos keeps current. To change a
description on this page, change the annotation on the handler or model it comes from
and regenerate — this file is build output. The auth model, error contract, and
conventions common to every endpoint are on the REST API overview.
POST /v1/upload
Section titled “POST /v1/upload”Open an upload session for one blob of an asset bundle.
Runs the refuse-by-default envelope battery — invariants 1–8 and the top-level↔envelope
consistency family — before anything is written, then stages the session’s file and
records the session. A request whose (owner, hash, album) tuple already has an active
session gets that session back rather than a second one.
Authentication: required — bearer.
| Parameter | In | Type | Description |
|---|---|---|---|
X-Capsule-Protocol | header | string | null | The protocol date the client speaks. Read as a string rather than a typed value so that a malformed one is this surface’s coded 400 rather than the framework’s uncoded one. |
Request body (required, application/json): CreateUploadRequest
| Status | Body | Description |
|---|---|---|
200 | CreateUploadResponse application/json | The active session for these bytes, to resume. Headers: Location, X-Capsule-Offset, X-Capsule-Suggested-Chunk-Size. |
201 | CreateUploadResponse application/json | Upload session created. Headers: Location, X-Capsule-Offset, X-Capsule-Suggested-Chunk-Size. |
400 | CodedProblem application/problem+json | Bad Request. |
401 | CodedProblem application/problem+json | Unauthorized. Headers: WWW-Authenticate. |
403 | CodedProblem application/problem+json | Forbidden. |
409 | DuplicateBlobProblem application/problem+json | Album quiescing. |
413 | CodedProblem application/problem+json | File too large. |
415 | CodedProblem application/problem+json | Unsupported Media Type. |
422 | CodedProblem application/problem+json | Unprocessable Entity. |
426 | ProtocolRangeProblem application/problem+json | Protocol version unsupported. |
500 | CodedProblem application/problem+json | Internal server error. |
GET /v1/upload/sessions
Section titled “GET /v1/upload/sessions”Every upload the caller can resume.
Oldest first, which is the order the store promises and the order a client wants: the oldest in-flight session is the one closest to eviction.
Authentication: required — bearer.
| Parameter | In | Type | Description |
|---|---|---|---|
status | query | string | null | Return only sessions in this state. One of pending, uploading, waiting_for_processing, completed, failed_processing — the same tokens the X-Capsule-Upload-Status header carries, so a client filters on the value it was already given rather than on a second vocabulary. |
| Status | Body | Description |
|---|---|---|
200 | SessionsResponse application/json | OK. |
400 | CodedProblem application/problem+json | Bad Request. |
401 | CodedProblem application/problem+json | Unauthorized. Headers: WWW-Authenticate. |
403 | CodedProblem application/problem+json | Forbidden. |
413 | — | the request body exceeds the configured limit. |
500 | CodedProblem application/problem+json | Internal server error. |
DELETE /v1/upload/{id}
Section titled “DELETE /v1/upload/{id}”Cancel a session: its record, its accepted chunks and its staged bytes, together.
Refused while finalization is running — it is not interruptible — and refused once the session is terminal, because there is nothing left to cancel and the receipt is what a client should read instead.
Authentication: required — bearer.
| Parameter | In | Type | Description |
|---|---|---|---|
X-Capsule-Protocol | header | string | null | The protocol date the client speaks. Read as a string rather than a typed value so that a malformed one is this surface’s coded 400 rather than the framework’s uncoded one. |
id | path | string | Required. The session’s identifier, as POST /v1/upload returned it. |
| Status | Body | Description |
|---|---|---|
204 | — | the request succeeded and there is no content to send. |
400 | CodedProblem application/problem+json | Bad Request. |
401 | CodedProblem application/problem+json | Unauthorized. Headers: WWW-Authenticate. |
403 | CodedProblem application/problem+json | Forbidden. |
404 | CodedProblem application/problem+json | Upload session not found. |
409 | CodedProblem application/problem+json | Session not active. |
413 | — | the request body exceeds the configured limit. |
426 | ProtocolRangeProblem application/problem+json | Protocol version unsupported. |
500 | CodedProblem application/problem+json | Internal server error. |
HEAD /v1/upload/{id}
Section titled “HEAD /v1/upload/{id}”Report a session’s progress and state.
The resumption primitive: a client that lost a connection, an acknowledgement, or a process
asks here and learns the authoritative offset, the declared length and the session’s state.
The answer carries no body — HTTP forbids one on HEAD, which is why the protocol puts
all three on headers.
Authentication: required — bearer.
| Parameter | In | Type | Description |
|---|---|---|---|
X-Capsule-Protocol | header | string | null | The protocol date the client speaks. Read as a string rather than a typed value so that a malformed one is this surface’s coded 400 rather than the framework’s uncoded one. |
id | path | string | Required. The session’s identifier, as POST /v1/upload returned it. |
| Status | Body | Description |
|---|---|---|
200 | — | Progress and state on X-Capsule-* headers, no body. Headers: Cache-Control, X-Capsule-Content-Length, X-Capsule-Offset, X-Capsule-Upload-Status. |
400 | CodedProblem application/problem+json | Bad Request. |
401 | CodedProblem application/problem+json | Unauthorized. Headers: WWW-Authenticate. |
403 | CodedProblem application/problem+json | Forbidden. |
404 | CodedProblem application/problem+json | Upload session not found. |
413 | — | the request body exceeds the configured limit. |
426 | ProtocolRangeProblem application/problem+json | Protocol version unsupported. |
500 | CodedProblem application/problem+json | Internal server error. |
PATCH /v1/upload/{id}
Section titled “PATCH /v1/upload/{id}”Append a chunk, and finalize when it completes the declared size.
Every rule the chunk contract fixes is checked before a byte is written, and the checksum is verified against the received bytes first, so a chunk corrupted in transit persists nothing.
Authentication: required — bearer.
| Parameter | In | Type | Description |
|---|---|---|---|
X-Capsule-Checksum | header | string | null | The chunk’s SHA-256, bare lowercase hex. Required: the idempotency tuple is undefined without it. |
X-Capsule-Offset | header | string | null | Where in the blob this chunk starts. |
X-Capsule-Protocol | header | string | null | The protocol date the client speaks. |
id | path | string | Required. The session’s identifier, as POST /v1/upload returned it. |
Request body (required, application/octet-stream): string
| Status | Body | Description |
|---|---|---|
204 | — | the request succeeded and there is no content to send. Headers: X-Capsule-Offset. |
400 | CodedProblem application/problem+json | Bad Request. |
401 | CodedProblem application/problem+json | Unauthorized. Headers: WWW-Authenticate. |
403 | CodedProblem application/problem+json | Forbidden. |
404 | CodedProblem application/problem+json | Upload session not found. |
409 | OffsetMismatchProblem application/problem+json | Offset mismatch. |
413 | CodedProblem application/problem+json | Chunk too large. |
415 | CodedProblem application/problem+json | Unsupported media type. |
426 | ProtocolRangeProblem application/problem+json | Protocol version unsupported. |
500 | CodedProblem application/problem+json | Internal server error. |
GET /v1/upload/{id}/receipt
Section titled “GET /v1/upload/{id}/receipt”Fetch the custody receipt for a finalized upload.
Authentication: required — bearer.
| Parameter | In | Type | Description |
|---|---|---|---|
id | path | string | Required. The session’s identifier, as POST /v1/upload returned it. |
| Status | Body | Description |
|---|---|---|
200 | string application/cbor | OK. |
400 | CodedProblem application/problem+json | Bad Request. |
401 | CodedProblem application/problem+json | Unauthorized. Headers: WWW-Authenticate. |
403 | CodedProblem application/problem+json | Forbidden. |
404 | CodedProblem application/problem+json | Not found. |
409 | CodedProblem application/problem+json | Receipt not available. |
413 | — | the request body exceeds the configured limit. |
500 | CodedProblem application/problem+json | Internal server error. |
Schemas
Section titled “Schemas”The models these endpoints carry. A field whose type names another model links to it when this page documents that model, which it does when some path from an operation reaches it within 4 references. A model only ever reached deeper than that is named without being expanded.
CodedProblem
Section titled “CodedProblem”An RFC 9457 problem detail.
| Field | Type | Description |
|---|---|---|
type | string | Required. |
title | string | — |
status | integer | Required. |
detail | string | — |
instance | string | — |
code | string | Required. The stable error.* catalog code. The client localizes this; detail stays English. Present on every problem this server renders. |
CreateUploadRequest
Section titled “CreateUploadRequest”The body of POST /v1/upload.
Strict (deny_unknown_fields): an unknown field is a client bug and is refused rather than
ignored. Plaintext metadata — a filename, a capture date, dimensions — is deliberately
absent: it rides the encrypted metadata blob and never the wire request.
| Field | Type | Description |
|---|---|---|
size | integer | Required. The ciphertext length in bytes. Immutable for the session’s life. |
hash | string | Required. The ciphertext content hash, lowercase hex; the digest length is the suite’s. |
content_type | string | Required. The media type, from the closed enum this protocol version fixes. |
crypto_suite_id | integer | Required. The crypto suite the blob was sealed under. |
protocol_version | string | Required. The protocol date (YYYY-MM-DD) this session is pinned to. |
blob_role | WireBlobRole | Required. The blob’s role in its bundle. |
manifest_envelope | ManifestEnvelope | Required. The unencrypted manifest fields the server validates. |
album_id | string | null | The album the asset is filed into. Optional on the wire because the contract reserves the shape for owner-scoped kinds and for the album-upgrade ceremony; required by this server, which has no way to check invariant 6 without one and refuses rather than skipping it. |
owner_id | string | null | The owner the asset is filed under, when it is not the uploader. Refused when it is anyone but the uploader: an on-behalf upload needs a verified relationship, and the port that would answer for one does not exist here. |
intent_id | string | null | The album-upgrade intent this write belongs to, when it belongs to one. Carried onto the session verbatim and read by nobody in this port; the ceremony that gives it meaning is S-C24. |
CreateUploadResponse
Section titled “CreateUploadResponse”What a client needs to start sending bytes.
| Field | Type | Description |
|---|---|---|
id | string | Required. The session’s identifier. |
upload_url | string | Required. Where to send chunks. |
suggested_chunk_size | integer | Required. A starting chunk size. A suggestion only — the client owns adaptation. |
DuplicateBlobProblem
Section titled “DuplicateBlobProblem”An RFC 9457 problem detail.
| Field | Type | Description |
|---|---|---|
type | string | Required. |
title | string | — |
status | integer | Required. |
detail | string | — |
instance | string | — |
code | string | Required. The stable error.* catalog code. The client localizes this; detail stays English. Present on every problem this server renders. |
existing_asset | string | The asset already holding these exact bytes in the same album. Structured so a client merges rather than re-parsing a sentence (slice S-C22). |
ManifestEnvelope
Section titled “ManifestEnvelope”The server-visible mirror of the signed manifest’s envelope fields, as declared at
POST /v1/upload.
Strict (deny_unknown_fields) like the rest of the transport JSON. The Postel asymmetry
the design draws — tolerant inside documents that outlive us, strict on the wire we own —
puts unknown-key tolerance in the signed CBOR interiors, never in this JSON projection.
| Field | Type | Description |
|---|---|---|
crypto_suite_id | integer | Required. The crypto suite the blob was sealed under. Must equal the top-level declaration. |
protocol_version | string | Required. The protocol date the manifest was written under (YYYY-MM-DD). |
album_id | string | null | The album the asset belongs to. Must equal the top-level declaration. |
file_id | string | Required. The asset this blob belongs to — the same id across the bundle’s members. |
amk_version | integer | Required. The album-key epoch the manifest was written under. |
ciphertext_hash | string | Required. The ciphertext content hash, lowercase hex. Must equal the top-level hash. This names the blob this session is uploading, not the manifest’s own ciphertext_hash. For the original the two coincide; for a metadata or provenance session they do not, and the projection reuses the manifest’s field name for a per-blob declaration. Invisible for a create, because the bundle is assembled in a pending row nobody can see and no member has to name another. It is not invisible for a replace, which is why [Self::original_blob_hash] exists (S-C43). |
plaintext_size | integer | Required. The plaintext length the manifest commits to. |
chunk_size | integer | Required. The STREAM plaintext chunk size. |
key_mode | string | Required. derived or wrapped. |
metadata_blob_hash | string | null | The content hash of the bundle’s metadata blob, when the manifest commits to one. |
original_blob_hash | string | null | The content hash of the bundle’s original blob, when the manifest commits to one (S-C43). The manifest’s own ciphertext_hash, under a name that cannot be confused with [Self::ciphertext_hash]‘s per-session meaning. Optional on the wire and required on a replace: a replace re-points roles that already have bytes, so it has to be applied as one act, and the only member of the bundle that can carry the whole change is the manifest — which therefore has to be able to name the original it commits to. A create may omit it. Its bundle is assembled incrementally in a row nobody can see, so no member needs to name another and requiring it would be a wire change for no gain. |
created_by_user | string | Required. The account that created the asset. |
created_by_device | string | Required. The device that created it, as a UUID — invariant 7’s subject. |
client_version | string | Required. The client build that wrote the manifest. |
timestamp | string | Required. The manifest’s self-asserted RFC3339 timestamp — invariants 7 and 8’s subject. |
action | string | Required. The lifecycle action. create or replace on this surface — the two that move blob bytes — and see [GateReject::ActionNotAllowed] for the rest. |
prior_provenance_hash | string | null | The provenance chain position this write continues from. |
retention_until | string | null | The retention floor the manifest carries, when it carries one. |
OffsetMismatchProblem
Section titled “OffsetMismatchProblem”An RFC 9457 problem detail.
| Field | Type | Description |
|---|---|---|
type | string | Required. |
title | string | — |
status | integer | Required. |
detail | string | — |
instance | string | — |
code | string | Required. The stable error.* catalog code. The client localizes this; detail stays English. Present on every problem this server renders. |
offset | integer | The offset the server is actually at, so a client resumes from it instead of asking again. |
ProtocolRangeProblem
Section titled “ProtocolRangeProblem”An RFC 9457 problem detail.
| Field | Type | Description |
|---|---|---|
type | string | Required. |
title | string | — |
status | integer | Required. |
detail | string | — |
instance | string | — |
code | string | Required. The stable error.* catalog code. The client localizes this; detail stays English. Present on every problem this server renders. |
protocol_min | string | The oldest protocol date this server still speaks (YYYY-MM-DD). |
protocol_max | string | The newest protocol date this server speaks (YYYY-MM-DD). |
SessionSummary
Section titled “SessionSummary”One in-flight upload, as the resumption listing serves it.
Deliberately not the whole [UploadSessionRecord]. The manifest envelope, the expected hash
and the crypto pin are finalization’s inputs and are already the client’s own — echoing them
to every listing would put a signed document in a response nobody reads it from.
| Field | Type | Description |
|---|---|---|
id | string | Required. The session id, which is what PATCH /v1/upload/{id} resumes against. |
asset_id | string | Required. The asset the bundle belongs to, so a client can group a bundle’s sessions. |
album_id | string | null | The album it is filed into, when it named one. |
blob_role | string | Required. This blob’s role in its bundle: original, derivative, metadata, provenance or backup. |
status | string | Required. Where the session is in its state machine. |
received_bytes | integer | Required. Bytes durably accepted so far — the offset a resume continues from. |
total_size | integer | Required. The declared total. |
created_at | string | Required. When the session was created, RFC 3339. |
last_progress_at | string | Required. When the last chunk was accepted, RFC 3339, or the creation time if none. What the survival floor is measured from, so a client can tell which of its stalled sessions is closest to being evicted. |
SessionsResponse
Section titled “SessionsResponse”The listing.
| Field | Type | Description |
|---|---|---|
sessions | SessionSummary[] | Required. Every session the caller can resume, oldest first, ties broken by upload id. |
WireBlobRole
Section titled “WireBlobRole”A blob’s role in its asset bundle, as the wire spells it.
A wire type of its own rather than a serde derive on [BlobRole]: the state ports’
records deliberately derive no serde traits, so that a record cannot be smuggled through a
store built for another. The mapping is one match in one direction.
One of: original, derivative, metadata, provenance, backup.