๐ง What Is API Versioning?
API versioning means managing multiple versions of your API so that changes don’t break existing client applications.
When you update your API — for example, by:
Changing response formats,
Renaming endpoints,
Updating authentication, or
Removing old fields —
you can introduce a new version while keeping older versions active for clients still using them.
⚙️ Why API Versioning Is Important
Benefit Description
Backward Compatibility Old clients can still work while new ones use updated features.
Smooth Upgrades You can gradually migrate users from older to newer versions.
Stability Prevents breaking integrations with external systems.
Testing and Debugging Different versions can run side-by-side for comparison.
Deprecation Strategy You can safely phase out outdated endpoints.
๐งฉ Common API Versioning Strategies
There are several ways to include versioning in your Python APIs. Let’s look at the most common ones:
1. URL Path Versioning (Most Common)
Add the version number directly to the endpoint path.
Example:
/api/v1/users
/api/v2/users
Pros: Simple, explicit, and easy to route.
Cons: Requires duplicating routes and controllers when upgrading.
✅ Example with FastAPI:
from fastapi import FastAPI
app = FastAPI()
@app.get("/api/v1/users")
def get_users_v1():
return {"version": "v1", "data": ["Alice", "Bob"]}
@app.get("/api/v2/users")
def get_users_v2():
return {"version": "v2", "data": [{"id": 1, "name": "Alice"}]}
✅ Example with Flask:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/api/v1/users")
def users_v1():
return jsonify({"version": "v1", "data": ["Alice", "Bob"]})
@app.route("/api/v2/users")
def users_v2():
return jsonify({"version": "v2", "data": [{"id": 1, "name": "Alice"}]})
2. Header Versioning
Clients specify the version in the HTTP header instead of the URL.
Example Request:
GET /api/users
Accept: application/vnd.company.v2+json
Pros: Keeps URLs clean and RESTful.
Cons: Harder for users to discover versions via browsing.
✅ Example (FastAPI):
from fastapi import FastAPI, Header, HTTPException
app = FastAPI()
@app.get("/api/users")
def get_users(accept: str = Header(default="application/vnd.company.v1+json")):
if "v2" in accept:
return {"version": "v2", "data": [{"id": 1, "name": "Alice"}]}
elif "v1" in accept:
return {"version": "v1", "data": ["Alice", "Bob"]}
else:
raise HTTPException(status_code=400, detail="Unsupported API version")
3. Query Parameter Versioning
Clients specify the version as a query parameter.
Example:
/api/users?version=1
/api/users?version=2
Pros: Simple to implement and test.
Cons: Not as RESTful; versioning appears optional.
✅ Example (Flask):
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/api/users")
def users():
version = request.args.get("version", "1")
if version == "2":
return jsonify({"version": "v2", "data": [{"id": 1, "name": "Alice"}]})
return jsonify({"version": "v1", "data": ["Alice", "Bob"]})
4. Domain/Subdomain Versioning
Each version is hosted under a different domain or subdomain.
Example:
https://v1.api.myapp.com/users
https://v2.api.myapp.com/users
Pros: Strong separation between versions.
Cons: More infrastructure complexity (requires DNS/routing setup).
This is common in large-scale systems or APIs managed through API gateways (like AWS API Gateway or Kong).
5. Content Negotiation
Clients specify the desired API version in the Content-Type or Accept header.
Example Header:
Accept: application/json; version=2
The server reads the version value and routes accordingly.
✅ Example (FastAPI):
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/api/users")
def get_users(accept: str = Header(default="application/json; version=1")):
if "version=2" in accept:
return {"version": "v2", "data": [{"id": 1, "name": "Alice"}]}
return {"version": "v1", "data": ["Alice", "Bob"]}
๐งฑ 6. Organizing Versioned APIs (Best Practices)
For large projects, it’s best to separate code by version to avoid confusion.
✅ Example Folder Structure (FastAPI/Flask)
project/
│
├── app/
│ ├── v1/
│ │ ├── routes.py
│ │ └── models.py
│ ├── v2/
│ │ ├── routes.py
│ │ └── models.py
│ ├── __init__.py
│
└── main.py
main.py:
from fastapi import FastAPI
from app.v1.routes import router as v1_router
from app.v2.routes import router as v2_router
app = FastAPI()
app.include_router(v1_router, prefix="/api/v1")
app.include_router(v2_router, prefix="/api/v2")
This allows your API to grow without breaking existing endpoints.
๐งฐ 7. Managing Versioning with Blueprints (Flask Example)
Flask’s Blueprints are perfect for organizing versioned routes.
from flask import Flask, Blueprint, jsonify
v1 = Blueprint("v1", __name__)
v2 = Blueprint("v2", __name__)
@v1.route("/users")
def users_v1():
return jsonify({"version": "v1", "data": ["Alice", "Bob"]})
@v2.route("/users")
def users_v2():
return jsonify({"version": "v2", "data": [{"id": 1, "name": "Alice"}]})
app = Flask(__name__)
app.register_blueprint(v1, url_prefix="/api/v1")
app.register_blueprint(v2, url_prefix="/api/v2")
✅ Advantages:
Each version can have independent routes, models, and logic.
Easier to deprecate or remove an old version cleanly.
๐งช 8. Deprecating Older Versions
You can’t support every version forever. At some point, older ones should be deprecated.
๐ ️ Steps to Deprecate an API Version
Announce deprecation ahead of time (e.g., email, documentation).
Add warnings in responses.
Monitor usage (with logs or analytics).
Disable the version after the grace period.
✅ Example Warning Header:
from fastapi import Response
@app.get("/api/v1/users")
def users_v1(response: Response):
response.headers["Warning"] = "299 - 'API v1 is deprecated. Use v2 instead.'"
return {"version": "v1", "message": "This version will be removed soon."}
๐ 9. Documentation and OpenAPI Integration
When you version your APIs, document each version clearly.
FastAPI and Flask (via Swagger) make this easy.
Each version can have its own OpenAPI docs, e.g.:
/docs/v1
/docs/v2
✅ FastAPI Example:
app_v1 = FastAPI(title="My API v1", version="1.0")
app_v2 = FastAPI(title="My API v2", version="2.0")
This helps developers know which endpoints belong to which version.
๐งพ Summary
Strategy How It Works Pros Cons
URL Path Versioning /api/v1/resource Simple, explicit Duplicate routes
Header Versioning Custom HTTP headers Clean URLs Harder to discover
Query Parameter /api/resource?version=2 Easy to implement Less RESTful
Subdomain Versioning v1.api.example.com Strong separation Complex setup
Content Negotiation Accept: version=2 RESTful Harder for clients
๐ก Best Practices
✅ Keep old versions functional for a while.
⚙️ Automate version routing (e.g., via routers or blueprints).
๐งฑ Separate codebases per version.
๐ Update documentation for every release.
๐จ Use headers or warnings to announce deprecation.
๐ Apply consistent authentication across versions.
๐งช Test all versions independently.
๐ง Final Thoughts
Building versioned APIs in Python ensures your application remains scalable, maintainable, and future-proof.
It gives you the freedom to evolve your system — improving performance and structure — without breaking existing clients.
Whether you’re using Flask, FastAPI, or Django REST Framework, versioning is a best practice that helps your full-stack app grow smoothly and sustainably.
Learn Fullstack Python Training in Hyderabad
Read More
How to Handle Errors and Responses in Full Stack Python APIs
Integrating Third-Party APIs with Full Stack Python
Creating APIs for Mobile Applications with Python
How to Handle API Requests in Python: Methods and Best Practices
At Our Quality Thought Training Institute in Hyderabad
Subscribe by Email
Follow Updates Articles from This Blog via Email
No Comments