Engineering Lessons

The API We Broke Because We Thought Nobody Was Using It

Jul 6, 2026·7 min read
"The endpoint hadn't been called in six weeks. It was called on the 1st of every quarter. We removed it on the 18th."

The API had been in production for four years. The endpoint in question, a bulk export that returned a formatted data structure, had been replaced eighteen months earlier by a newer endpoint that was more efficient and returned a slightly different response shape. The old endpoint still worked, but it used a code path we were about to remove as part of a refactoring. The standard process: check the logs, verify no traffic, announce deprecation, remove.

We checked the logs. No calls in the past six weeks. We sent a deprecation notice to the mailing list of registered API consumers. We waited two weeks. No responses. We removed the endpoint.

Three days after removal, a ticket arrived in our support queue from a third party analytics firm. Their quarterly data extraction job, which ran on the first of every quarter to pull a structured export for regulatory reporting, had failed. The job had been running successfully for three years against our old endpoint. The new endpoint had a different response format that would require work on their end to adapt to. The quarterly run was time-sensitive. They needed the old endpoint restored or a working migration path within 24 hours.

The endpoint hadn't been called in six weeks because it was called on the 1st of January, April, July, and October. We had removed it on the 18th of March.

What We Did Wrong

The problem wasn't that we removed the endpoint. Endpoints need to be removed eventually; APIs that accumulate stale surface area become expensive to maintain and document. The problem was our process for determining that removal was safe.

Six weeks of logs is not a sufficient window when you're not sure how frequently your API is called by all consumers. Most of our API usage was high-frequency, multiple calls per minute from our own web app and mobile clients, predictable patterns that made six weeks a reasonable proxy for "still in use." But we had external consumers with usage patterns we didn't control and hadn't explicitly inventoried. Some of those consumers had batch jobs that ran monthly. Some had quarterly reporting cycles. Some had annual data extracts. None of those showed up in a six-week access log window.

The second mistake was treating "no response to the deprecation notice" as confirmation of safe removal. A mailing list of registered API consumers, maintained when developers registered their API keys, is not a reliable channel for reaching every team currently using the API. People change jobs. Email addresses change. The person who registered the API key three years ago is no longer the person running the batch job today. The team running the quarterly extract had no one who read the deprecation mailing list.

The Immediate Fix and Its Complexity

Restoring the endpoint wasn't as simple as reverting the code. The refactoring we had done in the meantime had removed several of the internal components the old endpoint depended on. We had to either re-implement the old code path, which would mean re-adding code we had just cleaned up, with the associated regression risk, or build an adapter that translated between the new internal data structures and the old response format.

We chose the adapter. It took four hours to write, four hours to test against the partner's expected format, and two hours to deploy with monitoring. The partner's quarterly job ran successfully against the restored endpoint that evening. The total incident duration from discovery to resolution was about 12 hours. The cost, in engineer time and partner relations, was non-trivial.

What Good Deprecation Actually Looks Like

After this incident, we built a different process. It's not perfect, no process prevents every mistake, but it prevents this specific class of mistake.

Minimum lookback window tied to usage pattern. Before declaring an endpoint as having "no usage," we analyse the existing traffic for patterns. If the existing traffic shows a quarterly spike in January, April, July, and October, the minimum safe lookback window is 13 months, enough to cover a full calendar year plus a margin. If traffic has been truly zero for over a year with no seasonal pattern, six weeks is fine. The window depends on what the data actually shows, not a default value.

Active consumer inventory, not just logs. We built a lightweight registry that tracks which API keys have called which endpoints within a configurable time window, with the ability to send targeted emails directly to the API key owner's registered address when an endpoint they've used is being deprecated. This is a step up from a generic mailing list, it reaches the specific consumer who has used the endpoint, even if they've never joined the general API announcement list.

Sunset headers before removal. For endpoints scheduled for removal, we added a Sunset HTTP header (an IETF standard, RFC 8594) to every response for three months before the removal date. The header contains the deprecation date and a link to the migration documentation. Any consumer who is actively processing the API responses and logging headers will see this. Many automated consumers are not reading response headers, but some are, and for those it provides notice in the channel where they're already paying attention.

A shadow period, not a hard removal. The cleanest implementation we found: after the announced removal date, the endpoint returns a 410 Gone response with a detailed error body explaining what replaced it, not actually gone from the routing layer, but explicitly signalling that it's retired and providing the migration path. This gives consumers who missed the deprecation notice a clear, actionable error instead of a mysterious 404. The endpoint moves to the 410 state for three months before being actually removed from the routing configuration.

The Deeper Lesson

APIs are contracts. When you publish an endpoint and other parties build systems against it, you have made an implicit commitment that the endpoint will behave consistently. Removing it breaks that contract for everyone who accepted it, including people who accepted it years ago and haven't needed to renegotiate it recently. The technical ease of removing an endpoint, a few lines of code, is inversely proportional to the operational disruption it can cause at the consumers' end.

This doesn't mean APIs should never change or that endpoints should run forever regardless of the cost of maintaining them. It means that the process for changing APIs should account for the full blast radius: not just the consumers you can see in the recent logs, not just the consumers who read your announcement emails, but also the dormant consumers, the batch consumers, the quarterly-running automations that will only discover the breakage at the worst possible moment. Building a process that finds those consumers before the removal, rather than after, is the difference between a planned migration and a 12-hour incident.

Tags

#api#versioning#breaking-changes#engineering#integrations