Skip to content

API Deprecation — Complete Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you'll learn about API Deprecation. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

API deprecation is the Process of marking an API version or feature as no longer recommended for use, while communicating a timeline for its eventual removal to clients.

What You'll Learn

By the end of this lesson, you will implement deprecation warnings, design sunset policies, and manage the full lifecycle of API version deprecation.

Why It Matters

Proper deprecation gives clients time to migrate. Abrupt removal breaks integrations, erodes trust, and creates emergency work for client teams.

Real-World Use

Twilio deprecates API versions with 12+ months notice. They send deprecation emails, add headers to responses, and provide Migration guides.

Deprecation Lifecycle

flowchart LR
    Active[Active] --> Deprecated[Deprecated]
    Deprecated --> Sunset[Sunset Window]
    Sunset --> Removed[Removed]
    Active -.->|Critical Fix| Deprecated
    Deprecated -.->|Client Request| Deprecated

Deprecation Header Middleware

# deprecation.py
from datetime import datetime, timedelta
from typing import Dict, Optional

class DeprecationMiddleware:
    def __init__(self):
        self.deprecated_versions: Dict[str, Dict] = {}

    def deprecate(self, version: str, sunset_date: str,
                  migration_url: str, deprecation_date: str = None):
        self.deprecated_versions[version] = {
            "sunset": sunset_date,
            "migration_url": migration_url,
            "deprecation_date": deprecation_date or datetime.now().isoformat(),
        }

    def add_headers(self, version: str, status_code: int = 200) -> Dict:
        info = self.deprecated_versions.get(version)
        if not info:
            return {}

        headers = {
            "Deprecation": info["deprecation_date"],
            "Sunset": info["sunset"],
            "Link": f'<{info["migration_url"]}>; rel="deprecation"',
        }

        sunset_dt = datetime.fromisoformat(info["sunset"])
        days_left = (sunset_dt - datetime.now()).days

        if days_left < 30:
            headers["Warning"] = f'299 - "This version will be removed in {days_left} days. Migrate to the new version."'

        return headers

mw = DeprecationMiddleware()
mw.deprecate("v1", sunset_date="2026-12-31", migration_url="/docs/migrate-v2")

responses = [
    ("v1", 200),
    ("v2", 200),
    ("v1", 200),
]

for version, status in responses:
    headers = mw.add_headers(version)
    if headers:
        print(f"Version {version}: Deprecation={headers.get('Deprecation')}")
        print(f"               Sunset={headers.get('Sunset')}")
        print(f"               Link={headers.get('Link')}")
    else:
        print(f"Version {version}: No deprecation headers (current version)")

Expected output:

Version v1: Deprecation=<current-date>
               Sunset=2026-12-31
               Link=</docs/migrate-v2>; rel="deprecation"
Version v2: No deprecation headers (current version)
Version v1: Deprecation=<current-date>
               Sunset=2026-12-31
               Link=</docs/migrate-v2>; rel="deprecation"

Deprecation Logging and Metrics

# deprecation_logger.py
from datetime import datetime
from typing import Dict, Optional

class DeprecationLogger:
    def __init__(self):
        self.log: list = []

    def record_call(self, version: str, endpoint: str, client_id: str):
        self.log.append({
            "version": version,
            "endpoint": endpoint,
            "client_id": client_id,
            "timestamp": datetime.now().isoformat(),
        })

    def get_deprecated_usage(self, start_date: Optional[str] = None) -> Dict:
        result = {}
        for entry in self.log:
            ver = entry["version"]
            if ver not in result:
                result[ver] = {"count": 0, "clients": set(), "endpoints": set()}
            result[ver]["count"] += 1
            result[ver]["clients"].add(entry["client_id"])
            result[ver]["endpoints"].add(entry["endpoint"])
        for ver in result:
            result[ver]["clients"] = list(result[ver]["clients"])
            result[ver]["endpoints"] = list(result[ver]["endpoints"])
        return result

logger = DeprecationLogger()
logger.record_call("v1", "/users", "client-a")
logger.record_call("v1", "/users", "client-b")
logger.record_call("v2", "/users", "client-a")

usage = logger.get_deprecated_usage()
for ver, info in usage.items():
    print(f"{ver}: {info['count']} calls, {info['clients']}")

Expected output:

v1: 2 calls, ['client-a', 'client-b']
v2: 1 calls, ['client-a']

Migration Guide Generator

# migration_guide.py
from typing import Dict, List

class MigrationGuide:
    def __init__(self, old_version: str, new_version: str):
        self.old_version = old_version
        self.new_version = new_version
        self.changes: List[Dict] = []

    def add_change(self, endpoint: str, old: str, new: str, breaking: bool = False):
        self.changes.append({
            "endpoint": endpoint,
            "old": old,
            "new": new,
            "breaking": breaking,
        })

    def generate(self) -> str:
        lines = [f"# Migration Guide: {self.old_version} -> {self.new_version}"]
        breaking = [c for c in self.changes if c["breaking"]]
        additive = [c for c in self.changes if not c["breaking"]]

        if breaking:
            lines.append(f"\n## Breaking Changes ({len(breaking)})")
            for c in breaking:
                lines.append(f"### {c['endpoint']}")
                lines.append(f"- Old: {c['old']}")
                lines.append(f"- New: {c['new']}")

        if additive:
            lines.append(f"\n## Additive Changes ({len(additive)})")
            for c in additive:
                lines.append(f"- {c['endpoint']}: {c['new']}")
        lines.append(f"\n## Timeline\n- {self.old_version} deprecated: now\n- {self.old_version} sunset: +6mo\n- {self.old_version} removed: +12mo")

        return "\n".join(lines)

guide = MigrationGuide("v1", "v2")
guide.add_change("GET /users", "returns array", "returns object with data[] and meta", breaking=True)
guide.add_change("POST /users", "accepts name only", "accepts name, email (optional)", breaking=False)
guide.add_change("GET /users/{id}", "200", "200 with _version field", breaking=False)

print(guide.generate())

Expected output:

# Migration Guide: v1 -> v2

## Breaking Changes (1)
### GET /users
- Old: returns array
- New: returns object with data[] and meta

## Additive Changes (2)
- POST /users: accepts name, email (optional)
- GET /users/{id}: 200 with _version field

## Timeline
- v1 deprecated: now
- v1 sunset: +6mo
- v1 removed: +12mo

Common Mistakes

1. No Deprecation Warning

Removing an API version without any warning period or deprecation headers. Always give at least 6 months notice.

2. No Migration Path

Deprecating without providing a clear migration guide. Clients must know exactly what to change.

3. No Sunset Date

Vague deprecation like "will be removed eventually". Give a specific date for removal.

4. Deprecating Too Aggressively

Major version changes too frequently. Aim for 12-24 months between breaking changes for public APIs.

5. Not Monitoring Deprecated Usage

Without monitoring, you do not know which clients still use deprecated versions and when it is safe to remove them.

Practice Questions

1. What HTTP header indicates API deprecation?

The Deprecation header, which contains the date the version was deprecated.

2. How long should you support a deprecated version?

At minimum 6 months for public APIs. 12 months is better for enterprise APIs.

3. What is the Sunset header?

It indicates when the API version will be completely removed. Clients should migrate before this date.

4. How do you communicate deprecation to clients?

Deprecation headers, email notifications, dashboard warnings, API documentation notices, and migration guides.

Challenge

Create a full deprecation lifecycle manager that tracks all deprecated versions, monitors their usage, sends alerts to affected clients, and enforces sunset dates.

FAQ

Can I deprecate a single endpoint instead of the whole version?

Yes. Use the Deprecation header per endpoint or feature flag. Some endpoints may be deprecated while others remain active.

What happens after the sunset date?

The endpoint returns 410 Gone or 404 Not Found. Document what status code clients will receive after sunset.

Should I remove deprecated code or keep it?

Remove it after the sunset date to reduce maintenance burden. If keeping it, enforce that it returns errors.

How do I handle enterprise clients who need extended support?

Offer extended support contracts. Charge a premium for maintaining old versions beyond the standard sunset period.

Can I deprecate an API version without a replacement?

Avoid this. Clients depend on the functionality. Deprecation should only happen when a functional replacement exists.

Mini Project: Deprecation Manager

# deprecation_manager.py
from datetime import datetime, timedelta

class DeprecationManager:
    def __init__(self):
        self.versions = {}

    def schedule_deprecation(self, version: str, notice_months: int = 6):
        now = datetime.now()
        self.versions[version] = {
            "deprecated_at": now,
            "sunset": now + timedelta(days=notice_months * 30),
            "removed": False,
        }

    def check_status(self, version: str) -> str:
        info = self.versions.get(version)
        if not info:
            return "not_deprecated"
        if info["removed"]:
            return "removed"
        if datetime.now() > info["sunset"]:
            info["removed"] = True
            return "removed"
        if datetime.now() > info["deprecated_at"] + timedelta(days=30):
            return "ending_soon"
        return "deprecated"

mgr = DeprecationManager()
mgr.schedule_deprecation("v1")
print(f"v1 status: {mgr.check_status('v1')}")
print(f"v2 status: {mgr.check_status('v2')}")

Expected output:

v1 status: deprecated
v2 status: not_deprecated

What's Next

You understand API deprecation. Next, learn about sunset headers, then explore versioning database schemas.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro