Yeda AI Tips · #173

Español

Ship a Stable, Machine-Readable Error Code

Your error messages are a secret API. Callers regex them, then you cannot change them.

You wrote "User not found" to help a human debugging in a log. But some client couldn't branch on your errors any other way, so they wrote if "not found" in err.message. Now that sentence is load-bearing. Fix a typo, add context, translate it, and their code breaks in production. You signed a contract you never meant to sign.

Error text is an accidental contract

This is Hyrum's Law again: anything callers can observe, some caller will depend on. Error strings are the most seductive version of it because they look like an internal detail. They are not. If a message is the only machine-distinguishable signal you emit, integrators will parse it, and prose is the worst possible interface — unstable, localized, and untyped.

Ship a code they switch on

Separate the two audiences. Give machines a stable, enumerated code. Give humans a message that stays free to change.

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "You've sent 1001 requests this minute; the limit is 1000.",
    "retryable": true,
    "retry_after_ms": 4200
  }
}

The code is the contract: a closed vocabulary of stable symbols callers can branch on. The message is documentation for humans — reword it, add detail, localize it, and no integration breaks. Never make callers parse text to make a control-flow decision.

Define codes as an enum in one place so they're discoverable and hard to typo:

class ErrorCode(str, Enum):
    RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED"
    INVALID_ARGUMENT    = "INVALID_ARGUMENT"
    NOT_FOUND           = "NOT_FOUND"
    INTERNAL            = "INTERNAL"

Mark what is retry-safe

Callers also need to know whether retrying is safe, and prose won't tell them. Encode it. The HTTP convention is a good default: 4xx means the request itself is wrong — do not retry, fixing it requires a change. 5xx means a server-side failure — a retry may succeed. Make it explicit with a retryable boolean and, when relevant, a retry_after hint so clients back off instead of hammering you.

ClassMeaningRetry?
4xx (except 408/429)Client's request is invalidNo — will fail again
429Rate limitedYes, after retry_after
5xxServer-side failureYes, with backoff

Standards exist so you don't invent this: RFC 9457 "Problem Details for HTTP APIs" defines a type (stable URI), title, and detail, mapping cleanly onto stable-code plus free-message.

Power moves

Resources

Shipping AI or cloud systems? Yeda AI audits and hardens production LLM and API pipelines. Talk to us · Read the blog