Test and operate APIs

Retire and respond

Remove obsolete API versions, revoke old credentials, preserve evidence, and rehearse response to data exposure or authorization failure.

An API version is not retired when its documentation disappears. It’s retired when callers and credentials can no longer reach it. Deleting the /v1 docs does nothing to stop an old token from calling /v1.

The same goes for incidents. A response plan you’ve never run is a document, not a plan.

Retire with evidence, not hope

The sequence is: measure real use, announce a deadline, migrate clients, block new credentials, disable the deployment, then keep watching for attempted calls.

Start by finding out who still depends on the old version:

# which credentials still call /v1?
grep ' /v1/' /var/log/nginx/access.log | awk '{print $3}' | sort | uniq -c | sort -rn
#  4102 key_mobile_prod_x91b   <- this client needs a migration plan
#     7 key_partner_acme_v22

Shutting down immediately reduces exposure. But some clients can’t be updated on your schedule. A mobile app version already installed on phones takes months to drain. Those 4,102 calls are real users, and cutting them off is a business decision, not just a security one.

When the deadline arrives, make the block explicit and permanent:

curl -i https://api.example.com/v1/invoices \
  -H "Authorization: Bearer $OLD_TOKEN"
# HTTP/1.1 410 Gone
# {"error":"v1 retired 2026-10-01, see /docs/migration-v2"}

410 Gone tells the client this is permanent, and the message tells them where to go. Then keep watching denied calls for an agreed period. Those denials reveal forgotten clients without reopening the version, and without trusting an inventory that may already be incomplete.

Rehearse the incident before you have one

When something goes wrong, the steps are: identify the affected objects and identities, contain the path, preserve evidence, fix related routes, and communicate from facts.

The tenant-boundary rehearsal is the one I would run first. Someone claims “tenant acme’s export leaked”. Can you list which invoices were touched, by which credential, in what time range?

SELECT target, credential, ts FROM audit_events
WHERE tenant = 'acme' AND op LIKE '%export%'
  AND ts BETWEEN '2026-07-01' AND '2026-08-01';

If today’s logs can answer that query, you can respond from facts. If they can’t, you just found the gap in a rehearsal instead of during a disclosure deadline. Fix the logging first. Every other step in the plan depends on it.

Try this on your own API: record the real /v1 traffic, the credential owners, the migration dates, and the final block rule. Call the route after retirement and prove it can’t change any data. Then run the tenant-boundary rehearsal and see whether your logs can name every invoice a leaked export touched.

Lesson completed