# Changelog

Every change to the public API, newest first.

Every change to the public API, newest first. Additive changes ship inside v1;
anything breaking follows the [deprecation
policy](https://www.salafems.com/developers/versioning.md#deprecation).

## 2026-08-15 — The API speaks MCP

The API is now also a [Model Context Protocol
server](https://www.salafems.com/developers/ai.md#the-store-mcp-server) at
`https://api.salafems.com/ext/mcp/v1` — Streamable HTTP, the same `Bearer`
keys, the same tenant boundary, the same scopes. Nothing about REST changes;
this is a second, agent-shaped door into the same implementation.

- **22 curated tools**: 12 reads over products, orders, payments, customers,
  inventory, locations and channels, and 10 writes — one per REST write
  endpoint. Results are byte-equivalent to the corresponding REST responses.
- **Each tool requires its REST twin's scope.** A refusal is an in-band,
  readable error naming the missing scope; a read-scoped key cannot write.
- **Write tools take `idempotency_key` as an argument** (MCP has no
  headers) — required on `create_order`, `record_order_payment` and
  `adjust_inventory`, honoured everywhere else, with the
  [same replay/conflict rules](https://www.salafems.com/developers/idempotency.md) as the header.
- A companion **docs MCP server** — no auth, answers from these pages — runs
  at `https://www.salafems.com/developers/mcp`. See
  [Build with AI](https://www.salafems.com/developers/ai.md).

## 2026-08-11 — Write endpoints and idempotency

Ten write endpoints, all running the same code the dashboard runs — the same
stock reservation, coupon engine, totals math and status-transition rules. See
[Writes & idempotency](https://www.salafems.com/developers/idempotency.md).

| Resource  | Endpoints                                                                                           |
| --------- | --------------------------------------------------------------------------------------------------- |
| Orders    | `POST /orders`, `PATCH /orders/{id}/status`, `POST /orders/{id}/cancel`                             |
| Payments  | `POST /orders/{id}/payments`, and the read that completes the resource, `GET /orders/{id}/payments` |
| Customers | `POST /customers`, `PATCH /customers/{id}`                                                          |
| Inventory | `POST /inventory/adjustments`                                                                       |
| Products  | `POST /products`, `PATCH /products/{id}`, `POST /products/{id}/archive`                             |

- **`Idempotency-Key`** is now honoured on every write and **required** on
  `POST /orders`, `POST /orders/{id}/payments` and
  `POST /inventory/adjustments` (`400` naming the header when it is missing).
  A repeat of a completed request replays the original response verbatim with
  `Idempotent-Replayed: true`; a repeat with a different body is `422
  IDEMPOTENCY_CONFLICT`; a repeat while the original is running is `409
  IDEMPOTENCY_IN_FLIGHT`. Keys are isolated per API key and expire after 24
  hours.
- **Company keys must send `store_id` in the body** on every write (`422`
  without it). A store key may repeat its own store id and may not name another
  (`422`).
- **Find-or-create customers on order creation**, matched on the canonical
  Bangladesh phone number. An existing customer is never modified by an order.
- **Two new error codes are now reachable**: `INSUFFICIENT_STOCK` and
  `INVALID_STATUS_TRANSITION`, both `400`, both carrying `fields`.
- **The write rate-limit bucket is now in use** — 30 requests per minute per
  key by default, counted separately from reads.
- **The OpenAPI description now carries 50 response schemas**, so every example
  in the reference is generated from the same artifact that drives the server's
  validation instead of being written by hand.

### Write scopes are grantable

`orders:write`, `payments:write`, `customers:write`, `inventory:write` and
`products:write` now have endpoints behind them. `payments:read` does too.
`webhooks:manage` remains declared but not grantable.

## 2026-08-11 — Webhooks

Outbound events, so you can stop polling for the things you need to know about
quickly. See [Webhooks](https://www.salafems.com/developers/webhooks.md).

- **24 event types** across orders, returns, shipping, inventory, catalog,
  customers and POS, plus `salaf.ping` for testing. Subscribe to specific types
  or to `*`. The [catalog](https://www.salafems.com/developers/webhook-events.md) is also readable from
  the dashboard API.
- **Endpoints are created in the dashboard** under Settings → Developer →
  Webhooks. `https` only, public addresses only, no redirects followed, five
  per store by default.
- **HMAC-SHA256 signatures** over `"{timestamp}.{rawBody}"` in
  `X-Salaf-Signature`, with published
  [test vectors](https://www.salafems.com/developers/webhook-signatures.md#verify-your-implementation) so
  you can check your verification code before going live.
- **Rotation dual-signs for 24 hours** — the previous secret keeps co-signing
  as a second `v1=`, so rotating costs no downtime.
- **At-least-once delivery, no ordering guarantee.** De-duplicate on
  `X-Salaf-Event-Id` and write handlers as upserts.
- **An initial attempt plus eight retries over \~45 hours** (30s, 2m, 10m, 30m, 2h, 6h, 12h, 24h), then
  `dead`, with manual retry from the delivery log. An endpoint failing
  continuously for three days is auto-disabled and the merchant is notified.
- **Payloads are snapshots taken inside the transaction** that produced them,
  built by the same serializers as the REST responses, and kept — with their
  delivery logs — for 30 days.

## 2026-08-11 — API usage in the dashboard

Every API key now reports its own traffic under **Settings → Developer**:
requests, errors, rate-limited responses and average latency, per key per UTC
day, kept for 13 months.

Nothing changes on the API surface — no new endpoint, no new header, no cost to
your requests. It is there so a merchant can see which integration is making
which calls, and so "the API is slow" can be answered with numbers.

## 2026-08-11 — Public read API launch

The first release of the Salaf Commerce API. Everything below is new.

### Authentication

- **API keys.** `salaf_sk_` followed by 40 characters, presented as
  `Authorization: Bearer`. Created under **Settings → Developer**; the secret
  is shown exactly once and stored only as a SHA-256 hash.
- **Two key types.** Store keys read one store; company keys read every live
  store in the company and are restricted to company owners.
- **Scopes**, fixed at creation, with `write` implying `read` of the same
  resource. A key can only be granted scopes the person creating it can back
  with their own permissions.
- **Rotation with a 24-hour grace window**, so a key can be replaced without an
  outage, plus an immediate option for a leaked key.
- **Immediate revocation** — the authentication cache is evicted on revoke
  rather than left to expire.

### Endpoints

All read-only. 16 endpoints across 8 resources:

| Resource   | Endpoints                                                           |
| ---------- | ------------------------------------------------------------------- |
| Products   | `GET /products`, `GET /products/{id}`, `GET /variants/{identifier}` |
| Categories | `GET /categories`, `GET /categories/{id}`                           |
| Brands     | `GET /brands`, `GET /brands/{id}`                                   |
| Orders     | `GET /orders`, `GET /orders/{id}`                                   |
| Customers  | `GET /customers`, `GET /customers/{id}`                             |
| Inventory  | `GET /inventory/levels`, `GET /inventory/movements`                 |
| Locations  | `GET /locations`, `GET /locations/{id}`                             |
| Channels   | `GET /channels`                                                     |

`GET /variants/{identifier}` accepts an id, a SKU or a barcode — a barcode
scanner's output can be looked up directly.

### Conventions

- **Cursor pagination** on every list: `limit` (max 100, default 25),
  `starting_after` / `ending_before`, and a `meta` block carrying `has_more`
  and `next_cursor`.
- **`updated_after`** on orders and products, for incremental syncs.
- **`expand=customer,items`** on orders.
- **Money as strings** with two decimals (`"1250.00"`), so decimal precision
  survives JSON.
- **ISO-8601 UTC timestamps**, always with the `Z`.
- **`snake_case` field names** throughout.
- **A single error shape** — one `error` object with `code`, `message`,
  optional `fields`, and `request_id`.
- **Request ids** on every request, returned as `X-Request-Id`. A caller's own
  `X-Request-Id` is honoured.

### Limits

- **120 reads and 30 writes per minute per key** by default, raised by plan.
- `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` on every
  response; `429` carries `Retry-After`.

### Known limitations at launch

Stated plainly so you can plan around them rather than discover them. Several
were lifted the same day — see the write and webhook entries above; the list is
kept as it was written.

- **The base path is `/ext/v1`, not `/v1`.** The shorter alias is written but
  not yet deployed. When it goes live it will be announced here and both paths
  will serve the same routes.
- **No write endpoints.** Creating orders, adjusting stock and recording
  payments are built but unreleased.
- **No webhooks.** Poll with `updated_after` in the meantime.
- **`updated_after` is missing on customers, categories, brands, inventory,
  locations and channels.** Re-read those lists on a schedule.
- **`payments:read` is grantable but has no endpoint yet.** A key can hold the
  scope; nothing currently accepts it.
- **`webhooks:manage` is declared but not grantable.**
- **No sandbox or test mode.** There is no `salaf_sk_test_` prefix; every key
  acts on live data.
- **No SDKs.** The four sample languages in these docs are the support level
  for v1.
- **Locations ignore `store_id`.** They belong to the company and are shared
  across its stores.
- **Only `created_at` is sortable.** A cursor over a mutable column cannot page
  reliably — see [Pagination](https://www.salafems.com/developers/pagination.md#why-you-can-only-sort-by-created_at).
