Federation
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/federation/capabilities/refresh
Section titled “POST /v1/federation/capabilities/refresh”Exchange a capability for its successor.
The credential is the capability being refreshed; a session token has nothing to refresh here and is refused.
Authentication: required — bearer.
| Parameter | In | Type | Description |
|---|---|---|---|
X-Capsule-Crypto-Suite | header | integer | The crypto suite id from the primitives inventory. Sent on writes; a suite this server does not implement is refused with 400. |
X-Capsule-Protocol | header | string | Required. The YYYY-MM-DD protocol version this request is written against. Outside the server’s [X-Capsule-Protocol-Min, X-Capsule-Protocol-Max] window the request is refused with 426. |
X-Capsule-Sidecar-Schema | header | integer | The sidecar schema version declared at sidecar_schema field 0. Sent on metadata updates; a schema newer than this server indexes is refused with 400. |
| Status | Body | Description |
|---|---|---|
200 | RefreshedCapabilityResponse application/json | OK. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
400 | CodedProblem application/problem+json | Malformed handshake. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
401 | CodedProblem application/problem+json | Unauthorized. Headers: WWW-Authenticate, X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
403 | CodedProblem application/problem+json | Forbidden. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
409 | CodedProblem application/problem+json | Member not on roster. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
413 | — | the request body exceeds the configured limit. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
426 | CodedProblem application/problem+json | Protocol version unsupported. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
429 | CodedProblem application/problem+json | Rate budget exceeded. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
500 | CodedProblem application/problem+json | Internal server error. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
POST /v1/federation/reports
Section titled “POST /v1/federation/reports”File a signed moderation report from a peer server.
Its only reachable answer today is 403
Section titled “Its only reachable answer today is 403”A report is verified against the peer’s operator-pinned key, and nothing can pin one:
boot::assemble refuses the durable backend until #403 lands its
adapters, so an operator command that pinned a peer could only run against serve --memory
and would forget the moment it exited. The command is owed with #476. Until it lands this
operation answers 403 error.federation.peer_unknown to every real peer.
It is mounted anyway, deliberately: a peer implementing against the published contract needs the operation to exist and to answer honestly, and what is missing is the command, not the surface. What is not acceptable is a route that reads as protection it cannot provide — hence this paragraph, and the matching status notes in design/moderation.md and design/federation.md.
No bearer, and why that is not “unauthenticated”
Section titled “No bearer, and why that is not “unauthenticated””The reporting peer holds no capability here — it is reporting this server’s content, not
pulling it — so there is nothing to present. What it does hold is a key an operator has
pinned, and the report carries its own Ed25519 signature over the canonical CBOR of every
other field. A report from a server nobody has pinned is 403: intake is not the moment a
peer becomes trusted (design/federation.md’s TOFU is explicitly not done here).
The order the checks run in
Section titled “The order the checks run in”Bounds, then how much may be asked for at all, then who is speaking, then whether they are welcome, then whether they really said it, then whose account it is, then whether they have said it too often.
Every field is length-capped first, before a store is read or a byte is keyed on. Then
CounterKey::FederatedIntake — keyed on the
claimed origin, so it bounds one origin looping rather than a caller cycling origins, which
is the most this server can do without a trusted client address. Everything after it is a
store read and an Ed25519 verification, and this is the only place a bound on that work can
sit.
The policy budgets are charged last, after the signature verifies, so a third party
spoofing reporting_server cannot spend a real peer’s allowance. Two of them: the contract’s
per-(server, account) limit, and a per-peer ceiling that ignores the account, because
reported_user is a string the peer chooses and a peer cycling accounts would otherwise mint
itself a fresh allowance each time.
What is not bounded is bytes parsed per request: a per-operation body cap cannot be
expressed against this framework, and the reason is recorded on
MAX_FEDERATION_BODY_BYTES (issue #478).
What accepting one does
Section titled “What accepting one does”It writes a row an operator will read ([ModerationStore::pending_reports]) and nothing
else. A peer’s report is an input to a decision, never a decision: no standing changes, no
serving hold appears, and the reported account sees nothing — because nothing has been done
to them.
202 whether or not the account exists
Section titled “202 whether or not the account exists”A report naming an account this server does not host is accepted on the wire and dropped,
with a warn for the operator. It is not filed: an unresolvable report is a permanent orphan
row that nobody can act on, which is the reason the check exists at all.
The answer is deliberately the same one a filed report gets. An earlier version refused with a
distinct coded 404, and that manufactured an account-enumeration oracle out of a check that
did not need one: a pinned peer could walk identifiers and read existence off the status line.
“Pinned” is not “trusted with enumeration” — a peer key can be compromised, and a peer can be
adversarial toward its own users while remaining an operator’s legitimate partner — and this
codebase treats exists-versus-does-not as a first-order defect nearly everywhere else
([crate::routes::enroll]‘s indistinguishable code refusal, the album ceremonies’ “not yours
is not found”, [crate::serve::authority]‘s 404/403 boundary).
Probing is not free even so: every budget above is charged before this point is reached, so a
peer sweeping identifiers spends its allowance doing it and an operator sees the warn.
Authentication: none.
| Parameter | In | Type | Description |
|---|---|---|---|
X-Capsule-Crypto-Suite | header | integer | The crypto suite id from the primitives inventory. Sent on writes; a suite this server does not implement is refused with 400. |
X-Capsule-Protocol | header | string | Required. The YYYY-MM-DD protocol version this request is written against. Outside the server’s [X-Capsule-Protocol-Min, X-Capsule-Protocol-Max] window the request is refused with 426. |
X-Capsule-Sidecar-Schema | header | integer | The sidecar schema version declared at sidecar_schema field 0. Sent on metadata updates; a schema newer than this server indexes is refused with 400. |
Request body (required, application/json): FederatedReportRequest
| Status | Body | Description |
|---|---|---|
202 | FederatedReportResponse application/json | The report was accepted for review. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
400 | CodedProblem application/problem+json | Bad Request. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
401 | CodedProblem application/problem+json | Report unsigned. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
403 | CodedProblem application/problem+json | Peer unknown. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
413 | — | the request body exceeds the configured limit. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
415 | CodedProblem application/problem+json | Unsupported Media Type. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
422 | CodedProblem application/problem+json | Unprocessable Entity. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
426 | CodedProblem application/problem+json | Protocol version unsupported. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
429 | CodedProblem application/problem+json | Report rate limited. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
500 | CodedProblem application/problem+json | Internal server error. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min. |
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. |
FederatedReportRequest
Section titled “FederatedReportRequest”A moderation report one peer server files against an account on this one.
Every field except signature is covered by the signature, in canonical CBOR — see
ReportClaim.
| Field | Type | Description |
|---|---|---|
reporting_server | string | Required. The peer filing the report, as its own server-info names it. |
reported_user | string | Required. The account on this server the report is about. |
asset_hash | string | Required. The content address of the asset complained about. |
album_id | string | Required. The album it was pulled from. |
reason | string | null | A short reason, where the peer gives one. |
reported_at | string | Required. When the peer says it was reported, RFC 3339. |
signature | string | Required. The peer’s Ed25519 signature over the canonical CBOR of the fields above, base64. |
FederatedReportResponse
Section titled “FederatedReportResponse”An accepted report.
The identifier is this server’s, so an operator and the reporting peer can talk about one report. Nothing about the reported account is echoed — accepting a report says nothing about whether it is true, and a body that reported on the account’s standing would say it does.
| Field | Type | Description |
|---|---|---|
report_id | string | Required. This server’s identifier for the report. |
received_at | string | Required. When this server accepted it, RFC 3339. |
RefreshedCapabilityResponse
Section titled “RefreshedCapabilityResponse”A refreshed capability.
| Field | Type | Description |
|---|---|---|
token | string | Required. The successor token. |
jti | string | Required. Its identifier. |
expires_at | string | Required. When this token stops being honoured, RFC 3339. |
not_after | string | Required. When the whole grant dies, RFC 3339 — unchanged by this or any refresh. |
replayed | boolean | Required. Whether this call issued the successor, or answered one an earlier call already issued. Advisory. A peer never branches on it: both answers mean “here is the token to keep pulling with”. |