Blueprint 7draftv1.0.0
The cross-cutting contract of an API surface
The contract every operation on an API surface shares: operation taxonomy, request envelope, idempotent replay, state tokens, delegation chains, quota in two currencies, regional pinning, the request archive, and schema evolution — specified as day-one decisions.
Pull this in
https://zrz.io/zbp/7-cross-cutting-api-contract/v1.mdVersion-pinned. Latest tracks revisions; the pinned URL does not. Requirements are cited individually as ZBP-7-Rk, so an implementation can annotate and a review can check them one at a time.
TL;DR
This specifies the part of an API that is identical across every operation on it: how an operation is classified, what rides alongside the payload, what a retry means, how a caller learns the freshness of its own view, who is really making the call, what the call cost, which region answered it, what was recorded about it, and how any of it is allowed to change. It defines an operation taxonomy of four kinds; a request envelope carried in the transport’s metadata layer; idempotency keys that replay a stored response rather than merely suppressing a second execution; an opaque state token that makes read-your-writes a property of the protocol; a delegation chain rather than a single principal; quota denominated in both requests and work, admitted against an estimate and reconciled against a measurement; regional endpoints with failover as an explicitly new session; a request archive written off the critical path; and a schema that is introspectable, per-caller filtered, customer-extensible, and evolved by expand/migrate/contract.
Each of these is between free and a few days’ work before the surface has callers, and a coordinated migration across every client afterwards. That asymmetry — not any individual merit — is why they are specified together and as day-one decisions. The rationale is argued in ZFN-67Field Note · currentZFN-67 — The API I'd build todayWhat a new API needs before the first endpoint: operations kept apart, idempotency keys that replay the response, state tokens, DPoP, quota in requests and in work, regions, an archive. Cheap to decide before you have callers, a migration afterwards. Specified in ZBP-7.Why it's cited here: The argument this document specifies. It makes the case that these concerns share a cost curve and must be decided together on day one; this document is what to build.Open ZFN-67 →; this document is what to build.
Applicability
Use this when you are designing a new API surface intended for callers you do not control, or when you are retrofitting one of these concerns onto a surface that already has callers and cannot break them. It applies whether the transport is HTTP, gRPC, or a message protocol; the requirements are written against a generic envelope and mapped to concrete transports in Wire formats and interfaces.
Do not use this when the surface has exactly one first-party caller, deployed in lockstep with the server, with no external integrations and no independent retry behaviour. Most of this document exists because the caller is separately deployed, independently written, and cannot be fixed by you — and where that is not true, the machinery is overhead. Do not use it as guidance for designing an individual endpoint’s domain model, which it deliberately does not address.
This document assumes three others rather than restating them: ZBP-1Blueprint · v1.0.0ZBP-1 — DPoP-bound requests across an API surfaceYou are issuing or accepting access tokens over HTTP and want a stolen token to be useless without the holder's private key.Why it's cited here: The proof-of-possession mechanism this document assumes for every credential it discusses, rather than restating it.Open ZBP-1 → for proof-of-possession on credentials, ZBP-2Blueprint · v1.0.0ZBP-2 — A Security Token Service for workload identityServices in your platform need to authenticate to each other and you want every call attributable to a named workload holding no long-lived secret.Why it's cited here: The workload identity and token exchange machinery an internal hop uses to add its own assertion without discarding the caller's.Open ZBP-2 → for the workload identity an internal hop asserts with, and ZBP-3Blueprint · v1.0.0ZBP-3 — Tenant isolation in a multi-tenant systemOne system stores or serves data belonging to more than one customer, and one customer seeing another's data would be a serious incident.Why it's cited here: The tenant isolation this document assumes underneath it. The tenant named on a request here is an input to be verified against that context, never a value that establishes it.Open ZBP-3 → for the tenant context every requirement here operates inside.
Scope and non-goals
In scope. The operation taxonomy and its lifecycle; the envelope and its per-hop versus propagated split; idempotency and retry semantics; the error and retry-control contract; consistency tokens and pagination; the identity, delegation and authorization surface of a request; quota and cost accounting; cache declaration; regional partitioning and failover; degradation controls; the request archive and its relationship to the audit log; schema publication, per-tenant extension, versioning and client generation.
Out of scope, deliberately:
- Domain modelling. What an operation does, what its payload contains, and how its resources relate is the province of the team that owns it.
- Outbound delivery. Webhooks, push notifications, and callbacks are a separate subsystem with their own delivery guarantees, retry schedules, dead-lettering, signing, and subscription lifecycle. They are not the request surface with its arrow reversed, and the only requirement this document makes of them is a negative one: the request surface’s availability must not depend on theirs (ZFN-12Field Note · currentZFN-12 — Queues, topics, and journals are different tools — don't conflate themQueues (competing consumers), topics (fan-out), and journals (ordered, replayable logs) give different guarantees. Don't conflate them; a pipeline often uses several. Prefer journals over topics, but not where head-of-line blocking hurts. With queues, bound the concurrency.Why it's cited here: The distinction between a queue, a topic and a journal, which decides what the archive and the event surface each are.Open ZFN-12 →).
- Authentication ceremony. How a human or workload obtains a credential in the first place is ZBP-2Blueprint · v1.0.0ZBP-2 — A Security Token Service for workload identityServices in your platform need to authenticate to each other and you want every call attributable to a named workload holding no long-lived secret.Why it's cited here: The workload identity and token exchange machinery an internal hop uses to add its own assertion without discarding the caller's.Open ZBP-2 → and ZBP-4Blueprint · v1.0.0ZBP-4 — Authentication for a multi-tenant SaaS productYou are building sign-in for a product where people belong to customer organisations, and one person may belong to several.Why it's cited here: Where the credential presented to this surface comes from. Obtaining one is out of scope here; this document begins once it is presented.Open ZBP-4 →. This document begins once a credential is presented.
- Storage isolation. How tenant data is kept apart below the API is ZBP-3Blueprint · v1.0.0ZBP-3 — Tenant isolation in a multi-tenant systemOne system stores or serves data belonging to more than one customer, and one customer seeing another's data would be a serious incident.Why it's cited here: The tenant isolation this document assumes underneath it. The tenant named on a request here is an input to be verified against that context, never a value that establishes it.Open ZBP-3 →.
- Transport security. TLS configuration, certificate management, and network policy.
Vocabulary
These terms are used with exactly these meanings throughout. Where an existing standard defines a term, this document adopts that definition rather than a parallel one (ZFN-30Field Note · currentZFN-30 — Use the standard; don't reinvent the protocolWhen a standard exists for a common or complex problem, use it — don't reinvent the protocol. Standards encode huge adversarial expertise, especially in auth and crypto; a partial implementation beats rolling your own. You're not that special, and your problem isn't either.Why it's cited here: Why this document adopts existing header fields and token formats rather than minting private ones.Open ZFN-30 →).
- Surface — the complete set of operations exposed under one contract, versioned and evolved together.
- Operation — one named, invocable unit of the surface, of exactly one kind (below). Called an endpoint in HTTP and a method in RPC; operation is used throughout to avoid transport-specific connotations.
- Kind — one of query, mutation, subscription, or long-running operation (LRO).
- Payload — the domain data of a request or response.
- Envelope — metadata about a call rather than in it, carried in the transport’s metadata layer. Split into per-hop fields, meaningful only between two adjacent parties, and propagated fields, which travel the whole causal chain including asynchronous hops.
- Caller — the party issuing a request. Client — the software library through which it does so, generated from the schema.
- Principal chain — the ordered list of parties responsible for a call: the party on whose behalf it is made, and each party acting for them, in order.
- State token — an opaque, server-minted value describing how far through the system’s ordered history a caller’s view has reached.
- Idempotency key — a caller-minted value identifying one intent to perform a mutation, across every attempt at it.
- Idempotency record — the server-side association of a key with the request fingerprint and the stored response.
- Request fingerprint — a deterministic digest of the semantically significant parts of a request, used to detect a key reused with different content.
- Work unit — the declared unit in which computational cost is denominated and charged, distinct from a count of requests.
- Cost estimate — the a-priori upper bound on the work an operation will consume, computed before execution. Cost measurement — the a-posteriori actual, computed after.
- Region — a failure and residency domain that serves a disjoint partition of the surface’s state.
- Session — the period over which a caller’s state token, idempotency records, cursors, and operation identifiers remain meaningful. A session is scoped to exactly one region.
- Request archive — the durable, append-only record of requests and their outcomes, held outside the transactional store.
- Audit log — the customer-visible, retained, access-controlled record of who did what to what. It is not the request archive.
Normative keywords — MUST, MUST NOT, SHOULD, SHOULD NOT, MAY — are to be interpreted per RFC 8174RFC 8174 — Ambiguity of Uppercase vs Lowercase in RFC 2119 Key WordsClarifies that the RFC 2119 requirement keywords carry their special meaning only when capitalised, so that lowercase prose can use the same English words without being read as normative.rfc-editor.org ↗ and carry their normative meaning only when capitalised.
Architecture
The surface is a schema, a set of middleware that reads the envelope, and a set of handlers that see only payloads. Nothing in the handler layer is expected to implement any requirement in this document.
discovery (global, anycast)
│ returns regional endpoint
▼
caller ── generated client ──► regional ingress ──► middleware chain ──► handler
│ │ │
│ │ ├─ credential + proof (ZBP-1)
│ │ ├─ principal chain
│ │ ├─ tenant context (ZBP-3)
│ │ ├─ scope enforcement
│ │ ├─ quota admission (estimate)
│ │ ├─ idempotency lookup / replay
│ │ ├─ deadline budget
│ │ └─ visibility filter (serialisation)
│ │
│ └─────► request archive (buffered, off critical path)
│
└─ retry budget, key reuse, state token, region pinning
Four properties of this arrangement are what the requirements below protect.
The envelope is infrastructure’s data, not the handler’s. Everything the middleware chain reads — credential, principal chain, tenant, idempotency key, deadline, quota key, state token — rides the transport’s metadata layer, so an intermediary can route, admit, or reject on it without deserialising a domain object it has no schema for (ZFN-51Field Note · currentZFN-51 — Design the request envelope before the first endpointAuth, idempotency keys, trace IDs, vector clocks: context about a request, not part of it. Define an envelope alongside the payload in the schema on day one — transport-native, per-hop vs propagated, owned by generated SDKs and middleware. The retrofit is what costs you.Why it's cited here: Where the envelope concept comes from — context about a request rather than in it, given a home in the wire format before the first endpoint exists.Open ZFN-51 →).
The client is part of the implementation. Key reuse across retries, retry budgets, region pinning, state token echo, and deadline propagation are caller-side behaviours. A surface whose clients are hand-written in each language has as many dialects of this contract as it has clients (ZFN-14Field Note · currentZFN-14 — Define every API with a schema, and generate the clientsDefine every API with a machine-readable schema (OpenAPI, Protobuf, GraphQL) as the source of truth, and generate clients and server stubs from it — never hand-roll request-building and JSON parsing. Hand-written clients drift and break silently; check schema compatibility in CI.Why it's cited here: Why the contract is a schema that generates clients rather than a document humans implement, which is what makes every requirement here reachable by a caller.Open ZFN-14 →), which is why generation is normative here rather than a convenience.
Everything durable that is not the write itself is off the critical path. The request archive is a buffered append (ZFN-65Field Note · currentZFN-65 — Journal the write, apply it in micro-batchesIf nothing the caller does next depends on a write, it does not belong in the request path: append it to a durable ordered journal and apply it in micro-batches. The bill is ordering. Sequence comes from the journal, never the clock, and never two paths to one row.Why it's cited here: Why the archive is written as batched appends to a journal rather than a row per request.Open ZFN-65 →); the idempotency record is the one exception, because it is the write’s own receipt.
A front door may translate transports. Where an external HTTP surface maps onto an internal RPC one, that gateway is a legitimate place for protocol-specific concerns — content negotiation, header syntax, browser constraints — and an illegitimate place for business logic, because everything behind it then deploys through it.
Normative requirements
Operation taxonomy
- ZBP-7-R1 Every operation MUST be classified as exactly one of: query, mutation, subscription, or long-running operation. The classification MUST be declared in the schema and MUST be machine-readable, so that infrastructure can act on it without reading handler code.
- ZBP-7-R2 A query MUST be safe and repeatable in the sense of RFC 9110RFC 9110 — HTTP SemanticsThe core HTTP semantics specification, including the definitions of safe and idempotent methods, conditional requests, and the response status classes this document builds its operation taxonomy on.rfc-editor.org ↗: executing it any number of times MUST NOT produce a client-observable change of state. Counters, rate accounting, and access logging are not client-observable changes.
- ZBP-7-R3 A mutation MUST accept an idempotency key. A query MUST NOT require one.
- ZBP-7-R4 A subscription MUST declare its delivery guarantee (at-most-once, at-least-once, or exactly-once), its ordering guarantee, and its resumption semantics — including whether a resumption token exists and how long it remains valid.
- ZBP-7-R5 A mutation that cannot be guaranteed to complete within its deadline MUST be declared a long-running operation and MUST return an operation resource rather than blocking (AIP-151AIP-151 — Long-running operations (Google API Improvement Proposals)The design pattern for calls that cannot complete within one request: the method returns an operation resource immediately, which the client polls or waits on, and which carries the eventual response or error as part of its own state.google.aip.dev ↗).
- ZBP-7-R6 An operation resource MUST carry: a unique identifier, its current state, the time it was created, the time its state last changed, and — once in a terminal state — either its result or a structured error conforming to ZBP-7-R30 through ZBP-7-R32.
- ZBP-7-R7 Each long-running operation type MUST declare its own state machine in the schema: the set of named states, which transitions between them are legal, which states are terminal, and which terminal states are retryable. A single state enumeration MUST NOT be imposed across semantically different operation types.
- ZBP-7-R8 Every operation resource MUST additionally expose a coarse lifecycle common to all types — at minimum whether it is settled, and if settled whether it succeeded — so that a generic client can wait on any operation without understanding its domain states.
- ZBP-7-R9 The surface MUST offer a wait mechanism that does not require repeated polling: a server-pushed event stream, a long-poll bounded by the caller’s deadline, or an equivalent transport primitive. Polling MUST remain available as a fallback, and MUST NOT be the only mechanism offered.
- ZBP-7-R10 Progress events SHOULD be emitted over the wait mechanism, carrying the current phase and, where one is meaningful, a completion measure. A caller MUST NOT be required to consume progress events to observe the terminal outcome.
- ZBP-7-R11 The availability of the request surface MUST NOT depend on the availability of any outbound delivery subsystem. A failure to deliver a webhook, push notification, or callback MUST NOT fail, delay, or roll back the request that caused it.
The request envelope
- ZBP-7-R12 Envelope fields MUST be carried in the transport’s metadata layer — HTTP header fields, RPC metadata, or message attributes — and MUST NOT be carried in the payload.
- ZBP-7-R13 The envelope MUST be defined in the schema as a type distinct from any request or response message, and MUST be generated into every client and server stub.
- ZBP-7-R14 Every envelope field MUST be declared as either per-hop or propagated, and MUST be declared as either client-settable or server-only. An idempotency key MUST be per-hop. A trace identifier, the principal chain, and the tenant MUST be propagated.
- ZBP-7-R15 Every response MUST carry a request identifier, and every error response MUST carry the same identifier in its body as well as its envelope.
- ZBP-7-R16 The request identifier MUST be generated at ingress when the caller did not supply one, MUST be unique within the retention period of the request archive, and MUST be the key under which that request is retrievable under ZBP-7-R95.
- ZBP-7-R17 A caller MAY supply a deadline. When supplied, the server MUST NOT exceed it, MUST propagate the remaining budget to every downstream hop, and MUST fail with a distinct, classified error rather than returning a partial result on expiry.
- ZBP-7-R18 The envelope MUST carry structured client identity: the client name, its version, and its platform, as separate fields. A server MUST NOT require parsing a free-form user-agent string to obtain them.
- ZBP-7-R19 The response envelope MAY carry the support status of the calling client version, the current version, and whether upgrading is required or optional. Where the surface offers this, the generated client MUST expose it to the application rather than discarding it.
Idempotency and retry safety
- ZBP-7-R20 Every mutation MUST accept a caller-supplied idempotency key
(
Idempotency-KeyThe Idempotency-Key HTTP Header Field (IETF draft)Specifies an `Idempotency-Key` request header letting a client mark a mutating request with a unique key so servers can deduplicate retried requests, together with the expected server behaviour for concurrent, replayed, and mismatched-payload cases.datatracker.ietf.org ↗ on HTTP). - ZBP-7-R21 An idempotency key MUST be scoped to at least the principal chain and the operation. It MUST NOT be interpreted in a global namespace, where one caller’s key could collide with another’s.
- ZBP-7-R22 On first use of a key, the server MUST durably record an idempotency record before the response is returned, associating the key with the request fingerprint and the response.
- ZBP-7-R23 On reuse of a key with a matching fingerprint, the server MUST return the stored response — its status, its payload, and its state token — and MUST NOT re-execute the operation. Returning a conflict status instead of the stored response does not satisfy this requirement.
- ZBP-7-R24 The request fingerprint MUST be computed over the semantically significant parts of the request, MUST be deterministic across attempts, and MUST NOT include values that legitimately differ between attempts, such as the request identifier, timestamps, proof headers, or trace context.
- ZBP-7-R25 On reuse of a key whose fingerprint does not match the recorded one, the server MUST reject the request with a distinct, non-retryable error code, and MUST NOT execute it.
- ZBP-7-R26 When two requests carrying the same key arrive concurrently, at most one MUST execute. The others MUST either block until the first completes and then return its stored response, or fail with a distinct retryable “in progress” error. They MUST NOT execute.
- ZBP-7-R27 The surface MUST declare the retention period of idempotency records, and that period MUST exceed the maximum retry window of the generated client.
- ZBP-7-R28 For a long-running operation, an idempotency key MUST resolve to the same operation identifier on every attempt. A server MUST NOT replay a stream of results byte-for-byte; it MUST return the handle and allow the caller to re-attach.
- ZBP-7-R29 In a batch request, the idempotency key MUST apply per item, not per batch, so that a retry after a partial failure does not re-execute the items that already succeeded.
- ZBP-7-R30 A generated client MUST mint the idempotency key before its first attempt and MUST reuse the identical key for every retry of that intent. It MUST NOT generate a new key per attempt.
- ZBP-7-R31 A call that times out, is cancelled, or fails with an indeterminate transport error MUST be treated by the client as having an unknown outcome, and MUST be resolved by retrying with the same key rather than by assuming failure (ZFN-61Field Note · currentZFN-61 — Propagate the deadlineEvery request has a deadline whether you set one or not — the caller's patience. Make it explicit at the edge, carry it as remaining budget on every hop, check it before expensive steps, and cancel downstream when it dies. Work past the deadline is the fuel of cascading collapse.Why it's cited here: The deadline as a budget that every hop inherits, and why a cancelled call has an unknown rather than a failed outcome.Open ZFN-61 →).
Errors and retry control
- ZBP-7-R32 Every error the surface can return MUST be enumerated in the schema with a stable, machine-readable code. Codes MUST NOT be reused for a different meaning.
- ZBP-7-R33 Every error code MUST declare whether it is retryable, and MUST attribute the fault to the caller, the surface, or an upstream dependency.
- ZBP-7-R34 A client MUST NOT be required to parse human-readable message text to determine what happened. Message text MUST be free to change without notice.
- ZBP-7-R35 Error responses MUST carry structured, machine-readable parameters identifying what was wrong — the offending field, the exceeded limit, the conflicting identifier — rather than embedding them only in prose. On HTTP the response SHOULD use problem detailsRFC 9457 — Problem Details for HTTP APIsThe standard shape for machine-readable HTTP error responses: a `type` URI identifying the error class, `title`, `status`, `detail`, `instance`, and extension members. Obsoletes RFC 7807.rfc-editor.org ↗.
- ZBP-7-R36 A response that rejects a request for quota or overload reasons MUST carry a retry-after indication, and the client MUST honour it in preference to its own backoff schedule.
- ZBP-7-R37 A generated client MUST implement bounded exponential backoff with randomised jitter for retryable errors, and MUST NOT retry an error classified as non-retryable.
- ZBP-7-R38 A generated client MUST implement a retry budget expressed as a bounded ratio of retries to successful requests, and MUST stop retrying when the budget is exhausted, even where individual errors remain retryable. Per-request backoff alone MUST NOT be the only control (ZFN-13Field Note · currentZFN-13 — Fail fast and push back: retries, load shedding, and flow controlBuild client retries (backoff, jitter, Retry-After) from day one. Under overload, shed fast and push the failure back to the source to retry — don't retry internally and amplify it. Flow-control everywhere, bound every queue, and don't take more work than you can finish in time.Why it's cited here: Shedding and flow control, which is what a quota system degrades into when the limits are wrong or absent.Open ZFN-13 →).
Consistency, state tokens, and pagination
- ZBP-7-R39 Every response MUST carry a state token describing the position of the served view in the system’s ordered history.
- ZBP-7-R40 A generated client MUST retain the most recent state token it has observed and MUST present it on subsequent requests within the same session.
- ZBP-7-R41 When a request carries a state token, the server MUST serve a view at least as fresh as that token, or MUST fail with a distinct retryable error. It MUST NOT silently serve a staler view.
- ZBP-7-R42 The state token MUST be opaque. Its internal structure MUST NOT be documented as a contract, and clients MUST NOT parse, construct, or compare it other than by returning it verbatim.
- ZBP-7-R43 A state token MUST be scoped to the region that minted it, and MUST be rejected with a distinct error by any other region.
- ZBP-7-R44 Every operation returning an unbounded collection MUST paginate using an opaque, server-minted cursor. Numeric offsets and page numbers MUST NOT be the contract, because they encode an ordering guarantee a distributed store cannot keep across pages.
- ZBP-7-R45 A cursor MUST encode both position and the snapshot it is positioned within, so that concurrent writes cannot cause a page to be silently skipped or repeated.
- ZBP-7-R46 The surface MUST use one cursor dialect across every paginated operation.
Identity, delegation, and authorization
- ZBP-7-R47 Credentials accepted by the surface MUST be sender-constrained, bound to a key the caller holds, per ZBP-1Blueprint · v1.0.0ZBP-1 — DPoP-bound requests across an API surfaceYou are issuing or accepting access tokens over HTTP and want a stolen token to be useless without the holder's private key.Why it's cited here: The proof-of-possession mechanism this document assumes for every credential it discusses, rather than restating it.Open ZBP-1 →. A bearer credential accepted without proof of possession MUST NOT be the default for any operation that mutates state or reads customer data.
- ZBP-7-R48 More than one credential MUST be able to be valid for a principal at the same time, so that rotation can overlap. A surface that permits only one active credential per principal MUST NOT be considered conforming, because it makes rotation a flag-day cutover for the caller.
- ZBP-7-R49 The identity presented on a request MUST be able to express a principal chain rather
than a single subject, using the
actclaim of RFC 8693RFC 8693 — OAuth 2.0 Token ExchangeDefines exchanging one security token for another, and separates delegation from impersonation: an `act` claim naming the acting party in a chain, and a `may_act` claim stating who is permitted to act for whom.rfc-editor.org ↗. - ZBP-7-R50 Delegation, in which every party in the chain remains visible, MUST be the default representation. Impersonation, in which the acting party is not represented, MUST be explicitly marked where it is permitted at all, and MUST be recorded in the audit log with the acting party named.
- ZBP-7-R51 Authorization MUST be evaluated against the whole principal chain. A request MUST be denied unless every link in the chain is permitted to act as it claims, and the effective authority MUST NOT exceed the intersection of the authorities held by the chain.
- ZBP-7-R52 An internal hop MUST NOT discard the caller’s original credential when forwarding a request. Replacing it with a service credential makes every service beyond that point authorise the forwarding service rather than the caller.
- ZBP-7-R53 An internal hop that forwards a request MUST add its own assertion, naming itself and what it verified, over the preserved original, using the workload identity of ZBP-2Blueprint · v1.0.0ZBP-2 — A Security Token Service for workload identityServices in your platform need to authenticate to each other and you want every call attributable to a named workload holding no long-lived secret.Why it's cited here: The workload identity and token exchange machinery an internal hop uses to add its own assertion without discarding the caller's.Open ZBP-2 →.
- ZBP-7-R54 A service receiving a forwarded request MUST verify both the original credential and each forwarding assertion, and MUST NOT accept an unauthenticated header field as evidence of who the original caller was.
- ZBP-7-R55 Every scope MUST be declared in a machine-readable registry carrying: its identifier, a human description, what it grants, which other scopes it implies, which principals may grant it, and whether it is deprecated.
- ZBP-7-R56 Every operation MUST declare the scopes it requires, in the schema, and enforcement MUST be generated from that declaration rather than written per handler.
- ZBP-7-R57 Absent or unrecognised authority MUST deny.
- ZBP-7-R58 Field-level visibility MUST be declared in the schema, per field, and MUST be evaluated against the caller. The filter MUST be applied by one shared mechanism at serialisation, and MUST NOT be left to individual handlers.
- ZBP-7-R59 Schema introspection and generated documentation MUST apply the same visibility policy as data responses. A caller that cannot read a field MUST NOT be able to discover its existence through introspection or documentation.
- ZBP-7-R60 A request MUST name the tenant it intends to act within. That value MUST be verified against the tenant context established from authenticated material per ZBP-3-R3, and MUST NOT be used to establish it. A mismatch MUST deny.
Quota and cost
- ZBP-7-R61 The surface MUST enforce quota in two currencies: a rate of requests, and a measure of the work those requests consume. Request rate alone MUST NOT be the only limit, because it prices a trivial lookup and an expensive aggregation identically (ZFN-18Field Note · currentZFN-18 — Enforce a quota at ingress on every endpoint — even unabused onesPut a quota on every endpoint and enforce it at ingress from day one — per tenant, principal, IP — even for endpoints nobody abuses yet. Unlimited-by-default means the first runaway client or compromised key is an outage. Return 429 + Retry-After; retrofitting limits is painful.Why it's cited here: Why quota enforcement belongs at the edge rather than inside each service.Open ZFN-18 →).
- ZBP-7-R62 The work unit MUST be declared, documented, and stable. It MUST correspond to the resource that actually constrains the surface — processor time, rows examined, bytes returned, or a declared composite — and MUST NOT be redefined without a major version.
- ZBP-7-R63 Every operation MUST declare an upper bound on the work a single invocation may consume. An operation whose worst case is unbounded MUST NOT be exposed.
- ZBP-7-R64 Every operation returning a collection MUST declare and enforce a maximum page size, and every batch operation MUST declare and enforce a maximum batch size. A caller-supplied value above the maximum MUST be rejected or clamped, and the behaviour MUST be declared.
- ZBP-7-R65 An operation accepting caller-supplied filters, projections, or traversals MUST declare and enforce a complexity limit evaluated before execution.
- ZBP-7-R66 Admission control MUST evaluate a cost estimate computed before the operation executes. A limiter that can only act on measured cost MUST NOT be the sole control, because the work it would prevent has by then already been done.
- ZBP-7-R67 Actual cost MUST be measured and reconciled against the estimate. A caller whose estimates are systematically below measurement MUST be throttleable on that evidence.
- ZBP-7-R68 Every response MUST report the work its request consumed, in the declared work unit. On
HTTP this SHOULD use
Server-TimingServer Timing (W3C)Defines the `Server-Timing` response header field, letting a server report named metrics and durations for the work it did serving a request, and exposes them to the client through the browser performance APIs.w3.org ↗. This MUST be reported whether or not a work-based limit is currently enforced. - ZBP-7-R69 Quota state MUST be reported to the caller — the applicable policy, the remaining
allowance, and the window. On HTTP this SHOULD use the
RateLimitandRateLimit-PolicyfieldsRateLimit header fields for HTTP (IETF draft)Defines the `RateLimit` and `RateLimit-Policy` response header fields, expressing a server's quota policies and the caller's remaining quota as structured fields — including a quota-unit parameter, so a policy can be denominated in something other than requests.datatracker.ietf.org ↗, whose quota-unit parameter expresses a non-request currency. - ZBP-7-R70 Quota exhaustion MUST be distinguishable, by error code, from surface overload. The first is the caller’s own allowance and is retryable after a stated interval; the second is a shedding decision and carries different retry semantics.
- ZBP-7-R71 Every operation’s cost MUST be charged to a declared party in the principal chain, and which party bears it MUST be unambiguous. Work performed under delegation MUST NOT be chargeable to a party that cannot observe or limit it.
Caching
- ZBP-7-R72 Cacheability MUST be declared per operation in the schema: whether a response may be cached, keyed on what, and by what event it is invalidated.
- ZBP-7-R73 Correctness MUST NOT depend on a cache hit. The surface MUST serve correct results with every cache empty; a cold cache MUST be a latency event and MUST NOT be an availability event (ZFN-21Field Note · currentZFN-21 — Cache only immutable objects; treat caches as tech debtUse caches sparingly, only for immutable addressed objects — never for mutable DB results, where invalidation bugs and stale reads live; use projections instead. A cache in the data path is usually a patch over an architectural gap that trades correctness for performance.Why it's cited here: The discipline that keeps a cache from becoming an undurable database, which is why cacheability here is declared but never depended on.Open ZFN-21 →).
- ZBP-7-R74 The cold-cache path MUST be exercised deliberately and periodically, not discovered during an incident.
- ZBP-7-R75 A cache key MUST include every input that changes the response’s visible content — at minimum the tenant, per ZBP-3-R22, and any principal attribute that affects the field-visibility filter of ZBP-7-R58.
Regional partitioning and failover
- ZBP-7-R76 Each region MUST be addressed by its own hostname.
- ZBP-7-R77 A regional hostname MUST resolve to exactly one region. Requests to it MUST NOT be silently served by another region under any failure or capacity condition.
- ZBP-7-R78 The surface MUST provide a discovery operation that returns the regional endpoint a caller should use. That operation MAY be served from an anycast or globally-routed address, since it establishes the region rather than operating within one.
- ZBP-7-R79 Discovery MAY be piggybacked on authentication, so that the response conveying a credential also conveys the regional endpoint. Where it is, the generated client MUST use the returned endpoint for the remainder of the session.
- ZBP-7-R80 Every response MUST name the region that served it.
- ZBP-7-R81 A client that fails over to another region MUST treat the result as an entirely new session. Its state token, outstanding idempotency records, cursors, and operation identifiers MUST all be considered invalid, and MUST NOT be presented to the new region.
- ZBP-7-R82 A generated client MUST NOT carry session state across a region change, and MUST surface the failover to the application rather than performing it transparently.
- ZBP-7-R83 Data residency constraints MUST be enforced server-side. A surface MUST NOT rely on the client having chosen the correct region to keep data within a jurisdiction.
Degradation controls
- ZBP-7-R84 The surface MUST provide a control-plane mechanism to mark an operation, or a class of operations, as unavailable or restricted — by operation, by tenant, by caller class, or by cost.
- ZBP-7-R85 A restricted operation MUST return a specific, enumerated, correctly classified error. It MUST NOT be expressed as a timeout, a connection reset, or a generic internal error.
- ZBP-7-R86 Applying or removing a degradation control MUST be a configuration change and MUST NOT require a code deployment.
- ZBP-7-R87 Degradation controls SHOULD be exercised outside incidents, on a stated schedule, so that their behaviour and the procedure for using them are known before they are needed.
The request archive and the audit log
- ZBP-7-R88 Every request MUST be recorded with, at minimum: its request identifier, its envelope, the principal chain, the tenant, the operation, the outcome, the measured cost, and the region.
- ZBP-7-R89 Writing to the archive MUST NOT be on the critical path of the request. A failure, slowness, or unavailability of the archive MUST NOT fail, delay, or roll back the request.
- ZBP-7-R90 Archive writes MUST be buffered and committed in batches. A per-request synchronous insert MUST NOT be the write path (ZFN-65Field Note · currentZFN-65 — Journal the write, apply it in micro-batchesIf nothing the caller does next depends on a write, it does not belong in the request path: append it to a durable ordered journal and apply it in micro-batches. The bill is ordering. Sequence comes from the journal, never the clock, and never two paths to one row.Why it's cited here: Why the archive is written as batched appends to a journal rather than a row per request.Open ZFN-65 →).
- ZBP-7-R91 The archive MUST NOT be held in the transactional store that serves the surface. It is append-only, high-volume, and read far less often than written.
- ZBP-7-R92 The archive’s retention period MUST be declared and MUST be enforced automatically by a lifecycle policy rather than by a deletion job that can silently stop running.
- ZBP-7-R93 Fields declared sensitive in the schema MUST be redacted or tokenised at the point of writing to the archive, not at the point of reading from it.
- ZBP-7-R94 Erasure obligations MUST reach the archive. A surface that can delete a subject’s data from its transactional store but not from its archive MUST NOT be considered conforming (ZFN-57Field Note · currentZFN-57 — Deletion is a feature: design it on day oneA deleted_at column is not deletion. Real deletion is a workflow with an SLA: it must reach every replica, projection, index, cache, log, and backup — and you must prove it ran. Partition by owner, propagate tombstones on the event rails, crypto-shred what you can't rewrite.Why it's cited here: Why deletion is a designed capability, which is the obligation the request archive creates once it holds request bodies.Open ZFN-57 →).
- ZBP-7-R95 An archived request MUST be retrievable by its request identifier through an operation of the surface, subject to the same authorization and field-visibility rules as the original data.
- ZBP-7-R96 The audit log MUST be a distinct artifact from the request archive, with its own retention, its own access control, and its own schema. The two MUST NOT be the same store serving both purposes.
- ZBP-7-R97 An audit record MUST identify the full principal chain, the tenant, the operation, the affected resource, the outcome, and the time. Audit records MUST be retained for a declared period, MUST be access-controlled at least as tightly as the data they describe, and MUST NOT be sampled.
Schema, evolution, and clients
- ZBP-7-R98 The schema MUST be published in a machine-readable form covering operations and their kinds, types, error codes, required scopes, declared cost bounds, and cacheability.
- ZBP-7-R99 Every identifier exposed by the surface MUST be opaque and MUST carry a type prefix, so that its kind is determinable from the value alone.
- ZBP-7-R100 Identifiers MUST NOT be sequential or otherwise enumerable, and the storage key of a record MUST NOT be its public identifier (ZBP-3-R19Blueprint · v1.0.0ZBP-3 — Tenant isolation in a multi-tenant systemOne system stores or serves data belonging to more than one customer, and one customer seeing another's data would be a serious incident.Why it's cited here: The tenant isolation this document assumes underneath it. The tenant named on a request here is an input to be verified against that context, never a value that establishes it.Open ZBP-3 →).
- ZBP-7-R101 The identifier format MUST be treated as permanent. It MUST NOT be changed within a major version, and a surface SHOULD assume it can never be changed at all, because identifiers propagate into systems the surface’s operator does not control (ZFN-56Field Note · currentZFN-56 — IDs are an interface: prefix the type, randomise the bodyAn ID is read by more than your database: humans in logs, machines at boundaries, adversaries probing. Serve all three — a type prefix so IDs self-describe and misuse fails at parse time, a random body so nothing leaks or enumerates, time-ordered only when the index needs it.Why it's cited here: The argument for identifiers that carry their type and reveal nothing, and why the format can never be changed afterwards.Open ZFN-56 →).
- ZBP-7-R102 A generated client MUST tolerate unknown fields and unknown enumeration members, preserving them where it round-trips a value and never failing on their presence. A client that rejects what it does not recognise makes every additive server change a breaking one.
- ZBP-7-R103 Every change to the shape of a request, response, or stored record MUST follow expand, migrate, then contract as three separately deployable steps (ZFN-62Field Note · currentZFN-62 — Expand, migrate, contract: schema changes in three movesEvery deploy runs two code versions against one database — and rollback runs yesterday's code on today's schema. No schema change may break either. So every migration is three shippable moves: expand (additive), migrate (backfill, verify), contract (remove, later, deliberately).Why it's cited here: The three-phase mechanism every schema change here follows, and the reason a shape change is never one deploy.Open ZFN-62 →).
- ZBP-7-R104 A deprecated operation MUST be signalled in its responses with a deprecation date and,
once scheduled, the date it stops answering. On HTTP this MUST use
DeprecationRFC 9745 — The Deprecation HTTP Response Header FieldDefines a `Deprecation` response header carrying the date a resource became, or becomes, deprecated. Designed to be used alongside the `Sunset` header field.rfc-editor.org ↗ andSunsetRFC 8594 — The Sunset HTTP Header FieldDefines a `Sunset` response header field carrying the point in time at which a resource is expected to become unresponsive, so a client can detect a planned removal without out-of-band communication.rfc-editor.org ↗. - ZBP-7-R105 Deprecation MUST also be recorded in the schema and in generated documentation, so that a caller reading the contract sees the same facts as a caller reading a response.
- ZBP-7-R106 Usage telemetry MUST be available per operation and per client version before a deprecated operation is removed. Removal without evidence of who is still calling it MUST NOT occur.
- ZBP-7-R107 The schema mechanism MUST support per-tenant extension: fields and types defined by a customer MUST be declarable, validated against a declared type, introspectable through the same mechanism as first-party fields, and versioned.
- ZBP-7-R108 Customer-defined data MUST NOT be modelled as an unvalidated, untyped, unindexed opaque document as the only extension mechanism.
- ZBP-7-R109 Clients MUST be generated from the schema for every supported language. A hand-written client MUST NOT be the reference implementation of any requirement in this document.
- ZBP-7-R110 The generated client MUST implement, without application code: idempotency key minting and reuse (ZBP-7-R30), state token retention and echo (ZBP-7-R40), deadline propagation (ZBP-7-R17), retry classification and budget (ZBP-7-R37, ZBP-7-R38), retry-after honouring (ZBP-7-R36), region pinning (ZBP-7-R79), and failover as a new session (ZBP-7-R82).
- ZBP-7-R111 The surface MUST offer a test mode, selected by the credential presented rather than by a separate deployment, address, or schema.
- ZBP-7-R112 Every side effect reachable from the surface — quota accounting, outbound delivery, ledgers, exports, notifications to people, and every internal service a request reaches — MUST honour test mode. A side effect that cannot distinguish test from live MUST NOT be reachable from a test-mode credential.
- ZBP-7-R113 Test mode MUST allow a caller to deliberately provoke declared failure conditions — at minimum a chosen error code, a quota rejection, and a delayed or failed long-running operation — so that integration error handling can be exercised before it is needed.
Wire formats and interfaces
The requirements above are transport-neutral. This section binds them to concrete syntax. An implementation on a transport not covered here MUST provide an equivalent mapping and MUST document it.
Envelope fields on HTTP
| Field | Header | Direction | Scope | Requirement |
|---|---|---|---|---|
| Idempotency key | Idempotency-Key | request | per-hop | ZBP-7-R20 |
| Request identifier | X-Request-Id | both | per-hop | ZBP-7-R15 |
| Deadline | X-Deadline-Ms | request | propagated | ZBP-7-R17 |
| State token | X-State-Token | both | propagated | ZBP-7-R39 |
| Serving region | X-Region | response | per-hop | ZBP-7-R80 |
| Client identity | X-Client-Name, X-Client-Version, X-Client-Platform | request | per-hop | ZBP-7-R18 |
| Client support status | X-Client-Support | response | per-hop | ZBP-7-R19 |
| Tenant | X-Tenant | request | propagated | ZBP-7-R60 |
| Proof of possession | DPoP | request | per-hop | ZBP-1Blueprint · v1.0.0ZBP-1 — DPoP-bound requests across an API surfaceYou are issuing or accepting access tokens over HTTP and want a stolen token to be useless without the holder's private key.Why it's cited here: The proof-of-possession mechanism this document assumes for every credential it discusses, rather than restating it.Open ZBP-1 → |
| Trace context | traceparent, tracestate | both | propagated | — |
| Quota state | RateLimit, RateLimit-Policy | response | per-hop | ZBP-7-R69 |
| Measured cost | Server-Timing | response | per-hop | ZBP-7-R68 |
| Retry timing | Retry-After | response | per-hop | ZBP-7-R36 |
| Deprecation | Deprecation, Sunset | response | per-hop | ZBP-7-R104 |
The X- prefixed names above are placeholders for a surface’s own namespace; where a standard field
exists it MUST be used in preference to a private one. A surface MUST NOT mint a private field whose
meaning duplicates a registered one.
The operation resource
A long-running operation is represented as a resource carrying both its type-specific state and the common lifecycle of ZBP-7-R8:
{
"id": "op_01J9ZK4Q2M7X8V3B",
"type": "export.create",
"state": "transcoding",
"settled": false,
"succeeded": null,
"created_at": "2026-08-30T09:14:02Z",
"updated_at": "2026-08-30T09:14:19Z",
"progress": { "phase": "transcoding", "completed": 41, "total": 118 },
"result": null,
"error": null
}
state is drawn from the type’s own declared state machine (ZBP-7-R7). settled and succeeded are
the common lifecycle every type exposes, so a generic client can wait without understanding
transcoding. A settled operation has settled: true, succeeded either true or false, and
exactly one of result or error populated.
The type’s state machine is itself published in the schema:
{
"type": "export.create",
"initial": "queued",
"states": {
"queued": { "to": ["gathering", "cancelled"] },
"gathering": { "to": ["transcoding", "failed", "cancelled"] },
"transcoding": { "to": ["uploading", "failed", "cancelled"] },
"uploading": { "to": ["complete", "failed"] },
"complete": { "terminal": true, "succeeded": true },
"failed": { "terminal": true, "succeeded": false, "retryable": true },
"cancelled": { "terminal": true, "succeeded": false, "retryable": false }
}
}
Error responses
Errors use problem detailsRFC 9457 — Problem Details for HTTP APIsThe standard shape for machine-readable HTTP error responses: a `type` URI identifying the error class, `title`, `status`, `detail`, `instance`, and extension members. Obsoletes RFC 7807.rfc-editor.org ↗ with the extension members this document requires:
{
"type": "https://api.example.com/errors/quota-exceeded",
"title": "Work quota exceeded",
"status": 429,
"detail": "This request would consume 4200 work units; 1180 remain in the current window.",
"code": "quota_exceeded",
"retryable": true,
"fault": "client",
"request_id": "req_01J9ZK4Q2M7X8V3B",
"params": { "estimated_cost": 4200, "remaining": 1180, "window_seconds": 60 }
}
code, retryable, fault, request_id and params satisfy ZBP-7-R32 through ZBP-7-R35 and
ZBP-7-R15. title and detail are prose and MUST NOT be parsed (ZBP-7-R34).
Discovery
The discovery operation establishes the region for a session, and MAY be combined with the exchange that issues a credential (ZBP-7-R79):
{
"endpoint": "https://eu-west.api.example.com",
"region": "eu-west",
"session_ttl_seconds": 3600,
"alternates": [
{ "region": "us-east", "endpoint": "https://us-east.api.example.com" }
]
}
alternates exists so a client can fail over deliberately. Presenting a state token, cursor, or
idempotency key minted in eu-west to us-east is a new session under ZBP-7-R81, and the alternate
endpoint MUST reject those values rather than reinterpret them.
Envelope fields on gRPC
Every field in the HTTP table maps to a metadata key of the same name, lowercased, with binary values
suffixed -bin. Deadlines use the transport’s native mechanism rather than a metadata key, and its
value takes precedence where both are present. Trace context uses the standard traceparent and
tracestate keys.
Algorithms
Each algorithm is written as ordered steps with named outcomes. An implementation MUST produce the named outcome for the named condition; the ordering between steps is normative where a later step depends on an earlier one having rejected.
Handling a mutation with an idempotency key
- Validate the credential and proof per ZBP-1Blueprint · v1.0.0ZBP-1 — DPoP-bound requests across an API surfaceYou are issuing or accepting access tokens over HTTP and want a stolen token to be useless without the holder's private key.Why it's cited here: The proof-of-possession mechanism this document assumes for every credential it discusses, rather than restating it.Open ZBP-1 →. Outcome on failure:
unauthenticated. This precedes everything, so an unauthenticated caller can neither consume quota nor create records. - Establish the tenant context from authenticated material, then verify the tenant named on the
request against it (ZBP-7-R60). Mismatch outcome:
permission_denied. - Enforce scope for the operation (ZBP-7-R56). Outcome:
permission_denied. - Compute the request fingerprint over the canonical form defined in Test vectors (ZBP-7-R24).
- Look up the idempotency record for (principal chain, operation, key).
- No record: attempt to insert a record in state
in_progresswith the fingerprint, conditionally on no record existing. If the insert loses the race, restart this step. - Record exists in state
completewith a matching fingerprint: return the stored response verbatim (ZBP-7-R23). Stop. - Record exists with a differing fingerprint: outcome
idempotency_key_reused, non-retryable (ZBP-7-R25). Stop. - Record exists in state
in_progress: either wait for it to settle and then return its stored response, or returnidempotency_in_progress, retryable (ZBP-7-R26). Stop.
- No record: attempt to insert a record in state
- Estimate cost and admit against both quota currencies (ZBP-7-R61, ZBP-7-R66). On rejection,
mark the record
abandonedso the key is reusable, and returnquota_exceededwith a retry-after. - Execute the operation within the remaining deadline budget.
- Store the response — status, payload, and the state token of the resulting view — in the
idempotency record, and transition it to
complete. This MUST be durable before the response is returned (ZBP-7-R22). - Measure and reconcile cost, debit the actual, and attach the cost and quota headers (ZBP-7-R67, ZBP-7-R68, ZBP-7-R69).
- Enqueue the archive record on the buffered path (ZBP-7-R89), and return.
A failure between steps 7 and 8 is the case the whole mechanism exists for: the work may have
committed while the record says in_progress. The record MUST therefore be settled by the same
transaction as the write where the store permits it, and where it does not, a reconciliation process
MUST resolve records left in_progress beyond a bounded period by determining the write’s actual
outcome — never by assuming failure.
Quota admission
- Compute the estimate
Efor the request from its declared bounds and its caller-supplied parameters — page size, batch size, filter complexity. - If
Eexceeds the operation’s declared maximum (ZBP-7-R63), reject withrequest_too_large, non-retryable. The caller must ask for less, not ask again. - Read the caller’s remaining allowance
Afor the window in each currency. - If
E > Ain any currency, reject withquota_exceeded, retryable, with a retry-after computed from the window’s refill rate. - Provisionally debit
E. - Execute; measure actual cost
M. - Settle the debit to
M. WhereM > E, the excess MUST still be debited — the estimate bounds admission, not billing — and the discrepancy MUST be recorded for ZBP-7-R67.
Waiting on an operation
- The caller invokes the wait mechanism with the operation identifier and a deadline (ZBP-7-R9).
- The server streams progress events as the operation’s state advances (ZBP-7-R10).
- On reaching a terminal state, the server emits a final event carrying
settled: trueand the result or error, then closes the stream. - If the caller’s deadline expires first, the server closes the stream without a terminal event. This is not a failure of the operation, and the caller MUST re-attach rather than treat it as one.
- If the stream breaks, the caller re-attaches using the same operation identifier. Where the wait mechanism cannot be used at all, the caller polls the operation resource — the fallback of ZBP-7-R9, never the only path.
Region discovery and failover
- The client resolves the discovery address and obtains its regional endpoint (ZBP-7-R78), caching it for the returned session lifetime.
- All subsequent calls in the session go to that endpoint. The client MUST NOT re-resolve per request.
- On repeated failure of the regional endpoint, the client MAY select an alternate.
- Before issuing any request to the alternate, the client MUST discard its state token, cursors, operation identifiers, and outstanding idempotency keys (ZBP-7-R81).
- The client MUST surface the region change to the application (ZBP-7-R82). Work in flight at the moment of failover has an unknown outcome in the original region and MUST NOT be assumed failed; it is resolved when that region returns, using the original key.
Threat model and failure modes
The adversary assumed here is a caller — sometimes hostile, more often merely wrong. A caller with valid credentials retrying aggressively causes most of the damage this document prevents, and it does so without violating any rule the surface has stated.
Token replay. An access token observed in a log, a proxy, or a browser extension is replayable by whoever holds it. Mitigated by proof of possession (ZBP-7-R47, ZBP-1Blueprint · v1.0.0ZBP-1 — DPoP-bound requests across an API surfaceYou are issuing or accepting access tokens over HTTP and want a stolen token to be useless without the holder's private key.Why it's cited here: The proof-of-possession mechanism this document assumes for every credential it discusses, rather than restating it.Open ZBP-1 →). Residual risk: an attacker with the private key, which is why ZBP-7-R48 requires rotation to be possible without a flag day.
Confused deputy across tenants. A caller authenticated for one tenant induces the surface to act on another by supplying an identifier belonging to it. Mitigated by ZBP-7-R60 and the underlying ZBP-3-R3: the tenant on the request is verified, never trusted. Residual risk sits in operations that resolve an identifier before establishing context, which is why step 2 of the mutation algorithm precedes lookup.
Authority escalation through a forwarding hop. A service that replaces the caller’s credential with its own makes every downstream authorization decision about itself. Mitigated by ZBP-7-R52 through ZBP-7-R54. This is the failure that is invisible in testing, because the forwarded request succeeds — it simply succeeds for the wrong reason.
Existence disclosure through introspection. A schema that documents operator-only fields to every caller leaks the shape of internal tooling without returning any data. Mitigated by ZBP-7-R59. Field-level filtering applied only to data responses, and not to the schema, is the common half-measure.
Resource exhaustion within quota. A caller stays inside its request rate while consuming a shard, because the limit prices every request identically. Mitigated by the second currency and by pre-execution estimation (ZBP-7-R61, ZBP-7-R66). A limiter acting only on measured cost cannot prevent the request that exhausted the resource, only the next one.
Retry amplification. A partial failure multiplies every well-behaved client’s traffic by its retry factor, at the moment capacity is lowest, turning a degraded dependency into a total outage. Mitigated by retry budgets (ZBP-7-R38) and by honouring retry-after (ZBP-7-R36). This is a client-side control, which is why client generation is normative.
Duplicate execution under retry. A response lost in transit is indistinguishable from a request never processed. Mitigated by ZBP-7-R20 through ZBP-7-R31. The dangerous window is between execution and the durable idempotency record; the mutation algorithm names it and requires reconciliation rather than assumption.
Silent staleness after failover. A client moved between regions presents state minted elsewhere and receives an older view of the world, producing a data-consistency bug visible only during an incident. Mitigated by ZBP-7-R43 and ZBP-7-R81, which make the failure loud instead.
The archive as a disclosure surface. A store of every request body is a store of every secret a caller ever sent, retained long after the transactional record was deleted. Mitigated by write-time redaction (ZBP-7-R93), declared retention (ZBP-7-R92), erasure reach (ZBP-7-R94), and the same authorization on retrieval as on the original data (ZBP-7-R95).
Anti-patterns
If an implementation does any of the following, it has misread this document.
- Returning a conflict for a replayed idempotency key. The caller still does not know whether the work happened, which was the entire problem. See ZBP-7-R23.
- Generating a fresh idempotency key per attempt. Every retry is then a new intent, and the mechanism guarantees nothing. See ZBP-7-R30.
- Putting the state token, tenant, or trace context in the request body. It cannot be routed on at ingress without deserialising a domain object, and it will not survive a hop that lacks the schema. See ZBP-7-R12.
- Propagating the idempotency key downstream. Two genuinely distinct internal writes then collide on one key. See ZBP-7-R14.
- A single global operation state enumeration. Every operation type is flattened into
pending/running/done, and progress reporting loses the information that made it worth having. See ZBP-7-R7. - Polling as the only wait. Every client invents its own interval, and the quota system charges them for asking. See ZBP-7-R9.
- Rate limiting on requests alone. The expensive operation is priced identically to the trivial one. See ZBP-7-R61.
- Measuring cost without estimating it. The limiter can only ever punish work already performed. See ZBP-7-R66.
- Transparent cross-region failover. Correctness is traded for an availability number, and the resulting bug appears only under the conditions that make it hardest to diagnose. See ZBP-7-R81.
- A
metadatablob as the only extension mechanism. Customer data becomes unvalidated, unindexed, and permanently untyped. See ZBP-7-R108. - Test mode as a separate deployment. It drifts from production, and the code paths that matter are the ones that differ. See ZBP-7-R111.
- Rejecting unknown enumeration members. Every additive change becomes breaking, discovered from the callers who upgrade least often. See ZBP-7-R102.
- Writing the archive with a synchronous insert into the transactional store. The observability system becomes a source of latency and a cause of outages. See ZBP-7-R89, ZBP-7-R90.
Test vectors
These vectors are computed, not illustrative. Each canonical input is given as an exact byte string so the digest can be reproduced.
The request fingerprint
The fingerprint of ZBP-7-R24 is computed over the operation name, the tenant, and the canonical JSON encoding of the payload — keys sorted, no insignificant whitespace — each followed by U+000A. Fields that legitimately differ between attempts are excluded: the request identifier, timestamps, the proof header, and trace context.
Vector 1 — the canonical input.
payments.create\nten_7Q2XW9\n{"amount":2500,"currency":"usd","reference":"inv_884"}\n
sha256 = d387a19fab503058f90f73005976fdf4b8abdb6ea15d354dd5557b2b7216ce6d
Vector 2 — a retry of the same intent. The same request re-sent with a new request identifier, a
new DPoP proof, a new traceparent, and a later wall-clock time produces a byte-identical canonical
input, and therefore the identical digest. The idempotency record matches and the stored response is
replayed (ZBP-7-R23).
Vector 3 — the same key with a changed amount. amount becomes 9900:
payments.create\nten_7Q2XW9\n{"amount":9900,"currency":"usd","reference":"inv_884"}\n
sha256 = 4324a2874004663892d9831687b04823f6e6f8742d180190387ed7bc347d8c72
This differs from Vector 1, so the request MUST be rejected with idempotency_key_reused and MUST NOT
execute (ZBP-7-R25).
Vector 4 — the same key and payload under a different tenant. ten_ZZZZZZ in place of
ten_7Q2XW9:
sha256 = 66bcc0617e8c80c26c07ef0b19533b430a7bc9686a15f15e48e72b765404bf54
The digest differs, but this case MUST already have been rejected at step 2 of the mutation algorithm by ZBP-7-R60 and ZBP-3-R3. The vector exists to demonstrate that the fingerprint is not the tenant control — it is a change detector, not an authorization mechanism.
Quota admission
An operation declares a maximum of 5000 work units. The caller’s window holds 1180 remaining.
| Case | Estimate | Remaining | Outcome | Code |
|---|---|---|---|---|
| Page size 50 | 400 | 1180 | admitted, debit 400 | — |
| Page size 200 | 1600 | 1180 | rejected, retryable | quota_exceeded |
| Page size 1000 | 8000 | 1180 | rejected, non-retryable | request_too_large |
| Page size 50, actual 610 | 400 | 1180 | admitted, settled to 610 | — |
The third case is rejected before the allowance is consulted, because an estimate above the operation’s
declared maximum can never be admitted by waiting (ZBP-7-R63, step 2 of quota admission). The fourth
shows ZBP-7-R67: admission bounds on the estimate, settlement charges the measurement, and the +210
discrepancy is recorded.
The retry budget
A client with a budget of 20% of successful requests, over a window in which it has made 500 successful calls, may spend 100 retries.
| Retries spent | Next retryable error | Behaviour |
|---|---|---|
| 0–99 | any | retry after backoff and jitter |
| 100 | any | do not retry; surface the error |
| 100 | error carrying retry-after | do not retry; surface the error |
The budget binds even where individual errors remain retryable (ZBP-7-R38). This is what prevents a degraded dependency from being amplified into an outage by clients that are each behaving correctly.
Conformance checklist
An implementation conforms when every item below holds. Each cites the requirements it covers.
Operation taxonomy
- Every operation declares exactly one kind, machine-readably (ZBP-7-R1).
- Queries are safe and repeatable; mutations accept an idempotency key and queries do not require one (ZBP-7-R2, ZBP-7-R3).
- Subscriptions declare delivery, ordering, and resumption semantics (ZBP-7-R4).
- Operations that may exceed their deadline return an operation resource (ZBP-7-R5) carrying every required field (ZBP-7-R6).
- Each long-running operation type declares its own state machine (ZBP-7-R7), and every operation exposes the common settled/succeeded lifecycle (ZBP-7-R8).
- A wait mechanism exists that does not require polling, with polling retained as a fallback (ZBP-7-R9), and progress events are emitted over it (ZBP-7-R10).
- Request-surface availability does not depend on outbound delivery (ZBP-7-R11).
Envelope
- Envelope fields ride the transport metadata layer, never the payload (ZBP-7-R12), are defined in the schema and generated into clients and servers (ZBP-7-R13), and each is declared per-hop or propagated and client-settable or server-only (ZBP-7-R14).
- Every response and every error carries a request identifier (ZBP-7-R15), generated at ingress when absent, unique, and usable as the archive key (ZBP-7-R16).
- Supplied deadlines are honoured, propagated, and failed cleanly on expiry (ZBP-7-R17).
- Client identity is structured, requiring no user-agent parsing (ZBP-7-R18), and any support status returned is surfaced by the generated client (ZBP-7-R19).
Idempotency
- Every mutation accepts a key (ZBP-7-R20), scoped to at least the principal chain and operation (ZBP-7-R21).
- A durable record is written before the response is returned (ZBP-7-R22), and a matching replay returns the stored response rather than a conflict (ZBP-7-R23).
- The fingerprint is deterministic and excludes per-attempt values (ZBP-7-R24); a mismatch is rejected without executing (ZBP-7-R25).
- Concurrent use of one key executes at most once (ZBP-7-R26).
- Record retention is declared and exceeds the client’s retry window (ZBP-7-R27).
- A key resolves to the same operation identifier for a long-running operation, with no byte-wise stream replay (ZBP-7-R28), and applies per item within a batch (ZBP-7-R29).
- The generated client mints once and reuses across retries (ZBP-7-R30), treating timeouts and cancellations as unknown outcomes resolved by replay (ZBP-7-R31).
Errors and retry control
- Every error is enumerated with a stable code (ZBP-7-R32), classified for retryability and fault (ZBP-7-R33).
- No client behaviour depends on parsing prose (ZBP-7-R34); structured parameters are provided (ZBP-7-R35).
- Quota and overload responses carry a retry-after that clients honour in preference to their own backoff (ZBP-7-R36).
- The generated client implements bounded jittered backoff and never retries a non-retryable error (ZBP-7-R37), under a retry budget that can be exhausted (ZBP-7-R38).
Consistency and pagination
- Every response carries a state token (ZBP-7-R39) that the client retains and echoes (ZBP-7-R40).
- The server serves a view at least as fresh, or fails explicitly (ZBP-7-R41).
- The token is opaque and never parsed by clients (ZBP-7-R42), and is rejected outside its region (ZBP-7-R43).
- Collections paginate by opaque cursor, never by offset (ZBP-7-R44), encoding position and snapshot (ZBP-7-R45), in one dialect across the surface (ZBP-7-R46).
Identity, delegation, authorization
- Credentials are sender-constrained (ZBP-7-R47), and more than one may be valid at once so rotation can overlap (ZBP-7-R48).
- Identity expresses a principal chain (ZBP-7-R49); delegation is the default and impersonation is marked and audited (ZBP-7-R50); authorization evaluates the whole chain and never exceeds its intersection (ZBP-7-R51).
- Forwarding hops preserve the original credential (ZBP-7-R52), add their own assertion (ZBP-7-R53), and receivers verify both (ZBP-7-R54).
- Scopes are declared in a registry (ZBP-7-R55), required scopes are declared per operation with generated enforcement (ZBP-7-R56), and absent authority denies (ZBP-7-R57).
- Field visibility is declared and applied once at serialisation (ZBP-7-R58), and introspection and documentation honour the same policy (ZBP-7-R59).
- The request names its tenant, which is verified rather than trusted (ZBP-7-R60).
Quota and cost
- Quota is enforced in both requests and work (ZBP-7-R61), with a declared stable work unit (ZBP-7-R62).
- Every operation declares a work bound (ZBP-7-R63), a maximum page and batch size (ZBP-7-R64), and a complexity limit where it accepts caller-supplied shaping (ZBP-7-R65).
- Admission uses a pre-execution estimate (ZBP-7-R66), reconciled against measurement with systematic underestimation actionable (ZBP-7-R67).
- Every response reports measured cost (ZBP-7-R68) and quota state (ZBP-7-R69).
- Quota exhaustion is distinguishable from overload (ZBP-7-R70), and cost is charged to an unambiguous, observable party (ZBP-7-R71).
Caching
- Cacheability is declared per operation (ZBP-7-R72), correctness never depends on a hit (ZBP-7-R73), the cold path is exercised deliberately (ZBP-7-R74), and cache keys include every input affecting visible content (ZBP-7-R75).
Regions and failover
- Each region has its own hostname (ZBP-7-R76) resolving to exactly one region, never silently served elsewhere (ZBP-7-R77).
- A discovery operation returns the regional endpoint (ZBP-7-R78) and may accompany authentication, with the client honouring what it returns (ZBP-7-R79).
- Responses name the serving region (ZBP-7-R80).
- Failover discards all session state (ZBP-7-R81), is never transparent (ZBP-7-R82), and residency is enforced server-side (ZBP-7-R83).
Degradation
- Operations can be marked unavailable or restricted from the control plane (ZBP-7-R84), returning an enumerated classified error rather than a timeout (ZBP-7-R85), as configuration rather than a deployment (ZBP-7-R86), exercised on a schedule (ZBP-7-R87).
Archive and audit
- Every request is recorded with the required fields (ZBP-7-R88), off the critical path (ZBP-7-R89), in batched appends (ZBP-7-R90), outside the transactional store (ZBP-7-R91).
- Retention is declared and automatically enforced (ZBP-7-R92); sensitive fields are redacted at write time (ZBP-7-R93); erasure reaches the archive (ZBP-7-R94).
- Archived requests are retrievable by identifier under the same authorization as the original data (ZBP-7-R95).
- The audit log is a distinct artifact (ZBP-7-R96) with the required contents, retention, access control, and no sampling (ZBP-7-R97).
Schema, evolution, clients
- The schema is published machine-readably and completely (ZBP-7-R98).
- Identifiers are opaque and type-prefixed (ZBP-7-R99), non-enumerable and distinct from storage keys (ZBP-7-R100), and their format is treated as permanent (ZBP-7-R101).
- Clients tolerate unknown fields and enum members (ZBP-7-R102), and shape changes follow expand/migrate/contract (ZBP-7-R103).
- Deprecation is signalled in responses (ZBP-7-R104), recorded in schema and documentation (ZBP-7-R105), and supported by usage telemetry before removal (ZBP-7-R106).
- Per-tenant schema extension is declarable, validated, introspectable, and versioned (ZBP-7-R107), and is not merely an untyped blob (ZBP-7-R108).
- Clients are generated (ZBP-7-R109) and implement every listed caller-side behaviour (ZBP-7-R110).
- A credential-selected test mode exists (ZBP-7-R111), every reachable side effect honours it (ZBP-7-R112), and declared failure conditions can be provoked (ZBP-7-R113).
Rationale
The arguments are not repeated here; each lives in a Field Note.
The organising claim — that these concerns share a cost curve and therefore must be decided together, before the surface has callers — is ZFN-67Field Note · currentZFN-67 — The API I'd build todayWhat a new API needs before the first endpoint: operations kept apart, idempotency keys that replay the response, state tokens, DPoP, quota in requests and in work, regions, an archive. Cheap to decide before you have callers, a migration afterwards. Specified in ZBP-7.Why it's cited here: The argument this document specifies. It makes the case that these concerns share a cost curve and must be decided together on day one; this document is what to build.Open ZFN-67 →. The idea that this material belongs in a defined envelope rather than accreting into payloads is ZFN-51Field Note · currentZFN-51 — Design the request envelope before the first endpointAuth, idempotency keys, trace IDs, vector clocks: context about a request, not part of it. Define an envelope alongside the payload in the schema on day one — transport-native, per-hop vs propagated, owned by generated SDKs and middleware. The retrofit is what costs you.Why it's cited here: Where the envelope concept comes from — context about a request rather than in it, given a home in the wire format before the first endpoint exists.Open ZFN-51 →, and the reason the contract is a schema that generates clients is ZFN-14Field Note · currentZFN-14 — Define every API with a schema, and generate the clientsDefine every API with a machine-readable schema (OpenAPI, Protobuf, GraphQL) as the source of truth, and generate clients and server stubs from it — never hand-roll request-building and JSON parsing. Hand-written clients drift and break silently; check schema compatibility in CI.Why it's cited here: Why the contract is a schema that generates clients rather than a document humans implement, which is what makes every requirement here reachable by a caller.Open ZFN-14 → — without which every caller-side requirement here becomes a suggestion.
The state token comes from ZFN-25Field Note · currentZFN-25 — Track the version a client has seen for read-your-writesFor read-your-writes across backends, track the latest version a client has seen — a token or vector clock. Return it on write; reads then go to a backend at least that fresh. Hold it client-side (a token they present) or server-side (a gateway tracks the session and routes).Why it's cited here: The reasoning behind handing the client a token describing the freshness of its own view, rather than routing it to a primary.Open ZFN-25 →; the deadline as an inherited budget from ZFN-61Field Note · currentZFN-61 — Propagate the deadlineEvery request has a deadline whether you set one or not — the caller's patience. Make it explicit at the edge, carry it as remaining budget on every hop, check it before expensive steps, and cancel downstream when it dies. Work past the deadline is the fuel of cascading collapse.Why it's cited here: The deadline as a budget that every hop inherits, and why a cancelled call has an unknown rather than a failed outcome.Open ZFN-61 →; the enumerated, classified error surface from ZFN-58Field Note · currentZFN-58 — Errors are part of the contractError paths are the half of your API clients depend on most, and usually the half nobody designed. Enumerate error codes in the schema like any other type: stable code, retryable-or-not, whose fault, structured params. Machines branch on codes — anyone parsing prose is broken.Why it's cited here: Why the error surface is designed and enumerated rather than accreted, and why retry correctness depends on it.Open ZFN-58 →; the marking of read-only and idempotent operations from ZFN-19Field Note · currentZFN-19 — Annotate read-only and idempotent endpoints; make every mutation idempotentAnnotate every endpoint as read-only (safe) or idempotent, in the schema, so infrastructure can retry, route to replicas, and cache safely. Make every state-changing endpoint idempotent (idempotency keys for create/charge/send); a non-idempotent retry double-applies.Why it's cited here: The case for marking read-only and idempotent operations in the contract so infrastructure can act on the classification.Open ZFN-19 →. Quota at the edge is ZFN-18Field Note · currentZFN-18 — Enforce a quota at ingress on every endpoint — even unabused onesPut a quota on every endpoint and enforce it at ingress from day one — per tenant, principal, IP — even for endpoints nobody abuses yet. Unlimited-by-default means the first runaway client or compromised key is an outage. Return 429 + Retry-After; retrofitting limits is painful.Why it's cited here: Why quota enforcement belongs at the edge rather than inside each service.Open ZFN-18 →, what it degrades into is ZFN-13Field Note · currentZFN-13 — Fail fast and push back: retries, load shedding, and flow controlBuild client retries (backoff, jitter, Retry-After) from day one. Under overload, shed fast and push the failure back to the source to retry — don't retry internally and amplify it. Flow-control everywhere, bound every queue, and don't take more work than you can finish in time.Why it's cited here: Shedding and flow control, which is what a quota system degrades into when the limits are wrong or absent.Open ZFN-13 →, and the reasoning behind a second currency is ZFN-53Field Note · currentZFN-53 — Make abuse cost money: attack the unit economics, not the identityAbuse at scale is a business with a P&L. Detection is an arms race you eventually lose, because the attacker gets unlimited free queries against your classifier. Instead find the metered input they can't substitute away from, and inflate it — per attempt, dialled by risk.Why it's cited here: The reasoning behind pricing work rather than requests: making the expensive path expensive for the caller who chose it.Open ZFN-53 →. Cache discipline is ZFN-21Field Note · currentZFN-21 — Cache only immutable objects; treat caches as tech debtUse caches sparingly, only for immutable addressed objects — never for mutable DB results, where invalidation bugs and stale reads live; use projections instead. A cache in the data path is usually a patch over an architectural gap that trades correctness for performance.Why it's cited here: The discipline that keeps a cache from becoming an undurable database, which is why cacheability here is declared but never depended on.Open ZFN-21 →. Identifiers are ZFN-56Field Note · currentZFN-56 — IDs are an interface: prefix the type, randomise the bodyAn ID is read by more than your database: humans in logs, machines at boundaries, adversaries probing. Serve all three — a type prefix so IDs self-describe and misuse fails at parse time, a random body so nothing leaks or enumerates, time-ordered only when the index needs it.Why it's cited here: The argument for identifiers that carry their type and reveal nothing, and why the format can never be changed afterwards.Open ZFN-56 →. The archive’s write shape is ZFN-65Field Note · currentZFN-65 — Journal the write, apply it in micro-batchesIf nothing the caller does next depends on a write, it does not belong in the request path: append it to a durable ordered journal and apply it in micro-batches. The bill is ordering. Sequence comes from the journal, never the clock, and never two paths to one row.Why it's cited here: Why the archive is written as batched appends to a journal rather than a row per request.Open ZFN-65 → and its relationship to the surrounding messaging substrate is ZFN-12Field Note · currentZFN-12 — Queues, topics, and journals are different tools — don't conflate themQueues (competing consumers), topics (fan-out), and journals (ordered, replayable logs) give different guarantees. Don't conflate them; a pipeline often uses several. Prefer journals over topics, but not where head-of-line blocking hurts. With queues, bound the concurrency.Why it's cited here: The distinction between a queue, a topic and a journal, which decides what the archive and the event surface each are.Open ZFN-12 →; the obligation it creates is ZFN-57Field Note · currentZFN-57 — Deletion is a feature: design it on day oneA deleted_at column is not deletion. Real deletion is a workflow with an SLA: it must reach every replica, projection, index, cache, log, and backup — and you must prove it ran. Partition by owner, propagate tombstones on the event rails, crypto-shred what you can't rewrite.Why it's cited here: Why deletion is a designed capability, which is the obligation the request archive creates once it holds request bodies.Open ZFN-57 →. Schema change is ZFN-62Field Note · currentZFN-62 — Expand, migrate, contract: schema changes in three movesEvery deploy runs two code versions against one database — and rollback runs yesterday's code on today's schema. No schema change may break either. So every migration is three shippable moves: expand (additive), migrate (backfill, verify), contract (remove, later, deliberately).Why it's cited here: The three-phase mechanism every schema change here follows, and the reason a shape change is never one deploy.Open ZFN-62 →. The preference throughout for registered header fields and existing token formats over private ones is ZFN-30Field Note · currentZFN-30 — Use the standard; don't reinvent the protocolWhen a standard exists for a common or complex problem, use it — don't reinvent the protocol. Standards encode huge adversarial expertise, especially in auth and crypto; a partial implementation beats rolling your own. You're not that special, and your problem isn't either.Why it's cited here: Why this document adopts existing header fields and token formats rather than minting private ones.Open ZFN-30 →.
References
- ZBP-1Blueprint · v1.0.0ZBP-1 — DPoP-bound requests across an API surfaceYou are issuing or accepting access tokens over HTTP and want a stolen token to be useless without the holder's private key.Why it's cited here: The proof-of-possession mechanism this document assumes for every credential it discusses, rather than restating it.Open ZBP-1 → — proof of possession, assumed by ZBP-7-R47.
- ZBP-2Blueprint · v1.0.0ZBP-2 — A Security Token Service for workload identityServices in your platform need to authenticate to each other and you want every call attributable to a named workload holding no long-lived secret.Why it's cited here: The workload identity and token exchange machinery an internal hop uses to add its own assertion without discarding the caller's.Open ZBP-2 → — the workload identity a forwarding hop asserts with, assumed by ZBP-7-R53.
- ZBP-3Blueprint · v1.0.0ZBP-3 — Tenant isolation in a multi-tenant systemOne system stores or serves data belonging to more than one customer, and one customer seeing another's data would be a serious incident.Why it's cited here: The tenant isolation this document assumes underneath it. The tenant named on a request here is an input to be verified against that context, never a value that establishes it.Open ZBP-3 → — the tenant context ZBP-7-R60 verifies against.
- RFC 8174RFC 8174 — Ambiguity of Uppercase vs Lowercase in RFC 2119 Key WordsClarifies that the RFC 2119 requirement keywords carry their special meaning only when capitalised, so that lowercase prose can use the same English words without being read as normative.rfc-editor.org ↗ — the interpretation of the normative keywords used throughout.
- RFC 9110RFC 9110 — HTTP SemanticsThe core HTTP semantics specification, including the definitions of safe and idempotent methods, conditional requests, and the response status classes this document builds its operation taxonomy on.rfc-editor.org ↗ — safe and idempotent method semantics underlying the operation taxonomy.
- RFC 9449RFC 9449 — OAuth 2.0 Demonstrating Proof of Possession (DPoP)Defines a mechanism to bind an access token to a client-held key: the client sends a signed DPoP proof header on each request, so a stolen token cannot be replayed by a party that does not hold the corresponding private key.rfc-editor.org ↗ — DPoP.
- RFC 8693RFC 8693 — OAuth 2.0 Token ExchangeDefines exchanging one security token for another, and separates delegation from impersonation: an `act` claim naming the acting party in a chain, and a `may_act` claim stating who is permitted to act for whom.rfc-editor.org ↗ — token exchange, and the delegation-versus-impersonation distinction.
- RFC 9457RFC 9457 — Problem Details for HTTP APIsThe standard shape for machine-readable HTTP error responses: a `type` URI identifying the error class, `title`, `status`, `detail`, `instance`, and extension members. Obsoletes RFC 7807.rfc-editor.org ↗ — problem details, the error wire shape.
Idempotency-KeyThe Idempotency-Key HTTP Header Field (IETF draft)Specifies an `Idempotency-Key` request header letting a client mark a mutating request with a unique key so servers can deduplicate retried requests, together with the expected server behaviour for concurrent, replayed, and mismatched-payload cases.datatracker.ietf.org ↗ and RateLimit fieldsRateLimit header fields for HTTP (IETF draft)Defines the `RateLimit` and `RateLimit-Policy` response header fields, expressing a server's quota policies and the caller's remaining quota as structured fields — including a quota-unit parameter, so a policy can be denominated in something other than requests.datatracker.ietf.org ↗ — the IETF drafts adopted for idempotency and quota signalling.Server-TimingServer Timing (W3C)Defines the `Server-Timing` response header field, letting a server report named metrics and durations for the work it did serving a request, and exposes them to the client through the browser performance APIs.w3.org ↗ — the cost-reporting field.- RFC 9745RFC 9745 — The Deprecation HTTP Response Header FieldDefines a `Deprecation` response header carrying the date a resource became, or becomes, deprecated. Designed to be used alongside the `Sunset` header field.rfc-editor.org ↗ and RFC 8594RFC 8594 — The Sunset HTTP Header FieldDefines a `Sunset` response header field carrying the point in time at which a resource is expected to become unresponsive, so a client can detect a planned removal without out-of-band communication.rfc-editor.org ↗ — deprecation and sunset signalling.
- AIP-151AIP-151 — Long-running operations (Google API Improvement Proposals)The design pattern for calls that cannot complete within one request: the method returns an operation resource immediately, which the client polls or waits on, and which carries the eventual response or error as part of its own state.google.aip.dev ↗ — the long-running operation pattern.
Changelog
- 2026-08-30 (1.0.0): First published as a draft, deriving the normative specification from ZFN-67Field Note · currentZFN-67 — The API I'd build todayWhat a new API needs before the first endpoint: operations kept apart, idempotency keys that replay the response, state tokens, DPoP, quota in requests and in work, regions, an archive. Cheap to decide before you have callers, a migration afterwards. Specified in ZBP-7.Why it's cited here: The argument this document specifies. It makes the case that these concerns share a cost curve and must be decided together on day one; this document is what to build.Open ZFN-67 →.