How to check API versioning
Find out which of three places the server reads the version from, the URL path, a request header or a query parameter, and what it does when the version is unknown. curl -s -D - -H 'X-GitHub-Api-Version: 1999-01-01' https://api.github.com/ answers 200 and names the version it actually used, not the one asked for.
Why check this
A client pins a version so a server change cannot break it. That promise holds only if the server reads the pin and refuses what it cannot serve. Run this check when integrating a new API, and again whenever the provider announces a new version. The failure it prevents is silent: the client asks for a version that no longer exists, the server serves the current one, and a field that changed type reaches production untested.
Prerequisites
- curl 7.0 or later. See the curl manual.
- Node 22 for the target used in steps 4 to 7. Save it as
versioned-api.jsand start it withnode versioned-api.js. It reads the version from the path first, then a header, then a query parameter:
const http = require('node:http');
const SUPPORTED = ['v1', 'v2'];
http.createServer((req, res) => {
const url = new URL(req.url, 'http://localhost');
const fromPath = /^\/(v\d+)\//.exec(url.pathname);
const version = fromPath?.[1] ?? req.headers['api-version'] ?? url.searchParams.get('version') ?? 'v1';
const send = (code, body) => {
res.writeHead(code, { 'content-type': 'application/json', 'api-version': version });
res.end(JSON.stringify(body));
};
if (!SUPPORTED.includes(version)) {
return send(400, { title: 'Unsupported API version', status: 400, requested: version, supported: SUPPORTED });
}
if (version === 'v1') return send(200, { version, price: 10 });
send(200, { version, price: { amount: 10, currency: 'EUR' } });
}).listen(8790, () => console.log('versioned-api on 8790'));
Stop it with Ctrl+C when the run is over.
Steps
- Step 1.
Call a path-versioned endpoint and confirm the version in the URL is served.
curl -s -w '\nHTTP %{http_code} %{content_type}\n' 'https://developer.mozilla.org/api/v1/whoami'{"geo":{"country":"Ukraine","country_iso":"UA"},"username":null, …} HTTP 200 application/json - Step 2.
Ask the same service for a version number that does not exist.
curl -s -o /dev/null -w 'HTTP %{http_code} %{content_type}\n' 'https://developer.mozilla.org/api/v2/whoami'HTTP 404 text/htmlPath versioning answers a missing version through the router, so the reply is a 404 and its body is an HTML page. A JSON client fails at the parser rather than on the status.
- Step 3.
Now a header-versioned API. Send a version that was never published and read the response headers.
curl -s -D - -o /dev/null -H 'X-GitHub-Api-Version: 1999-01-01' 'https://api.github.com/' | grep -i -E '^HTTP|^x-github-api-version-selected|^x-github-media-type'HTTP/2 200 x-github-api-version-selected: 2022-11-28 x-github-media-type: github.v3; format=jsonThe request asked for
1999-01-01and the answer came from2022-11-28. The version that was served is in the response, and it is the only reliable place to read it. - Step 4.
Against the local target, call two path versions of one resource and compare the bodies.
for v in v1 v2; do curl -s -w '\n' "http://localhost:8790/$v/products/1"; done{"version":"v1","price":10} {"version":"v2","price":{"amount":10,"currency":"EUR"}}pricechanged from a number to an object. This is the change that a pinned version is supposed to hide from old clients. - Step 5.
Select the version with a header instead, on a path that carries no version, and check the response says which one it used.
curl -s -D - -H 'Api-Version: v2' 'http://localhost:8790/products/1' | grep -i -E '^HTTP|^api-version'HTTP/1.1 200 OK api-version: v2 - Step 6.
Select it with a query parameter and confirm the same body comes back.
curl -s -w '\n' 'http://localhost:8790/products/1?version=v2'{"version":"v2","price":{"amount":10,"currency":"EUR"}}Three selection styles on one service is two too many. Find out which one your API treats as authoritative when they disagree.
- Step 7.
Ask for a version the target does not support.
curl -s -w ' HTTP %{http_code}\n' -H 'Api-Version: v9' 'http://localhost:8790/products/1'{"title":"Unsupported API version","status":400,"requested":"v9","supported":["v1","v2"]} HTTP 400A 400 that names what was requested and what exists is the answer a client can act on. Compare it with step 3.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| A response header naming the served version | The server tells you what answered | Assert that header in every contract test. |
| 200 for a version that does not exist | The pin is decorative | Treat the client as unpinned. A default change will reach it. |
| 400 listing the supported versions | The server refuses clearly | Assert the list. It is the deprecation notice you get earliest. |
| 404 with an HTML body | A path version was routed, not validated | Confirm the client handles a non-JSON error on this route. |
| No version anywhere in the response | You cannot prove which code answered | Ask for a version header before writing contract tests. |
| Path and header disagree and one wins silently | Precedence is undefined in the docs | Test both orders and write down the winner. |
| Sunset or Deprecation headers present | The version has an end date | Read the date, then plan the client change. |
Common mistakes
What to check next
- How to test API error responses: the 400 or 404 that a version miss produces has to be readable by the client.
- How to check HTTP status code: the status is the first signal that a version was refused.
- How to test API with curl: sending custom headers and reading response headers, one flag at a time.
- How to test API with invalid input: an unknown version is one more input the server may ignore in silence.
- Api testing checklist: where this check sits in a release pass.
FAQ
How do I check which API version a service is running?
Read the response, not the docs. Look for a header such as x-github-api-version-selected or a version field in the body. If neither exists, request two versions and compare the bodies, since a difference proves the selection worked.
Which versioning style should I expect?
Path versioning, as in /api/v1/, a request header, or a media type such as application/vnd.acme.v2+json. A query parameter is the fourth. All four appear in production APIs, so check the documentation first and then confirm with a request.
What should happen when a client asks for a version that does not exist?
A 4xx that names the supported versions, as in step 7. Serving the default instead leaves the client believing it is pinned while it is not.
Does a version in the URL mean the response body cannot change?
No. Providers add fields inside a version and call it compatible, which it is for clients that ignore unknown fields. Assert the fields you read rather than the whole body, and pin the version as well.
Verified
Verified by Maks Vernycurl 8.21.0node 22.23.2
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
basic8 minpublished updated Maks Verny