Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Error Modeling
HLD

Error Modeling

Errors as part of your API's public surface — consistent structure, actionable codes, and the 4xx/5xx split.

Errors Are API Surface

Consumers build against your failure modes as much as your successes. Inconsistent errors (sometimes a string, sometimes an object, sometimes HTML) force every client into defensive parsing — and turn debugging into archaeology.

 every error response answers FOUR questions:
   WHAT happened        (stable machine code)
   WHY                  (human message)
   WHERE                (which field/param)
   WHAT NEXT            (retry? fix request? contact support?)

A Consistent Envelope

{
  "error": {
    "code": "card_declined",
    "message": "Your card was declined by the issuer.",
    "field": null,
    "request_id": "req_9f2ka83",
    "doc_url": "https://api.ride.example/errors#card_declined",
    "retryable": false
  }
}
ElementPurpose
codeStable snake_case string — clients branch on this, never on messages
messageHuman-readable, safe to display, no internals
fieldPoints at offending input for validation errors
request_idCorrelates client report ↔ server logs — debugging lifeline
retryableEncodes the retry decision so clients don’t guess

Machine Codes vs Messages

 CODES are contract:  "insufficient_balance" forever means that
 MESSAGES are prose:  reword freely, localize, A/B test

 branching on message text = broken clients at next copy edit.
 rule: codes for logic, messages for humans, never swapped

The Retryability Axis

The most consequential field is retryable:

 retryable: true     → transient: 503s, lock timeouts, upstream blips
                       client SHOULD retry with backoff
 retryable: false    → deterministic: validation, authz, insufficient funds
                       retrying is waste; user must act
 
 encoding this explicitly beats clients inferring from status class,
 because some 4xx ARE transient (429 with Retry-After) and
 some 5xx are NOT (unrecoverable internal assertion)

Validation Errors

{
  "error": {
    "code": "validation_failed",
    "message": "Request has invalid fields.",
    "fields": [
      {"field": "pickup.lat", "code": "out_of_range", "message": "Latitude must be -90..90"},
      {"field": "scheduled_at", "code": "in_past", "message": "Cannot schedule in the past"}
    ]
  }
}

Return all field violations per request, not the first — one round-trip per bug instead of N.

Anti-Patterns

 ✗ 200 OK { "success": false }         breaks caches, monitors, retries
 ✗ leaking stack traces / SQL          security + professionalism fail
 ✗ generic "Something went wrong"      no correlation, no action possible
 ✗ new error shape per endpoint        client-side combinatorial explosion
 ✗ HTTP 500 for user input errors     poisons alerting AND retry logic

That last one cuts both ways operationally: pages fire on 5xx spikes; if validation noise rides in 5xx, real outages hide in the pager.

Interview Framing

Error modeling rarely gets its own question but scores constantly: any design answer improves when you sketch the error envelope once and reference it (“all endpoints return this shape; 5xx carries retryable=true”). The request_id mention specifically signals production experience — it’s the field everyone wishes they’d had.

My Private Notes

Notes are auto-saved locally to this device.