API Version Negotiation — How Clients and Servers Agree on Versions
In this tutorial, you'll learn about Version Negotiation. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
API version negotiation is the Process by which client and server agree on which API version to use, balancing client capabilities with server support.
What You'll Learn
By the end of this lesson, you will implement client-driven and server-driven version negotiation, version discovery endpoints, capability detection, and graceful degradation when versions mismatch.
Why It Matters
Proper negotiation ensures clients always use a compatible version and fail gracefully when version mismatches occur, preventing integration failures.
Real-World Use
Durga Antivirus Pro clients negotiate API versions by requesting /api/version, receiving supported versions and capabilities, then selecting the latest compatible version.
Negotiation Strategies
flowchart TD
Request-->Negotiation{Negotiation Type}
Negotiation-->|Client-Driven|Client[Client Picks Version]
Negotiation-->|Server-Driven|Server[Server Assigns Version]
Negotiation-->|Content Negotiation|Content[Accept Header]
Client-->Request2[Client includes version]
Server-->Response[Server responds with version]
Content-->MediaType[Media type includes version]
Version Discovery Endpoint
Provide a version discovery endpoint for clients.
from typing import Dict, List, Optional
class VersionDiscovery:
def __init__(self):
self.versions: Dict[str, Dict] = {}
def add_version(self, version_id: str,
base_url: str,
status: str = "stable",
release_date: str = "",
sunset_date: str = "",
capabilities: Optional[List[str]] = None):
self.versions[version_id] = {
"version": version_id,
"base_url": base_url,
"status": status,
"release_date": release_date,
"sunset_date": sunset_date,
"capabilities": capabilities or [],
}
def get_discovery_response(self,
client_version: Optional[str] = None
) -> Dict:
if client_version and client_version in self.versions:
return self.versions[client_version]
latest = self._get_latest_stable()
return {
"latest_version": latest["version"],
"current_version": latest,
"all_versions": list(self.versions.values()),
}
def _get_latest_stable(self) -> Optional[Dict]:
stable = [
v for v in self.versions.values()
if v["status"] == "stable"
]
if stable:
return stable[-1]
return None
def get_supported_versions(self) -> List[str]:
return sorted(self.versions.keys())
discovery = VersionDiscovery()
discovery.add_version("v1", "/api/v1", "deprecated",
sunset_date="2026-12-31")
discovery.add_version("v2", "/api/v2", "stable",
capabilities=["scan", "reports"])
discovery.add_version("v3", "/api/v3", "beta",
capabilities=["scan", "reports", "analytics"])
resp = discovery.get_discovery_response()
print(f"Latest stable: {resp['latest_version']}")
print(f"Supported: {discovery.get_supported_versions()}")
Client-Driven Negotiation
Clients explicitly specify their desired version.
from typing import Dict, Optional, List, Tuple
class ClientDrivenNegotiation:
def __init__(self):
self.supported = set()
def add_supported(self, version: str):
self.supported.add(version)
def negotiate(self, client_headers: Dict
) -> Tuple[str, Optional[str]]:
requested = client_headers.get("X-API-Version", "")
accept = client_headers.get("Accept", "")
if requested in self.supported:
return requested, None
media_version = self._extract_media_version(accept)
if media_version and media_version in self.supported:
return media_version, None
default = sorted(self.supported)[-1]
return default, f"Requested version not available, using {default}"
def _extract_media_version(self, accept: str) -> Optional[str]:
import re
match = re.search(r"vnd\.api\.v(\d+)", accept)
if match:
return f"v{match.group(1)}"
return None
def get_best_version(self, client_capabilities: List[str]
) -> Optional[str]:
for version in sorted(self.supported, reverse=True):
vcaps = set()
if all(c in vcaps for c in client_capabilities):
return version
return None
cneg = ClientDrivenNegotiation()
cneg.add_supported("v1")
cneg.add_supported("v2")
version, warning = cneg.negotiate({"X-API-Version": "v2"})
print(f"Negotiated: {version}, warning: {warning}")
Server-Driven Negotiation
Server selects the best version based on client profile.
from typing import Dict, Optional, List
class ServerDrivenNegotiation:
def __init__(self):
self.client_profiles: Dict[str, str] = {}
def register_client(self, client_id: str,
preferred_version: str):
self.client_profiles[client_id] = preferred_version
def negotiate(self, client_id: str,
user_agent: str,
supported_versions: List[str]
) -> str:
preferred = self.client_profiles.get(client_id)
if preferred and preferred in supported_versions:
return preferred
if "mobile" in user_agent.lower():
return supported_versions[0]
return supported_versions[-1]
def upgrade_client(self, client_id: str,
new_version: str):
self.client_profiles[client_id] = new_version
def get_client_version(self, client_id: str
) -> Optional[str]:
return self.client_profiles.get(client_id)
sneg = ServerDrivenNegotiation()
sneg.register_client("partner-1", "v2")
version = sneg.negotiate(
"partner-1",
"DodaBrowser/2.0",
["v1", "v2", "v3"]
)
print(f"Server assigned version: {version}")
Common Mistakes
Mistake 1: No Version Discovery Endpoint
Clients should not guess which versions are available. Provide a /api/version or /.well-known endpoint.
Mistake 2: Silently Downgrading
If a client requests v3 but gets v1, it may break. Warn the client about version mismatches.
Mistake 3: No Negotiation Fallback
When negotiation fails, provide a clear error. Do not silently serve the wrong version.
Mistake 4: Ignoring Client Capabilities
Not all clients support all features. Negotiate based on client capabilities, not just version numbers.
Mistake 5: Hardcoded Version Selection
Version selection should be configurable per client, not hardcoded in the negotiation logic.
Practice Questions
- What is the difference between client-driven and server-driven negotiation?
- Why provide a version discovery endpoint?
- How does content negotiation work for versioning?
- What happens when version negotiation fails?
- How do you handle client capability detection?
Challenge
Build a version negotiation system that supports both client-driven (X-API-Version header) and server-driven (client profile-based) negotiation, provides a version discovery endpoint at /api/versions, and includes graceful fallback with clear warnings.
FAQ
Mini Project
Build a version negotiation system with a discovery endpoint (/api/versions), client-driven negotiation via X-API-Version header, server-driven fallback based on client profile, capability-based version selection, and clear error messages when negotiation fails.
What's Next
Learn about Accept Header Versioning for content-negotiation-based versioning, or explore Custom Header Versioning for header-based approaches.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro