Map the API
Keep an API inventory
Track deployed hosts, routes, methods, versions, owners, data classes, and retirement dates so forgotten endpoints do not remain exposed.
You can’t protect an endpoint nobody remembers. Old API versions keep old bugs alive, and attackers look for them on purpose. They know teams forget.
An API inventory is the list of everything your servers answer to. Hosts, routes, methods, versions, who owns each one, and when it goes away.
Build the inventory from more than one source
Documentation drifts. So don’t trust one source. Take the routes from the code, from the gateway, from the deployment config, and from real traffic. Then compare them.
Two commands get you most of the way:
# routes the code declares
grep -rE "router\.(get|post|put|patch|delete)" src/routes/ | sort -u
# routes production actually serves
awk '{print $6, $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
Real traffic is the strongest evidence you have. A mobile client may still call /v1/invoices months after the web app moved to /v2. Remove the route too early and you break customers. Leave it there with no owner and you keep its old authorization bugs.
Record ownership and lifecycle
Every route needs an owner and a state. Here’s a row I would consider complete:
host: api.example.com
route: GET /v1/invoices
owner: billing-team
state: deprecated
deployment: invoices-v1 (build 2026-05-02)
last-seen: 2026-07-29T14:22:10Z
retirement: 2026-10-01
The deployment identifier and the last-seen timestamp are the two fields people skip. Keep them. They let the owner tell a live route from stale gateway configuration.
And when a route or a credential is unused, remove it. “Temporary” compatibility has a way of becoming permanent.
The inventory catches two kinds of ghosts. Shadow APIs are routes that shipped without ever going through a review. Zombie APIs are routes that outlived their owners. Both are invisible until you look, and the inventory is how you look before an attacker does.
Verify the inventory catches drift
The inventory earns your trust the first time it flags a route you did not expect. Pick one and call it:
curl -i https://api.example.com/v1/debug/config
# HTTP/1.1 404 Not Found <- good, it was removed
# HTTP/1.1 200 OK <- bad: undocumented, no owner, still serving
Here’s a failure I’ve seen more than once. The gateway routes /v1/* to an old deployment as a catch-all. Routes deleted from the code still resolve in production, because the old build is still running behind that rule. Neither the code nor the gateway config shows the problem alone. Only comparing them does.
Try this on one environment: list the routes from source, gateway, and access logs, and save the mismatches. Then request one undocumented route. Either prove it’s gone, or give it an owner and a retirement date.
Lesson completed