Skip to content
Language:en Author:Laborci Gergely
web-api

API versioning, compatibility, and documentation

Objectives

By the end of the lesson, you can decide whether an API change is backward compatible or breaking, you know the options for versioning, and you can say what usable API documentation must contain.

Why do changes to an API have to be managed?

For several clients an API is a long-term contract. The server, however, changes: new features, renames, fixes. Clients do not update at the same moment: a browser interface immediately, a mobile app after weeks or months, a partner's system perhaps never. If the provider changes the contract overnight, the old clients break, while the network and HTTP work flawlessly. Versioning, compatibility, and documentation make change traceable and manageable.

Renaming a field with several clients

So far the API of Kurzuspont has sent a cim field. A new server development would rename it to megnevezes. The new interface would read the new field, but the old mobile app still expects cim. Tried, an old and a newer client with the same three responses:

const regiKliens = v => "Kurzus: " + v.cim;
const ujKliens   = v => "Kurzus: " + (v.megnevezes ?? v.cim);
Server response Old client New client
{"id":"webprog","cim":"Webprogramozás 1."} (old) Kurzus: Webprogramozás 1. Kurzus: Webprogramozás 1.
{"id":"webprog","megnevezes":"Webprogramozás 1."} (renamed) Kurzus: undefined Kurzus: Webprogramozás 1.
{"id":"webprog","cim":"...","megnevezes":"...","nyelv":"hu"} (extended) Kurzus: Webprogramozás 1. Kurzus: Webprogramozás 1.

The rename broke the old client (undefined), the extension did not. The third solution temporarily sends the two fields together, so both clients work.

flowchart LR
    R[Old client: cim] --> A[API response]
    U[New client: megnevezes] --> A
    A --> D[Compatibility decision]
    D --> M[Temporary coexistence or a new version]

Compatible and breaking changes

Change Effect on the old client Typically
New, optional response field The old client can ignore it backward compatible
New endpoint The old client does not use it compatible
Removing a mandatory field The client cannot find it breaking
Renaming a field As above breaking
Changing the type of a field (number → string) May cause a type error (see 5.2) breaking
Changing the meaning of a field while keeping its name Silently wrong behavior breaking, especially insidious
New mandatory request field The old client does not send it breaking

Whether a modification is really compatible depends on the actual contract and the behavior of the clients. The error responses (status code, error format) are also parts of the contract: if the client has so far counted on a given status and error shape, a change can break the operation there too.

Versioning options

A new version can be distinguished in several ways:

  • in the path: /api/v2/kurzusok;
  • in a request header: for example the client signals the requested version in a header or in the media type;
  • in the schema: with GraphQL, introducing new fields and marking the old ones as deprecated, without a version number.

There is no single mandatory form. The goal is that the client knows which contract it is using, and that there is a transition if the old version is later retired.

What should the documentation contain?

A usable API description says:

  1. what the service is for and what access is needed for it;
  2. where the operations can be reached, with which method;
  3. what input is needed, which fields are mandatory or optional;
  4. what successful and failed response is expected (status code and body);
  5. what the fields mean, what type they are, what can be missing or null;
  6. how the contract has changed, what is being retired.

Examples help, but they do not replace the description of the rules: a sample response does not say what happens with missing data or an authorization error.

flowchart TD
    S[API contract] --> M[Operations and inputs]
    S --> V[Responses and errors]
    S --> A[Meaning of data fields]
    S --> K[Compatibility and changes]

The OpenAPI Specification is a language- and vendor-neutral description format for HTTP APIs: according to the specification, both people and machines can understand from it what the service can do, without knowing the source code, the documentation, or the network traffic. The description is also suitable for machine processing (for example for generating client or test code). According to the site, the latest release of the specification is 3.2.1. Good documentation, however, is not merely the product of a file format: the exact meaning of the fields and the terms of use are needed too. With GraphQL, the schema and the field descriptions fill the same role.

A short OpenAPI excerpt for the GET /api/kurzusok/{id} operation of Kurzuspont (illustrative, we did not check the syntax with a validator):

paths:
  /api/kurzusok/{id}:
    get:
      summary: Egy kurzus lekérése
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200": { description: A kurzus }
        "404": { description: Nincs ilyen kurzus }

Testing the contract

The cooperation of provider and client is worth checking from the viewpoint of both the old and the new client: does the old client still receive the needed fields? Does it react understandably to a missing resource? Has the meaning of a field changed while keeping its name? These questions examine compatibility concretely, not just the existence of a version number.

Worked example: v2 of Kurzuspont

The operator of Kurzuspont wants to rename the cim field, and to introduce a szabadHely field next to ferohely.

Step Decision
Adding szabadHely An optional extension, old clients ignore it: no new version is needed.
Renaming cim to megnevezes A breaking change. Temporarily both fields are sent (the third row above), and cim is marked "deprecated" in the documentation.
Retirement The documentation gives the deadline; after the deadline cim disappears in the new version.
Documentation An OpenAPI description, a changelog, a description of the error format.

This way there is time to update the old mobile app, and nobody sees the text undefined overnight.

Common misconceptions

Statement Clarification
"With a new server version all clients update at once." Clients change at different paces.
"A new field is always a breaking change." An optional extension can in many cases be handled for old clients.
"A version number by itself preserves compatibility." Maintaining the old contract and a transition are also needed.
"A sample response is complete documentation." Errors, conditions, and field meanings need separate description.
"An OpenAPI file is good documentation." It is an important tool, but the meaning of the fields and the conditions are separate text.

In brief

  • An API contract is long-lived, and clients update at different paces.
  • An extension is generally compatible; removal, renaming, and a change of type or meaning are breaking.
  • The version can be signaled by the path, a header, or the schema; a transition and a retirement plan are needed with it.
  • Good documentation describes the operations, fields, errors, and changes too.
  • OpenAPI is a machine-readable description, but it does not replace describing the meaning of the fields.

Review questions

  1. Why is renaming a field a breaking change, and why is a new optional field not?
  2. How can a rename be introduced gradually?
  3. What does a sample response not tell about the API?
  4. Why is silently changing the meaning of a field dangerous?

Learning resources

Prerequisites (2)

  • web API
  • JSON

Teaches (2)

  • API versioning
  • API documentation