ScreenshotNeo

BlogHow-to

How to Deprecate a REST API

Deprecate a REST API without surprising clients: headers, migration docs, monitoring, sunset planning, rollout steps, code, and troubleshooting.

By the ScreenshotNeo team1 October 20268 min read

Deprecating a REST API is a managed transition. Tell consumers which resource or version should no longer be used, publish a supported replacement and migration instructions, signal the lifecycle state in responses, monitor real usage, help remaining consumers, and retire the old interface only according to a documented plan.

Deprecation alone does not change how a resource behaves. RFC 9745 defines the Deprecation response header as a lifecycle signal; the resource can continue responding normally. A separate Sunset header communicates that a URI is expected to become unresponsive at a specified time, but it does not guarantee a particular response after that date. See RFC 9745 and RFC 8594.

Deprecation and sunset are different

Signal Meaning What it does not mean
Deprecation The resource is, or will be, deprecated. Its date may be in the past or future. It does not disable the resource or alter its behavior.
Sunset The URI is expected to become unresponsive at a specified future time. It does not guarantee shutdown, a status code, or a specific response body.

Use Sunset for the expected retirement stage, not merely to say that an API is no longer preferred. If both headers are present, the sunset timestamp must not be earlier than the deprecation date.

A complete deprecation lifecycle

1. Define the scope

Write down exactly what is changing: one endpoint, a resource family, a field, a feature, or an entire API version. A header on one response can be ambiguous when the intended scope is broader, so document the scope explicitly.

2. Inventory consumers before announcing

Use gateway logs, API keys, account-level metrics, and request labels to identify callers. Record a baseline by endpoint, version, client, and account. You need this baseline to measure migration and to find consumers that never read response headers.

3. Choose and document the replacement

Publish the replacement URI or version, request and response examples, authentication differences, changed fields, pagination rules, error behavior, and an ordered migration checklist. Include breaking-change notes and a date for each required action. A replacement link in a response is useful, but it cannot replace human-readable documentation.

4. Set dates that match your obligations

Choose a deprecation date and, if retirement is planned, an expected sunset date after reviewing contracts, support commitments, regulatory requirements, and the effort required from known consumers. RFCs define header semantics, not a universal grace period.

5. Announce through channels consumers receive

Combine runtime headers with your changelog, developer portal, email, dashboard notices, support process, and account contacts as appropriate. Automated clients can see headers while the people responsible for upgrades may never inspect them.

6. Add runtime signals

For affected responses, send Deprecation and a Link to the migration information. Add Sunset only when you have an expected unresponsive date. The date syntaxes differ:

  • Deprecation uses an HTTP Structured Field Date, such as Deprecation: @1688169599.
  • Sunset uses an HTTP-date, such as Sunset: Wed, 31 Dec 2025 23:59:59 GMT.
HTTP/1.1 200 OK
Content-Type: application/json
Deprecation: @1767225599
Sunset: Thu, 31 Dec 2026 23:59:59 GMT
Link: <https://api.example.com/docs/migrate-v1>; rel="deprecation"; type="text/html"
Link: <https://api.example.com/v2/orders>; rel="successor-version"

{"id":"ord_123","status":"paid"}

Choose relation types and links that your documentation defines consistently. A deprecation link should explain the change and migration path; a replacement link should identify the supported successor.

7. Observe migration and help lagging clients

Track requests to the old surface continuously. Segment traffic by consumer, version, endpoint, and error rate. Contact high-volume or business-critical consumers where possible. Do not infer that a client migrated merely because it supports, ignores, or stops reporting a header.

8. Retire deliberately

At the announced date, apply the behavior you documented. That may be an error response, a routing change, or another controlled outcome. RFC 8594 warns that Sunset itself does not promise what happens afterward. If you return 410 Gone, document that as your provider policy; it is not a universal requirement.

Minimal implementation examples

Express (Node.js)

import express from 'express';

const app = express();
const deprecationEpoch = Math.floor(Date.parse('2026-06-30T23:59:59Z') / 1000);
const sunsetDate = new Date('2026-12-31T23:59:59Z').toUTCString();

app.get('/v1/orders/:id', (req, res) => {
  res.set('Deprecation', `@${deprecationEpoch}`);
  res.set('Sunset', sunsetDate);
  res.set('Link', '<https://api.example.com/docs/migrate-v2>; rel="deprecation"; type="text/html", <https://api.example.com/v2/orders>; rel="successor-version"');
  res.json({ id: req.params.id, status: 'paid' });
});

app.listen(3000, () => console.log('API listening on port 3000'));

FastAPI (Python)

from datetime import datetime, timezone
from fastapi import FastAPI, Response

app = FastAPI()
DEPRECATION = int(datetime(2026, 6, 30, 23, 59, 59, tzinfo=timezone.utc).timestamp())
SUNSET = 'Thu, 31 Dec 2026 23:59:59 GMT'

@app.get('/v1/orders/{order_id}')
def get_order(order_id: str, response: Response):
    response.headers['Deprecation'] = f'@{DEPRECATION}'
    response.headers['Sunset'] = SUNSET
    response.headers['Link'] = ('<https://api.example.com/docs/migrate-v2>; '
                                'rel="deprecation"; type="text/html", '
                                '<https://api.example.com/v2/orders>; rel="successor-version"')
    return {'id': order_id, 'status': 'paid'}

Go net/http

package main

import (
  "fmt"
  "net/http"
)

func order(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Deprecation", "@1782863999")
  w.Header().Set("Sunset", "Thu, 31 Dec 2026 23:59:59 GMT")
  w.Header().Set("Link", `<https://api.example.com/docs/migrate-v2>; rel="deprecation"; type="text/html", <https://api.example.com/v2/orders>; rel="successor-version"`)
  w.Header().Set("Content-Type", "application/json")
  fmt.Fprint(w, `{"id":"ord_123","status":"paid"}`)
}

func main() {
  http.HandleFunc("/v1/orders/", order)
  http.ListenAndServe(":8080", nil)
}

Inspect a response with cURL

curl -i https://api.example.com/v1/orders/ord_123

Version-wide retirement: a concrete model

GitHub’s REST API documentation illustrates one provider-specific approach: clients select an API version with X-GitHub-Api-Version, review a breaking-change changelog, receive Deprecation and Sunset signals as a version approaches closure, and can receive 410 Gone after the support window. Use this as an example of connecting version selection, documentation, headers, and retirement behavior—not as a policy every API must copy. See GitHub’s API versioning documentation.

Operational checklist

  • Scope is named in documentation and dashboards.
  • Known consumers and baseline traffic are recorded.
  • A supported replacement and migration guide are published.
  • Breaking changes, examples, and authentication differences are explicit.
  • Deprecation uses Structured Field Date syntax.
  • Sunset uses HTTP-date syntax and is later than deprecation.
  • Changelog, email, dashboard, and support notifications are planned.
  • Traffic to the old surface is monitored throughout the transition.
  • Lagging or critical consumers have an escalation path.
  • Post-sunset response behavior is documented and observable.

Common errors and fixes

Symptom Cause Fix
Clients receive a deprecation warning but requests break immediately Deprecation was treated as a shutdown switch. Keep behavior compatible during the transition; announce any separate breaking change.
A Sunset header says “not recommended” The header is being used for preference rather than expected unresponsiveness. Use Deprecation for that stage; reserve Sunset for planned retirement.
Dates are rejected or parsed inconsistently The two headers have different date formats. Use Structured Field Date for Deprecation and HTTP-date for Sunset.
Consumers cannot find migration instructions The response has no documentation link or the link is not stable. Publish a durable migration page and include it with Link and in release communications.
Traffic appears to be zero, but customers report failures Monitoring omitted an alias, proxy, cached route, or an uninstrumented client. Reconcile gateway, application, account, and support data before retirement.
Clients keep calling after the sunset date The date is a signal, not an enforcement mechanism, and some clients never inspect headers. Contact owners, continue measured support where required, and apply the documented retirement behavior only when ready.
Every endpoint returns the same lifecycle headers Middleware scope is broader than the announced change. Attach headers only to affected resources or clearly document the version-wide scope.

Performance, reliability, and cost considerations

Adding static response headers has negligible processing cost compared with request handling, but migration monitoring can add log volume and metric cardinality. Keep labels bounded (for example, account or client IDs with controlled cardinality), sample high-volume success traffic when appropriate, and retain complete records for errors and retirement decisions.

Reliability depends on making the migration path available even when the old endpoint is degraded. Host documentation independently when possible, version it, and monitor the replacement as well as the deprecated route. Treat dates as configuration with review and change control so a clock or timezone mistake cannot retire a resource unexpectedly.

There is no standards-defined universal number of days between deprecation and sunset. Set the interval from consumer impact, migration complexity, observability, contractual commitments, and operational behavior. Document the decision and revisit it when usage data changes.

Or skip the browser setup

If your migration work needs repeatable screenshots of API documentation, dashboards, or test pages, ScreenshotNeo provides a single website screenshot request. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the result with X-Page-Verdict and X-Billed headers. Its MCP server includes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. Every plan includes the features; 1,000 screenshots per month are free without a card, and paid plans start at $5 for 3,000 shots.

See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://api.example.com/docs/migrate-v2 -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://api.example.com/docs/migrate-v2"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://api.example.com/docs/migrate-v2' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Create a free ScreenshotNeo account with 1,000 screenshots per month and no card.

FAQ

Does deprecation require an error response?

No. Deprecation is a lifecycle signal and does not itself change resource behavior. Choose and document any error behavior separately.

Can I send only Sunset?

You can, but it communicates expected unresponsiveness rather than the earlier “no longer preferred” stage. Use Deprecation when that earlier signal is useful.

Is a sunset date legally binding?

The RFC defines the header semantics, not your contracts or legal duties. Review provider-specific commitments and applicable law before setting dates.

Should every client be forced to upgrade at once?

Only when your documented support and operational plan requires it. Monitor actual usage and transition consumers in a controlled way.

What should clients do when they receive these headers?

Read the linked migration documentation, stop creating new dependencies on the resource, schedule the replacement work, and alert the owning team before the expected sunset.