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.
2026-08-15 — The API speaks MCP
The API is now also a Model Context Protocol
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_keyas an argument (MCP has no headers) — required oncreate_order,record_order_paymentandadjust_inventory, honoured everywhere else, with the same replay/conflict rules 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.
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.
| 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-Keyis now honoured on every write and required onPOST /orders,POST /orders/{id}/paymentsandPOST /inventory/adjustments(400naming the header when it is missing). A repeat of a completed request replays the original response verbatim withIdempotent-Replayed: true; a repeat with a different body is422 IDEMPOTENCY_CONFLICT; a repeat while the original is running is409 IDEMPOTENCY_IN_FLIGHT. Keys are isolated per API key and expire after 24 hours.- Company keys must send
store_idin the body on every write (422without 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_STOCKandINVALID_STATUS_TRANSITION, both400, both carryingfields. - 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.
- 24 event types across orders, returns, shipping, inventory, catalog,
customers and POS, plus
salaf.pingfor testing. Subscribe to specific types or to*. The catalog is also readable from the dashboard API. - Endpoints are created in the dashboard under Settings → Developer →
Webhooks.
httpsonly, public addresses only, no redirects followed, five per store by default. - HMAC-SHA256 signatures over
"{timestamp}.{rawBody}"inX-Salaf-Signature, with published test vectors 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-Idand 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 asAuthorization: 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
writeimplyingreadof 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 ametablock carryinghas_moreandnext_cursor. updated_afteron orders and products, for incremental syncs.expand=customer,itemson orders.- Money as strings with two decimals (
"1250.00"), so decimal precision survives JSON. - ISO-8601 UTC timestamps, always with the
Z. snake_casefield names throughout.- A single error shape — one
errorobject withcode,message, optionalfields, andrequest_id. - Request ids on every request, returned as
X-Request-Id. A caller's ownX-Request-Idis honoured.
Limits
- 120 reads and 30 writes per minute per key by default, raised by plan.
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reseton every response;429carriesRetry-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_afterin the meantime. updated_afteris missing on customers, categories, brands, inventory, locations and channels. Re-read those lists on a schedule.payments:readis grantable but has no endpoint yet. A key can hold the scope; nothing currently accepts it.webhooks:manageis 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_atis sortable. A cursor over a mutable column cannot page reliably — see Pagination.