API Design & Best Practices

API Deprecation Strategy: Sunset Headers and Migration Paths

How to gracefully deprecate API endpoints using Sunset and Deprecation headers (RFC 8594), versioned migration guides, and structured client communication.

Why Deprecation Needs a Strategy

Removing or changing an API endpoint without warning breaks integrations that took clients weeks to build. Enterprise contracts, SLAs, and developer trust all depend on predictable, respectful deprecation. A good deprecation strategy protects both sides: you can evolve your API, and clients get enough runway to migrate.

The core challenge: you cannot force clients to update. You can only communicate clearly, make migration easy, and eventually enforce the sunset after adequate notice.

The Sunset Header (RFC 8594)

RFC 8594 defines the Sunset header — a machine-readable timestamp indicating when a resource will become unavailable:

Sunset: Sat, 01 Aug 2026 00:00:00 GMT

This lets clients programmatically detect deprecation and trigger alerts in their own monitoring pipelines. Any API client worth its salt will log or alert on a Sunset header — especially if it is within a short window.

Deprecation Header (Companion)

The Deprecation header, defined in the same RFC 8594 ecosystem (and separately in IETF draft-ietf-httpapi-deprecation-header), marks the date when deprecation started:

Deprecation: Sat, 01 Mar 2026 00:00:00 GMT
Sunset: Sat, 01 Aug 2026 00:00:00 GMT
Link: <https://docs.example.com/api/v3/migration>; rel="deprecation"

The Link header with rel="deprecation" points to your migration guide — clients that follow RFC 8594 can auto-discover the migration documentation.

Implementation Example

# Django middleware to add Sunset headers to deprecated endpoints
from datetime import datetime, timezone
from email.utils import format_datetime

DEPRECATED_ENDPOINTS = {
    '/api/v1/': {
        'sunset': datetime(2026, 8, 1, tzinfo=timezone.utc),
        'deprecation': datetime(2026, 3, 1, tzinfo=timezone.utc),
        'migration_url': 'https://docs.example.com/api/v2/migration',
    },
}

class DeprecationMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        for prefix, info in DEPRECATED_ENDPOINTS.items():
            if request.path.startswith(prefix):
                response['Sunset'] = format_datetime(info['sunset'], usegmt=True)
                response['Deprecation'] = format_datetime(
                    info['deprecation'], usegmt=True
                )
                response['Link'] = (
                    f'<{info["migration_url"]}>; rel="deprecation"'
                )
        return response

Communication Plan

Headers are machine-readable but humans need more than HTTP headers. A complete communication plan includes:

Announcement Cadence

TimelineAction
T-6 monthsBlog post + changelog entry + email to all users
T-3 monthsDashboard banner for active API users
T-1 monthEmail reminder to users still on deprecated endpoint
T-2 weeksFinal warning email; support escalation for enterprise clients
T-0Sunset enforcement

Changelog and Documentation

Your migration guide should include:

  • Side-by-side old vs new endpoint comparison
  • Request/response schema diffs
  • Code samples for the most popular SDKs
  • A migration checklist developers can track
## Migrating from v1 to v2

### Changed Endpoints
| v1 | v2 |
|----|----|
| GET /api/v1/users | GET /api/v2/users |
| POST /api/v1/users | POST /api/v2/users |

### Response Schema Changes
- `user.name` is now `user.full_name`
- `user.created` (epoch int) is now `user.created_at` (ISO 8601 string)

Migration Tooling

Redirect Proxies

During the deprecation window, serve the old endpoint via a compatibility proxy that translates requests to the new format. This lets clients migrate lazily:

# v1 endpoint proxies to v2 with field translation
@api_view(['GET'])
def users_v1(request):
    # Add deprecation headers
    response = users_v2(request)  # delegate to v2 logic
    # Translate v2 response schema back to v1
    data = response.data
    for user in data:
        user['name'] = user.pop('full_name', '')
    return Response(data, headers={
        'Sunset': 'Sat, 01 Aug 2026 00:00:00 GMT',
    })

Compatibility Shims

For SDK-based APIs, publish a compatibility shim that maps old method signatures to new ones and emits deprecation warnings in the client's language:

import warnings

class APIClientV1:
    def get_user(self, user_id: int) -> dict:
        warnings.warn(
            'get_user() is deprecated. Use client.users.get() instead. '
            'This method will be removed on 2026-08-01.',
            DeprecationWarning,
            stacklevel=2,
        )
        return self._v2_client.users.get(user_id)

Measuring Adoption

Tracking Deprecated Endpoint Usage

# Log every hit to deprecated endpoints for migration tracking
import structlog

logger = structlog.get_logger(__name__)

class DeprecationMiddleware:
    def __call__(self, request):
        response = self.get_response(request)
        if self._is_deprecated(request.path):
            logger.warning(
                'deprecated_endpoint_hit',
                path=request.path,
                client_id=request.META.get('HTTP_X_CLIENT_ID'),
                days_until_sunset=self._days_remaining(request.path),
            )
        return response

Build a dashboard showing:

  • Daily request volume to deprecated endpoints by client ID
  • Migration progress (% reduction since announcement)
  • Projected sunset feasibility based on current migration velocity

Automated Sunset Enforcement

On the sunset date, return 410 Gone with a clear error body pointing to the migration guide:

{
  "error": "endpoint_sunset",
  "message": "This endpoint was retired on 2026-08-01.",
  "migration_guide": "https://docs.example.com/api/v2/migration"
}

Use 410 Gone rather than 404 Not Found — it explicitly signals intentional removal rather than a missing resource, and is treated differently by crawlers and client retry logic.

Related Protocols

Related Glossary Terms

More in API Design & Best Practices