Skip to content
About
Main content

What a protocol must publish, and what protocols forbid

What a protocol must publish

Error semantics as part of the contract

The most important thing a protocol can tell a caller is whether retrying is safe. Without that, the caller must choose between under-retrying (losing work) and over-retrying (duplicating work or amplifying an outage). So the error taxonomy of a protocol must at least separate:

ClassMeaningCaller shouldExample
Retryable, transientThe problem may go away with HTTP 503 (unavailable), 429 (too many requests, often with a Retry-After header)
Terminal, deterministicThe same request will fail againDo not retry; fix the requestHTTP 400 (bad request), 404 (not found)
Some items worked, some did notHandle each itemA batch API that reports a result per item

Partial success must be explicit, not implicit. Where this is vague, every caller invents a rule of its own, usually by matching text in error messages, and the rules disagree. Retry logic and failure handling in every client depend on this: they cannot do their job if the interface will not say what happened.

Published limits as obligations

Rate limits, payload caps, result-size caps and timeouts. The enforcement lives in the server's flow-control and multi-tenancy machinery. But the publication is an interface artifact, and it changes behaviour. A documented limit lets the caller shape its traffic in advance. An undocumented limit is found by being throttled, usually at the worst moment. An unstated limit is an assumption the caller cannot verify, and it gets verified for them, under load, in production. A published limit also becomes a promise you can no longer quietly change, which is the real reason many limits are never published.

Versioning and deprecation

Schema compatibility inside one system is a different topic. This is the lifecycle of the interface itself: version numbers, , policy. Because no change at a boundary is (Principle A), every change needs an interval in which both contracts hold. So the real cost of a protocol change is:


Negotiation and expiring credentials

Content negotiation. Both parties announce what they can handle, and they settle on the intersection. For example, an HTTP client sends Accept: application/json and the server picks a format it supports. This is the only place here where agreement is reached at run time, rather than in advance (a ) or never (text ). It is a third option next to carried schemas and registry schemas. It matters because it is how a protocol stays compatible across versions without running everything twice. Its failure mode is distinctive: the intersection silently degrades to the weakest option both support, and nobody notices they are on the slow path. It sits next to versioning.

Authentication as an expiring, rate-limited dependency. Getting and refreshing a token is on the critical path of every integration, and it fails differently from everything else: not slowly, not partially, but as a cliff at a fixed time. The failure modes that appear nowhere else:

  • a token expires in the middle of a long transfer,
  • clock skew makes a valid token look invalid (a token that is "not yet valid" or "already expired" according to the other side's clock),
  • the authentication service itself is the throttled resource.

(How access models work is a separate topic. The operational fact that credentials expire during long operations belongs here.)


What protocols forbid

  • You cannot know whether a failed request happened.
  • You cannot have both caller-chosen response shapes and predictable server cost.
  • You cannot get semantic compatibility by copying a wire protocol.
  • You cannot push without accepting duplicates.
  • You cannot change a widely adopted interface atomically, and you cannot cheaply change one at all.

The escapes and their prices

EscapeWhat it buysWhat it costs
IdempotencyEscapes retry ambiguityA deduplication key and the state to remember it
Escapes server affinityRe-sent context and extra round trips
Declared schemasEscapes run-time type errorsBuild-time coupling
Escapes the 's pace-matchingDurable storage in the middle, and no interactivity
Compatibility layersEscape integration costA semantic gap that the user finds later
Published limitsEscape surprise throttlingThe limit becomes a promise you cannot quietly change