intermediate 2 min answer

A platform's plugin API returns generic 500 errors when anything goes wrong. Developers cannot tell whether to retry, fix their code, or contact support. Redesign the error model.

error-handlingapi-designretryabilitydeveloper-experiencefigmawhat-would-you-change
Show the full answer Hide the answer

What a useful error must tell the caller

Four things, and a generic 500 provides none of them:

  1. Whose fault is it? Client error, platform error, or an upstream dependency.
  2. Is it retryable? And if so, after how long.
  3. What specifically failed? Which field, which resource, which constraint.
  4. What should I do? A stable identifier the developer can search and that support can act on.

The redesigned model

A stable machine-readable error code, separate from the HTTP status. RATE_LIMIT_EXCEEDED, INVALID_FIELD_VALUE, RESOURCE_LOCKED, UPSTREAM_TIMEOUT. The status code is too coarse to branch on and too easy to change; the code is the contract.

An explicit retryability signal. Rather than making developers infer it, state it: a boolean or a category, plus Retry-After where a delay is meaningful. This single addition prevents both harmful retries of permanent failures and abandoned requests that would have succeeded.

Field-level detail for validation errors. Which field, what was wrong, what was expected. And return all validation errors, not the first — otherwise a developer fixes one field per round trip.

A request identifier in every response, success and failure alike, logged on both sides. This converts support from "can you describe what happened" into "here is the trace".

Documentation of what is not included. Internal error detail, stack traces and upstream messages must be deliberately excluded, because leaking them is both an information disclosure risk and an accidental contract.

The categories that matter most

Category Status Retryable Developer action
Validation failure 400 No Fix the request
Authentication failure 401 No Fix credentials
Permission denied 403 No Request access
Not found 404 No Check the identifier
Conflict / version mismatch 409 After re-read Re-read and retry
Rate limited 429 Yes, after delay Back off
Platform error 500 Yes, with backoff Retry, then report
Upstream unavailable 503 Yes, with backoff Retry with circuit breaker

The distinction between 409 and 429 is worth emphasising: both are retryable but require different client behaviour — one needs a fresh read first, the other needs a delay. Collapsing them into a generic retryable error produces clients that retry conflicts immediately and fail forever.

The operational benefit

A good error taxonomy is not only developer experience — it is observability. Errors categorised by cause let the platform distinguish "our customers are sending bad requests" from "we are failing", which a uniform 500 rate makes impossible. The most common consequence of a poor error model is that the platform's own dashboards cannot tell the difference between a customer bug and an outage.