Skip to content

Federation

Stable

Generated 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.

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.

ParameterInTypeDescription
X-Capsule-Crypto-SuiteheaderintegerThe crypto suite id from the primitives inventory. Sent on writes; a suite this server does not implement is refused with 400.
X-Capsule-ProtocolheaderstringRequired. 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-SchemaheaderintegerThe sidecar schema version declared at sidecar_schema field 0. Sent on metadata updates; a schema newer than this server indexes is refused with 400.
StatusBodyDescription
200RefreshedCapabilityResponse
application/json
OK. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
400CodedProblem
application/problem+json
Malformed handshake. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
401CodedProblem
application/problem+json
Unauthorized. Headers: WWW-Authenticate, X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
403CodedProblem
application/problem+json
Forbidden. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
409CodedProblem
application/problem+json
Member not on roster. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
413the request body exceeds the configured limit. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
426CodedProblem
application/problem+json
Protocol version unsupported. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
429CodedProblem
application/problem+json
Rate budget exceeded. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
500CodedProblem
application/problem+json
Internal server error. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.

File a signed moderation report from a peer server.

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).

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).

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.

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.

ParameterInTypeDescription
X-Capsule-Crypto-SuiteheaderintegerThe crypto suite id from the primitives inventory. Sent on writes; a suite this server does not implement is refused with 400.
X-Capsule-ProtocolheaderstringRequired. 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-SchemaheaderintegerThe 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

StatusBodyDescription
202FederatedReportResponse
application/json
The report was accepted for review. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
400CodedProblem
application/problem+json
Bad Request. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
401CodedProblem
application/problem+json
Report unsigned. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
403CodedProblem
application/problem+json
Peer unknown. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
413the request body exceeds the configured limit. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
415CodedProblem
application/problem+json
Unsupported Media Type. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
422CodedProblem
application/problem+json
Unprocessable Entity. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
426CodedProblem
application/problem+json
Protocol version unsupported. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
429CodedProblem
application/problem+json
Report rate limited. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.
500CodedProblem
application/problem+json
Internal server error. Headers: X-Capsule-Min-Client-Build, X-Capsule-Protocol-Max, X-Capsule-Protocol-Min.

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.

An RFC 9457 problem detail.

FieldTypeDescription
typestringRequired.
titlestring
statusintegerRequired.
detailstring
instancestring
codestringRequired. The stable error.* catalog code. The client localizes this; detail stays English. Present on every problem this server renders.

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.

FieldTypeDescription
reporting_serverstringRequired. The peer filing the report, as its own server-info names it.
reported_userstringRequired. The account on this server the report is about.
asset_hashstringRequired. The content address of the asset complained about.
album_idstringRequired. The album it was pulled from.
reasonstring | nullA short reason, where the peer gives one.
reported_atstringRequired. When the peer says it was reported, RFC 3339.
signaturestringRequired. The peer’s Ed25519 signature over the canonical CBOR of the fields above, base64.

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.

FieldTypeDescription
report_idstringRequired. This server’s identifier for the report.
received_atstringRequired. When this server accepted it, RFC 3339.

A refreshed capability.

FieldTypeDescription
tokenstringRequired. The successor token.
jtistringRequired. Its identifier.
expires_atstringRequired. When this token stops being honoured, RFC 3339.
not_afterstringRequired. When the whole grant dies, RFC 3339 — unchanged by this or any refresh.
replayedbooleanRequired. 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”.