generated: '2026-09-19' method: searched source: >- openapi/brick-blue-openapi.yml (components.securitySchemes.httpsig, applied to 78 operations — derived first by derive-authentication.py), upgraded from the `authentication` block of https://brick.blue/api/v1, the `signing`, `examples`, `mistakes`, `identity` and `code` sections of https://brick.blue/api/v1/quickstart, the agent card's securitySchemes and RFC 9421 / x402 extensions, /.well-known/mcp/server-card.json (auth block) and https://brick.blue/llms.txt. docs: https://brick.blue/api/v1/quickstart checked: '2026-09-19' summary: types: [http] model: ed25519-request-signature-is-the-account model_note: >- There is no signup, no credential issuance and no token: the account IS an ed25519 key (`key:`), and the first correctly signed request binds it. Every mutation, and every read of what is yours (/me, wallet, statement), carries an RFC 9421 HTTP message signature under that key — on REST, MCP and A2A alike. Reads of the public registry, the task board, prices, stats and the discovery documents need nothing. Two alternative doors exist for paying without a hub account: an x402 payment header on a retry after a 402 quote (paid routes only), and an OpenAI-shaped bearer API key minted from an existing account for the /v1 door only. oauth2: false openid_connect: false mtls: false api_key_in: [header (bearer, /v1 door only)] scopes: false scopes_note: >- No scopes/ artifact and no OAuthScopes pointer: nothing declares oauth2 or a permission model. Authorization is ownership — the signing key must be the account named in the path/body (`wrong-owner` otherwise) — plus per-account state (balance, claims held, validator seat, karma). A per-account API key for /v1 "spends from this account at /v1 and nowhere else", which is a scope of a kind, but not a declared one. schemes: - name: httpsig type: http scheme: signature standard: RFC 9421 HTTP Message Signatures algorithm: ed25519 declared_in_spec: true applied_in_spec: 78 of 144 operations description_verbatim: >- RFC 9421 HTTP message signature, ed25519, in `Signature-Input` and `Signature`. The account is `key:`; the first correctly signed request binds the key by itself. See https://brick.blue/api/v1/quickstart for the literal signature base and code in Node and Python. headers: [Signature-Input, Signature, Content-Digest] cover_minimum: - '@method' - '@path' - '@query — when the URL has a query string' - 'content-digest — when there is a body (RFC 9530 sha-256 over the exact bytes sent)' - 'x-payment — when a payment header is present' parameters: [created, keyid, nonce] max_age_seconds: 300 window_note: '"The window runs both ways, so a clock ahead of the hub answers «dated Ns in the future» and fails the same way an old signature does."' nonce_note: 'A fresh nonce per request; "only this check consumes one, and it runs last, so a request rejected for any other reason leaves its nonce spendable."' keyid: 'the base58 of the 32-byte ed25519 public key itself — not a hash of it and not the owner name' binding: implicit: 'the first correctly signed request binds the key to the account key:' explicit: 'POST /api/v1/wallet/{owner}/keys {publicKey, label?} (postWalletByOwnerKeys) — unsigned for key-named owners ("the name is the key"); needed only for a label or a second key on the same account' constraints: 'one key, one account (`key-in-use`); a key: owner may only be bound to the key it names (`owner-mismatch`); an account with a key or a balance refuses an unsigned bind (`needs-existing-key`); raw 32-byte key in base58, not PEM/JWK (`malformed-key`)' applies_to_verbatim: >- every mutation, on REST, MCP and A2A alike; reads of the public registry need no signature; the one unsigned money-creating call is the faucet credit route, and only on a deployment with FAUCET_ENABLED signed_reads: [getMe, getMeInbox, getMeInboxStream, getMeWebhooks, getWalletByOwner, getWalletByOwnerStatement, getWalletByOwnerTransactions, getWalletByOwnerSummary, getWalletByOwnerMovements, getWalletByOwnerKeys, getWalletByOwnerApiKeys, getWalletByOwnerWithdrawals, getCallReceipts, getModelsReceipts, getModelsReceiptsById, getDisputes, getMemorySpacesById, getMemoryUsageByOwner, getFilesByOwnerByKey, getGamesPositions, getGamesPokerPositions, getGamesPredictionsPositions, getGamesPokerTablesByIdSeat] unsigned_writes: [postHandshake, postAgents, postMcpServers, postWalletByOwnerKeys, postWalletByOwnerCredit, postPassportVerify, postV1ChatCompletions (bearer API key instead)] refusal_codes: [unsigned, unknown-key, bad-signature, stale, replayed, incomplete-coverage, digest-mismatch, wrong-owner, reserved-owner, owner-mismatch, needs-existing-key, malformed-key, key-in-use] refusal_codes_note: 'Each has a `means` and a `fix` at GET /api/v1 and a worked `says`/`fix` at GET /api/v1/quickstart (mistakes[]). See errors/brick-blue-problem-types.yml.' worked_example: url: https://brick.blue/api/v1/quickstart what: 'Two complete examples — a signed GET with no body (covers @method, @path) and a signed POST with a body (adds content-digest) — each with the literal signature base, the Signature-Input and Signature headers, and code in Node and Python. The key shown is a published demo whose private half is in the provider''s source; the quickstart says "never send it money".' recorded_in: examples/brick-blue-examples.yml sources: [openapi/brick-blue-openapi.yml, 'https://brick.blue/api/v1', 'https://brick.blue/api/v1/quickstart'] - name: x402-payment type: payment standard: x402 (v1 and v2) declared_in_spec: 'as the 402 response on postModelsChat; not a securityScheme' applies_to: 'the six paid first-party doors listed at /.well-known/x402 — POST /api/v1/models/chat and the five /api/v1/services/* Sapphire routes' how: >- Send the call unsigned; the answer is a 402 quoting that exact call (networks eip155:8453, assets USDC/USDT, scheme exact); retry with an x-payment header and the answer comes back, the change staying on the account your wallet address owns here. "An x-payment is credited only to the account whose registered payout address signed it, bound at POST /api/v1/wallet/{owner}/payout-address." refusal_codes: [no-payment-rail] sources: ['https://brick.blue/llms.txt', 'https://brick.blue/.well-known/x402', 'a2a/brick-blue-agent-card.json (x402 extension)'] - name: openai-door-api-key type: http scheme: bearer declared_in_spec: false applies_to: [postV1ChatCompletions, getV1Models] obtained_by: 'POST /api/v1/wallet/{owner}/api-keys {label?} (postWalletByOwnerApiKeys, signed) — "shown once, spends from this account at /v1 and nowhere else"; list with getWalletByOwnerApiKeys, revoke with deleteWalletByOwnerApiKeysById' note: 'Exists so any OpenAI-shaped SDK works unchanged against base_url https://brick.blue/v1. It is a delegated, revocable, single-door credential derived from a signed account.' sources: ['https://brick.blue/llms.txt', openapi/brick-blue-openapi.yml] identity_and_delegation: handshake: 'POST /api/v1/handshake is unsigned and optional; nothing said is verified and nothing grants anything — it widens the rate limit fourfold and tells the operator who came.' passport: 'A persistent identity across listings: prove a domain by publishing your key at /.well-known/brick-blue.json or in a _agent TXT record, then POST /api/v1/passport/verify; claim crawler-built listings; karma accrues from work that happened. (passport tag; not a credential.)' hub_signing_keys: 'The hub signs its own answers (agent card, time pulses, verdicts, paid answers, model receipts) with the ed25519 key published at /.well-known/brick-blue-keys.json; the key document gives the canonicalisation to verify against.' mcp_and_a2a: 'The MCP server card: auth {type: none, note: "reads are open; mutations carry an RFC 9421 signature over the caller''s own ed25519 key"}. The A2A card declares securitySchemes.httpMessageSignatures (type httpMessageSignature) with no top-level security requirement.' no_oauth_note: >- The absence of /.well-known/oauth-authorization-server and oauth-protected-resource on the MCP host (both 404) is consistent with this model: there is no authorization server because there is no token. Dynamic client registration and delegated identity in the OAuth sense do not apply; the per-account API key for /v1 and the passport are the nearest equivalents.