← APIS

Running a REST API: caching, versioning, deprecation and release

How to run a REST API after launch: Cache-Control and ETags, URL versioning, a five-step deprecation process, OpenAPI documentation and a pre-release checklist.

On this page
  1. Cache-Control
  2. ETags and conditional requests
  3. When to version
  4. URL path versioning
  5. Deprecating an endpoint
  6. Documentation
  7. Pre-release checklist

This page covers what keeps a REST API working for clients once it is live: caching, versioning, retiring old endpoints, documentation, and a checklist to run before each release.

Cache-Control

The Cache-Control header tells the client and any proxy in between whether to store a response and for how long:

Cache-Control: public, max-age=3600

This response can be stored by any cache and stays fresh for 3600 seconds, one hour. Use private in place of public when the response holds user-specific data that a shared proxy must not store. For data that should never be cached, such as authenticated data that changes often, say so:

Cache-Control: no-store

Cache GET responses for data that changes slowly: catalogues, public content and reference data. Do not cache responses to POST, PUT, PATCH or DELETE.

The Bullion API is a fair example of the trade. Its metal prices are cached and refreshed hourly, so they are not live, and its data page says so. Whatever a cache does, tell clients how fresh the data is.

ETags and conditional requests

An ETag is an identifier for one version of a response, often a hash of the body:

ETag: "a1b2c3d4e5f6"

The client sends it back on the next request for the same resource:

If-None-Match: "a1b2c3d4e5f6"

If nothing has changed, the server returns 304 Not Modified with an empty body and the client uses its copy. If the resource has changed, the server returns 200 OK with the new body and a new ETag.

The server still receives the request, but it skips building and sending the full response. For endpoints that clients poll often, the empty 304 bodies save a lot of transfer.

When to version

Additive changes do not need a new version: new endpoints, new optional request fields and new response fields are all backwards compatible. Save a new major version for changes that break clients:

  • Removing an endpoint or a required field.
  • Changing the shape of a response.
  • Changing authentication requirements.
  • Renaming resources or moving them to a new path.

URL path versioning

Put the version in the path:

GET /v1/users
GET /v2/users

The version then shows up in logs, browser history and documentation, and it does not depend on custom headers that some HTTP clients handle badly. The Bullion API uses this style, for example GET /v1/gold/real-price.

Once v1 is published, treat it as a contract. Keep serving it unchanged until every known client has moved, and watch its traffic so you know when that has happened.

Deprecating an endpoint

Removing an endpoint without warning breaks integrations. Follow these steps.

  1. Signal it in the API. Send the IETF Sunset header with the date the endpoint stops working:

    Sunset: Sat, 01 Jan 2027 00:00:00 GMT

    If your envelope has a meta object, add a flag there as well:

    {
        "meta": {
            "deprecated": true,
            "sunset_date": "2027-01-01",
            "replacement": "/v2/users"
        }
    }
  2. Announce the timeline. Tell every known client by email, developer portal or release notes, at least six months ahead. Say which endpoints are going, the exact sunset date, what to use instead, and link the migration guide.

  3. Write the migration guide. Map each old endpoint to its replacement, with before-and-after requests and responses, and point out any change in behaviour.

  4. Monitor usage. Track traffic to the old endpoints up to the sunset date. If usage is still significant near the deadline, contact the remaining clients directly. Extending the deadline is better than breaking someone's integration.

  5. Remove it. Once traffic reaches zero and the remaining clients have confirmed, remove the endpoint. Return 410 Gone for a while afterwards, so late callers get a clear answer and not a 404.

Documentation

Keep an OpenAPI spec, openapi.yaml or openapi.json, in the repository as the one authoritative description of the API. Swagger UI, Redoc and Stoplight can render it as interactive docs.

Each endpoint in the spec needs:

  • A description of what it does and when to use it.
  • Every path parameter, query parameter, header and request body field, with types, constraints and defaults.
  • An example of the success response, showing the exact JSON.
  • An example for each error code it can return, such as 400, 401, 404 and 422, in your standard error envelope.

A spec that drifts from the code misleads the people reading it. Update it in the same change as the code:

  • Generate it from code annotations where the framework supports it. Laravel has packages for this and FastAPI does it natively.
  • Validate responses against the spec in your tests, so drift fails the build.
  • Review the spec as part of each pull request.

Keep a changelog of every version, what it changed and any migration steps. Clients rely on it most when an endpoint is deprecated or removed.

Pre-release checklist

Run through this before promoting any version to production. The design and response rules it checks are in REST API design rules for URLs, methods and fields and REST API response shape, pagination and errors. The security items are in REST API security, authentication and rate limiting.

Documentation:

  • The OpenAPI spec describes every endpoint, parameter and response shape.
  • Each endpoint has request, success and error examples.
  • The spec validates against the OpenAPI schema.
  • The changelog has an entry for this release.

Correctness and consistency:

  • Status codes are correct and the same across endpoints.
  • Every error, including validation errors, uses the error envelope.
  • Field names follow the agreed casing everywhere.
  • Datetimes are UTC ISO 8601 with a trailing Z.
  • Every list endpoint is paginated, with a sensible default and a documented maximum.

Security:

  • Every endpoint is served over HTTPS.
  • Strict-Transport-Security and X-Content-Type-Options are set.
  • Authentication is enforced on every endpoint that needs it.
  • Input validation rejects malformed requests before they reach application code.

Performance and reliability:

  • Rate limiting is on and the rate limit headers are in responses.
  • Cacheable GET endpoints send Cache-Control and ETag.
  • POST endpoints where a duplicate would do harm accept an idempotency key.

Testing:

  • Automated tests cover the success path of every endpoint.
  • Tests cover the expected failures: invalid input, missing authentication, not found and rate limit exceeded.
  • Edge cases are tested, including empty collections, the maximum page size and concurrent requests.

Observability:

  • Every response has a unique request ID.
  • Server logs capture errors with full context, and responses never include stack traces.
  • Monitoring and alerts are set up for error rates and latency.