Handling errors

Every error is an RFC 9457 problem document, served as application/problem+json. The set of error kinds is closed — each one is listed under Errors — so you can write exhaustive handling rather than pattern-matching on prose.

{
  "type": "https://developer.winteriscoming.fyi/problems/insufficient_scope",
  "title": "Insufficient scope",
  "status": 403,
  "code": "insufficient_scope",
  "detail": "This API key is missing the \"sites:read\" scope. Granted: services:read.",
  "instance": "/v1/sites",
  "required_scope": "sites:read"
}

Switch on code

code is a Yeti extension to the RFC and it exists for exactly this reason: type is a URI, and asking every client to parse a URI to find out what went wrong is a poor trade. code is the stable token. It does not change when the documentation moves.

problem = response.json()
match problem["code"]:
    case "rate_limit_exceeded": back_off(response.headers["Retry-After"])
    case "insufficient_scope":  alert(f"key needs {problem['required_scope']}")
    case _:                     raise ApiError(problem)

Do not switch on title, which is human-readable text we may reword, or on the HTTP status alone, which is shared by several kinds — 403 is insufficient_scope, company_not_granted or plain forbidden, and they need different reactions.

Extension members

Some kinds carry extra fields beyond the RFC's own, and they are the actionable part:

Member On What it gives you
required_scope insufficient_scope Exactly which grant is missing
errors validation_failed, invalid_query_parameter Field or parameter → what is wrong with it

A 404 is not proof an id never existed

A record belonging to another company also returns not_found. That is deliberate: a 403 there would confirm the id exists and turn the endpoint into a way to enumerate another tenant's records.

Nothing is ignored silently

An unrecognised filter, sort, include or fields parameter fails the request with invalid_query_parameter. It does not return an unfiltered page that looks correct — which is the failure that makes an export quietly wrong rather than loudly broken. The detail lists what the endpoint does accept.