API Versioning Strategies for AI Agent Platforms: URL, Header, and Content Negotiation
Explore URL-based, header-based, and content negotiation approaches to API versioning for AI agent platforms. Learn backward compatibility patterns, deprecation workflows, and migration strategies with FastAPI examples.
Why API Versioning Is Critical for AI Agent Platforms
AI agent platforms evolve rapidly. New model capabilities require new parameters. Tool call formats change. Response structures expand. Without versioning, every change risks breaking existing agent integrations. A broken integration means an agent silently fails, produces incorrect results, or crashes entirely — with no human in the loop to catch the error.
Versioning lets you evolve your API while giving consumers a stable contract. The three primary approaches — URL path versioning, header versioning, and content negotiation — each have distinct tradeoffs in discoverability, flexibility, and cacheability.
URL Path Versioning: The Most Common Approach
URL path versioning embeds the version number directly in the URL. It is the approach used by OpenAI (/v1/chat/completions), Stripe (/v1/charges), and most major APIs.
from fastapi import FastAPI, APIRouter
app = FastAPI()
# Version 1 router
v1_router = APIRouter(prefix="/v1")
@v1_router.post("/chat/completions")
async def v1_chat_completions(request: dict):
"""V1: Returns flat response with 'text' field."""
return {
"id": "resp_001",
"text": "Hello from v1",
"model": request.get("model", "gpt-4o"),
"usage": {"prompt_tokens": 10, "completion_tokens": 5},
}
# Version 2 router
v2_router = APIRouter(prefix="/v2")
@v2_router.post("/chat/completions")
async def v2_chat_completions(request: dict):
"""V2: Returns structured response with 'choices' array."""
return {
"id": "resp_001",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "Hello from v2"},
"finish_reason": "stop",
}
],
"model": request.get("model", "gpt-4o"),
"usage": {"prompt_tokens": 10, "completion_tokens": 5},
}
app.include_router(v1_router)
app.include_router(v2_router)
URL versioning is highly discoverable — you can see the version in every request — and works perfectly with caching, load balancing, and monitoring. The downside is URL proliferation: every version multiplies your route count.
Header-Based Versioning: Cleaner URLs
Header versioning uses a custom HTTP header to specify the desired API version, keeping URLs clean and version-independent.
from fastapi import Header, HTTPException
@app.post("/chat/completions")
async def chat_completions(
request: dict,
x_api_version: str = Header("2024-01-01", alias="X-API-Version"),
):
if x_api_version == "2024-01-01":
return format_v1_response(request)
elif x_api_version == "2025-06-01":
return format_v2_response(request)
else:
raise HTTPException(
status_code=400,
detail=f"Unsupported API version: {x_api_version}",
)
def format_v1_response(request: dict) -> dict:
return {"text": "Hello", "model": request.get("model")}
def format_v2_response(request: dict) -> dict:
return {
"choices": [{"message": {"content": "Hello"}}],
"model": request.get("model"),
}
Stripe uses a hybrid approach: URL path for major versions (/v1/) and a Stripe-Version header for minor, date-based versions. This is a powerful pattern for AI agent platforms that need fine-grained version control.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
Content Negotiation: The REST Purist Approach
Content negotiation uses the Accept header with vendor-specific media types to indicate the desired version. It is the most RESTful approach but also the least commonly used in practice.
from fastapi import Request, HTTPException
@app.post("/chat/completions")
async def chat_completions_negotiate(request: Request):
body = await request.json()
accept = request.headers.get("Accept", "application/json")
if "application/vnd.agentapi.v2+json" in accept:
return format_v2_response(body)
elif "application/vnd.agentapi.v1+json" in accept:
return format_v1_response(body)
else:
return format_v2_response(body) # default to latest
Implementing a Version Router
For larger platforms, centralize version routing into a middleware that extracts the version and routes to the appropriate handler.
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
class VersionMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Extract version from header, defaulting to latest
version = request.headers.get("X-API-Version", "2025-06-01")
request.state.api_version = version
# Add version to response headers for debugging
response = await call_next(request)
response.headers["X-API-Version"] = version
return response
app = FastAPI()
app.add_middleware(VersionMiddleware)
Deprecation and Migration Workflow
When deprecating an API version, give consumers adequate notice. Return deprecation headers in responses to old versions so agents and monitoring systems can detect them.
from datetime import date
DEPRECATED_VERSIONS = {
"2024-01-01": {
"sunset_date": "2026-06-01",
"successor": "2025-06-01",
},
}
def add_deprecation_headers(response, version: str):
if version in DEPRECATED_VERSIONS:
info = DEPRECATED_VERSIONS[version]
response.headers["Deprecation"] = "true"
response.headers["Sunset"] = info["sunset_date"]
response.headers["Link"] = (
f'</docs/migration/{info["successor"]}>; rel="successor-version"'
)
return response
FAQ
Which versioning strategy should I choose for a new AI agent API?
Start with URL path versioning. It is the most widely understood, simplest to implement, and easiest to debug. Use a single major version number (/v1/) and commit to backward compatibility within that version. If you later need finer-grained versioning within the major version, add date-based header versioning as Stripe does. Avoid content negotiation unless your consumers specifically require it.
How do I maintain backward compatibility when adding new fields?
Adding new fields to responses is always safe — clients should ignore unknown fields. Adding optional fields to request bodies is also safe. Breaking changes include removing fields, renaming fields, changing field types, and changing default behavior. When you must make breaking changes, introduce a new version and maintain the old version until consumers have migrated.
How long should I maintain deprecated API versions?
A minimum of 6 months after the deprecation announcement is standard for commercial APIs. For AI agent platforms where integrations are complex and agents may be deployed in production workflows, 12 months is safer. Monitor usage of deprecated versions and reach out to high-volume consumers directly before sunsetting.
#APIVersioning #BackwardCompatibility #FastAPI #AIPlatforms #APIDesign #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.