Understanding The HTTP Deprecation Header

Understanding The HTTP Deprecation Header

The HTTP Deprecation header informs clients that an API endpoint is or will be deprecated. The date can be in the past (deprecated) or future (to be deprecated).

What is the HTTP Deprecation Header?

The HTTP Deprecation header is used to inform clients that an API endpoint is or will be deprecated. According to the latest draft proposal, the field should be an RFC 9651 Date, which is expressed in Unix time. The date can either be in the past (the API has already been deprecated) or can be in the future (the API will be deprecated at this date). Here's an example for an API that was deprecated Friday, June 30, 2023 at 23:59:59 UTC:

Deprecation: @1688169599

Benefits of Implementing the Deprecation Header

  • Additional Warning: Your API consumers may not read the deprecation warning you send out via email/other comms so this is another way for them to become aware of the impending deprecation
  • Maintain in Code: In comparison to docs (which are often decoupled from your API code) the deprecation header is sent by the API at runtime and can be updated directly by the developers.
  • Tooling Support: API clients can eventually adopt a pattern of inspecting network responses for the deprecation header and logging a warning in non-production environments

Other Deprecation Header Formats

This convention has not always been the case, and many API providers stray from the format. Some prefer to use the RFC 7231 HTTP-date format (often expressed in GMT) instead, which looks like this:

Deprecation: Wed, 11 Oct 2023 23:59:59 GMT

The obvious benefit of this format is that it's much more easily readable by an API developer inspecting network responses. Some tooling (ex. API clients) might find this format more difficult to parse than unix time however.

Even simpler still is using a boolean instead of a date:

Deprecation: true

As a binary answer to the question "is this API deprecated?". The drawback of the boolean is that is necessitates communicating the API's deprecation ahead of time through other means than the headers. Given that the deprecation header is not widely adopted, this process is usually necessary anyways. The other drawback is that it limits API client libraries' ability to warn you of an upcoming deprecation.

Extending the HTTP Deprecation Header

Although the deprecation header is useful on its own, you can actually couple it with other headers as a part of a holistic API deprecation process.

You can use the link relation type to communicate more information about the deprecation of the API. For example, you can link to documentation or a blog post about the deprecation like this:

Link:
  <https://developer.example.com/deprecation>; rel="deprecation";
  type="text/html"

This can be particularly useful if more than just a single endpoint is being deprecated. One limitation of the HTTP deprecation header is that it doesn't provide context on the scope of the deprecation - is the whole API being deprecated, or just this endpoint? Using a link should help with filling in that gap.

The HTTP Sunset Header

Sunsetting is the point in time in which the deprecated API becomes unresponsive. The HTTP sunset header (RFC 8594) is typically used to convey when the sunset will occur. This is often coupled with the deprecation header to give a full picture of the API's end-of-life.

Deprecation: @1688169599
Sunset: Sun, 30 Jun 2024 23:59:59 UTC

Note that the sunset header uses a different date format than the deprecation header due to "historical reasons".

Implementing the HTTP Deprecation Header

Below are some code samples on how to implement the deprecation header in various popular API frameworks.

Node.js with Express

const express = require("express");
const app = express();

app.get("/v1/old-endpoint", (req, res) => {
  const deprecationDate = new Date().now();
  res.set("Deprecation", deprecationDate);
  res.json({ message: "This endpoint is deprecated." });
});

app.listen(3000, () => console.log("Server running on port 3000"));

Python with Flask

from flask import Flask, make_response
from datetime import datetime

app = Flask(__name__)

@app.route('/v1/old-endpoint')
def old_endpoint():
    response = make_response({'message': 'This endpoint is deprecated.'})
    deprecation_date = int(datetime(2023, 10, 11, 23, 59, 59).timestamp())
    response.headers['Deprecation'] = deprecation_date
    return response

if __name__ == '__main__':
    app.run(port=3000)

Java with Spring Boot


import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.temporal.*;

@RestController
public class ApiController {

    @GetMapping("/v1/old-endpoint")
    public ResponseEntity<String> oldEndpoint() {
        String deprecationDate = Instant.now().getEpochSecond()
        return ResponseEntity.ok()
                .header("Deprecation", deprecationDate)
                .body("This endpoint is deprecated.");
    }
}

Go with net/http

package main

import (
    "fmt"
    "net/http"
    "time"
)

func oldEndpoint(w http.ResponseWriter, r *http.Request) {
    deprecationDate := time.Now().Unix()
    w.Header().Set("Deprecation", deprecationDate)
    w.Header().Set("Sunset", "Wed, 11 Nov 2023 23:59:59 GMT")
    fmt.Fprintln(w, `{"message": "This endpoint is deprecated."}`)
}

func main() {
    http.HandleFunc("/v1/old-endpoint", oldEndpoint)
    http.ListenAndServe(":3000", nil)
}

PHP with Laravel

<?php

use Illuminate\Support\Facades\Route;

Route::get('/v1/old-endpoint', function () {
    $deprecationDate = time();
    return response()->json(['message' => 'This endpoint is deprecated.'])
        ->header('Deprecation', $deprecationDate);
});

Parsing the Deprecation Header Client Side

There's no use in sending the header if you don't use it client-side. Here's a simple javascript example:

fetch("/v1/old-endpoint")
  .then((response) => {
    const deprecationHeader = response.headers.get("Deprecation");
    if (deprecationHeader) {
      const deprecationDate = new Date(parseInt(deprecationHeader) * 1000);
      console.warn(
        `This API was deprecated on ${deprecationDate.toUTCString()}`,
      );
    }
    return response.json();
  })
  .then((data) => {
    console.log(data);
  });

Alternatives to the HTTP Deprecation Header

There are only a handful of alternatives in terms of runtime indication of deprecation status.

Legacy: Using the HTTP Warning Header

The HTTP Warning header is a general-purpose header that carries additional information about the status or transformation of a message. It's primarily used to warn clients about potential issues that might not be reflected in the status code.

The Warning header uses a three-digit warning code, an optional agent (usually the hostname), and a warning text enclosed in double quotes. The 299 warning code is ideal for indicating deprecation because it's a persistent warning that can convey any miscellaneous message.

Warning: 299 - "Deprecated API: This endpoint is deprecated and will be removed on 2023-11-11."

Some companies like UKG have documented this pattern but these APIs likely predate the introduction of the deprecation header.

Pros and Cons of Using the Warning Header

Pros

  • Standardized Format: The Warning header is part of the HTTP specification.
  • Client Awareness: Many HTTP clients and browsers are designed to handle Warning headers.
  • Flexibility: Allows for detailed messages about the deprecation.

Cons

  • Less Specific: Not specifically designed for deprecation notices.
  • Possible Misinterpretation: Clients might associate warnings with caching issues rather than deprecation.

I'd recommend that you steer clear of using the warning header and reserve it for actual runtime issues. Recall the deprecated APIs are still supposed to be functional until sunset. Now how would you report an actual issue if the warning header is being occupied by a deprecation message?

Unorthodox: Use the JSON Response Body

Depending on the format of your API response, you can include additional properties that are parsed at runtime.

{
  "deprecated": 1688169599,
  "sunset": "2023-11-11T23:59:59Z",
  "data": {...}
}

I suppose the only benefit is that developers are more likely to look at response bodies than they are at headers - but its impossible for API client tooling to adopt this pattern, which is a bad tradeoff. Additionally, how would you deprecate an API that doesn't return JSON? Don't try this in prod, kids.

The Deprecation Header is Just the Start

Although the HTTP deprecation header is helpful in the process of deprecating an API - there's a lot more you need to know. Check out our full guide on deprecating REST APIs to learn more.