Versioning
What the /api/v1 prefix guarantees, how to read the version you are on, and how deprecations are announced.
The version is in the URL
https://api.postal.music/api/v1The v1 segment is the whole contract. There is no version header to send
and nothing to pin in your client beyond the base URL: a request to /api/v1
today gets the same shapes it will get in a year.
What is stable within a version
| Never changes within v1 | May change at any time (additive only) |
|---|---|
| Paths and HTTP methods | New endpoints |
| Required inputs and their types | New optional parameters and body fields |
Names and types of fields under data | New fields under data |
messageCode strings on errors | New messageCodes for new failure modes |
| Scope names | New scopes for new endpoints |
Pagination shapes (paging.next_cursor, page/pageSize) | Default and maximum page sizes |
| Webhook event names and payload fields | New webhook events and fields |
Tag categories, enum values, message text, field order |
Anything in the left column only changes under a new prefix. Anything in the right column can appear in a release without notice, so a client should ignore fields it does not know and treat enumerations as open.
Read the version you are on
Every response from /api/v1 carries the version that served it:
HTTP/1.1 200 OK
X-Postal-Api-Version: 1GET /api/v1/version describes the version without an API key:
curl https://api.postal.music/api/v1/version{
"version": "1",
"prefix": "/api/v1",
"latest": "1",
"status": "current",
"sunset": null,
"build": "a1b2c3d",
"deprecatedRoutes": []
}| Field | Meaning |
|---|---|
version | The version in the URL you called |
latest | The newest version available; differs from version once a v2 exists |
status | current, or deprecated once a newer version exists |
sunset | ISO date the whole version stops being served, or null |
build | Opaque build identifier, useful in a support ticket; may be null |
deprecatedRoutes | Routes inside this version scheduled for removal, see below |
Deprecations
Occasionally a single route has to go before a new version is warranted, usually because a better endpoint replaced it. When that happens:
- The change is announced in the changelog.
- The route keeps working unchanged for at least 90 days from the announcement.
- Every response from the route carries deprecation headers.
- The route is listed under
deprecatedRoutesonGET /api/v1/version. - On the sunset date the route is removed and returns
404.
The headers follow the IETF standards for this, so generic HTTP tooling can pick them up:
HTTP/1.1 200 OK
X-Postal-Api-Version: 1
Deprecation: @1757203200
Sunset: Thu, 07 Jan 2027 00:00:00 GMT
Link: <https://docs.postal.music/api/versioning>; rel="deprecation", <https://api.postal.music/api/v1/tracks/search>; rel="successor-version"| Header | Standard | Value |
|---|---|---|
Deprecation | RFC 9745 | @ followed by the Unix time the route was deprecated |
Sunset | RFC 8594 | HTTP date after which the route may be removed; absent while undecided |
Link | RFC 8288 | rel="deprecation" points here; rel="successor-version" points at the replacement when there is one |
The same routes appear on the version endpoint with a human note:
{
"deprecatedRoutes": [
{
"method": "POST",
"path": "/api/v1/tracks/legacy-search",
"since": "2026-09-07",
"sunset": "2027-01-07",
"successor": "https://api.postal.music/api/v1/tracks/search",
"note": "Use POST /tracks/search; results are identical."
}
]
}Log the Deprecation header when you see it. A client that does so finds out
about a removal the first time it calls the route, not when it breaks.
When a new version ships
A v2 arrives only for changes the table above forbids. When it does, v1 keeps
running alongside it: GET /api/v1/version reports latest: "2",
status: "deprecated" and a sunset date, and every v1 response gains the
Deprecation and Sunset headers. The overlap is announced in the changelog
and is never shorter than the 90-day route window. Migrating is a base URL
change plus whatever the v2 changelog entry lists.
Writing a client that survives releases
- Ignore unknown fields in responses and webhook payloads.
- Key off
messageCode, never offmessagetext or theerrorlabel. - Treat enumerations as open: new tag categories, sort keys and event names appear without a version bump.
- Do not depend on field order in JSON objects.
- Page with
next_cursorrather than assuming a page size. - Watch the
Deprecationheader and surface it in your logs.
MCP server
The MCP server is versioned separately with semantic versions, because its surface (tool names and schemas) evolves faster than the REST contract it sits on. See MCP versioning.