# Should I move my API error bodies to the RFC 7807 problem+json format?

> Standardize on RFC 9457 rather than 7807, produce every error body in one central exception handler, and manage the `type` URI as a versionable contract.

- Asked: 2026-09-09
- Answered: 2026-09-11
- Asked by: Serkan
- Tags: api, error-handling, api-design
- Source: https://www.muhammetsafak.com.tr/en/just-ask/move-api-error-bodies-rfc-7807-problem-json/
- Language: en-US
- Author: Muhammet Şafak

---
**Question:** We have a platform where each service (some Laravel, some Go) returns errors in a slightly different body: one uses `{"error": "..."}`, another `{"message": "...", "code": 42}`, a third a nested `errors` object.

The client-side teams have to parse each service's errors separately and it's become unsustainable. Would it make sense to move the error bodies to the RFC 7807 `application/problem+json` format to establish a single contract, or does it just add unnecessary standards overhead?


Short answer: yes, standardize.

## Short answer

But two conditions: base it on RFC 9457 (which updates/obsoletes 7807) rather than 7807 itself, and enforce it in one central layer, not per-endpoint by hand.

## Why

1. **7807 is now RFC 9457.** Same shape, refreshed in 2023. Reference 9457 in new work. The fields are the same: `type` (URI), `title`, `status`, `detail`, `instance` — plus as many extra (extension) members as you like.

2. **The real win is the uniform envelope, not the field names.** Client teams now parse a single shape across every service. That was exactly your pain, and it's the whole value of the standard. So yes, it's worth doing.

## What to do

1. **Set the content type correctly: application/problem+json.** So clients and intermediary proxies can distinguish it from a normal body. Forgetting this header is implementing the standard halfway.

2. **type is a stable machine key — treat it as a contract.** Use a URN or a documentation URL. Clients branch on `type`, not on human-readable fields like `title`/`detail`. Don't leak stack traces or internals into `detail`; that's a [human-readable sentence for the caller](/en/blog/api-error-contract-returning-meaningful-errors-to-clients/).

   ```json
   {
     "type": "https://api.example.com/problems/insufficient-funds",
     "title": "Insufficient funds",
     "status": 422,
     "detail": "Wallet 7f3a has balance 12.00, needs 50.00.",
     "instance": "/wallets/7f3a/withdrawals/9910"
   }
   ```

3. **Enforce it in one place — the exception handler / middleware.** In Laravel, produce it in the `Handler`'s `render` layer (since Laravel 11, that's `withExceptions()->render()` in `bootstrap/app.php` instead); in Go, in a central "error → problem" mapper. Write it per-endpoint and it drifts over time, defeating the point of the standard.

4. **Validation errors need an extension.** 9457 doesn't define a field-level error list. Add an `errors` array extension for validation and fix that shape once across all services — otherwise it'll diverge per service again.

5. **Plan the migration to be backward compatible.** Existing clients may expect the old body. Switching the server side is easy, but clients don't update overnight. Tie the new `problem+json` format to an API version, or during a transition window return both fields (e.g. the old `message` alongside the new `detail`) for a while, and drop the old one as clients migrate.

6. **Don't over-standardize everything.** problem+json is for error bodies; it doesn't change the shape of successful responses. And automate the docs too: reference the `type` catalog in your OpenAPI schema so clients can see which error types to expect from the contract itself.

**Bottom line:** personally I'd base it on 9457, define a versionable URI scheme for `type`, consolidate production in a single exception handler, and define a shared `errors` extension for validation. I'd also migrate gradually — filling out the `type` catalog and moving clients over as I go — rather than in one big bang. The value of the standard is in the discipline: one envelope, one production point, `type` managed like a contract.

## Related Reading

- [API error contract: returning meaningful errors to clients](/en/blog/api-error-contract-returning-meaningful-errors-to-clients/) — Blog
- [Standardizing API responses: a consistent contract](/en/blog/standardizing-api-responses-a-consistent-contract/) — Blog
- [Should I author the OpenAPI spec first or generate it from my code?](https://www.muhammetsafak.com.tr/en/just-ask/should-i-author-the-openapi-spec-first-or-generate-it-from/) — Just Ask
- [How should I run the sunset process when deprecating an endpoint in my public API?](https://www.muhammetsafak.com.tr/en/just-ask/how-should-i-run-the-sunset-process-when-deprecating-an-endpoint/) — Just Ask
- [The payment webhook keeps re-sending the same notification; how do I set up idempotency?](https://www.muhammetsafak.com.tr/en/just-ask/webhook-idempotency-and-hmac-for-duplicate-payment-notifications/) — Just Ask
