Versioning
The compatibility requirement says a change to the API must not break a client that has not changed. CourseTracker was written against the representations in this chapter. It reads credits as a number and professor.email as a string. It will keep running for years without anyone changing its code. The registrar’s office will change the API in that time. So we need to know which changes CourseTracker can handle and which changes break it.
Safe changes
Adding a field is safe when clients ignore fields they do not recognize. If the registrar adds "level": "undergraduate" to a course, CourseTracker ignores the new field and keeps working. Adding an optional query parameter is safe if a client that does not send it gets the old behavior. Adding an endpoint is safe. A client that does not request it never sees it.
So the API can grow without a version. Most of what the registrar’s office will want to do over the years is growth. The problem is breaking changes to fields, types, and endpoints that already exist.
Breaking changes
Removing or renaming a field breaks every client that read it. Changing a type breaks them, such as making credits a string, or making professor a list because two professors now teach a course. Changing what a value means breaks them, such as term codes going from fall-2026 to 2026-fall. Changing a status code breaks them. And removing an endpoint breaks every client that sends a request to it.
Unless the client is told about these breaking changes, it will go on reading the old representation and misinterpreting it. That can show the user wrong information, or it can crash the client application.
Two versions at once
The solution is to run the old API and the new API at the same time, and let each client say which one it wants. To do that, we put a version number in the base URL:
https://registrar.university.edu/api/v1
https://registrar.university.edu/api/v2
Everything we have designed in this chapter is version 1. A request to /api/v1/courses/EN.601.226 returns the course as this chapter describes it, for as long as version 1 is running. When the registrar’s office needs to make a breaking change, it releases version 2 at /api/v2 with the new representation, and it keeps version 1 running. CourseTracker keeps sending its requests to v1, so it keeps working. When CourseTracker’s developers have time, they read what changed in v2, update their code, and switch to v2.
The version number could go in a request header instead of the URL. I put it in the URL for two reasons. First, developers copy URLs into browsers, scripts, and bug reports, and this way the version number goes with them. Second, a cache stores responses by URL, so putting the version in the URL keeps the responses of the two versions separate in every cache.
Retiring the old version
Running two versions has a cost. The registrar’s office has to maintain two representations of the same data, and it does not want to do that forever. So each version gets a retirement date. The date is announced when the next version is released, and it is far enough in the future that clients have time to move. This is how the API meets the compatibility requirement, which says CourseTracker must be told before a breaking change happens. The breaking change goes in v2, and v1 keeps the old representation until the retirement date.
Retiring a version is a breaking change for any client that is still using it. After the retirement date, a request to /api/v1 gets 410 Gone, and the error body tells the client where to find v2.