Postal Developers
REST API

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/v1

The 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 v1May change at any time (additive only)
Paths and HTTP methodsNew endpoints
Required inputs and their typesNew optional parameters and body fields
Names and types of fields under dataNew fields under data
messageCode strings on errorsNew messageCodes for new failure modes
Scope namesNew scopes for new endpoints
Pagination shapes (paging.next_cursor, page/pageSize)Default and maximum page sizes
Webhook event names and payload fieldsNew 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: 1

GET /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": []
}
FieldMeaning
versionThe version in the URL you called
latestThe newest version available; differs from version once a v2 exists
statuscurrent, or deprecated once a newer version exists
sunsetISO date the whole version stops being served, or null
buildOpaque build identifier, useful in a support ticket; may be null
deprecatedRoutesRoutes 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:

  1. The change is announced in the changelog.
  2. The route keeps working unchanged for at least 90 days from the announcement.
  3. Every response from the route carries deprecation headers.
  4. The route is listed under deprecatedRoutes on GET /api/v1/version.
  5. 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"
HeaderStandardValue
DeprecationRFC 9745@ followed by the Unix time the route was deprecated
SunsetRFC 8594HTTP date after which the route may be removed; absent while undecided
LinkRFC 8288rel="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 off message text or the error label.
  • 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_cursor rather than assuming a page size.
  • Watch the Deprecation header 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.

On this page