# API contract and deprecation policy

What we commit to, so you can build on this without watching it.

A feed is only worth integrating if its shape holds still. These are
commitments, not aspirations — each is testable, and breaking one should be
reported as a bug.

## Within a major version (`/v1`)

**Fields are never removed.** If a field appears in a `/v1` response today, it
will appear for the life of `/v1`.

**Fields are never retyped.** A string stays a string. A nullable field stays
nullable. `free_count` will not become a string; `starts_at` will not stop
being nullable.

**Fields may be added.** New fields can appear at any time without notice.
Consumers must ignore unknown fields rather than failing on them — this is the
one obligation on your side, and it is what allows us to improve without
breaking you.

**Semantics do not silently change.** The meaning of an existing field will not
be redefined. If `status: "ok"` means "we asked the store and this is the
answer" today, it will not quietly come to mean something weaker. A change in
meaning is a breaking change and gets a new version, even if the type is
identical.

**Enum values may be added, not repurposed.** A new store may appear in
`stores`. `status` may gain a value. An existing value will not change meaning.

## What counts as breaking

Anything that could make correct code wrong:

- removing or renaming a field
- changing a type, including nullability
- changing what an existing value means
- changing the canonical form used to compute `version`
- changing the authentication or payment flow in a way that invalidates an
  existing integration

Breaking changes ship as a **new major version** at a new path (`/v2`). They do
not arrive in `/v1`.

## Deprecation

If `/v1` is ever retired:

1. **Six months' notice minimum**, from the day `/v2` is available — not from
   the day the decision is made.
2. Responses carry the standard [RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)
   headers throughout the notice period:
   - `Deprecation: <date>` — when it was deprecated
   - `Sunset: <date>` — when it stops responding
   - `Link: <https://soren.com/v2/...>; rel="successor-version"`
3. The same information appears in the payload as a `deprecation` object, so a
   consumer parsing only the body still sees it.
4. `/v1` keeps working, unchanged, until the sunset date. A deprecated version
   is not a degraded one.

## What is *not* covered

Being explicit about the limits of the promise, because a commitment with
unstated exceptions is worth less than a narrower honest one:

- **The data itself changes constantly.** That is the product. Games become
  free and stop being free; entries appear and disappear. The *shape* is
  stable, the *contents* are not.
- **Prices may change.** Announced through the same channels, but a price is
  not part of the response contract.
- **Store coverage may change.** Adding a store is additive. Removing one — if
  a storefront shuts down or blocks us — will be reported in `stores` as
  `not_configured` rather than silently disappearing.
- **Rate limits may be adjusted.** They are reported in every response, so a
  well-behaved client adapts automatically.
- **This is a small operation.** There is no SLA and no uptime guarantee. What
  there is instead is a published operating record at `/v1/status`, and a
  commitment to fail loudly rather than serve data we cannot vouch for.

## The version hash is part of the contract

`version` is a SHA-256 over a canonical form documented in the `verification`
block of every payload. **That canonical form will not change within `/v1`** —
if it did, every consumer's integrity check would break at once, and their
billing assumptions with it.

Timestamps are excluded from the hash and will remain excluded. This is not an
implementation detail: it is why an unchanged `version` genuinely means
unchanged data, and why conditional requests are not billed.
