v1 surface — an endpoint, a response
field, an SDK export — never disappears or changes shape under you without a named
deprecation, an advance-notice window, and a machine-readable signal on the wire. This
page is that contract: what “deprecated” means, how long you get, what you will see,
and how to migrate.
Verified live. The endpoint behavior and header presence on this page were
captured from
https://phosra-api-sandbox-production.up.railway.app on 2026-07-06.
For how the four version layers pin together, start with Versioning.The one guarantee
We do not make silent, breaking, immediate changes to a stable v1 surface.
Every breaking change is announced first, carries a machine-readable deprecation
signal, and honors a minimum notice window before the old behavior can be removed.
Everything below is the mechanism behind that sentence.
Lifecycle stages
Every stable surface moves through four stages, in order. It can sit in a stage indefinitely, but it can never skip one.| Stage | What it means | What your code should do |
|---|---|---|
| Active | Fully supported. The default state of everything in v1. | Nothing. |
| Deprecated | Still works exactly as before, but a replacement exists and a removal date may be set. Marked in the changelog, in the SDK (@deprecated), and — once it enters a sunset window — on the wire via a Deprecation header. | Plan your migration. No rush, but stop building new usage on it. |
| Sunset | A removal date is now committed and published in a Sunset header. The surface still responds until that date. | Migrate before the Sunset date. |
| Removed | Gone. The endpoint returns 404; the field is absent; the SDK export no longer exists in that major line. | Already migrated. |
“Deprecated” is not “broken.” A deprecated endpoint returns the same status
codes and the same body it always did. The only thing that changes is that we have
told you a better path exists and, eventually, when the old one stops. You are never
forced to move on the day of the announcement.
Advance-notice windows
The minimum notice you are guaranteed depends on the blast radius of the change. These are floors, not targets — most changes get more.| Change class | Example | Minimum notice before removal |
|---|---|---|
| Additive (non-breaking) | New endpoint, new optional request field, new response field or enum value | None required — it cannot break a lenient client. See Forward compatibility. |
Deprecated endpoint or field inside v1 | A route superseded by a better-named one; a response field renamed with both served during overlap | ≥ 90 days from the Sunset header / changelog entry to removal |
New major API version (/api/v2) | A genuinely incompatible contract | A new path. v1 keeps serving; ≥ 12 months overlap before a v1 sunset can even be proposed, and that proposal is its own separately-announced deprecation |
| Security-forced break | A change we must make to close a vulnerability | As much notice as is safe to give, clearly labeled security. This is the only lane that can be shorter than 90 days, and only when a longer window would leave users exposed. |
On the wire: Deprecation and Sunset headers
Phosra follows the two IETF standards for signalling this in HTTP, so a generic client
or gateway can detect a deprecation without reading these docs:
Deprecation— RFC 9745. A Structured-Field Date (@+ Unix seconds) marking when the resource became (or will become) deprecated. It may be a future date.Sunset— RFC 8594. An HTTP-date marking the moment the resource stops responding.Link— a pointer to the human migration guide, related byrel="deprecation"and/orrel="sunset".
Deprecated response (format)
@1788220800), it stops responding
after 2026-12-01, and the migration guide is at the linked URL.” The 200 and the body
are exactly what you got before — the headers are the only difference.
Deprecation carries a Structured-Field Date, so the value is @ followed by
Unix seconds — not an HTTP-date. Sunset carries a classic HTTP-date. They are
intentionally different formats because they come from two different RFCs; parse each
accordingly.What you see today
Nov1 endpoint has entered a sunset window yet, so you will not see a Sunset
header on a live response today — and we would rather you verify that than take our
word for it:
Verify — no live sunset headers today
The one deprecation live today
There is exactly one deprecated route on the current census: the legacy advisor self-registration shim.| Deprecated | Replacement | |
|---|---|---|
| Route | POST /api/v1/advisors/register | POST /api/v1/advisors/self-register |
| Status | Still serving — forwards to the replacement, adds no new access | Canonical route |
| Why | The old route multiplexed two different call shapes on one path; the split gives each its own single-contract endpoint | — |
did discriminator and returns the same result the
replacement would:
The deprecated route still forwards (live)
@phosra/cli already calls self-register, so phosra register needs no
change — only hand-rolled callers of the old path do. See the
CLI reference and self-serve registration.
How a deprecation reaches you
We announce on every channel a developer actually watches — no single point of failure:The changelog
Every deprecation lands as a dated entry, tagged with the replacement and the
removal date. This is the canonical, human record.
Response headers
Deprecation + Sunset + Link on the wire, so automated monitoring catches it
without reading docs.Your account email
Owners of orgs with recent traffic on a deprecated surface get a direct notice at
announcement and before the sunset date.
The SDK
Deprecated exports carry a
@deprecated JSDoc tag (your editor and tsc flag it)
and a 0.x → next-minor removal follows semver. Read the changelog before bumping.Detect deprecations programmatically
Fail loud in CI or a health check the moment any endpoint you depend on starts advertising a sunset. This runs today (and prints nothing, correctly):What we will never do
Break v1 in place
A breaking change gets a new path (
/api/v2), never a mutation of v1.Remove without a Sunset
Nothing in
v1 is removed until a Sunset date has been published and the notice
window has elapsed.Change a field's meaning
We add a new field beside the old one; we do not silently repurpose an existing
field or enum value.
Substitute frozen artifacts
A pinned Annex B edition is content-addressed — its bytes never change, and a
missing edition is a clean
404, never a nearby substitution. See
Versioning.Migration expectations
When you receive a deprecation notice, the contract you can count on:- A named replacement exists before the notice ships. We never deprecate a path without a place to go.
- The old and new surfaces overlap for at least the notice window — you can run both, cut over, and verify without a flag-day.
- The migration is documented at the
Link: rel="deprecation"URL, with a before/after example like the one above. - Idempotent, replay-safe cutover. Re-issuing the same write against the new path is safe — see Idempotency.
Next steps
Versioning
The four version layers — API path, OCSS spec version, Annex B editions, SDK
semver — and how to pin each.
Changelog
The canonical dated record of every change, deprecation, and SDK release.
Forward compatibility
Why most evolution reaches you as runtime data with no reinstall — and how to write
a client that never breaks on an additive change.
Errors
The closed set of error classes a deprecated or removed surface returns.