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.
https://api.salafems.com/ext/v1/ordersThere 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 |
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 oncode. - 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
nulltoday.
Deprecation
If something in v1 ever has to go, it goes slowly and loudly:
- Announced in the changelog with the replacement and a date.
DeprecationandSunsetheaders 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.- At least six months between the announcement and the shutdown.
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 11 Aug 2027 00:00:00 GMTNothing 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 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.
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.