# 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.shopdeskbd.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.

There is no shorter `/v1` alias either. One was planned and never deployed;
it has been dropped, so `/ext/v1` is the base path for the life of v1.

## 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`

That last rule is why `INSUFFICIENT_STOCK` and `INVALID_STATUS_TRANSITION`
stay `400` — not `422` like other validation failures — for as long as v1
exists. See [Errors](https://www.shopdeskbd.com/developers/errors.md).

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.shopdeskbd.com/developers/changelog.md)** with the
   replacement and a date. Behaviour changes that are not breaking are
   announced there too.
2. **`Deprecation`, `Sunset` and `Link` headers** appear on every response
   from the affected endpoint, so your logs can tell you that you are using
   something on the way out even if nobody read the changelog.
3. **At least twelve months** between the deprecation and the shutdown. The
   server refuses to start with a shorter window, so this is enforced rather
   than remembered.

```http
HTTP/1.1 200 OK
Deprecation: @1790812800
Sunset: Fri, 01 Oct 2027 00:00:00 GMT
Link: <https://www.shopdeskbd.com/developers/changelog>; rel="deprecation"; type="text/html"
```

| Header        | Format                        | Meaning                                            |
| ------------- | ----------------------------- | -------------------------------------------------- |
| `Deprecation` | `@` + Unix seconds (RFC 9745) | When the endpoint was, or will be, deprecated.     |
| `Sunset`      | HTTP date (RFC 8594)          | After this moment the endpoint may stop answering. |
| `Link`        | `rel="deprecation"`           | The changelog entry naming the replacement.        |

All three are readable from a browser (they are in
`Access-Control-Expose-Headers`). A breaking change is only ever delivered as a
new major version, mounted beside v1, with v1 kept running for the whole
overlap.

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.shopdeskbd.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-Shopdesk-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.shopdeskbd.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.
