Migration Guides for APIs — Helping Consumers Transition Between Versions
In this tutorial, you'll learn about Migration Guides. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
API migration guides help consumers transition from one API version to another with clear instructions, code examples, and automated tools to minimize effort.
What You'll Learn
By the end of this lesson, you will create structured migration guides, provide side-by-side code examples, build automated migration scripts, establish consumer support channels, and track migration completion.
Why It Matters
Well-written migration guides reduce support tickets, accelerate consumer adoption of new versions, and build trust by demonstrating that you care about consumer experience.
Real-World Use
Durga Antivirus Pro provides interactive migration guides for each API version upgrade, including automated curl command translation and side-by-side response comparisons.
Migration Guide Structure
flowchart LR
Guide[Migration Guide]-->Overview[Overview & Timeline]
Guide-->Changes[Breaking Changes List]
Guide-->Examples[Code Examples]
Guide-->Tools[Migration Tools]
Guide-->Support[Support Channels]
Changes-->Old[Old Version]
Changes-->New[New Version]
Examples-->Before[Before Code]
Examples-->After[After Code]
Migration Guide Generator
Generate structured migration guides from API spec diffs.
from typing import Dict, List, Optional
from datetime import datetime
class MigrationGuide:
def __init__(self, title: str,
from_version: str,
to_version: str):
self.title = title
self.from_version = from_version
self.to_version = to_version
self.changes: List[Dict] = []
self.steps: List[str] = []
self.code_examples: List[Dict] = []
self.faqs: List[Dict] = []
def add_change(self, endpoint: str,
description: str,
impact: str,
old_behavior: str,
new_behavior: str,
severity: str = "medium"):
self.changes.append({
"endpoint": endpoint,
"description": description,
"impact": impact,
"old_behavior": old_behavior,
"new_behavior": new_behavior,
"severity": severity,
})
def add_step(self, step: str):
self.steps.append(step)
def add_code_example(self, language: str,
title: str,
before: str,
after: str):
self.code_examples.append({
"language": language,
"title": title,
"before": before,
"after": after,
})
def add_faq(self, question: str, answer: str):
self.faqs.append({"question": question,
"answer": answer})
def generate_markdown(self) -> str:
md = f"# {self.title}\n\n"
md += f"## Migration: {self.from_version} to "
md += f"{self.to_version}\n\n"
md += "## Breaking Changes\n\n"
for change in self.changes:
md += f"### {change['endpoint']}\n"
md += f"**{change['description']}**\n\n"
md += f"Impact: {change['impact']}\n\n"
md += f"Old: `{change['old_behavior']}`\n\n"
md += f"New: `{change['new_behavior']}`\n\n"
md += "## Migration Steps\n\n"
for i, step in enumerate(self.steps, 1):
md += f"{i}. {step}\n"
md += "\n## Code Examples\n\n"
for ex in self.code_examples:
md += f"### {ex['title']}\n\n"
md += f"**Before ({ex['language']}):**\n"
md += f"```{ex['language']}\n{ex['before']}\n```\n"
md += f"**After ({ex['language']}):**\n"
md += f"```{ex['language']}\n{ex['after']}\n```\n"
md += "\n## FAQ\n\n"
for faq in self.faqs:
md += f"### {faq['question']}\n{faq['answer']}\n\n"
return md
guide = MigrationGuide(
"API v1 to v2 Migration Guide",
"v1", "v2"
)
guide.add_change("/api/scan",
"Scan response format updated",
"All consumers must update response parsing",
'{ "scan_id": "abc", "status": "complete" }',
'{ "id": "abc", "state": "completed", "scan_result": {...} }',
"high"
)
guide.add_step("Replace 'scan_id' with 'id' in all requests")
guide.add_step("Replace 'status' field parsing with 'state'")
guide.add_code_example("python", "Response Parsing",
'data = response.json()\nscan_id = data["scan_id"]\nstatus = data["status"]',
'data = response.json()\nscan_id = data["id"]\nstatus = data["state"]'
)
print(guide.generate_markdown()[:500] + "...")
Automated Migration Script
Build automated scripts to help consumers migrate.
from typing import Dict, Any, Optional
import re
class MigrationTransformer:
def __init__(self, from_version: str,
to_version: str):
self.from_version = from_version
self.to_version = to_version
self.rules: list = []
def add_field_rename(self, old_name: str,
new_name: str):
self.rules.append({
"type": "rename",
"old": old_name,
"new": new_name,
})
def add_value_mapping(self, field: str,
mapping: Dict[str, str]):
self.rules.append({
"type": "value_map",
"field": field,
"mapping": mapping,
})
def transform_response(self, data: Dict) -> Dict:
result = {}
for key, value in data.items():
new_key = key
for rule in self.rules:
if rule["type"] == "rename" and rule["old"] == key:
new_key = rule["new"]
if rule["type"] == "value_map" and key == rule["field"]:
if isinstance(value, str):
value = rule["mapping"].get(value, value)
result[new_key] = value
return result
def transform_request(self, data: Dict) -> Dict:
reverse_rules = []
for rule in self.rules:
if rule["type"] == "rename":
reverse_rules.append({
"type": "rename",
"old": rule["new"],
"new": rule["old"],
})
result = {}
for key, value in data.items():
new_key = key
for rule in reverse_rules:
if rule["type"] == "rename" and rule["old"] == key:
new_key = rule["new"]
result[new_key] = value
return result
def generate_migration_script(self) -> str:
script = "#!/usr/bin/env python3\n"
script += '"""Auto-generated migration script'
script += f' {self.from_version} -> {self.to_version}'
script += '"""\n\n'
script += "def transform_v1_to_v2(data):\n"
for rule in self.rules:
if rule["type"] == "rename":
script += f' if "{rule["old"]}" in data:\n'
script += f' data["{rule["new"]}"] = '
script += f'data.pop("{rule["old"]}")\n'
script += " return data\n"
return script
transformer = MigrationTransformer("v1", "v2")
transformer.add_field_rename("scan_id", "id")
transformer.add_field_rename("status", "state")
transformer.add_value_mapping("state", {
"complete": "completed",
"pending": "pending",
"error": "failed"
})
old_response = {"scan_id": "abc", "status": "complete"}
new_response = transformer.transform_response(old_response)
print(f"Transformed: {new_response}")
print(transformer.generate_migration_script())
Migration Progress Tracking
Track which consumers have completed migration.
from typing import Dict, List, Optional, Set
from datetime import datetime
class MigrationTracker:
def __init__(self):
self.consumers: Dict[str, Dict] = {}
self.migration_log: list = []
def register_consumer(self, consumer_id: str,
current_version: str):
self.consumers[consumer_id] = {
"current_version": current_version,
"target_version": "",
"status": "not_started",
"started_at": None,
"completed_at": None,
"endpoints_tested": set(),
}
def start_migration(self, consumer_id: str,
target_version: str):
consumer = self.consumers.get(consumer_id)
if consumer:
consumer["target_version"] = target_version
consumer["status"] = "in_progress"
consumer["started_at"] = datetime.utcnow()
self.migration_log.append({
"consumer": consumer_id,
"event": "started",
"target": target_version,
"timestamp": datetime.utcnow(),
})
def mark_endpoint_tested(self, consumer_id: str,
endpoint: str):
consumer = self.consumers.get(consumer_id)
if consumer:
consumer["endpoints_tested"].add(endpoint)
def complete_migration(self, consumer_id: str):
consumer = self.consumers.get(consumer_id)
if consumer:
consumer["status"] = "completed"
consumer["completed_at"] = datetime.utcnow()
self.migration_log.append({
"consumer": consumer_id,
"event": "completed",
"timestamp": datetime.utcnow(),
})
def get_migration_report(self) -> Dict:
total = len(self.consumers)
completed = sum(
1 for c in self.consumers.values()
if c["status"] == "completed"
)
in_progress = sum(
1 for c in self.consumers.values()
if c["status"] == "in_progress"
)
return {
"total_consumers": total,
"completed": completed,
"in_progress": in_progress,
"not_started": total - completed - in_progress,
"completion_percentage": round(
completed / total * 100, 1
) if total > 0 else 0,
}
tracker = MigrationTracker()
tracker.register_consumer("partner-1", "v1")
tracker.start_migration("partner-1", "v2")
tracker.mark_endpoint_tested("partner-1", "/api/v2/scan")
tracker.complete_migration("partner-1")
print(f"Migration report: {tracker.get_migration_report()}")
Common Mistakes
Mistake 1: Too Much Information
Overwhelming consumers with every minor change. Focus on breaking changes and actionable steps.
Mistake 2: No Side-by-Side Examples
Consumers need to see what changed. Provide before-and-after code examples for every breaking change.
Mistake 3: Single Language Examples
Not all consumers use the same language. Provide examples in at least 2-3 common languages.
Mistake 4: No Automated Tools
Manual migration is error-prone. Provide automated scripts that transform requests and responses.
Mistake 5: No Support Channel
Consumers will have questions. Provide a dedicated support channel during the migration period.
Practice Questions
- What sections should a migration guide include?
- Why are side-by-side code examples important?
- How do automated migration tools help consumers?
- How do you track consumer migration progress?
- What support should you provide during migration?
Challenge
Build a migration guide system that generates guides from OpenAPI spec diffs, provides side-by-side code examples in Python and JavaScript, generates automated migration scripts, tracks consumer progress, and sends reminders to consumers who have not migrated.
FAQ
Mini Project
Build a migration guide system that generates guides from OpenAPI spec comparisons, creates side-by-side code examples in Python and JavaScript, produces automated migration scripts for request/response transformation, tracks consumer migration status, and provides a dashboard showing migration progress.
What's Next
Learn about Deprecation and Sunset Headers for programmatic version communication, or explore Versioning REST APIs for REST-specific versioning patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro