response-formats.md 4.6 KB

Response Formats

Error responses, versioning, bulk operations, and HATEOAS patterns.

Error Response Format

Standard Structure

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input data",
    "details": [
      {"field": "email", "message": "Invalid email format"},
      {"field": "age", "message": "Must be 18 or older"}
    ],
    "request_id": "abc-123",
    "documentation_url": "https://api.example.com/docs/errors#validation"
  }
}

Minimal Error

{
  "error": {
    "code": "NOT_FOUND",
    "message": "User not found"
  }
}

Validation Errors (422)

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "field": "email",
        "code": "INVALID_FORMAT",
        "message": "Must be a valid email address"
      },
      {
        "field": "password",
        "code": "TOO_SHORT",
        "message": "Must be at least 8 characters"
      }
    ]
  }
}

RFC 7807 Problem Details

{
  "type": "https://api.example.com/errors/validation",
  "title": "Validation Error",
  "status": 422,
  "detail": "The request body contains invalid data",
  "instance": "/users/123",
  "errors": [
    {"pointer": "/email", "detail": "Invalid format"}
  ]
}

Versioning Strategies

URI Versioning (Most Common)

GET /v1/users
GET /v2/users

Pros: Clear, easy to route, cacheable Cons: URL pollution, hard to deprecate

Header Versioning

GET /users
Accept: application/vnd.api.v1+json

Pros: Clean URLs Cons: Harder to test, less visible

Query Parameter

GET /users?version=1
GET /users?api-version=2024-01-15

Pros: Easy to implement Cons: Less RESTful, affects caching

Date-Based Versioning

GET /users
API-Version: 2024-01-15

Pros: Fine-grained, Stripe-style Cons: Complex to maintain


Bulk Operations

Batch Endpoint

POST /batch
Content-Type: application/json

{
  "operations": [
    {"method": "POST", "path": "/users", "body": {"name": "Alice"}},
    {"method": "PATCH", "path": "/users/123", "body": {"status": "active"}},
    {"method": "DELETE", "path": "/users/456"}
  ]
}

Response:

{
  "results": [
    {"status": 201, "body": {"id": 789, "name": "Alice"}},
    {"status": 200, "body": {"id": 123, "status": "active"}},
    {"status": 204, "body": null}
  ]
}

Bulk Create

POST /users/bulk
Content-Type: application/json

[
  {"name": "Alice", "email": "alice@example.com"},
  {"name": "Bob", "email": "bob@example.com"}
]

Response:

{
  "created": 2,
  "items": [
    {"id": 123, "name": "Alice"},
    {"id": 124, "name": "Bob"}
  ]
}

Bulk Create with Partial Failure

{
  "created": 1,
  "failed": 1,
  "items": [
    {"id": 123, "name": "Alice", "status": "created"}
  ],
  "errors": [
    {"index": 1, "error": {"code": "DUPLICATE", "message": "Email exists"}}
  ]
}

Bulk Delete

DELETE /users/bulk
Content-Type: application/json

{"ids": [1, 2, 3, 4, 5]}

Response:

{
  "deleted": 5
}

HATEOAS Links

Single Resource

{
  "id": 123,
  "name": "Alice",
  "email": "alice@example.com",
  "_links": {
    "self": {"href": "/users/123"},
    "orders": {"href": "/users/123/orders"},
    "profile": {"href": "/users/123/profile"},
    "update": {"href": "/users/123", "method": "PATCH"},
    "delete": {"href": "/users/123", "method": "DELETE"}
  }
}

Collection with Pagination

{
  "data": [
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"}
  ],
  "meta": {
    "total": 150,
    "page": 2,
    "per_page": 20,
    "total_pages": 8
  },
  "_links": {
    "self": {"href": "/users?page=2"},
    "first": {"href": "/users?page=1"},
    "prev": {"href": "/users?page=1"},
    "next": {"href": "/users?page=3"},
    "last": {"href": "/users?page=8"}
  }
}

HAL Format

{
  "_embedded": {
    "users": [
      {"id": 1, "name": "Alice", "_links": {"self": {"href": "/users/1"}}},
      {"id": 2, "name": "Bob", "_links": {"self": {"href": "/users/2"}}}
    ]
  },
  "_links": {
    "self": {"href": "/users?page=1"},
    "next": {"href": "/users?page=2"}
  },
  "page": 1,
  "total": 100
}

JSON:API Format

{
  "data": [
    {
      "type": "users",
      "id": "1",
      "attributes": {"name": "Alice"},
      "relationships": {
        "orders": {"links": {"related": "/users/1/orders"}}
      },
      "links": {"self": "/users/1"}
    }
  ],
  "links": {
    "self": "/users?page=1",
    "next": "/users?page=2"
  },
  "meta": {"total": 100}
}