The wire API between a fukura client and a fukurahub server. Every endpoint is prefixed with /v1/. Breaking changes would move to /v2/.
HTTPS + JSON + UTF-8. All routes sit under /v1/. Today the hub serves routes under /api/; the /v1/ path is introduced in P3 Slice 3 alongside the existing routes for a compat window.
Bearer tokens over Authorization: Bearer <token>. Tokens are opaque to the client; servers decide the format and scopes. In this implementation we embed org_id in the claim (ADR 0004), so users can operate as themselves (personal) or as any organisation they belong to. Context is flipped via POST /api/auth/switch-context, which reissues the token. Write endpoints always require auth; a hub MAY expose anonymous reads for public.
private / org / public. private MUST stay client-local: clients reject it before the wire, servers respond 422 if one slips through. Today the hub’s enum is team; the rename to org is performed in Slice 4 as a 3-stage zero-downtime deploy.
Upload a single note. Body is a NoteEnvelope carrying the EKP ontology. Response on create:
201 Created
{
"object_id": "sha256:…",
"url": "https://hub.example.com/v1/notes/sha256:…",
"ontology": { "fingerprint": "sha256:…", "category": "cargo.compile.e0308" }
}Reposting the same body MUST return 200 OK with the existing object_id. Privacy=private → 422 Unprocessable Entity.
Fetch a note by content-addressable object_id. 200 OK with the envelope. 404 Not Found for unknown ids; 410 Gone for ids that were once valid but have been deleted (alignment D4a).
Query parameters: q, fingerprint, category (prefix with .*), repeated tag, repeated privacy, cursor, limit (1..100, default 20). Responses include an optional effectiveness block per hit when attempt data exists.
Batch upload of SolutionAttempt records. Shell hooks can emit high volumes; one-at-a-time would waste round-trips.
202 Accepted
{ "accepted": 42, "rejected": 0, "errors": [] }Reposting the same attempt_id MUST be a no-op and still count toward accepted.
Aggregate success / failure / abandoned counts per fingerprint. fingerprint optional; omit to get top-N most-attempted.
Unauthenticated. Returns { "status": "ok", "version": "...", "hub_id": "..." }. Clients use it for connectivity checks and to pin a specific hub_id.
Authenticated server policy advertisement: max_note_bytes, max_attempts_per_batch, rate_limit_per_minute, retained_privacy_tiers, server_time. Clients MUST honor these limits (SHOULD cache for up to 1 hour).
Uploads are naturally idempotent via the EKP fingerprint / object_id: reposting the same note returns 200 OK with the pre-existing id. Attempts dedup by attempt_id (UUID). Clients SHOULD include an Idempotency-Key on retries.
Opaque cursors. Clients MUST NOT parse, decode, or construct them. A response with next_cursor indicates more results are available; pass it back verbatim.
Per token per minute. On exceed: 429 Too Many Requests with Retry-After. Clients MUST honor Retry-After and SHOULD add exponential backoff on top (base 2s, cap 60s).
Single note body: default 256 KiB. Attempts batch: default 500. Total request body: ≥ 1 MiB accepted; > 8 MiB rejected with 413 Payload Too Large.
A conforming client: (1) redacts locally before sending; (2) rejects private pre-network; (3) parses info and honors limits; (4) retries 5xx / 429 with backoff. Item (5) is a SHOULD: include Idempotency-Key on retries.
Fukura v0.3 satisfies (1). Items (2)–(5) land in Task 3 for v0.4.
All error responses share:
{ "error": { "code": "note_too_large", "message": "...", "retryable": false } }Clients MUST switch on code, not message. Initial vocabulary: invalid_envelope, invalid_privacy, note_too_large, attempts_batch_too_large, unauthorized, forbidden, not_found, rate_limited, server_unavailable, internal_error.
Both sides ignore unknown fields. Servers MUST round-trip unknown EKP ontology fields — they were added in a newer producer and other consumers may need them. Clients treat unknown enum values as unknown.
Authoritative text: docs/fukurahub-api.md. Cross-repo status matrix: docs/implementation-status.md.