Deprecating API Endpoints

  • Phil Sturgeon
    Phil Sturgeon
    December 4 2020

Some APIs last for decades, some last a few years, and some are even shorter-lived than that.

An API might be replaced by another totally different API, or a new version of the API is rolled out with /v2/ in the URL somewhere. More popular these days is the concept of API Evolution, an ages-old concept recommended by the REST community for decades, and popularized by the GraphQL and gRPC communities more recently. Evolution suggests you only make additive changes to a resource/procedure, then replace the resource/procedure with another one entirely when that’s no longer possible, instead of replacing the entire API and forcing all that upgrade work onto all of the API consumers.

However you handle versioning or evolution, one thing is for sure: at some point, something needs to get deprecated. Deprecated basically means giving people advanced noticed that something is about to go away, and in HTTP API land that usually ends up being an endpoint.

If global API versions are used, then every endpoint inside that version is deprecated at the same time: /v1/foo and /v1/bar.

If it’s evolution, then maybe /payments is being replaced by /charges.

There are a few common approaches that API producers use to let API consumers know change is coming.

  1. Documentation - Make sure no new users start using the deprecated functionality by making it clear in your documentation that this endpoint is deprecated.
  2. @everyone - For private APIs maybe blast it out on the company Slack and hope everyone notices and gets around to doing the work before you pull the plug.
  3. Email - If you’ve asked API consumers to sign up for an API Key or Access Token, you might have an email address, which might be active, and might have somebody monitoring the inbox, so you can send an email with warnings. GitHub, Facebook, Twitter, and all sorts of other huge API companies do this.
  4. Monitoring - Keep an eye out for requests coming into those deprecated endpoints and set up a call with the team responsible. Tough to do for public APIs but can work internally at a small-to-medium-sized company.
  5. Deprecated/Sunset Headers - Get the robots on the job! Developers can flag an endpoint as deprecated then so long as they’ve got a Sunset-aware HTTP middleware enabled they’ll start seeing warnings or errors in their codebase.

Most API organizations pick a mixture of the above. Doing all of it would probably be overkill, but doing only one thing would probably lead to unexpected outages for consumers who didn’t notice the deprecation notice.

🔗 Deprecations in OpenAPI

Updating documentation might just mean updating whatever CMS you use to write documentation, but these days if you’re making API Reference Documentation for an HTTP API that usually means using OpenAPI, which has a standardized way of doing this.

Both OpenAPI v2.0 (formerly known as Swagger), and OpenAPI v3.x support deprecating “Operations”, which is what they call a path/endpoint + HTTP method.

openapi: 3.0.3

paths:
  /old:
    get:
      summary: Old Operation
      deprecated: true
  /new:
    get:
      summary: New Operation

If you don’t like looking at YAML, Stoplight Studio will do this for you in Form view, and a badge will pop up in Stoplight Documentation, Explorer, etc.

An on/off toggle exists in Studio to mark operations as Deprecated

Once you push the changes it’ll show in your Stoplight Documentation, but this is only going to stop new users from using the endpoint, and maybe existing users who stumble back onto the docs in time.

We’re considering building a notification system so teams can subscribe to breaking changes and deprecations for API’s, so please vote for Notifications if you’re interested in the feature.

🔗 Runtime HTTP Headers

Setting the deprecation in OpenAPI is a good start, but what about all the folks already using your API? Sure, you could send an email, spam on slack, or track Gary down between games of ping-pong and tell him to get on with switching, but these approaches do not always scale so well.

As with most things in the world of HTTP APIs, the problem has been solved with standards. There are two HTTP headers which are a bit similar:

  • Deprecation - This endpoint has been, or will be, deprecated, meaning you should move away from using it when you can.
  • Sunset - This endpoint will become unresponsive after this specified date, meaning things will break.

These headers are brought to you by Sanjay Dalal and Erik Wilde. Deprecation is a join effort which at the time of writing is still going through the motions of becoming an RFC, and Sunset was brought to by Erik, who managed to get it over the finish line as RFC 8594.

A deprecation header in its most simple form might look like this:

Deprecation: true

The API developers might decide to start emitting these headers with a date in the future, to give folks a little more advanced notice.

Deprecation: Thu, 31 Dec 2020 23:59:59 GMT
Link: <https://api.example.com/new>; rel="alternate"

This effectively does nothing unless a human spots it, or robots have been trained to spot it. If you develop your own internal SDKs or have a common HTTP library configuration with a bunch of HTTP middlewares enabled, making a simple middleware which notices deprecation warnings and sends them to your error reporting or logging system as “info” might be a good idea. Once the date is in the past, set it to a warning.

Detecting Sunset headers is a similar situation. If it is months away, that can be a warning. If it is in the past? Throw an error, this endpoint could vanish any second now and that will be a production outage. Ring alarm bells.

There’s two libraries around that do this for Sunset in PHP and Ruby:

Reading these headers is one thing, but how do you emit these headers? In a previous life I built rails-sunset to make this simple in Ruby on Rails:

class OldController
  sunset DateTime.new(2021, 1, 1)
end

Laravel (PHP) has a similar package: laravel-sunset.

<?php

namespace App\Http\Controllers;

use HSkrasek\Laravel\Sunset\SunsetsEndpoints;

class APIController extends Controller {
    public function index()
    {
        // Other logic here
        return $this->sunsetsResponse(
            response()->json(['foo' => 'bar',]),
            '2020-12-31 23:59:59', // When this endpoint is being deprecated
            'http://example.com' // Optional link explaining the deprecation
        );
    }
}

You can just directly emit the HTTP headers yourself without any sugar.

fastify.get('/old', options, function (request, reply) {
  reply
    .code(200)
    .header('Content-Type', 'application/json; charset=utf-8')
    .header('Deprecation', 'true')
    .header('Sunset', 'Thu, 31 Dec 2020 23:59:59')
    .send({ hello: 'world' });
});

These packages only support Sunset as Deprecation is newer than they are, so updating them or forking them to support the latest Deprecation draft would be a good open-source thing for someone to do.

To see this used in the wild, GitHub has started using both of these headers:

curl -I https://api.github.com/teams/123

... snip ...

deprecation: Sat, 01 Feb 2020 00:00:00 GMT
sunset: Mon, 01 Feb 2021 00:00:00 GMT
link: <https://developer.github.com/changes/2020-01-21-moving-the-team-api-endpoints/>; rel="deprecation"; type="text/html", <https://api.github.com/organizations/0/team/0>; rel="alternate"

That link header contains a URL to a blog post explaining the changes, so humans can see what is going on if the HTTP headers are not enough.

🔗 Automatically Emitting Deprecations

Updating API description documentation and source code to match is pretty much the bane of everyone’s existence when they first start working with OpenAPI.

Eventually folks realise that the API design-first workflow means having a machine readable document that can power your production code and remove all the repetition. Instead of putting deprecation in two places, why not have your code emit headers when the description says so?

Whatever stack or framework you have, if it’s any good it probably supports “HTTP server middlewares”. Ruby has Rack, PHP has PSR-7, various NodeJS frameworks like Express and Fastly have their own approach to middleware, any there are lots of OpenAPI-based request validation middleware tools for various stacks.

These middlewares would be a great place to put this logic. They’re already looking at requests coming in, they’re aware of what Operation is being called in the OpenAPI, and they’re already there to avoid API developers needing to duplicate information from their OpenAPI description in their source code (why do something twice).

I’ve suggested my friends maintaining PHP’s league/openapi-psr7-validator see if they can work it into their library, and I’d love to see other middleware maintainers doing the same.

Here at Stoplight we can update Prism to emit the Deprecation header when you talk to the mock, or the validation proxy. That’d be a handy way to surface this to more developers and maybe even get your end-to-end test suites noticing trouble too.

Next? Maybe we could start warning API consumers if they’re submitting deprecated properties too! 😎

Tying all this stuff together takes the HTTP API world a step closer to the “all in one” solutions that has helped GraphQL gain popularity over the last few years, but without all the rewriting, retooling and retraining of all API developers and client application developers involved.

Just hook up your OpenAPI, write less code, follow standards and conventions, and automate deprecation warnings. 🥳

Peek-a-Boo (1)

Subscribe for the latest in API Design

By submitting this you will be receiving our latest updates on post.

Take a Listen to API Intersection

The podcast on the intersection between API design & digital transformation
Listen

Related Posts