If you work with REST APIs, you will encounter certain JSON patterns over and over again. Recognising these common structures saves significant time when integrating new APIs - you can immediately understand the shape of a response rather than reading through documentation from scratch. This guide covers the most common patterns used by production APIs today, with real examples for each.

1. The Wrapped Response (Envelope Pattern)

Most production APIs do not return raw data at the root level. Instead, they wrap it in an envelope object that includes metadata about the response. This makes error handling consistent across all endpoints.

{
  "data": {
    "id": 42,
    "name": "Alice Johnson",
    "email": "alice@example.com",
    "createdAt": "2024-01-15T09:30:00Z"
  },
  "status": "success",
  "message": "User retrieved successfully"
}

The actual resource lives inside data, while status and message provide metadata. Your code should always check status before accessing data. The GitHub API, Stripe API, and many others follow this pattern.

2. Paginated List Responses

When an endpoint can return many items, it paginates them to keep response times fast and payloads manageable. There are two main pagination styles:

Page-based pagination:

{
  "data": [
    { "id": 1, "name": "Item One", "price": 9.99 },
    { "id": 2, "name": "Item Two", "price": 14.99 }
  ],
  "pagination": {
    "page": 1,
    "per_page": 10,
    "total": 245,
    "total_pages": 25
  },
  "links": {
    "self": "/api/items?page=1",
    "next": "/api/items?page=2",
    "prev": null,
    "last": "/api/items?page=25"
  }
}

Cursor-based pagination (used by Facebook, Twitter, Slack):

{
  "data": [
    { "id": "post_abc", "text": "Hello world" },
    { "id": "post_def", "text": "Second post" }
  ],
  "paging": {
    "cursors": {
      "before": "NjAyMjQ2",
      "after": "MTAxNTEx"
    },
    "next": "/api/posts?after=MTAxNTEx",
    "previous": null
  }
}

Cursor-based pagination is more efficient for large, frequently-changing datasets because it does not require counting total records. The cursor is an opaque token representing a position in the result set.

3. Error Responses

A consistent error structure is a hallmark of a well-designed API. The best APIs use the same JSON structure for errors across all endpoints:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "One or more fields failed validation",
    "details": [
      {
        "field": "email",
        "issue": "required",
        "message": "Email address is required"
      },
      {
        "field": "name",
        "issue": "too_short",
        "minimum": 2,
        "message": "Name must be at least 2 characters"
      }
    ],
    "requestId": "req_8f7d3a",
    "timestamp": "2024-03-12T10:15:00Z"
  }
}

The code field is a machine-readable constant your code can switch on. The message is human-readable. The details array allows multiple validation errors to be reported at once. The requestId helps with debugging by correlating the error to server logs.

4. Nested Resource Responses

APIs often embed related resources in a single response to reduce the number of round trips. This is called "eager loading" or "sideloading":

{
  "order": {
    "id": "ord_001",
    "status": "shipped",
    "createdAt": "2024-03-01T14:00:00Z",
    "customer": {
      "id": 99,
      "name": "Bob Smith",
      "email": "bob@example.com"
    },
    "shippingAddress": {
      "street": "456 Oak Ave",
      "city": "Chicago",
      "state": "IL",
      "zip": "60601"
    },
    "items": [
      {
        "productId": 12,
        "name": "Wireless Headphones",
        "quantity": 2,
        "unitPrice": 49.99,
        "subtotal": 99.98
      }
    ],
    "totals": {
      "subtotal": 99.98,
      "tax": 8.99,
      "shipping": 5.00,
      "total": 113.97
    },
    "currency": "USD"
  }
}

5. JSON Array at Root

Some endpoints return a plain array at the root level rather than an envelope object. This is simpler but less extensible:

[
  { "id": 1, "name": "Tag A", "slug": "tag-a", "count": 42 },
  { "id": 2, "name": "Tag B", "slug": "tag-b", "count": 17 },
  { "id": 3, "name": "Tag C", "slug": "tag-c", "count": 5 }
]

The downside of root arrays: there is no room to add pagination metadata, request IDs, or status codes without a breaking change to the API contract.

6. Authentication Token Responses

OAuth and token-based authentication APIs return a standard JSON structure after successful login:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "8xLOxBtZp8",
  "scope": "read write",
  "user": {
    "id": 42,
    "email": "alice@example.com"
  }
}

The access_token is sent in the Authorization: Bearer <token> header for subsequent requests. The refresh_token is used to get a new access token when it expires.

7. Rate Limiting Responses

When you exceed an API's rate limit, you typically receive a 429 Too Many Requests status with a JSON body:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "You have exceeded your rate limit of 1000 requests per hour",
    "retryAfter": 3600,
    "limit": 1000,
    "remaining": 0,
    "resetAt": "2024-03-12T11:00:00Z"
  }
}

The retryAfter field tells you how many seconds to wait before retrying. Many APIs also return rate limit headers like X-RateLimit-Remaining and X-RateLimit-Reset on every response.

8. Webhook Payloads

Webhooks send JSON payloads to your server when events occur. A typical webhook payload includes the event type, timestamp, and the event data:

{
  "event": "payment.completed",
  "id": "evt_1234567890",
  "created": 1710241200,
  "data": {
    "object": {
      "id": "pay_abc123",
      "amount": 4999,
      "currency": "usd",
      "status": "succeeded",
      "customer": "cus_xyz789",
      "metadata": {
        "orderId": "ord_001"
      }
    }
  },
  "livemode": true,
  "pending_webhooks": 1,
  "request": {
    "id": "req_abc",
    "idempotency_key": null
  }
}

9. GraphQL Responses

GraphQL always returns JSON with a specific structure, regardless of the query:

{
  "data": {
    "user": {
      "id": "42",
      "name": "Alice",
      "posts": [
        { "id": "1", "title": "Hello World" }
      ]
    }
  },
  "errors": null
}

When errors occur, they appear in the errors array alongside partial data:

{
  "data": { "user": null },
  "errors": [
    {
      "message": "User not found",
      "locations": [{ "line": 2, "column": 3 }],
      "path": ["user"],
      "extensions": { "code": "NOT_FOUND" }
    }
  ]
}

Mapping API Responses Quickly

When you first integrate a new API, the fastest way to understand its response structure is to extract all key paths at once. Rather than scrolling through potentially hundreds of lines of JSON, you can paste the response into JSON Keyper and get a complete flat list of every field path in seconds.

This is especially useful for:

  • Discovering which fields are available before writing any code
  • Finding the exact path to a deeply nested field
  • Comparing response shapes between different API versions
  • Documenting an API's response structure for your team

Understand Any API Response Instantly

Paste your API response into JSON Keyper and get a complete map of every key path - including nested objects and array indices.

Open JSON Keyper