nova

Reference

Nova HTTP API

The public-facing HTTP API surface of a Nova deployment.

Spec version
0.2.0-beta.1+ad629d7
OpenAPI
3.1.0
Operations
66 across 9 groups
Base URL
https://nova.thebugreport.dev
Generated
2026-09-11 10:30 UTC

Regenerated from docs/specs/openapi.yaml on Nova's main branch, so it follows the source rather than being kept in step by hand. Where it and the specification disagree, the specification wins. All documentation →

content-blob

Content Blob

Generic blob content routes (nova-storage).

GET/blob/{cid}

Retrieve the bytes of a blob by CID.

Returns the bytes verbatim with the stored Content-Type. For encrypted collections the bytes are plaintext (the gateway decrypts on the way out). For collections the operator marked public_archival: true, the bytes are stored unencrypted and the gateway streams them directly.

Cache-Control varies by collection visibility and blob state (v2 stratified policy):

collection state request Cache-Control
public active anonymous public, max-age=31536000, immutable
public-archival active anonymous public, max-age=31536000, immutable
unlisted active anonymous (CID is the capability) private, max-age=300, must-revalidate
private active signed URL / bearer private, max-age=300, must-revalidate
any soft_deleted/quarantined/tombstoned any no-store

Range requests are supported only on public-archival (plaintext) blobs. A Range request against an encrypted blob returns 416 Range Not Satisfiable. Streaming AEAD is a Phase 2 deliverable; until then, single-shot decryption cannot serve byte ranges.

Parameters

NameInTypeNotes
cidpath · requiredCid
RangeheaderstringHTTP/1.1 byte range, e.g., bytes=0-1023.

Responses

CodeMeaningTypeShape
200

Blob bytes.

application/octet-streamstring (binary)
206

Partial blob bytes (Range request).

application/octet-streamstring (binary)
401

The blob belongs to a private collection; provide a valid signed URL or a bearer token for an authorized user.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
410

The CID was deleted; the per-blob encryption key has been shredded and the gateway can no longer decrypt the bytes (which may still exist as ciphertext on volunteer disks).

application/jsonError
416

The Range header could not be satisfied: the first byte position is at or past the end of the object, or the range set is otherwise unsatisfiable. The response carries a Content-Range header naming the true length, so a client can correct itself without a second probe; HEAD on the same path reports the length as well.

A syntactically malformed Range is not an error. Per RFC 9110 it is ignored and the complete object is returned with 200.

application/jsonError
451

The CID is under moderation hold. Reads are suspended pending operator review.

Quarantine is reversible, and deliberately so: the bytes and the per-blob key both still exist, which is what makes a counter-notice meaningful. If the hold is lifted the same URL resolves again, unchanged, because the address is a hash of content that never moved. If it is not lifted, the blob is tombstoned when the window closes and this becomes a 410.

Signed URLs minted for the CID are revoked at the moment of quarantine, so an embedded copy stops rendering without waiting for its own expiry.

application/jsonError
HEAD/blob/{cid}

Probe a blob's existence and metadata via headers.

Parameters

NameInTypeNotes
cidpath · requiredCid

Responses

CodeMeaningTypeShape
200

Blob exists; metadata returned in headers.

404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
410

The CID was deleted; the per-blob encryption key has been shredded and the gateway can no longer decrypt the bytes (which may still exist as ciphertext on volunteer disks).

application/jsonError
GET/blob/{cid}.json

Public JSON metadata for a blob.

Parameters

NameInTypeNotes
cidpath · requiredCid

Responses

CodeMeaningTypeShape
200

Blob metadata.

application/jsonBlob
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
410

The CID was deleted; the per-blob encryption key has been shredded and the gateway can no longer decrypt the bytes (which may still exist as ciphertext on volunteer disks).

application/jsonError

content-image

Content Image

Image content with on-the-fly transforms (nova-image).

GET/i/{cid}

Retrieve an image at its original format.

Equivalent to /blob/{cid} but rejects with 415 if the underlying blob is not an image. Same caching headers.

Parameters

NameInTypeNotes
cidpath · requiredCid

Responses

CodeMeaningTypeShape
200

Image bytes.

image/jpeg, image/png, image/webp, image/avifstring (binary)
401

The blob belongs to a private collection; provide a valid signed URL or a bearer token for an authorized user.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
410

The CID was deleted; the per-blob encryption key has been shredded and the gateway can no longer decrypt the bytes (which may still exist as ciphertext on volunteer disks).

application/jsonError
415

The CID resolves, but the stored object is not an image and the image transform pipeline cannot act on it. Fetch it through /blob/{cid} instead, which serves any product type as-is.

application/jsonError
451

The CID is under moderation hold. Reads are suspended pending operator review.

Quarantine is reversible, and deliberately so: the bytes and the per-blob key both still exist, which is what makes a counter-notice meaningful. If the hold is lifted the same URL resolves again, unchanged, because the address is a hash of content that never moved. If it is not lifted, the blob is tombstoned when the window closes and this becomes a 410.

Signed URLs minted for the CID are revoked at the moment of quarantine, so an embedded copy stops rendering without waiting for its own expiry.

application/jsonError
HEAD/i/{cid}

Probe an image's existence and metadata via headers.

Parameters

NameInTypeNotes
cidpath · requiredCid

Responses

CodeMeaningTypeShape
200

Image exists.

404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
GET/i/{cid}.json

Public JSON metadata for an image (incl. perceptual hash).

Parameters

NameInTypeNotes
cidpath · requiredCid

Responses

CodeMeaningTypeShape
200

Image metadata.

application/jsonImage
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
415

The CID resolves, but the stored object is not an image, so there is no image metadata to return. Use /blob/{cid}.json for the product-agnostic metadata document.

application/jsonError
GET/i/{cid}.{ext}

Retrieve the image, transcoded if necessary.

Parameters

NameInTypeNotes
cidpath · requiredCid
extpath · requiredstringOutput format file extension. The gateway re-encodes if necessary. avif and jxl are operator-gated (off by default).

Responses

CodeMeaningTypeShape
200

Image bytes.

image/jpeg, image/png, image/webp, image/avifstring (binary)
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
406

The extension names a format this operator has not enabled. The enabled set is deployment configuration rather than a property of the stored image; Accept negotiation is not performed, so request one of the enabled extensions explicitly.

application/jsonError
410

The CID was deleted; the per-blob encryption key has been shredded and the gateway can no longer decrypt the bytes (which may still exist as ciphertext on volunteer disks).

application/jsonError
GET/i/{cid}/p/{preset}.{ext}

Retrieve the image rendered through a named operator preset.

Parameters

NameInTypeNotes
cidpath · requiredCid
presetpath · requiredstringOperator-defined preset name (e.g., thumb, og, hero).
extpath · requiredstringOutput format file extension. avif and jxl are operator-gated (off by default).

Responses

CodeMeaningTypeShape
200

Image bytes.

image/jpeg, image/png, image/webp, image/avifstring (binary)
404

Either no object with that CID is known here, or the named preset is not configured on this deployment. The two are deliberately not distinguished, so that probing preset names cannot be used to enumerate stored CIDs.

application/jsonError
406

The extension names a format this operator has not enabled. The enabled set is deployment configuration rather than a property of the stored image; Accept negotiation is not performed, so request one of the enabled extensions explicitly.

application/jsonError
GET/i/{cid}/w{w}.{ext}

Retrieve the image resized to a target width, aspect preserved.

Parameters

NameInTypeNotes
cidpath · requiredCid
wpath · requiredintegerTarget width in pixels (height computed to preserve aspect).
extpath · requiredstringOutput format file extension. avif and jxl are operator-gated (off by default).

Responses

CodeMeaningTypeShape
200

Image bytes.

image/jpeg, image/png, image/webp, image/avifstring (binary)
400

The requested dimensions are not on this operator's whitelist. Arbitrary sizes are refused deliberately: each distinct size is a distinct derivative to generate, store and replicate, so the whitelist is what stops a crawler minting unbounded work. Use a whitelisted size or a named preset.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
406

The extension names a format this operator has not enabled. The enabled set is deployment configuration rather than a property of the stored image; Accept negotiation is not performed, so request one of the enabled extensions explicitly.

application/jsonError
GET/i/{cid}/{w}x{h}.{ext}

Retrieve the image resized to fit a w x h box (cover crop).

Parameters

NameInTypeNotes
cidpath · requiredCid
wpath · requiredintegerTarget width in pixels (1..8192).
hpath · requiredintegerTarget height in pixels (1..8192).
extpath · requiredstringOutput format file extension. avif and jxl are operator-gated (off by default).

Responses

CodeMeaningTypeShape
200

Image bytes.

image/jpeg, image/png, image/webp, image/avifstring (binary)
400

The requested dimensions are not on this operator's whitelist. Arbitrary sizes are refused deliberately: each distinct size is a distinct derivative to generate, store and replicate, so the whitelist is what stops a crawler minting unbounded work. Use a whitelisted size or a named preset.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
406

The extension names a format this operator has not enabled. The enabled set is deployment configuration rather than a property of the stored image; Accept negotiation is not performed, so request one of the enabled extensions explicitly.

application/jsonError

upload

Upload

Resumable (tus.io) and multipart upload endpoints. The M12 browser widget (web/widget/) is the reference consumer of this surface; after the final tus PATCH, it calls POST .../finalize. When NOVA_WIDGET_DIST_DIR is set the coordinator also serves the widget bundle as static assets at /widget/* (a non-API surface, not described here).

POST/api/v1/blobs

Upload a generic blob via multipart/form-data.

Requires authentication.

Request body (required)

multipart/form-data

file: <binary contents of report.pdf>
product: document
collection_id: 31818242-97c3-4cdf-8025-44ffcb3d0242
alt_text: Q3 availability report
caption: Generated from the coordinator's own metrics.

Responses

CodeMeaningTypeShape
201

Blob created.

application/jsonUploadResult
400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
413

The declared or actual body length exceeds this operator's max_upload_size. The limit is enforced before the bytes are accepted, so nothing was stored and no upload session remains to resume. Split the object or ask the operator to raise the ceiling.

application/jsonError

Example response · 201

{
  "cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
  "byte_size": 204815,
  "mime_type": "image/jpeg",
  "product": "image",
  "urls": {
    "original": "https://nova.thebugreport.dev/i/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
    "json": "https://nova.thebugreport.dev/i/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi.json",
    "presets": {
      "thumb": "https://nova.thebugreport.dev/i/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/p/thumb.webp",
      "medium": "https://nova.thebugreport.dev/i/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/p/medium.webp"
    }
  }
}
POST/api/v1/images

Upload an image with image-specific validation.

Identical to POST /api/v1/blobs with product=image, but rejects non-image MIME types early and checks the CID against the operator-curated CID blocklist so the caller learns immediately if the upload was rejected on moderation grounds. The blocklist is a direct indexed lookup; no perceptual scan is performed at upload time (Phase-3 deferral).

Requires authentication.

Request body (required)

multipart/form-data

file: <binary contents of photo.jpg>
collection_id: 31818242-97c3-4cdf-8025-44ffcb3d0242
alt_text: A patch panel with sixteen cables terminated
caption: The rack as it stood in July.

Responses

CodeMeaningTypeShape
201

Image created.

application/jsonUploadResult
400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
415

The uploaded file's detected media type is not one this coordinator accepts as an image. Detection is by content sniffing rather than by the supplied filename or Content- Type, so renaming the file will not change the outcome. Upload it through POST /api/v1/blobs if you want it stored as a non-image product.

application/jsonError
451

The content hashes to a CID on this operator's blocklist and was refused at ingest. Because the address is derived from the bytes, an identical file will always be refused; nothing was stored.

application/jsonError

Example response · 201

{
  "cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
  "byte_size": 204815,
  "mime_type": "image/jpeg",
  "product": "image",
  "urls": {
    "original": "https://nova.thebugreport.dev/i/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
    "json": "https://nova.thebugreport.dev/i/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi.json",
    "presets": {
      "thumb": "https://nova.thebugreport.dev/i/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/p/thumb.webp",
      "medium": "https://nova.thebugreport.dev/i/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/p/medium.webp"
    }
  }
}
POST/api/v1/uploads

Create a resumable upload (tus).

Implements the tus.io resumable upload protocol v1.0.0. The response Location header points at the upload-specific endpoint that subsequent PATCH, HEAD, and DELETE requests target.

After the final PATCH completes, call POST .../finalize to commit the upload, compute the CID, and write the metadata row.

Requires authentication.

Parameters

NameInTypeNotes
Tus-Resumableheader · requiredstring
Upload-Lengthheader · requiredinteger (int64)
Upload-Metadataheaderstringtus metadata field. Nova recognizes:

Request body

application/offset+octet-stream

# tus creation-with-upload: the body is the first chunk of raw bytes.
# Length and metadata travel in headers, not in the body:
#   Upload-Length: 20481
#   Upload-Metadata: filename cGhvdG8uanBn,product aW1hZ2U=
#   Content-Type: application/offset+octet-stream
<20481 bytes of image/jpeg>

Responses

CodeMeaningTypeShape
201

Upload created.

400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
413

The declared or actual body length exceeds this operator's max_upload_size. The limit is enforced before the bytes are accepted, so nothing was stored and no upload session remains to resume. Split the object or ask the operator to raise the ceiling.

application/jsonError
PATCH/api/v1/uploads/{id}

Append a chunk to an in-progress upload (tus PATCH).

Requires authentication.

Parameters

NameInTypeNotes
idpath · requiredstring
Upload-Offsetheader · requiredinteger (int64)
Tus-Resumableheader · requiredstring
Content-Typeheader · requiredstring

Request body (required)

application/offset+octet-stream

# Each PATCH carries the next contiguous run of bytes. Upload-Offset must
# equal the server's current offset or the request is rejected with 409:
#   Upload-Offset: 16384
#   Content-Type: application/offset+octet-stream
<4097 bytes continuing the object>

Responses

CodeMeaningTypeShape
204

Chunk accepted.

404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
409

Upload-Offset does not match the offset the coordinator holds for this session, so the chunk would leave a gap or overwrite bytes already committed. Issue HEAD /api/v1/uploads/{id} to read the authoritative offset and resume from exactly there. This is the normal tus recovery path after a dropped connection, not an error condition in itself.

application/jsonError
DELETE/api/v1/uploads/{id}

Abandon an in-progress upload.

Requires authentication.

Parameters

NameInTypeNotes
idpath · requiredstring

Responses

CodeMeaningTypeShape
204

Upload deleted.

404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
HEAD/api/v1/uploads/{id}

Probe the current offset of an in-progress upload.

Requires authentication.

Parameters

NameInTypeNotes
idpath · requiredstring

Responses

CodeMeaningTypeShape
200

Offset and metadata returned in headers.

404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
POST/api/v1/uploads/{id}/finalize

Finalize an upload, compute its CID, and persist metadata.

Requires authentication.

Parameters

NameInTypeNotes
idpath · requiredstring

Responses

CodeMeaningTypeShape
200

Upload finalized.

application/jsonUploadResult
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
409

Finalisation was requested before every byte arrived: the session's offset is short of the declared Upload-Length. The session is untouched and still resumable — continue PATCHing from the current offset and call finalize again once they match.

application/jsonError

metadata

Metadata

Read and update blob/image metadata.

GET/api/v1/blobs/{cid}

Authenticated metadata read for a blob (any state; owner or operator/moderator).

Returns the blob row in ANY state (unlike the public read path, which rejects non-active states). Authorization: the blob's owner, or an operator/moderator. Mounted in M11 (M6-deferred).

Requires authentication.

Parameters

NameInTypeNotes
cidpath · requiredCid

Responses

CodeMeaningTypeShape
200

Blob metadata.

application/jsonBlob
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError

Example response · 200

{
  "cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
  "mime_type": "image/jpeg",
  "byte_size": 204815,
  "uploaded_at": "2026-07-29T11:04:22Z",
  "state": "active",
  "product": "image",
  "owner_id": "9a1d4e6f-2b77-4c1a-8f0e-5d3b2a7c9e01",
  "urls": {
    "bytes": "https://nova.thebugreport.dev/blob/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
    "json": "https://nova.thebugreport.dev/blob/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi.json"
  }
}
PATCH/api/v1/blobs/{cid}

Update mutable owner-controlled fields on a blob.

Requires authentication.

Parameters

NameInTypeNotes
cidpath · requiredCid

Request body (required)

application/json

{
  "alt_text": "Scanned page 14 of the 1987 service manual",
  "caption": "Section 3.2, power supply rework."
}

Responses

CodeMeaningTypeShape
200

Blob metadata updated.

application/jsonBlob
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError

Example response · 200

{
  "cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
  "mime_type": "image/jpeg",
  "byte_size": 204815,
  "uploaded_at": "2026-07-29T11:04:22Z",
  "state": "active",
  "product": "image",
  "owner_id": "9a1d4e6f-2b77-4c1a-8f0e-5d3b2a7c9e01",
  "urls": {
    "bytes": "https://nova.thebugreport.dev/blob/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
    "json": "https://nova.thebugreport.dev/blob/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi.json"
  }
}
DELETE/api/v1/blobs/{cid}

Soft-delete a blob (initiates crypto-shred and unpin broadcast).

The blob row enters state = 'soft_deleted' for the operator's configured grace window, then transitions to tombstoned. On tombstone the per-blob encryption key is zeroed and an unpin broadcast is sent across the federation. Bytes on volunteer disks may persist as unrecoverable ciphertext.

Requires authentication.

Parameters

NameInTypeNotes
cidpath · requiredCid

Responses

CodeMeaningTypeShape
204

Soft-delete accepted (owner or operator).

403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
409

The blob is not active (already soft-deleted, quarantined, or tombstoned).

application/jsonError
GET/api/v1/images/{cid}

Authenticated metadata read for an image.

Requires authentication.

Parameters

NameInTypeNotes
cidpath · requiredCid

Responses

CodeMeaningTypeShape
200

Image metadata.

application/jsonImage
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
PATCH/api/v1/images/{cid}

Update mutable owner-controlled fields on an image.

Requires authentication.

Parameters

NameInTypeNotes
cidpath · requiredCid

Request body (required)

application/json

{
  "alt_text": "A patch panel with sixteen cables terminated",
  "caption": "The rack as it stood in July."
}

Responses

CodeMeaningTypeShape
200

Image metadata updated.

application/jsonImage
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
DELETE/api/v1/images/{cid}

Soft-delete an image (alias for DELETE /api/v1/blobs/{cid}).

Requires authentication.

Parameters

NameInTypeNotes
cidpath · requiredCid

Responses

CodeMeaningTypeShape
204

Deletion accepted.

404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
GET/api/v1/users/me

Return the authenticated user's profile.

Requires authentication.

Responses

CodeMeaningTypeShape
200

Current user.

application/jsonUser
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError

collections

Collections

Curated collections of blobs and images.

GET/api/v1/collections

List collections owned by the caller (or public ones).

Requires authentication.

Parameters

NameInTypeNotes
pagequeryinteger
per_pagequeryinteger
visibilityquerystring

Responses

CodeMeaningTypeShape
200

Collection list.

application/jsonPaginatedCollections
POST/api/v1/collections

Create a collection.

Requires authentication.

Request body (required)

application/json

{
  "name": "Forum attachments",
  "slug": "forum-attachments",
  "visibility": "public"
}

Responses

CodeMeaningTypeShape
201

Collection created.

application/jsonCollection

Example response · 201

{
  "id": "31818242-97c3-4cdf-8025-44ffcb3d0242",
  "owner_id": "9a1d4e6f-2b77-4c1a-8f0e-5d3b2a7c9e01",
  "name": "Forum attachments",
  "slug": "forum-attachments",
  "visibility": "public",
  "created_at": "2026-07-28T09:12:00Z",
  "item_count": 1284
}
GET/api/v1/collections/{id}

Read a collection.

Parameters

NameInTypeNotes
idpath · requiredstring (uuid)

Responses

CodeMeaningTypeShape
200

Collection.

application/jsonCollection
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError

Example response · 200

{
  "id": "31818242-97c3-4cdf-8025-44ffcb3d0242",
  "owner_id": "9a1d4e6f-2b77-4c1a-8f0e-5d3b2a7c9e01",
  "name": "Forum attachments",
  "slug": "forum-attachments",
  "visibility": "public",
  "created_at": "2026-07-28T09:12:00Z",
  "item_count": 1284
}
PATCH/api/v1/collections/{id}

Update collection metadata.

Requires authentication.

Parameters

NameInTypeNotes
idpath · requiredstring (uuid)

Request body (required)

application/json

{
  "name": "Forum attachments (archived)",
  "visibility": "unlisted"
}

Responses

CodeMeaningTypeShape
200

Updated collection.

application/jsonCollection
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError

Example response · 200

{
  "id": "31818242-97c3-4cdf-8025-44ffcb3d0242",
  "owner_id": "9a1d4e6f-2b77-4c1a-8f0e-5d3b2a7c9e01",
  "name": "Forum attachments",
  "slug": "forum-attachments",
  "visibility": "public",
  "created_at": "2026-07-28T09:12:00Z",
  "item_count": 1284
}
DELETE/api/v1/collections/{id}

Delete a collection (does not delete its items).

Requires authentication.

Parameters

NameInTypeNotes
idpath · requiredstring (uuid)

Responses

CodeMeaningTypeShape
204

Collection deleted.

403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
POST/api/v1/collections/{id}/items

Add a blob to a collection.

Requires authentication.

Parameters

NameInTypeNotes
idpath · requiredstring (uuid)

Request body (required)

application/json

{
  "cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
  "position": 3
}

Responses

CodeMeaningTypeShape
201

Item added.

application/jsonCollectionItem
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError

Example response · 201

{
  "collection_id": "31818242-97c3-4cdf-8025-44ffcb3d0242",
  "cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
  "position": 3,
  "added_at": "2026-07-30T12:00:00Z"
}
DELETE/api/v1/collections/{id}/items/{cid}

Remove a blob from a collection.

Requires authentication.

Parameters

NameInTypeNotes
idpath · requiredstring (uuid)
cidpath · requiredCid

Responses

CodeMeaningTypeShape
204

Item removed.

404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError

admin

Admin

Operator and moderator endpoints (require role). Separately, the M13 first-run setup wizard exposes an ephemeral /setup/* surface (internal/api/handlers/setup.go): it is mounted only while the .bootstrap-complete sentinel is absent, binds loopback-only, and is removed permanently once the wizard commits — /setup/* returns 404 in normal operation. It is a bootstrap-only, non-API surface and is not described here.

GET/api/v1/admin/audit-log

List operator action audit-log entries (newest-first).

Paginated listing of audit_log rows. Optional filters: action (e.g. dmca.quarantined) and target_type (e.g. cid). The audit log is append-only and monthly-partitioned; rows are never deleted (recommended retention ≥ 7 years).

Requires authentication.

Parameters

NameInTypeNotes
pagequeryinteger
per_pagequeryinteger
actionquerystring
target_typequerystring

Responses

CodeMeaningTypeShape
200

Audit log entries.

application/jsonPaginatedAuditLogEntries
400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
GET/api/v1/admin/audits/integrity

List local integrity audit results.

Phase 1 local-fixity audits. See docs/specs/INTEGRITY_AUDIT.md.

Requires authentication.

Parameters

NameInTypeNotes
pagequeryinteger
per_pagequeryinteger
resultquerystring
audit_kindquerystring

Responses

CodeMeaningTypeShape
200

Integrity audit log entries.

application/jsonPaginatedIntegrityAudits
400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
GET/api/v1/admin/audits/possession

List donor possession spot-check results.

Phase 2 donor possession audits. See docs/specs/POSSESSION_AUDIT.md.

Requires authentication.

Parameters

NameInTypeNotes
pagequeryinteger
per_pagequeryinteger
resultquerystring
node_idquerystring (uuid)

Responses

CodeMeaningTypeShape
200

Pin audit log entries.

application/jsonPaginatedPossessionAudits
GET/api/v1/admin/blobs

List blobs operator-wide (any state), paginated and filterable.

The M11 admin blob-management listing. Optional filters: state, product, owner_id. The owner-facing detail view is GET /api/v1/blobs/{cid}. Requires operator or moderator.

Requires authentication.

Parameters

NameInTypeNotes
pagequeryinteger
per_pagequeryinteger
statequerystring
productquerystring
owner_idquerystring (uuid)

Responses

CodeMeaningTypeShape
200

Blob listing.

application/jsonPaginatedBlobs
400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
GET/api/v1/admin/dmca

List DMCA cases.

Requires authentication.

Parameters

NameInTypeNotes
pagequeryinteger
per_pagequeryinteger
statusquerystring

Responses

CodeMeaningTypeShape
200

DMCA cases.

application/jsonPaginatedDmcaCases
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
GET/api/v1/admin/dmca/{id}

Get a single DMCA case by id.

Requires authentication.

Parameters

NameInTypeNotes
idpath · requiredstring (uuid)

Responses

CodeMeaningTypeShape
200

DMCA case detail.

application/jsonDmcaCase
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
GET/api/v1/admin/federation/version-census

Fleet version census (operator-only).

What every donor reports it is running, classified on orthogonal axes against this coordinator's compiled-in release catalog.

Operator-only. The /admin group admits moderators because they run takedowns; this endpoint exposes the operator's supply-chain expectations and every donor's self-reported build identity, which is not moderation.

Axes are independent. A donor can be current, stale and digest-mismatched at the same time; one enum would force a precedence order onto facts that have none, and whichever fact lost would be the one the operator needed.

Reported values are untrusted claims. A process cannot discover its own OCI manifest digest without a Docker socket, which Nova gives neither coordinator nor doctor, so image_digest and bundle_lock_digest are configured declarations. They never gate protocol access, placement, durability counting, trust graduation, drain or eviction. A mismatch against what the operator authorized via novactl node rollout authorize is a supply-chain warning.

unknown is not unsupported. A donor that reports nothing is judged by negotiated protocol and capabilities, which is the actual interop contract. unavailable is different again: the applied schema cannot answer that axis (schema_aware: false, which is what a preflight run against schema 18 sees).

Rendered by the admin console in P2-M7.6.

Requires authentication.

Responses

CodeMeaningTypeShape
200

The census.

application/jsonobject
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
GET/api/v1/admin/jobs

List background-queue jobs (read-only), paginated and filterable.

The M11 jobs view — stuck / failed / recent work with last_error. Optional filters: state, kind. Read-only; requeue is a deliberate M11 non-goal. Requires operator or moderator.

Requires authentication.

Parameters

NameInTypeNotes
pagequeryinteger
per_pagequeryinteger
statequerystring
kindquerystring

Responses

CodeMeaningTypeShape
200

Job listing.

application/jsonPaginatedJobs
400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
POST/api/v1/admin/keys/rotate-master

Trigger a master-key rotation (operator-only).

Validates that to_version equals the currently-active master-key label, marks the from_version as rotating, and starts the background worker that re-wraps every DEK and non-shredded signing key from the old version to the new one via atomic, version-guarded UPDATEs. Rotation is online; reads work against either version during the drain.

Prerequisites. The operator must: 1. Deploy the new key to the secret mount (e.g. NOVA_MASTER_KEY_V2_FILE), keeping the old key present. 2. Set NOVA_MASTER_KEY_ACTIVE=<to_version> and restart the coordinator. 3. Only then call this endpoint.

to_version MUST equal the active label at the time of the call or the endpoint returns 400 to_not_active. See docs/specs/ENCRYPTION_ENVELOPE.md § "Rotation procedure".

Requires authentication.

Request body (required)

application/json

{
  "from_version": "v1",
  "to_version": "v2"
}

Responses

CodeMeaningTypeShape
202

Rotation accepted. The total_deks and total_signing_keys counts are the initial totals (returned once here); the CLI derives progress from initial − remaining by polling GET /api/v1/admin/keys/rotation-status.

application/jsonRotateMasterStarted
400

Validation failure. code is one of: - to_not_active: to_version is not the current active label (restart required). - invalid_from_version: from_version is not loaded, unknown, already retired, or equals to_version.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
409

A master-key rotation is already in progress. Only one may run at a time, because two concurrent rewrap passes over the same key material could leave envelopes wrapped under a version neither pass completes. Poll GET /api/v1/admin/keys/rotation-status and start the next rotation once this one settles.

application/jsonError
POST/api/v1/admin/keys/rotate-signing

Rotate the HMAC signing key used for signed URLs.

Requires authentication.

Request body

application/json

{
  "grace_seconds": 3600
}

Responses

CodeMeaningTypeShape
201

Key rotated; previous key remains valid until grace_expires_at.

application/jsonobject
GET/api/v1/admin/keys/rotation-status

Get master-key rotation progress (operator-only).

Returns the active master-key label, the current in-progress rotation (if any), and a per-version summary. When idle, in_progress is null.

The CLI (novactl keys status) uses this to poll until the rotation completes or stalls. A stalled rotation means the from key was removed before the drain finished; /readyz will also degrade.

Requires authentication.

Responses

CodeMeaningTypeShape
200

Current rotation status.

application/jsonRotationStatus
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
GET/api/v1/admin/moderation/blocklist

List operator-curated CID blocklist entries.

The Phase-1 blocklist is CID-based (operator-curated exact-CID deny). Perceptual-hash / visual re-upload prevention is a Phase-3 deferral.

Requires authentication.

Parameters

NameInTypeNotes
pagequeryinteger
per_pagequeryinteger

Responses

CodeMeaningTypeShape
200

Blocklist entries.

application/jsonPaginatedBlocklistEntries
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
POST/api/v1/admin/moderation/blocklist

Add a CID to the operator-curated blocklist.

Adds the exact CID to the deny registry. Enforced at both the read path (storage.Resolve451) and the import/commit path (storage.Put451). The Phase-1 blocklist is CID-based; perceptual-hash matching is Phase 3.

Requires authentication.

Request body (required)

application/json

{
  "cid": "bafybeihq2n7hpx6djvsxaqzfhbmi2ftfqkrgqcxk3xrvmlmemkwr4ldwsm",
  "reason": "Known-bad hash; refuse on ingest as well as on read."
}

Responses

CodeMeaningTypeShape
201

CID added to blocklist.

400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
DELETE/api/v1/admin/moderation/blocklist/{cid}

Remove a CID from the operator-curated blocklist.

Requires authentication.

Parameters

NameInTypeNotes
cidpath · requiredCid

Responses

CodeMeaningTypeShape
204

CID removed from blocklist.

401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
POST/api/v1/admin/moderation/clear-legal-hold

Clear the legal-hold flag on a CID so the next sweep tombstones it (operator-only).

Clears data_encryption_keys.legal_hold for the parent and all derivatives, sets scheduled_tombstone_at = now() on the originating quarantine decision, and writes an audit row. The ≈1-minute sweep will tombstone and shred on its next tick.

Operator role required — moderators receive 403.

Requires authentication.

Request body (required)

application/json

{
  "cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
  "case_ref": "DMCA-2026-0117",
  "reason": "Matter closed; hold released so the scheduled shred can proceed."
}

Responses

CodeMeaningTypeShape
200

Legal hold cleared; tombstone scheduled.

400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
POST/api/v1/admin/moderation/counter-notice

Record a counter-notice and clear the scheduled tombstone.

Clears scheduled_tombstone_at so the sweep will not auto-tombstone during the counter-notice review window. The blob remains quarantined; the operator later restores or lets a renewed schedule proceed to tombstone. Appends notes to the associated DMCA case.

Requires operator or moderator role.

Requires authentication.

Request body (required)

application/json

{
  "cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
  "notes": "Uploader supplied a licence; forwarded to the claimant 2026-07-28."
}

Responses

CodeMeaningTypeShape
200

Counter-notice recorded; tombstone schedule cleared.

400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
POST/api/v1/admin/moderation/quarantine

Quarantine a CID (blocks reads, preserves bytes for counter-notice window).

Sets the blob and its derivatives to quarantined, inserts a signed-URL revocation, and schedules a tombstone after tombstone_after (default 14 days). With legal_hold: true the DEK(s) are pinned (legal_hold = true in data_encryption_keys) and no scheduled_tombstone_at is set — crypto-shred is refused at the DB layer until POST /moderation/clear-legal-hold.

Requires operator or moderator role.

Requires authentication.

Request body (required)

application/json

{
  "cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
  "rule": "dmca",
  "case_id": "6c3f0a4e-6b1c-4a2f-9d55-1c0d9b7a2e11",
  "reason": "DMCA notice received; holding bytes for the counter-notice window.",
  "legal_hold": true
}

Responses

CodeMeaningTypeShape
200

CID quarantined.

application/jsonobject
400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
GET/api/v1/admin/moderation/queue

List pending moderation decisions.

Requires authentication.

Parameters

NameInTypeNotes
pagequeryinteger
per_pagequeryinteger

Responses

CodeMeaningTypeShape
200

Moderation queue.

application/jsonPaginatedModerationDecisions
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
POST/api/v1/admin/moderation/restore

Restore a quarantined CID to active (only valid from quarantined state).

Sets the blob and its derivatives back to active and clears scheduled_tombstone_at. The (cid, cid) signed-URL revocation is not removed (it is indistinguishable from a manual operator revocation; re-mint new signed URLs after restore if needed). Returns 409 if the blob is not in quarantined state.

Requires operator or moderator role.

Requires authentication.

Request body (required)

application/json

{
  "cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
  "reason": "Counter-notice accepted; quarantine lifted."
}

Responses

CodeMeaningTypeShape
200

CID restored to active.

400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
409

Restore applies only to a blob currently in quarantined. The target is in some other state — already active, or tombstoned, which is terminal because the key is gone. GET /api/v1/admin/blobs/{cid} reports the current state.

application/jsonError
POST/api/v1/admin/moderation/takedown

Immediate tombstone + crypto-shred of a CID (local Kubo unpin after commit).

Bypasses the counter-notice window: tombstones and shreds the DEK immediately. Returns 409 legal_hold if the DEK carries a legal-hold flag (use clear-legal-hold first). The local Kubo unpin is best-effort after commit; federation unpin broadcast is Phase 2.

Requires operator or moderator role.

Requires authentication.

Request body (required)

application/json

{
  "cid": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
  "case_id": "6c3f0a4e-6b1c-4a2f-9d55-1c0d9b7a2e11",
  "reason": "Uncontested DMCA notice; counter-notice window elapsed."
}

Responses

CodeMeaningTypeShape
200

CID tombstoned and DEK shredded.

400

The request could not be parsed or failed validation before any work was attempted: a malformed CID, a body that is not valid JSON, a missing required field, or a parameter outside its permitted range. The code field names the specific failure and details carries the offending field where one can be identified. Retrying the identical request will fail identically; fix the request first.

application/jsonError
401

No usable credential was presented. Either the Authorization header was absent, or the bearer token was malformed, expired, or signed by a key this coordinator no longer trusts. Obtain a new access token from POST /api/v1/auth/refresh and retry once; a second 401 means the refresh token is also spent and the client must log in again.

Note the difference from 403: a 401 says the coordinator does not know who you are, not that you may not do this.

application/jsonError
403

The caller was identified but is not permitted to perform this action. Three distinct situations produce it, distinguishable by code:

  • the account's role does not carry the required permission (management routes are operator- or moderator-only);
  • the object belongs to another owner and the caller is not an operator;
  • a signed URL was presented and failed verification — expired, bound to a different aud, signed by a revoked kid, or tampered with. Those return a signature_* code.

Re-authenticating does not help. The credential is valid; the action is not permitted for it.

application/jsonError
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
409

The blob's data encryption key carries a legal hold, and the database refuses to shred a held key. This is the intended interlock: it stops a takedown destroying evidence that a live matter requires. Clear the hold with POST /api/v1/admin/moderation/clear-legal-hold first, then retry.

application/jsonError
GET/api/v1/admin/nodes

List federated pinning nodes.

Requires authentication.

Responses

CodeMeaningTypeShape
200

Node list.

application/jsonobject
GET/api/v1/admin/nodes/{id}

Requires authentication.

Parameters

NameInTypeNotes
idpath · requiredstring (uuid)

Responses

CodeMeaningTypeShape
200

Node detail.

application/jsonNode
404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
POST/api/v1/admin/nodes/{id}/revoke

Mark a node compromised; revoke its cert and re-replicate its pins.

The poison-pill protocol from FEDERATION_PROTOCOL.md.

On revocation the coordinator (a) revokes the node's Nebula certificate, (b) tombstones the node's pin assignments, (c) emits the federation.node_revoked webhook, and (d) enqueues re-replication for any CIDs whose factor dropped below target.

Requires authentication.

Parameters

NameInTypeNotes
idpath · requiredstring (uuid)

Request body

application/json

{
  "reason": "Host reported compromised by its operator on 2026-07-29."
}

Responses

CodeMeaningTypeShape
202

Revocation accepted; healing dispatched asynchronously.

404

No object with that identifier is known to this coordinator. For content routes this means the CID has never been ingested here — content addressing makes it a statement about this federation, not about the bytes, so the same CID may well resolve on another one.

Distinguish from 410, which means the object was here and has been deleted, and from 403, which means it exists but is not yours to read.

application/jsonError
GET/api/v1/admin/pins

List pin assignments (filterable).

Requires authentication.

Parameters

NameInTypeNotes
pagequeryinteger
per_pagequeryinteger
statequerystring
node_idquerystring (uuid)
cidqueryCid

Responses

CodeMeaningTypeShape
200

Pin assignments.

application/jsonPaginatedPinAssignments
POST/api/v1/admin/signed-urls/revoke

Revoke signed URLs by structured (kind, value) tuple.

v2 revocation is structured, not prefix-based. See docs/specs/SIGNED_URL_FORMAT.md § "Revocation".

Requires authentication.

Request body (required)

application/json

{
  "kind": "aud",
  "value": "https://forum.example"
}

Responses

CodeMeaningTypeShape
201

Revocation recorded (idempotent on the (kind, value) pair).

application/jsonobject
400

Unknown kind (must be cid, aud, kid, or path_prefix) or empty value.

POST/api/v1/admin/signed-urls/sign

Mint a signed URL for a content path.

Server-side minting: the unwrapped HMAC signing key never leaves the coordinator, so signed URLs cannot be minted client-side. The URL is signed with the current active signing key, bound to aud, and valid for ttl_seconds (clamped to the configured maximum). The matching read endpoints (/blob/{cid}, /i/{cid}/...) verify it per docs/specs/SIGNED_URL_FORMAT.md; a failed verification returns 403 with a signature_* code.

Requires authentication.

Request body (required)

application/json

{
  "path": "/i/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/640x480.webp",
  "ttl_seconds": 86400,
  "aud": "https://forum.example"
}

Responses

CodeMeaningTypeShape
201

Signed URL minted.

application/jsonobject
400

The signing request was rejected before a URL was minted. One of: path is not a content route (only /blob/{cid} and /i/{cid}/... may be signed); aud is not a bare origin of the form scheme://host[:port] — a path or a wildcard will be refused; or ttl_seconds is zero or negative. No signing key was touched.

GET/api/v1/auth/config

Unauthenticated auth-mode discovery document for clients (SPA/CLI).

Always present in both modes. In local mode returns {mode: local}. In external mode returns the IdP issuer_url (and client_id/scopes) so the client can initiate the OIDC authorization-code + PKCE flow itself.

Responses

CodeMeaningTypeShape
200

Auth discovery document.

application/jsonAuthConfig
GET/api/v1/auth/jwks.json

JSON Web Key Set for verifying local-issuer access tokens.

Responses

CodeMeaningTypeShape
200

JWKS (OKP/Ed25519 signing key).

application/jsonJwks
404

This coordinator is configured to delegate authentication to an external OIDC provider, so its own token issuer is switched off and the route does not exist. Obtain a token from the configured provider instead; GET /api/v1/auth/config reports which mode is active.

POST/api/v1/auth/login

Exchange username + password for an access + refresh token pair (local issuer).

Request body (required)

application/json

{
  "username": "operator",
  "password": "correct-horse-battery-staple"
}

Responses

CodeMeaningTypeShape
200

Token pair.

application/jsonTokenResponse
401

Authentication failed. The response is deliberately identical for an unknown username and a wrong password so the endpoint cannot be used to enumerate accounts, and it is rate-limited per address. Repeated failures will begin returning 503 before they return anything more informative.

application/jsonError
404

This coordinator is configured to delegate authentication to an external OIDC provider, so its own token issuer is switched off and the route does not exist. Obtain a token from the configured provider instead; GET /api/v1/auth/config reports which mode is active.

application/jsonError
503

The login path is shedding load: either the per-address rate limit tripped or the bounded password-hashing pool is saturated. This is back-pressure rather than a failure — honour Retry-After where it is present and retry with backoff. Credentials were not evaluated.

application/jsonError

Example response · 200

{
  "access_token": "eyJhbGciOiJFZERTQSIsImtpZCI6InNpZy0yMDI2LTA3In0...",
  "refresh_token": "v1.MEQCIB3q...c2VjcmV0.9f2b1a7c",
  "token_type": "bearer",
  "expires_in": 900,
  "kid": "sig-2026-07"
}
POST/api/v1/auth/logout

Revoke a refresh token.

Request body (required)

application/json

{
  "refresh_token": "v1.MEQCIB3q...c2VjcmV0.9f2b1a7c"
}

Responses

CodeMeaningTypeShape
204

Logged out (idempotent).

404

This coordinator is configured to delegate authentication to an external OIDC provider, so its own token issuer is switched off and the route does not exist. Obtain a token from the configured provider instead; GET /api/v1/auth/config reports which mode is active.

POST/api/v1/auth/refresh

Rotate a refresh token for a new pair (single-use; reuse revokes the family).

Request body (required)

application/json

{
  "refresh_token": "v1.MEQCIB3q...c2VjcmV0.9f2b1a7c"
}

Responses

CodeMeaningTypeShape
200

New token pair.

application/jsonTokenResponse
401

The refresh token was rejected. It was expired, already revoked, or — the case worth knowing about — presented a second time. Refresh tokens are single-use, and replay is treated as evidence of theft: the whole token family is revoked, which logs out every session descended from that login. Authenticate again with credentials.

application/jsonError
404

This coordinator is configured to delegate authentication to an external OIDC provider, so its own token issuer is switched off and the route does not exist. Obtain a token from the configured provider instead; GET /api/v1/auth/config reports which mode is active.

Example response · 200

{
  "access_token": "eyJhbGciOiJFZERTQSIsImtpZCI6InNpZy0yMDI2LTA3In0...",
  "refresh_token": "v1.MEQCIFp7...c2VjcmV0.31d8c04a",
  "token_type": "bearer",
  "expires_in": 900,
  "kid": "sig-2026-07"
}

health

Health

Liveness probe.

GET/health

Liveness probe.

Always returns 200 if the coordinator is accepting traffic.

Responses

CodeMeaningTypeShape
200

Healthy.

application/jsonHealth

Example response · 200

{
  "status": "ok",
  "version": "0.2.0-beta.1",
  "time": "2026-07-30T12:00:00Z"
}