REST API Response Examples: Writing Them So Frontend Teams Can Use Them Directly
AI generated
{ }
GET
REST API · JSON · Frontend Integration · Documentation
Writing API response examples
that frontend teams can use directly

Poor API documentation costs frontend teams hours on follow-up questions that never needed to be asked. A consistent response structure, machine-readable error formats and complete typing turn an API specification into a tool that frontend developers can use productively from day one.

15 min read JSON · RFC 9457 · Pagination · OpenAPI · TypeScript REST API · Frontend Integration

1. Why poor response structures block frontend teams

A REST API is not done when it returns HTTP 200. It is done when a frontend developer can write their code without going back to the backend developer with questions. That sounds like a soft requirement, but it has hard consequences: every time a frontend team has to infer the type of a field from context, it costs time. Every time an error response has a different structure than the success response, special-case code accumulates in the frontend.

The most common problem in practice is inconsistency. One collection endpoint returns an array directly, another returns an object with a data key. One error has a message field, another has error and description. Date fields arrive sometimes as a Unix timestamp, sometimes as an ISO 8601 string, sometimes in a local format. From the frontend's perspective, this means every new endpoint requires another look at the documentation, and the documentation is rarely complete.

The solution is not a specific JSON format, but conventions that are consistently upheld across all endpoints. A frontend developer who has once understood how the API's responses are structured should be able to integrate any new endpoint without reading the documentation. This tutorial shows how to get there.

2. The envelope pattern: a consistent shell for all responses

The envelope pattern wraps all API responses in a uniform JSON shell. The basic structure is an object with a data key for the actual payload, a meta key for pagination and other metadata, and an errors key for error cases. This structure applies to every endpoint, whether it is a single object, a collection or an empty response. The advantage: the frontend always knows where to find the data.

A common objection to the envelope pattern is that it creates unnecessary nesting for simple responses. That is true for the simplest case, but it is a trade-off worth making: a flat structure for simple responses creates inconsistency as soon as you add pagination or metadata. The envelope pattern invests one level of nesting in exchange for consistency across all endpoints. In TypeScript that means a single generic ApiResponse<T> type definition that works for every endpoint.


// Single resource, GET /api/v1/orders/42
{
  "data": {
    "id": "ord_7f3a9b2c",
    "status": "confirmed",
    "total": { "amount": 12490, "currency": "EUR", "formatted": "124,90 €" },
    "customer": { "id": "cus_1a2b3c", "email": "customer@example.de" },
    "createdAt": "2026-05-09T14:22:00Z",
    "updatedAt": "2026-05-09T14:25:33Z"
  },
  "meta": {
    "requestId": "req_abc123",
    "version": "1.0"
  }
}

// Collection, GET /api/v1/orders?page=2&per_page=25
{
  "data": [ /* array of order objects */ ],
  "meta": {
    "pagination": {
      "total": 247,
      "perPage": 25,
      "currentPage": 2,
      "lastPage": 10,
      "from": 26,
      "to": 50
    },
    "requestId": "req_def456"
  }
}

// Empty result, 204 No Content alternative
{
  "data": null,
  "meta": { "requestId": "req_ghi789" }
}

3. Error formats following RFC 9457: Problem Details

RFC 9457 (Problem Details for HTTP APIs) is the current IETF standard for machine-readable error formats in REST APIs. It defines a JSON object with exactly five fields: type (a URI that uniquely identifies the error type), title (a human-readable short description), status (the HTTP status code as an integer), detail (a concrete description of the error in this instance) and instance (the URI of the specific resource that caused the error). The frontend can react programmatically to error categories based on type.

The most important feature of RFC 9457 is extensibility. The standard problem object can be enriched with your own fields as long as type, title and status are present. For validation errors it makes sense to add a violations array that describes each individual field validation error with the affected field, the violated constraint and a message. This lets the frontend display error messages directly next to the corresponding form field without having to parse errors itself.


// HTTP 422 Unprocessable Entity, validation error (RFC 9457)
// Content-Type: application/problem+json
{
  "type": "https://mironsoft.de/errors/validation-failed",
  "title": "Validation Failed",
  "status": 422,
  "detail": "The request body contains 2 validation errors.",
  "instance": "/api/v1/orders",
  "violations": [
    {
      "field": "items[0].quantity",
      "constraint": "min",
      "message": "Quantity must be at least 1.",
      "rejectedValue": 0
    },
    {
      "field": "shippingAddress.postalCode",
      "constraint": "pattern",
      "message": "Postal code must consist of 5 digits.",
      "rejectedValue": "1234"
    }
  ]
}

// HTTP 404 Not Found
{
  "type": "https://mironsoft.de/errors/resource-not-found",
  "title": "Resource Not Found",
  "status": 404,
  "detail": "Order with ID ord_999xyz does not exist.",
  "instance": "/api/v1/orders/ord_999xyz"
}

// HTTP 429 Too Many Requests
{
  "type": "https://mironsoft.de/errors/rate-limit-exceeded",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "You have exceeded 100 requests per minute.",
  "instance": "/api/v1/orders",
  "retryAfter": 47
}

4. Making types explicit: dates, enums and nullability

Typing in JSON APIs is always a convention between server and client, because JSON itself only has a handful of basic types. The critical fields are dates, enums and fields that can be null. Dates should, without exception, be transmitted as an ISO 8601 UTC string ("2026-05-09T14:22:00Z"). No Unix timestamp, no local format, no ambiguous date without a time zone. The reason: time zone conversion belongs in the frontend, not in the API. The API delivers UTC, the frontend converts to the user's local time.

Enums should be transmitted as strings, not as integers. "status": "confirmed" is self-documenting in network traffic and does not break when the order of enum values changes. The complete list of all possible enum values belongs in the OpenAPI specification as an enum array. Fields that can be null must be explicitly marked as nullable, both in the OpenAPI schema and in example data. A frontend that has never seen null for a field in test data has no code path for that case.

5. Documenting pagination metadata completely

Pagination metadata is one of the fields where APIs are frequently documented minimally, even though frontend teams need all the information to build a complete pagination component. The minimum for offset pagination: the total number of records (total), the current page (currentPage), the last page (lastPage), the number per page (perPage), and the first and last index of the current page (from and to). Without total no page navigator can be built. Without lastPage no "next" button can be disabled correctly.

For cursor-based pagination, which makes sense for large datasets or real-time feeds, nextCursor and prevCursor belong in the metadata. The frontend sets the cursor as a query parameter for the next request. Important: the cursor is opaque to the frontend. Its internal structure must not be documented and must be treated as a stable but implementation-dependent string. This prevents frontend teams from accidentally relying on the cursor's structure.


// Offset pagination, GET /api/v1/products?page=3&per_page=20
{
  "data": [ /* 20 product objects */ ],
  "meta": {
    "pagination": {
      "total": 583,
      "perPage": 20,
      "currentPage": 3,
      "lastPage": 30,
      "from": 41,
      "to": 60,
      "links": {
        "first": "/api/v1/products?page=1&per_page=20",
        "prev":  "/api/v1/products?page=2&per_page=20",
        "next":  "/api/v1/products?page=4&per_page=20",
        "last":  "/api/v1/products?page=30&per_page=20"
      }
    }
  }
}

// Cursor pagination, GET /api/v1/feed?cursor=eyJpZCI6MTAwfQ
{
  "data": [ /* feed items */ ],
  "meta": {
    "pagination": {
      "perPage": 25,
      "count": 25,
      "hasMore": true,
      "nextCursor": "eyJpZCI6MTI1fQ",
      "prevCursor": "eyJpZCI6NzV9"
    }
  }
}

6. Nested resources: when to embed, when to link

One of the most common design decisions in REST APIs is whether related resources should be embedded or referenced as a link. The rule of thumb: if the frontend needs the related resource for every rendering of the main resource, it should be embedded. If the related resource is only sometimes needed or can be navigated to independently, only a link (the ID or a URL) should be transmitted. Embedding saves round trips but increases response size and creates synchronization problems when the related resource changes.

A pragmatic solution is the compound document pattern used by JSON:API: the main resource contains only IDs for related resources, and all embedded objects go into a separate included array. The frontend assembles the objects itself. Alternatively, the query parameter ?include=category,images gives the frontend control over which relations are embedded. Both are better than silent embedding without an opt-out, which leads to oversized responses.

7. Partial responses and field selection

Partial responses let the frontend request only the fields it actually needs. The Google pattern uses the query parameter ?fields=id,name,price. The GraphQL-inspired pattern uses ?fields[product]=id,name&fields[category]=id,slug for nested selection. Both approaches reduce the amount of data transmitted and the CPU load for serialization on the server, especially for endpoints that return many large objects.

Important: partial responses are an optimization feature, not a replacement for a clean response structure. An endpoint with a poor base structure does not get better through field selection. The documentation must clearly state which fields are selectable, and the OpenAPI specification should show the complete schema without field selection as a baseline for every endpoint. Error handling for invalid field names (?fields=nonexistentfield) should return a clear 400 error, not silently ignore the value.

8. Realistic example data instead of placeholders

The biggest practical difference between API documentation that frontend teams actually use and one they ignore is the example data. "name": "string" or "id": 1 are useless to a frontend team. Realistic example data shows what the data will look like in production: real product names, real addresses, typical price values, correct date stamps in ISO format, enum values from the actual domain model.

Edge cases in the example data matter especially: a product name with special characters and accented letters. An address with an unusually long street name. A price of 0.00 EUR for free products. An array with a single element. An array with zero elements. An optional field that is null. These cases should be documented in the OpenAPI examples as named examples, not just as a single example. The frontend team can use them to test its rendering logic before developing against the real backend.


# OpenAPI 3.1, realistic example data with multiple scenarios
components:
  schemas:
    Order:
      type: object
      required: [id, status, total, createdAt]
      properties:
        id:
          type: string
          pattern: '^ord_[a-z0-9]{8}$'
          example: ord_7f3a9b2c
        status:
          type: string
          enum: [pending, confirmed, shipped, delivered, cancelled]
          example: confirmed
        total:
          $ref: '#/components/schemas/Money'
        note:
          type: string
          nullable: true
          description: Optional order note from the customer
          example: null
        createdAt:
          type: string
          format: date-time
          example: "2026-05-09T14:22:00Z"

  examples:
    OrderConfirmed:
      summary: Confirmed order with long note
      value:
        id: ord_7f3a9b2c
        status: confirmed
        total: { amount: 12490, currency: EUR, formatted: "124,90 €" }
        note: "Please deliver after 6pm, office building is closed otherwise"
        createdAt: "2026-05-09T14:22:00Z"
    OrderNullNote:
      summary: Order without a note (null field example)
      value:
        id: ord_8a1c4d3e
        status: pending
        total: { amount: 0, currency: EUR, formatted: "0,00 €" }
        note: null
        createdAt: "2026-05-09T09:00:00Z"

9. Response formats side by side

The choice of response format has long-term consequences for frontend development. The following table compares the most common anti-patterns with the recommended alternatives.

Aspect Anti-pattern Recommended pattern Benefit
Response shell Returning an array directly { "data": [], "meta": {} } Metadata extensible without a breaking change
Error format { "message": "Error" } RFC 9457 Problem Details Machine-readable, extensible, standardized
Date format Unix timestamp or local format ISO 8601 UTC (…Z) Timezone-safe, parseable in every language
Enum values Integer (0, 1, 2) String ("confirmed") Self-documenting, order-independent
Monetary amounts Float (124.9) Integer cents plus formatted string No floating-point errors

The most important takeaway from this table: all anti-patterns arise from short-term thinking. Returning an array directly saves one level of nesting today, but creates a breaking change as soon as pagination is added. Float for monetary amounts works until the first rounding discrepancy shows up in accounting. The recommended patterns have minimal extra implementation effort, but save far more time during integration and debugging.

Mironsoft

REST API design, documentation and frontend integration

API responses that frontend teams can really use?

We review your API response structure, standardize error formats following RFC 9457, and create OpenAPI specifications with realistic example data, so frontend teams can integrate without follow-up questions.

API review

Analyze and standardize response structure, error formats and typing

OpenAPI specification

Complete schemas with realistic example data and edge cases

Frontend onboarding

Generate TypeScript types from OpenAPI and document integration patterns

10. Summary

API responses that frontend teams can use directly rest on four pillars: consistency, typing, complete metadata and realistic example data. The envelope pattern ensures that the response shell is the same for every endpoint, whether it is a single resource, a collection or an error. RFC 9457 Problem Details provide machine-readable, extensible error formats that the frontend can process programmatically.

Pagination metadata must be complete: total count, current page, last page and links for every direction. Date fields always as an ISO 8601 UTC string, monetary amounts as integer cents with a formatted string, enums as descriptive strings. And finally: OpenAPI example data must show edge cases, null fields, empty arrays, special characters, boundary values. Only when the frontend sees these cases in the documentation does it write code that handles them.

API response patterns, the essentials at a glance

Consistent structure

Envelope pattern: { "data": …, "meta": {} } for every response. No returning a bare array without a shell.

Error formats

RFC 9457 Problem Details with type, title, status, detail. Validation errors with a violations array.

Typing

ISO 8601 UTC for dates, integer cents for money, descriptive strings for enums, nullable marking for optional fields.

Example data

Realistic data with special characters, edge cases and null fields in OpenAPI examples. Multiple named examples per schema.

11. FAQ: REST API response examples for frontend teams

1Why the envelope pattern?
Allows metadata extension without a breaking change. Returning an array directly works today, but breaks as soon as pagination fields become necessary.
2What is RFC 9457?
IETF standard for machine-readable HTTP error formats. Defines type, title, status, detail as core fields. Extensible with your own fields such as violations.
3Why no float for money?
IEEE 754 cannot represent cent amounts exactly. Always transmit integer cents (12490) plus a formatted string ("124.90 EUR").
4Minimal pagination metadata?
total, currentPage, lastPage, perPage, from, to for offset pagination. Without total, no page navigator is possible.
5Embed vs. link?
Embed when always needed. Link (ID only) when optional or independently navigable. Offer a ?include= query parameter for frontend control.
6Enums as string instead of integer?
Self-documenting, order-independent, readable in debug logs. Integers require a mapping table in the frontend.
7Documenting error types in OpenAPI?
ProblemDetails schema as $ref for all 4xx/5xx responses. Content-Type: application/problem+json. Dedicated schemas for validation errors with a violations array.
8What is realistic example data?
Real names with accented characters, typical prices, ISO date stamps, all enum values, null fields for nullable properties, empty arrays. Multiple named examples per schema.
9Partial responses in Symfony?
Parse ?fields=, set it as allowed_attributes in the serializer context. Reject invalid field names with HTTP 400, do not ignore them silently.
10Envelope for 204 No Content?
204 has no body. If a body is desired: use HTTP 200 with { "data": null, "meta": {} }.