Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

API Error Handling RFC 7807 Problem Details

By Kokil Thapa | Last reviewed: August 2026

Inconsistent JSON error responses break client integrations and waste developer time debugging ambiguous messages. Adopting API Error Handling RFC 7807 Problem Details gives your Laravel or Symfony API a standardised, machine-readable error structure that both humans and automated clients can parse reliably. If you are building public APIs or integrating payment gateways like eSewa or Khalti, this specification removes guesswork from failure states and aligns your stack with modern REST best practices outlined in our Laravel API best practices guide.

What Is API Error Handling RFC 7807 Problem Details and Why Use It?

RFC 7807, updated by RFC 9457 in 2023 but still universally referred to by its original number, defines a simple JSON object for conveying error information in HTTP APIs. The core value proposition is predictability. Before this standard, every API invented its own error shape: some used { "error": "..." }, others { "message": "...", "code": 400 }, and legacy systems sometimes returned HTML error pages even for JSON requests. This fragmentation forces every client developer to write custom parsers for each service they consume.

The specification mandates the content type application/problem+json. This signals to HTTP clients, proxies, and monitoring tools that the body contains structured problem metadata rather than arbitrary application data. The five standard members are:

  • type (string): A URI reference identifying the specific error category. This should be stable and dereferenceable to human documentation.
  • title (string): A short, human-readable summary of the error type. Should not change between occurrences of the same error.
  • status (integer): The HTTP status code generated by the origin server.
  • detail (string): A human-readable explanation specific to this occurrence.
  • instance (string): A URI reference identifying the specific resource or request occurrence, useful for tracing logs.

In my experience shipping legal-tech portals and eCommerce platforms in Nepal, adopting this format reduced support tickets related to "unclear API errors" significantly. When integrating with Nepali payment providers or government verification services, having a predictable error contract makes debugging webhook failures and callback rejections far faster. For teams hiring junior developers or outsourcing frontend work, as discussed in hiring web developers in Nepal, a standard error format reduces onboarding friction because new team members don't need to learn proprietary error schemas.

RFC 7807 Problem Details StructuretypetitlestatusdetailinstanceContent-Type: application/problem+jsonMachine-readable + Human-friendlyExtensible via custom propertiesStandardised across all endpoints
Core fields of API Error Handling RFC 7807 Problem Details providing structured context for every failure

How Do You Implement API Error Handling RFC 7807 Problem Details in Laravel 12?

Laravel 12 (requiring PHP 8.2 minimum) does not ship RFC 7807 responses out of the box; the default exception renderer returns { "message": "..." } for JSON requests. You must override this behaviour centrally. The cleanest approach in 2026 is using the withExceptions() callback in bootstrap/app.php or a dedicated Exception Handler class if you prefer the traditional structure.

Step 1: Create a Problem Details Response Builder

Create a reusable builder so you never manually construct arrays. This enforces consistency and makes testing straightforward.

<?php
// app/Support/ProblemDetails.php
namespace App\Support;

use Illuminate\Http\JsonResponse;

class ProblemDetails
{
    public static function respond(
        string $type,
        string $title,
        int $status,
        string $detail = '',
        string $instance = '',
        array $extensions = []
    ): JsonResponse {
        $payload = array_filter([
            'type'     => $type,
            'title'    => $title,
            'status'   => $status,
            'detail'   => $detail,
            'instance' => $instance ?: request()->path(),
        ], fn ($v) => $v !== '');

        return response()->json(
            array_merge($payload, $extensions),
            $status,
            ['Content-Type' => 'application/problem+json']
        );
    }
}

Step 2: Register Global Exception Mapping

In Laravel 12's streamlined configuration, map common exceptions to RFC 7807 responses inside bootstrap/app.php:

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (ValidationException $e) {
        return ProblemDetails::respond(
            type: 'https://api.example.com/errors/validation-failed',
            title: 'Validation Failed',
            status: 422,
            detail: 'One or more fields failed validation.',
            extensions: ['errors' => $e->errors()]
        );
    });

    $exceptions->render(function (NotFoundHttpException $e) {
        return ProblemDetails::respond(
            type: 'https://api.example.com/errors/resource-not-found',
            title: 'Resource Not Found',
            status: 404,
            detail: $e->getMessage() ?: 'The requested resource does not exist.'
        );
    });

    $exceptions->render(function (Throwable $e) {
        if (request()->expectsJson()) {
            return ProblemDetails::respond(
                type: 'https://api.example.com/errors/internal-error',
                title: 'Internal Server Error',
                status: 500,
                detail: app()->hasDebugModeEnabled() ? $e->getMessage() : 'An unexpected error occurred.'
            );
        }
    });
})

This pattern ensures every JSON client receives API Error Handling RFC 7807 Problem Details regardless of which controller throws. Note the security guard on 500 errors: never leak stack traces or internal paths in production. On legal-tech projects handling sensitive case data, I always strip internal details from production error responses while keeping them available in local/staging environments for debugging.

Laravel Exception → RFC 7807 FlowException ThrownException Handler(bootstrap/app.php)ProblemDetails BuilderFormats JSON PayloadHTTP Responseproblem+jsonKey Implementation Rules• Always set Content-Type: application/problem+json• Never expose internals in production 500 errors• Include validation errors as extension property• Use stable URIs for type field documentation
Exception handling pipeline transforming Laravel errors into standardised RFC 7807 responses

How Does RFC 7807 Compare to Traditional API Error Formats?

Many teams hesitate to adopt a new standard because existing clients expect legacy formats. Understanding the concrete trade-offs helps justify the migration cost. Below is a comparison based on real migrations I've performed on Laravel APIs serving both mobile apps and third-party integrators.

CriteriaLegacy Custom FormatRFC 7807 Problem Details
Content NegotiationAmbiguous; often application/jsonExplicit application/problem+json
Client ParsingCustom parser per API vendorGeneric parser works across services
Documentation LinkageManual lookup requiredtype URI links directly to docs
ExtensibilityAd-hoc; breaks contractsDefined extension mechanism
Tooling SupportNone; manual inspectionPostman, Insomnia, OpenAPI native support
Migration EffortN/AModerate; requires client updates
SEO / Crawlability ImpactSearch engines may misinterpret errorsClear semantic signal for crawlers

The SEO row deserves attention. While APIs aren't typically indexed, public-facing hybrid sites (like legal information portals) sometimes serve JSON-LD or structured content alongside API endpoints. Consistent error semantics prevent search engines from indexing error states as valid content. This aligns with technical SEO audits covered in our technical SEO audit guide for Nepal, where proper HTTP semantics form part of crawl health.

What Are Common Mistakes When Implementing RFC 7807 in Production?

I've reviewed dozens of API implementations during code audits and migrations. These anti-patterns appear repeatedly and undermine the benefits of standardisation.

  1. Omitting the Content-Type header. Returning the correct JSON shape with application/json defeats the purpose. Clients checking headers will treat it as generic data. Always set application/problem+json explicitly.
  2. Using dynamic strings in the type field. The type URI should be a stable identifier like https://api.example.com/errors/validation-failed, not /errors/val-err-2026-08-14. Dynamic types break client routing logic and documentation links.
  3. Leaking sensitive data in detail. Database column names, file paths, SQL fragments, and user IDs have no place in production error responses. Sanitise aggressively. On legal-tech systems, this is non-negotiable due to client confidentiality obligations.
  4. Ignoring the instance field. This field exists for correlation. Populate it with the request path, trace ID, or transaction reference. When debugging payment gateway callbacks at 2 AM, being able to grep logs by instance value saves hours.
  5. Over-engineering extension properties. Only add custom fields when clients programmatically need them. Adding user_id, server_region, or debug_trace "just in case" creates maintenance burden and potential privacy issues.
  6. Inconsistent adoption across endpoints. Mixing RFC 7807 responses with legacy formats in the same API confuses clients. Migrate atomically or version the API. Partial adoption is worse than no adoption.
Should You Add a Custom Extension Property?Client needs it programmatically?YesNoDo NOT add. Keep payload lean.Contains PII or secrets?NoYesSanitise or omit entirelyAdd to extensionsRule: Every extension must have a documented consumer use case
Decision framework for safely extending RFC 7807 payloads without leaking data or adding bloat

How Do You Test and Validate RFC 7807 Responses Automatically?

Manual curl checks don't scale. Build validation into your test suite and CI pipeline. In Laravel 12 with PHPUnit or Pest, assert both structure and content type:

it('returns RFC 7807 validation error', function () {
    $response = $this->postJson('/api/v1/cases', [
        'client_name' => '', // invalid
    ]);

    $response->assertStatus(422)
        ->assertHeader('Content-Type', 'application/problem+json')
        ->assertJsonStructure([
            'type',
            'title',
            'status',
            'detail',
            'instance',
            'errors', // extension
        ])
        ->assertJsonFragment([
            'type' => 'https://api.example.com/errors/validation-failed',
            'title' => 'Validation Failed',
        ]);
});

For integration tests hitting real staging environments, consider writing a reusable assertion macro. Also validate that non-JSON requests (e.g., browser navigation) still receive appropriate HTML error pages — RFC 7807 applies only when the client signals JSON acceptance via Accept header or URL suffix.

On projects using OpenAPI/Swagger, document problem details responses explicitly in your spec. Tools like Stoplight and Scalar render these beautifully for frontend consumers. When working with international clients or documenting APIs for outsourced teams, as many Nepal-based agencies do, this documentation becomes the single source of truth that prevents miscommunication.

Practical Next Steps for Your API

Implementing API Error Handling RFC 7807 Problem Details is a one-day investment that pays dividends across every future integration. Start by creating the response builder utility shown above, then migrate your exception handler incrementally. Prioritise high-traffic endpoints and authentication flows first, as these generate the most client-side error handling code. Remember to update your API documentation and notify existing consumers before switching content types in production.

If you're building or refactoring APIs in Laravel or Symfony and want hands-on guidance tailored to your stack, reach out through my contact page. Whether you're integrating Nepali payment gateways, building legal-tech platforms, or modernising legacy systems, getting error handling right early prevents costly rewrites and support overhead later.

Frequently Asked Questions

RFC 7807 defines a standard JSON format for API error responses using application/problem+json media type. It includes type, title, status, detail, and instance fields to provide consistent, machine-readable error information across REST APIs.

Standardization reduces client-side parsing complexity and improves interoperability. Clients can build generic error handlers once rather than adapting to each API's unique structure. In my experience integrating multiple payment gateways like eSewa and Khalti, consistent error formats significantly reduce frontend debugging time and integration friction.

Create a custom exception handler extending Illuminate\Foundation\Exceptions\Handler and override the render method to return JsonResponse with application/problem+json content type. Use Spatie's laravel-problem-details package or build a simple transformer mapping your exceptions to the RFC schema. This approach works cleanly with Laravel 12's refined exception handling on PHP 8.2+.

Only type and title are technically required by the specification. However, production APIs should always include status, detail, and instance for practical debugging. The type field must be a URI reference identifying the error category, while instance identifies the specific occurrence for log correlation and support ticketing.

Yes, the specification explicitly allows extension members. Common additions include trace_id, validation_errors, or documentation links. On legal-tech portals I've built, we include field-level validation errors and Nepali-language messages alongside English details to support bilingual client applications consuming the same API endpoints.

OpenAPI describes what errors an endpoint might return at design time, while RFC 7807 standardizes the actual runtime response format. They complement each other: define expected error types in your OpenAPI spec, then ensure your implementation returns them in RFC 7807 format. This alignment helps frontend developers and QA testers validate error handling against documented contracts.

Not directly. GraphQL has its own error specification within the response envelope. However, the principles of structured, machine-readable errors apply equally. For hybrid architectures where Laravel serves both REST and GraphQL endpoints, maintain separate error formats per protocol rather than forcing RFC 7807 into GraphQL responses where it creates confusion.

Map Laravel's ValidationException to a 422 Unprocessable Entity response with type pointing to a validation error URI. Include an invalid_params extension array containing field names, rejection reasons, and optional pointers. This preserves RFC compliance while providing the granular feedback forms need. I use this pattern extensively on booking systems like Adventure Third Pole Trek.

Any 4xx or 5xx status code can carry a Problem Details body. The status field inside the JSON must match the HTTP status code to prevent confusion. Common mappings include 400 for bad requests, 401 for authentication failures, 403 for authorization denials, 404 for missing resources, 422 for validation errors, and 500 for unexpected server failures.

Write integration tests asserting the Content-Type header equals application/problem+json and validating required fields exist with correct types. Use tools like Dredd or Schemathesis to contract-test against your OpenAPI spec. In GitLab CI pipelines for projects like notarykathmandu.com, automated tests verify every documented error path returns valid Problem Details before deployment proceeds.

It can if implemented carelessly. Never include stack traces, database queries, or internal paths in production detail fields. Sanitize error messages to avoid leaking system architecture. On client portals handling legal documents, we return generic authorization errors without revealing whether a resource exists versus being inaccessible, preventing enumeration attacks through error message differences.

For a typical Laravel 12 application, initial implementation takes 8-16 hours including exception handler setup, testing, and documentation updates. At Nepal freelance rates around NPR 2,500-4,000 per hour (USD 19-30), expect Rs 20,000-64,000 total. Existing well-structured exception hierarchies reduce effort significantly compared to legacy codebases with scattered error handling logic.

Developers often omit the Content-Type header, mismatch HTTP and JSON status codes, use vague type URIs like /error instead of meaningful categories, or forget to update API documentation. Another frequent issue is inconsistent adoption where only some endpoints return Problem Details. Audit all error paths systematically rather than converting incrementally without tracking coverage.

Keep the core RFC structure stable across versions since clients depend on predictable parsing. Add new extension fields for additional context without breaking existing consumers. If you must change error semantics, introduce new type URIs rather than modifying existing ones. Document deprecated error types with sunset headers so clients can migrate gracefully before removal in future major versions.

Yes, spatie/laravel-problem-details and mateusjunges/laravel-api-response-builder both provide solid foundations. Spatie's package integrates with Laravel's exception handler and supports custom extensions out of the box. Evaluate based on your needs: Spatie offers stricter RFC compliance while Junges provides broader response formatting. Test both against your validation and authentication error scenarios before committing to either in production.

Share this article

Quick Contact Options
Choose how you want to connect me: