By Sagar Shankaran, Founder of CallSphere
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.
Key takeaways
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 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.
flowchart LR
CLIENT(["Client SDK"])
GW["API Gateway<br/>auth plus rate limit"]
APP["FastAPI app<br/>handlers and DI"]
VAL["Pydantic validation"]
SVC["Service layer<br/>business logic"]
DB[(Database)]
QUEUE[(Background queue)]
OBS[(Tracing)]
CLIENT --> GW --> APP --> VAL --> SVC
SVC --> DB
SVC --> QUEUE
SVC --> OBS
SVC --> CLIENT
style GW fill:#4f46e5,stroke:#4338ca,color:#fff
style APP fill:#f59e0b,stroke:#d97706,color:#1f2937
style DB fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
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.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
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.
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
For larger platforms, centralize version routing into a middleware that extracts the version and routes to the appropriate handler.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
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)
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
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.
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.
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

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Deploy a HIPAA-eligible voice agent on AWS App Runner: FastAPI WebSocket bridge, Bedrock Claude for reasoning, ECR auto-deploy from GitHub, VPC connector for private RDS.
Deploy a FastAPI voice agent to Render with native WebSocket support, free TLS, autoscaling, and a managed Postgres. Real working code, render.yaml, deploy on push.
Ship a production voice agent in 5 minutes on Railway: FastAPI bridge, OpenAI Realtime, Postgres for sessions, and a one-click template. No Docker knowledge required.
How to run FastAPI WebSockets in production for AI voice agents: connection managers, the broadcast bug everyone hits, JWT auth on upgrade, and Uvicorn worker tuning.
How to design tool functions that LLMs can use effectively with clear naming, enum parameters, structured responses, informative error messages, and documentation.
Synthflow's no-code builder hits walls fast — branching tools, custom auth, real CRMs. Move to a self-hosted FastAPI agent and unlock everything in 600 lines.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI