# Versioning

What we may change without warning, and what we may not.

The version is in the path. Everything served under `/ext/v1` keeps its
promises for as long as v1 exists.

```text
https://api.salafems.com/ext/v1/orders
```

There is no version header, no date pinning per request, and no account-level
version. One product team and a modest surface do not justify that machinery —
a path segment is the whole strategy, and it is legible from a log line.

## What we may change inside v1

These are **additive** and can ship any time. Your integration must tolerate
them.

| Change                            | Example                                           |
| --------------------------------- | ------------------------------------------------- |
| A new field on an existing object | An order gains `delivery_window`                  |
| A new endpoint                    | `GET /ext/v1/returns`                             |
| A new query parameter or filter   | `updated_after` arriving on customers             |
| A new value in an existing enum   | An order status we do not have today              |
| A new error `code`                | A more specific failure than the one you get now  |
| A new webhook event type          | An endpoint subscribed to `*` starts receiving it |

> **Warning — Treat every enum as an open set**
>
> This is the single most common way an integration breaks on an additive
> change. Code that switches exhaustively on `order.status` — or worse, throws
> on an unrecognised value — will fail the day a new status ships, and that day
> is not a breaking change on our side.
>
> Handle the values you care about; let everything else fall through a default
> branch.

## What we will not change inside v1

These are **breaking** and require a new major version:

- Removing a field, or renaming one
- Changing a field's type — a money string will never become a number
- Changing what a field means while keeping its name
- Removing an endpoint or a query parameter
- Removing an enum value
- Making an optional parameter required
- Changing the meaning of an HTTP status or an error `code`

A v2 would mount **beside** v1 at `/ext/v2`, never over it. v1 does not change
underneath you the day v2 appears.

## Writing an integration that survives

- **Ignore fields you do not recognise.** Never validate a response against a
  closed schema that rejects unknown keys.
- **Default-branch every enum.** See the warning above.
- **Do not depend on field order**, or on the absence of a field.
- **Do not depend on the exact wording of `message`.** Branch on `code`.
- **Do not parse ids.** They are opaque strings. Their format is not a
  contract, and code that assumes a shape will break when a resource adopts a
  different one.
- **Do not assume a field is missing forever** because it is `null` today.

## Deprecation

If something in v1 ever has to go, it goes slowly and loudly:

1. **Announced in the [changelog](https://www.salafems.com/developers/changelog.md)** with the
   replacement and a date.
2. **`Deprecation` and `Sunset` headers** appear on the affected endpoints, so
   your logs can tell you that you are using something on the way out even if
   nobody read the changelog.
3. **At least six months** between the announcement and the shutdown.

```http
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 11 Aug 2027 00:00:00 GMT
```

Nothing is deprecated today.

## The version tag

Responses and this documentation carry a date-based version tag —
currently **2026-08**. For the REST endpoints it is informational: the path
segment is what governs compatibility.

For [webhooks](https://www.salafems.com/developers/webhooks.md) it is load-bearing. Each webhook endpoint
is pinned to a version **when it is created**, and nothing afterwards changes
it — not editing the endpoint, not rotating its secret. A change to payload
shape mints a new date tag, and existing endpoints keep receiving the shape
they were built against. Every delivery states its version twice: in the
`X-Salaf-Api-Version` header and as `api_version` in the body.

Event *names* are governed by a stricter rule still: they are additive forever.
A new event type is a new name, and an existing name is never renamed and never
changes meaning — a rename would be a silent outage that no deprecation header
could reach, because merchants subscribe by string. See the
[event catalog](https://www.salafems.com/developers/webhook-events.md).

## What is not covered by any of this

**Data is not a contract.** A store can rename a product, archive a category or
change a price at any moment; a status your integration has never seen may
appear because a merchant started using a workflow they had not used before.
Versioning governs the *shape* of the API, not the values flowing through it.

Rate limits are also outside the versioning promise — they are plan
configuration, and a higher plan changing your quota is not a breaking change.
Read the limit from `X-RateLimit-Limit` rather than hard-coding it.
