SevyDevy

Guide · Backend

API Versioning Strategies: URL, Header or Evolve in Place

24 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • API versioning is how you change an API, the agreed way one program asks another for data, without breaking apps that already call it. You label incompatible shapes with versions, or make only changes old apps survive.
  • There are three common strategies. Put the version in the URL path (/v1/invoices), or in a request header (a labelled line sent with each request), or evolve in place with only additive changes.
  • A breaking change is anything that can make a working client fail. Examples: removing or renaming a field (one named value in the reply), changing its type or meaning, or adding a required input.
  • Most teams do best with a mix. Evolve in place by default, and save a new version for the rare change that cannot be made safely.
  • Beginners can stop after the section on choosing a strategy. Engineers then get runnable adapter code that serves old dated versions from one codebase, and the exact Deprecation and Sunset retirement headers with a one-line check you can run.

What goes wrong when you change an API other apps depend on?

Their code breaks, because it was written against the old shape of your replies and nothing warned it that the shape had changed. Protecting those callers is what API versioning is for.

Picture a common setup. A GST-invoicing API serves about 600 accounting apps, which call it to create invoices, work out tax and file e-invoices for their customers. The setup is made up, but the problem is real.

GST, the Goods and Services Tax, is India's tax on most sales. For a sale inside one state, the tax has two parts, CGST (central) and SGST (state). For a sale between states, it is one part, IGST (integrated).

For three years, every invoice the API returned carried the tax as one number, in a field called tax_amount. JSON is the plain-text format most web APIs reply in, and a field is one named value in it, like a labelled box. Accounting apps read tax_amount and printed it on the bill.

Then customers asked to see the tax split into its parts. One Friday evening in September 2025 (made up), a developer replaced tax_amount with a new object called tax, holding cgst, sgst and igst. The change looked tidy and passed every test the team had.

By Saturday morning, 140 apps were showing blank tax lines or crashing. Their code still looked for tax_amount, and it was gone. Support had 300 tickets by noon, many from accountants racing a filing deadline.

The fix took an hour: put tax_amount back. The lesson took longer. The team had no plan for changing its API without hurting the people who depended on it, and this guide builds that plan.

What is API versioning?

API versioning is the set of rules that lets you change an API while apps built on its old behaviour keep working. It answers two questions: which changes need a new version, and how a caller asks for one.

An API (application programming interface) is the agreed way one program talks to another. Here, it is a list of web addresses called endpoints, plus the JSON each one accepts and returns. A client is any program that calls those endpoints.

The hard part is that you do not control your clients. An accounting app in Surat may ship an update once a quarter. A small firm's in-house script may never be updated at all. Whatever you change, their old code keeps running.

Think about the plug points in an Indian home. Older rooms often have two-pin sockets, while newer appliances arrive with three-pin plugs. Nobody rewires the whole house when a new plug shape appears. API versioning makes software keep the same promise: new shapes arrive, and old plugs still work.

There are three main ways to keep the promise:

  • Version in the URL. The version is part of the path, like /v1/invoices and /v2/invoices. Each version is a separate address.
  • Version in a header. The path stays the same, and the client names a version in an HTTP header, a small labelled line sent with each request.
  • Evolve in place. There is effectively one version, and you only make changes that old clients can safely ignore.

What counts as a breaking change in an API?

A breaking change is any change that can make a correctly written client fail or behave wrongly. Removing a field, renaming it, changing its type or meaning, and adding a new required input are the classic examples.

Kubernetes, the open-source system that runs containers (packaged apps) across many machines, states the rule plainly. As of 19 October 2026, Rule #1 of its deprecation policy says this. Once an API element is added at a particular version, it cannot be removed from that version or have its behaviour significantly changed.

Here is how the invoicing team now sorts changes, using its own API:

  • Breaking: removing tax_amount, the change that started all this.
  • Breaking: renaming gstin (the seller's GST registration number) to gst_number. To an old client, a rename looks exactly like a removal.
  • Breaking: changing amount from whole paise (100 paise make a rupee) to rupees with decimals. The name stays the same but the meaning does not, so old apps show totals 100 times too small.
  • Breaking: making a new input, like e_invoice_mode, required when creating an invoice. Every old request now fails validation.
  • Breaking: a list endpoint that used to return everything now returns 20 items a page. Old clients silently miss the rest.
  • Usually safe: adding a new optional input, a new field in a response, or a whole new endpoint.

Notice the word usually. Adding a response field is safe only if clients ignore fields they do not know. Zalando, a European online fashion retailer, publishes API guidelines that make this a rule for clients (rule 108). Be tolerant of unknown fields, and be ready for new values in enums the API marks as extensible.

An enum is a field that takes one value from a fixed list. On this API, an invoice status is draft, issued or cancelled. If the team adds amended, a client that crashes on unknown values breaks, even though nothing was removed.

How does URL versioning work?

With URL versioning, the major version is part of the path, so /v1/invoices and /v2/invoices are separate endpoints that can return different shapes. A client chooses a version by choosing which address to call.

A major version is a number that only goes up when something breaks. Small, safe changes ship inside the current major version without a new number.

Back to the plug points. URL versioning is like fitting a new three-pin socket right next to the old two-pin one. Each plug goes into the socket made for it, and nothing about the old socket changes.

Here is how the invoicing API would run it. An API gateway is the front door that receives every request first. Look at the gateway box: it reads only the path prefix and sends each request to the matching handler. Then notice that both handlers lead to the same service.

URL versioning for the invoicing API

Notice that two versions share one invoice service and one database. Only the thin handlers at the edge differ.

Diagram connections:

  • Older accounting app to API gateway (/v1/ request)
  • Updated accounting app to API gateway (/v2/ request)
  • API gateway to v1 invoice handler
  • API gateway to v2 invoice handler
  • v1 invoice handler to Invoice service
  • v2 invoice handler to Invoice service
  • Invoice service to Invoice database
  • Older accounting app: Still calls /v1/invoices
  • Updated accounting app: Calls /v2/invoices
  • API gateway: Routes by the path prefix
  • v1 invoice handler: Shapes replies with tax_amount
  • v2 invoice handler: Shapes replies with the tax object
  • Invoice service: One copy of the GST rules
  • Invoice database: One store for every version

The shared invoice service is the key design choice. Both handlers call the same tax logic and read the same database. The v1 handler only reshapes the reply into the old form. If each version kept its own copy of the GST rules, every rate change would need fixing twice.

URL versioning is the easiest style to see and debug. The version shows up in logs, browser tabs and support tickets. It also suits caches (stores of recent replies), because a cache files each reply under its URL, so different paths are naturally different entries.

The cost is size. A path version usually covers the whole API, so one breaking change to invoices creates a new version of every endpoint. Clients must move everything at once, and old versions tend to live for years.

Opinions differ sharply here. Zalando's guidelines forbid URL versioning (rule 115), because the consumer has to wait until the provider has released and deployed, and the two become tightly coupled. When a new version is unavoidable, Zalando requires media type versioning instead (rule 114).

Kubernetes takes the opposite view and puts versions right into its API paths. Its official Python client, version 34.1.0, calls both /apis/autoscaling/v1/ and /apis/autoscaling/v2/ paths for the same kind of object, the HorizontalPodAutoscaler. That object adds or removes copies of an app as load changes. Notice the version belongs to one API group, autoscaling, not to the whole API.

Each Kubernetes version name also carries a stability promise. A beta version, named like v1beta1, must be deprecated no more than 9 months or 3 minor releases after it appears, whichever is longer. A stable version, like v1, must not be removed within a major version of Kubernetes.

How does header versioning work?

With header versioning, every version shares the same URL, and the client names the version it wants in an HTTP header. The server reads that header and shapes the reply to match.

There are two common forms:

  • A custom header, such as Api-Version: 2026-03-01. It is simple to read and easy to log.
  • The Accept header, which tells the server what format the client wants. The version rides inside the format name, as in application/vnd.example.invoice+json;version=2. This is called media type versioning.

A query parameter, such as version=2 after a question mark at the end of the address, looks similar but is part of the URL, not a header. It shows in links, and caches treat each value as a separate entry, so it behaves like URL versioning.

Dated versions and pinning

Some header-based APIs name versions by release date instead of a number. Stripe, the payments company, is the best-known example and described this in a 2017 engineering post. Each dated version holds a small set of breaking changes, so each upgrade stays small.

The same post explains pinning. The first time an account makes a request, it is pinned to the newest version, and later calls use that version by default. A Stripe-Version header can override it for one request.

Stripe has since changed the rhythm. As of 19 October 2026, its docs describe monthly versions with no breaking changes, plus two major releases a year that start with breaking changes. Versions are still named by date, such as 2026-09-30.endive.

Pinning answers a question every header scheme must face: what does a request with no version header get? If it got the latest version, every breaking release would break those callers. The invoicing team now pins each account to the version that was newest on its first call.

In plug terms, header versioning is one modern socket on the wall plus a small adapter for each old plug. The wiring behind the wall is built once, for the new shape. The adapters do the converting, and the engineer section below builds them in code.

Header versioning keeps addresses stable and lets you break things in small steps. The cost is visibility: you cannot see the version in a plain link. Caches must also be told that the reply depends on the header, which is the job of the Vary response header.

Vary lists the request headers that shaped a reply, so a cache keeps one stored copy per value. A path or query parameter version needs no Vary, because it is already part of the URL the cache files the reply under.

How do you evolve an API in place without new versions?

You evolve in place by making only additive changes that old clients can safely ignore, and by never removing or changing what is already there. Done with discipline, most changes never need a new version.

Zalando's guidelines make this the default (rule 113): change APIs in a compatible way and avoid creating additional versions. They pair it with the client rule from earlier, often called the tolerant reader. A tolerant reader ignores fields it does not recognise instead of failing.

In plug terms, this is a socket designed from the start to take both plug shapes. It gained a new ability without losing an old one.

Where the picture breaks: a socket's shape is fixed once it is fitted, but an API keeps adding things for years. And no socket has to cope with a plug whose meaning changed, like paise quietly becoming rupees.

Here is how the team should have shipped the tax split. Instead of replacing tax_amount, add the tax object next to it. Old apps keep reading tax_amount, and new apps read the parts. Both are correct on every invoice.

  1. Add the new field alongside

    Ship tax next to tax_amount. Both are filled on every invoice, and both come from the same calculation, so they can never disagree.

  2. Mark the old field deprecated

    Update the docs and the API description file (often an OpenAPI file) to say tax_amount is deprecated. Deprecated means it still works, but new code should not use it.

  3. Decide whether it ever needs to go

    You cannot see which clients read a response field. For a cheap field like tax_amount, keeping it for good is often the right call.

  4. Remove it only inside a new version

    If the old field must go, drop it only for clients on a newer version. Older versions get it back through an adapter, shown in the engineer section.

Evolving in place has limits. You cannot fix a bad name, a wrong type or a confusing design. Over the years, the API collects deprecated fields that nobody dares remove. That clutter is the price of never breaking anyone.

GraphQL APIs lean heavily on this style. GraphQL's own guidance says it takes a strong opinion on avoiding versioning, because a server returns only the fields a client asks for. Old fields are marked with the built-in @deprecated directive, a label in the schema (the typed list of everything the API offers).

The guide comparing REST, GraphQL and gRPC (a way for programs to call each other's functions over the network) covers that style in more depth. The invoicing team now runs every change through the decision path below, starting at the top box. Most changes stop at the first question.

Does this change need a new version?

Most changes end at Ship in place. Notice that a new version is the last resort, reached only when new and old cannot sit side by side.

Diagram connections:

  • Proposed API change to Removes or changes anything?
  • Removes or changes anything? to Ship in place (No, it only adds)
  • Removes or changes anything? to Can new sit beside old? (Yes)
  • Can new sit beside old? to Add new, keep old (Yes)
  • Add new, keep old to Ship in place (Now it only adds)
  • Can new sit beside old? to Release a dated version (No)
  • Release a dated version to Changelog and emails
  • Proposed API change: For example, split tax into its parts
  • Removes or changes anything?: Field, type, meaning or a required input
  • Ship in place: All current versions get it
  • Can new sit beside old?: Keep the old field and add the new one
  • Add new, keep old: tax next to tax_amount
  • Release a dated version: Old shape served through an adapter
  • Changelog and emails: Tell every app owner, with dates

Which API versioning strategy should you choose?

Choose evolve in place as your default, and use URL or header versioning only for breaking changes you truly cannot avoid. Which of those two fits depends on who your clients are and how often you break things.

The three strategies compared for an API like the invoicing one
Version in the URLVersion in a headerEvolve in place
Where the version livesIn the path, like /v2/In a request header, often a dateNowhere, effectively one version
Easy to see when debuggingYes, it is in every link and logOnly if you log the headerNothing to see
Size of one new versionUsually the whole APIOne small set of changesNo new versions
Work for the API teamRun and patch several handlersWrite and keep one adapter per changeCareful review of every change
Work for clientsBig moves, rarelySmall upgrades, when they chooseNone, if they ignore unknown fields
CachingSimple, paths differNeeds the Vary headerSimple
Best fitRare, large redesignsMany clients upgrading at different speedsMost day-to-day changes

The team's final plan mixes all three. The path keeps /v1/, which the team has no plan to change. Day-to-day changes are additive. The rare breaking change gets a dated version, chosen with an Api-Version header and pinned per account.

Stripe's 2017 post describes a similar mix: a /v1/ path prefix for major versions, plus dated versions for smaller changes.

Beginners can stop here: you know what breaks clients, the three strategies and which to choose. The rest is for engineers: the adapter code behind dated versions, and how to retire a version with standard headers.

How do dated API versions work in code?

You write the code once, for the newest shape, and convert each reply back for older callers. Stripe's 2017 post calls the converters version change modules. The API formats a response at the current version, then walks back through time, applying each module it finds.

Suppose that, despite the advice to keep it, the team later decided tax_amount had to go from new versions, in a dated release on 1 September 2026. Older callers must still get it. Follow the chain below from left to right. The request comes from an app built in late 2025, so it needs two changes undone.

Serving an old dated version

Code is written once, for the newest shape. Notice that each adapter undoes one breaking change, newest first, until the reply matches the caller's version.

Diagram connections:

  • Request arrives to Version header sent?
  • Version header sent? to Build newest response (Yes, 2025-10-01)
  • Version header sent? to Account's pinned version (No)
  • Account's pinned version to Build newest response (Use the pinned date)
  • Build newest response to Undo 2026-09-01 change
  • Undo 2026-09-01 change to Undo 2026-03-01 change
  • Undo 2026-03-01 change to Reply in the old shape
  • Request arrives: From an app built in late 2025
  • Version header sent?: Api-Version on the request
  • Account's pinned version: Saved on the account's first call
  • Build newest response: Shape of version 2026-09-01
  • Undo 2026-09-01 change: Adds tax_amount back from the tax parts
  • Undo 2026-03-01 change: Turns the date back into DD/MM/YYYY
  • Reply in the old shape: Exactly what the 2025 app expects

Here is the heart of it in Python, small enough to run as it stands. Amounts are whole paise in every version, so adding them never causes rounding errors.

python
def build_latest(inv):
    # Newest shape (2026-09-01): tax in parts, ISO date, no tax_amount
    return {"id": inv["id"], "invoice_date": inv["date"],
            "tax": {"cgst": inv["cgst"], "sgst": inv["sgst"], "igst": inv["igst"]}}

def add_back_tax_amount(body):         # undoes the 2026-09-01 change
    body = dict(body)                  # copy, never edit in place
    t = body["tax"]
    body["tax_amount"] = t["cgst"] + t["sgst"] + t["igst"]
    return body

def undo_iso_date(body):               # undoes the 2026-03-01 change
    body = dict(body)
    y, m, d = body["invoice_date"].split("-")
    body["invoice_date"] = f"{d}/{m}/{y}"
    return body

# (version that made a breaking change, adapter that undoes it), newest first
ADAPTERS = [("2026-09-01", add_back_tax_amount),
            ("2026-03-01", undo_iso_date)]
PUBLISHED = {"2025-10-01", "2026-03-01", "2026-09-01"}

def render_invoice(inv, headers, pinned_version):
    wanted = headers.get("Api-Version") or pinned_version
    if wanted not in PUBLISHED:
        raise ValueError(f"unknown API version: {wanted}")
    body = build_latest(inv)
    for changed_in, undo in ADAPTERS:
        if wanted < changed_in:        # caller predates this change
            body = undo(body)
    return body

inv = {"id": "INV-1", "date": "2026-10-19", "cgst": 9000, "sgst": 9000, "igst": 0}
print(render_invoice(inv, {}, "2025-10-01"))
print(render_invoice(inv, {"Api-Version": "2026-09-01"}, "2025-10-01"))
# {'id': 'INV-1', 'invoice_date': '19/10/2026', 'tax': {'cgst': 9000, 'sgst': 9000, 'igst': 0}, 'tax_amount': 18000}
# {'id': 'INV-1', 'invoice_date': '2026-10-19', 'tax': {'cgst': 9000, 'sgst': 9000, 'igst': 0}}

Pause and predict: an account pinned to 2026-03-01 sends no header. Which adapters run?

Here is what the sample did. The first call sends no header, so the account's pinned version, 2025-10-01, applies. Both adapters run: tax_amount comes back as 18000 paise (180 rupees), and the date returns to DD/MM/YYYY. The second call asks for 2026-09-01, so no adapter runs.

Two lines matter most. The test wanted < changed_in works because dates written as YYYY-MM-DD, with zero-padded months and days, sort correctly as plain text. The list runs newest first, so each adapter receives exactly the shape its own release produced.

That answers the prediction. For an account pinned to 2026-03-01, only add_back_tax_amount runs, because 2026-03-01 is earlier than 2026-09-01 but not earlier than itself.

The PUBLISHED check matters too. Without it, a typo like 2026-13-01 still compares as text and silently gets the newest shape. Web frameworks usually look up headers case-insensitively, as Python's own http.server does. A plain Python dictionary does not, so use your framework's header object.

Kubernetes holds itself to the same idea. Rule #2 of its deprecation policy says API objects must round-trip between versions in a release without losing information, with one narrow exception. Round-trip means converting to another version and back gives the original object.

How do you deprecate and retire an old API version?

You retire a version in stages: announce it, mark its responses with Deprecation and Sunset headers, track who still calls it, help them move, and only then switch it off. Skipping the tracking is how teams break their biggest customers.

RFC 9745, published in March 2025 by the IETF (the group that writes internet standards), defines a Deprecation response header. An RFC is one of the IETF's numbered documents, and this one is on the standards track, meaning it is a proposed internet standard.

The header's value is a date. A past date means the resource was deprecated; a future date means it will be. The RFC also says the act of deprecation does not change any behaviour of the resource. It is a warning, not a switch.

Its partner is the Sunset header from RFC 8594, an informational RFC from May 2019. Sunset gives the time after which the resource is likely to become unresponsive. RFC 9745 adds that the Sunset time must not be earlier than the Deprecation time.

RFC 9745 also defines a deprecation link, which points callers to human-readable notes such as a migration guide. Here is the version list the team's gateway reads, written in YAML (a plain-text format for settings files). The oldest dated version, from October 2025, is now on its way out.

yaml
# versions.yaml: every dated version the invoicing API still serves
latest: "2026-09-01"
new_accounts_pin_to: latest
versions:
  - date: "2026-09-01"
    change: "tax_amount removed; read the tax object"
  - date: "2026-03-01"
    change: "invoice_date is YYYY-MM-DD (was DD/MM/YYYY)"
  - date: "2025-10-01"
    change: "first dated version"
    deprecation: "2026-11-01T00:00:00Z"
    sunset: "2027-05-01T00:00:00Z"
    count_calls_per_client: true

The gateway turns those two dates into the formats each header needs. Deprecation uses the Date type from RFC 9651, the HTTP structured fields standard: an @ sign, then seconds since 1 January 1970 UTC. Sunset uses the usual HTTP date format. This snippet shows both conversions:

python
from datetime import datetime, timezone
from email.utils import format_datetime

deprecation = datetime(2026, 11, 1, tzinfo=timezone.utc)
sunset = datetime(2027, 5, 1, tzinfo=timezone.utc)
print(f"Deprecation: @{int(deprecation.timestamp())}")
print(f"Sunset: {format_datetime(sunset, usegmt=True)}")
# Deprecation: @1793491200
# Sunset: Sat, 01 May 2027 00:00:00 GMT

To check a live endpoint, use curl, a command-line HTTP client. The command below sends a normal GET for the old version and prints only the reply's headers. Replace YOUR_API_URL with your API's base address.

bash
# GET the old version and print only the response headers
curl -s -D - -o /dev/null -H "Api-Version: 2025-10-01" YOUR_API_URL/v1/invoices/INV-1
# Among the lines printed, expect exactly these two:
# Deprecation: @1793491200
# Sunset: Sat, 01 May 2027 00:00:00 GMT

Header names are case-insensitive, and over HTTP/2 tools often show them in lower case, such as deprecation:. What must match exactly is the value after the colon.

The line that matters most in versions.yaml is count_calls_per_client. It turns retirement from a guess into a list of names. Zalando's guidelines make this a must (rule 188): monitor usage of anything scheduled for sunset to watch the migration.

Zalando goes further in rule 185: before shutting an API down, the producer must make sure every client has agreed to the sunset date. A public API with outside clients usually cannot wait for every one of them to agree, so it needs the steps below instead.

  1. Announce with a date

    Post the plan in the changelog and email every app owner. The notice names the sunset date and points to the migration notes.

  2. Send the headers

    From 1 November 2026, every reply on the old version carries Deprecation and Sunset headers. Client libraries can log a warning when they see them.

  3. Watch the numbers

    A weekly report lists every client still on the old version, sorted by call volume. The goal is a shrinking list, not a calendar date.

  4. Help the last few by name

    Someone on the team calls the top remaining apps directly. A large accounting firm often has one forgotten script that nobody on its team remembers writing.

  5. Brown out before shutting down

    In the final weeks, the old version returns errors for one planned hour a day. Forgotten scripts fail loudly while people are still around to fix them.

  6. Switch off with a clear error

    After the sunset date, old calls get a clear error such as 410 Gone, the HTTP status for something removed for good. The message names the version to move to.

How do you explain API versioning in a system design interview?

Explain it as a promise to clients, then show the mechanics: what counts as breaking, how a client picks a version, and how old versions retire.

  1. Start with the clients

    Ask who calls the API and how fast they can update. Outside clients you do not control change the whole answer.

  2. Define breaking

    Removing, renaming, retyping or changing the meaning of a field, plus new required inputs. Everything else can ship in place.

  3. Pick the mechanism

    A URL major version for rare big redesigns, or dated header versions with a pinned default for frequent small ones.

  4. Keep one core

    Old versions are thin adapters over one current implementation, never a full copy of the code.

  5. Plan retirement

    Deprecation and Sunset headers, usage tracking per client, direct outreach to the stragglers, then a clear switch-off.

Questions people ask

Should I put v1 in my API URL from day one?
Many teams do, because it costs almost nothing. A /v1/ prefix leaves room for a future major version if you ever truly need one. Then work hard never to need it. Stripe's 2017 post describes this: a /v1/ prefix, with most change handled by dated versions. Zalando's guidelines are a notable exception and forbid URL versions.
Is header versioning better than URL versioning?
Neither is better everywhere. URL versioning is easier to see, test and cache, which suits public APIs with rare redesigns. Header versioning keeps addresses stable and lets you ship small breaking changes one at a time. Many teams combine a URL major version with headers for the smaller steps.
Is adding a new field to a JSON response a breaking change?
Usually not, as long as clients ignore fields they do not know. That habit is called being a tolerant reader. It can break clients that check replies against a strict schema that rejects extra fields. Say in your docs from day one that new fields may appear, so client authors write tolerant code.
What is the difference between deprecation and sunset?
Deprecation means the API still works, but you should stop using it and plan a move. Sunset is the time after which it will likely stop responding. RFC 9745 defines the Deprecation header and RFC 8594 defines the Sunset header. RFC 9745 says the Sunset time must not be earlier than the Deprecation time.
How long should an old API version be supported?
Long enough for your slowest important client to move, and publish that period up front. Be generous, because outside teams plan releases months ahead. Kubernetes, for example, serves a deprecated beta API until 9 months or 3 minor releases after deprecation, whichever is longer. Check real usage per client before you switch anything off.
What is semantic versioning, and does it apply to web APIs?
Semantic versioning is a numbering scheme, MAJOR.MINOR.PATCH, where the major number goes up only for incompatible changes. Minor adds compatible features and patch adds compatible fixes. Web API clients usually see only the major number, as in /v1/, because compatible changes need no choice from them.
Do GraphQL and gRPC APIs need versioning?
They need the same care, with different tools. GraphQL APIs usually evolve in place, marking old fields with @deprecated while clients request only the fields they use. A gRPC service usually describes messages with Protocol Buffers, a compact binary format where every field has a fixed number. Adding fields is safe, because old code ignores them. Reusing a deleted field's number can corrupt data, so reserve it.

Sources and further reading

Practise this with Lala

Practise