Versioning and deprecation
What /api/v1 promises, how a change is announced, and how long a route keeps working after it is deprecated.
x.md is meant to be integrated once and then left running. This page is the promise about when that integration can break, and how much warning it gets.
Two surfaces
| Surface | Example | Contract |
|---|---|---|
| Versioned machine surface | GET /api/v1/search?q=vercel |
v1 is the current major version. Breaking changes ship as /api/v2/*, never inside v1. |
| Permalink product surface | GET /search?q=vercel |
Mirrors the shape of an x.com URL. Unversioned, stable, not deprecated. |
The two surfaces run the same code and return the same bodies. Pick the versioned paths when you are writing a client that has to keep working unattended; pick the permalink paths when you are rewriting an x.com URL you already hold.
The versioned surface is:
GET /api/v1/posts # ?url= or ?handle= + ?id=
GET /api/v1/profiles/{handle}
GET /api/v1/profiles/{handle}/followers
GET /api/v1/profiles/{handle}/following
GET /api/v1/search # ?q=
GET /api/v1/oembed # ?url=
What can change inside v1
These are additive and ship without a version bump:
- New optional query parameters.
- New fields in a JSON response body, and new keys inside existing objects.
- New response headers.
- New values in an open-ended field such as
source. - Changes to the rendered Markdown: wording, ordering, and layout are presentation, not contract.
Ignore JSON fields you do not recognise, and do not parse the Markdown as if it were a data format. If you need fields, ask for format=json or send Accept: application/json.
These are breaking, and ship only under a new path prefix:
- Removing or renaming a query parameter, a JSON field, or a route.
- Changing the type of an existing field, or narrowing what an existing parameter accepts.
- Changing the meaning of an existing status code or error
code.
/api/v1/* keeps working for at least 12 months after its successor is published.
How a deprecation is announced
A deprecated route keeps answering exactly as before and adds three signals to every response it produces, success and error alike:
Deprecation: @1789430400
Sunset: Wed, 15 Sep 2027 00:00:00 GMT
Link: <https://x.pcstyle.dev/api/v1/posts>; rel="successor-version", <https://x.pcstyle.dev/docs/versioning>; rel="deprecation"; type="text/html", <https://x.pcstyle.dev/docs/versioning>; rel="sunset"; type="text/html"
| Header | Specification | How to read it |
|---|---|---|
Deprecation |
RFC 9745 | A structured-field Date: @ followed by Unix seconds. @1789430400 is 2026-09-15T00:00:00Z. A date in the future means the route becomes deprecated then. |
Sunset |
RFC 8594 | An HTTP-date after which the route may stop answering. Never earlier than the Deprecation date. |
Link; rel="successor-version" |
RFC 8288 | The route to move to. Safe to follow automatically. |
Link; rel="deprecation" |
RFC 9745 | This page. |
Link; rel="sunset" |
RFC 8594 | This page. |
The /api/* routes list Deprecation, Sunset, and Link in Access-Control-Expose-Headers, so a browser client can read them cross-origin.
Scheduled for deprecation
| Route | Successor | Deprecation date | Sunset |
|---|---|---|---|
GET /api/convert |
GET /api/v1/posts |
2026-09-15 | 2027-09-15 |
GET /api/browse |
GET /api/v1/profiles/{handle}, /followers, /following, GET /api/v1/search |
2026-09-15 | 2027-09-15 |
Both aliases send Deprecation and Sunset on every response now, before the date the Deprecation field names. That is the point of a future date: it is notice that the route becomes deprecated on 2026-09-15, not a claim that it already is.
/api/browse selects its successor by resource: profile, followers, and following map to the matching /api/v1/profiles/{handle} route, and search maps to /api/v1/search. A call that names no resolvable resource still carries Deprecation and Sunset, but no successor-version link.
Both aliases keep working unchanged until the sunset date. Nothing else is deprecated. The permalink routes are not aliases and are not going away.
Check it from code
Deprecation is visible on a HEAD, so a client can check without spending a read:
curl -sSI 'https://x.pcstyle.dev/api/convert?url=https://x.com/jack/status/20' \
| grep -iE '^(deprecation|sunset|link):'
A reasonable client policy: log a warning when Deprecation is present, follow rel="successor-version" on the next deploy, and fail loudly if Sunset is less than 30 days away.
The same policy is machine-readable in two places:
/openapi.json— thex-api-lifecycleobject carriescurrent_major_version,supported_versions,minimum_support_window_months,deprecation_signals, and every deprecated operation with its dates and its replacement:successorwhen one route replaces the alias,successor_parameterplussuccessorsfor/api/browse, which picks its replacement fromresource. The operations themselves carryx-successor-version(/api/convert) orx-successor-version-map(/api/browse), alongside OpenAPI’s owndeprecated: true./api— theversioningobject repeats the same dates in a smaller document, for an agent that has only the domain. Its/api/browseentry carriessuccessor_parameterandsuccessorsrather than a singlesuccessorstring, so parse both shapes.
Release notes
Changes are tagged on main and listed in CHANGELOG.md. The hosted API at x.pcstyle.dev runs the latest tag.
x.md is a free, MIT-licensed, best-effort project. This policy describes how change is signalled; it is not an SLA. If a route is broken by an upstream provider rather than by a release, the failure shows up as a 502 or 503 problem document, not as a version bump.