Build with AI
Markdown twins, llms.txt, the spec URL, and the two MCP servers.
These docs are published for two readers: you, and whatever agent is helping you integrate. Everything the human pages say also exists in a machine-shaped form — a markdown twin of every page, an index and a single-file corpus for LLMs, the OpenAPI description the reference is generated from, and two MCP servers: one that answers from the real pages, and one that reads — and, scope permitting, operates — a store's live data through the API itself. This page is where each one lives and how to point your tools at it.
None of it is required to use the API. It exists so an agent reads what these docs actually say instead of guessing from APIs that merely resemble ours.
Markdown for every page
Append .md to any docs URL and you get that page as raw markdown — no nav
chrome, no highlighting markup, just the content:
| Human page | Markdown twin |
|---|---|
https://www.salafems.com/developers/pagination | https://www.salafems.com/developers/pagination.md |
The overview's twin is /developers.md, and the generated reference follows
the same rule — /developers/reference/orders.md is the orders reference as
markdown.
Two files cover the corpus as a whole:
/llms.txt— the index, per the llms.txt convention: every page as an absolute.mdlink with a one-line summary. Give this to tools that crawl and follow links./llms-full.txt— every page concatenated in sidebar reading order. Give this to tools that read: it fits comfortably in a modern context window, so pasting it into a chat hands the model the entire documentation at once.
Every page header also carries a Copy page button that copies the page's markdown twin, and a menu that opens the page in Claude or ChatGPT with the twin's URL already in the prompt.
The OpenAPI description
The API is described by one published OpenAPI document:
https://www.salafems.com/openapi/public-v1.jsonIt is the same artifact the reference pages are generated from, so the two cannot disagree — anything the spec declares, the reference shows, and nothing in the reference was written by hand.
One URL unlocks the usual tooling: generate a typed client, import the collection into Postman or Insomnia, or hand it to a ChatGPT GPT Action, which takes an OpenAPI URL directly.
Add the docs to your tools
Cursor — Settings → Indexing & Docs → Add Doc, then paste
https://www.salafems.com/llms.txt. Cursor indexes the linked pages and
cites them in chat.
Claude Code — connect the MCP server with the one-liner in the next
section. Without MCP, telling it to fetch
https://www.salafems.com/llms.txt and follow the links it needs works
nearly as well.
ChatGPT — a GPT Action takes the OpenAPI URL above directly; for
questions rather than calls, paste /llms-full.txt into the conversation.
Everything else — any agent that can fetch a URL can read the .md
twins, and any tool that accepts a document can take /llms-full.txt.
The docs MCP server
The docs run a Model Context Protocol server at:
https://www.salafems.com/developers/mcpTransport is Streamable HTTP, and there is no authentication — it serves the same public pages you are reading and nothing else.
| Tool | Answers |
|---|---|
search_docs | Where in the docs a term or topic is covered. |
get_page | One page in full, as markdown, by slug. |
list_endpoints | Every operation, optionally narrowed to one resource. |
get_endpoint | One operation in detail — parameters, scope, examples. |
For Claude Code, one command:
claude mcp add --transport http salaf-docs https://www.salafems.com/developers/mcpFor other MCP clients, the generic configuration block:
{
"mcpServers": {
"salaf-docs": {
"type": "http",
"url": "https://www.salafems.com/developers/mcp"
}
}
}The store MCP server
The API itself also speaks MCP. A second server exposes a store's live data as curated tools, at:
https://api.salafems.com/ext/mcp/v1Transport is Streamable HTTP, and it authenticates exactly like the REST API:
the same Authorization: Bearer header, the same
keys, the same tenant boundary. The key the
connection carries decides which company — and, for a store key, which
store — every tool can see, and each tool requires the same
scope as its REST counterpart. A call
made with a key that lacks the scope comes back as a readable refusal naming
the missing scope, so an agent can tell its human exactly which key to ask
for.
Twelve tools read:
| Tool | Answers |
|---|---|
list_products / get_product | Products with their variants nested — the sellable units. |
list_categories / list_brands | Catalog metadata; ids feed the product filters. |
list_orders / get_order | Orders, compact by default, expandable with customer and items. |
list_order_payments | Every payment row recorded against one order. |
list_customers / get_customer | Customer records; the detail includes addresses. |
get_inventory_levels | Stock per variant per location, with a low_stock filter. |
list_locations / list_channels | Warehouses and sales channels. |
And ten write — one per write endpoint, running the same engine the merchant's own staff use, so a tool call cannot do anything the dashboard could not:
| Tool | Does |
|---|---|
create_order | Places an order: stock reserved, totals computed server-side, coupons priced by the real engine. |
update_order_status / cancel_order | Status moves under the same strict transition map as the desk. |
record_order_payment | Records a settled payment, capped at the order's net due. |
create_customer / update_customer | Customer records, with per-store phone/email uniqueness. |
adjust_inventory | A signed, audited stock change — reason required, on-hand can never go negative. |
create_product / update_product / archive_product | Catalog writes, variants included. |
The write tools are opt-in, and the opt-in is the key: each tool requires
the same write scope as its REST twin, so a key minted with read scopes
simply cannot write, whatever an agent asks of it. Writes carry the
idempotency contract as an idempotency_key
argument — required on create_order, record_order_payment and
adjust_inventory, honoured everywhere else. The rules are the header's,
verbatim: one unique value per logical operation, the same value on every
retry of it, replays answer with the original result.
List tools page exactly like the REST API — pass meta.next_cursor back as
starting_after until it comes back null — and results are byte-equivalent
to the JSON their REST twins return, because each tool calls the same
service its twin calls. That holds for writes too: a write answers with
what its REST twin answers — the object re-read through the public
serializer (or, for archive_product, the same small { id, archived }
receipt).
For Claude Code, one command (the key travels as a header):
claude mcp add --transport http salaf-store https://api.salafems.com/ext/mcp/v1 --header "Authorization: Bearer salaf_sk_your_key_here"For other MCP clients:
{
"mcpServers": {
"salaf-store": {
"type": "http",
"url": "https://api.salafems.com/ext/mcp/v1",
"headers": {
"Authorization": "Bearer salaf_sk_your_key_here"
}
}
}
}Drop this in your repo
If a coding agent works on your integration, put a short description of this
API where your agent reads instructions — an AGENTS.md, a CLAUDE.md, a
system prompt. The block below is deliberately compact: enough to make
correct calls, with links to where everything else lives.
## Salaf Commerce API
- REST API over a Salaf EMS store's live data: products, orders, customers,
inventory, locations and sales channels. JSON in, JSON out.
- Base URL: `https://api.salafems.com/ext/v1`
- Auth: `Authorization: Bearer <key>`. Keys start with `salaf_sk_` and are
created in the dashboard under Settings → Developer. Server-side only.
- Errors: every failure is `{ error: { code, message, request_id } }`.
Branch on `code` — it is stable. `message` is human copy and may be
reworded at any time. Validation failures add a `fields` map.
- Writes: send an `Idempotency-Key` header (any unique value up to 255
characters; a UUID is ideal) and reuse the SAME key when retrying the
same operation. Required on `POST /orders`, `POST /orders/{id}/payments`
and `POST /inventory/adjustments`; honoured on every other write.
- Pagination: cursor-based. Pass `limit` (max 100) and `starting_after` =
the previous page's `meta.next_cursor`; stop when `next_cursor` is null.
- Machine-readable docs:
- OpenAPI: https://www.salafems.com/openapi/public-v1.json
- Docs index: https://www.salafems.com/llms.txt
(whole corpus in one file: https://www.salafems.com/llms-full.txt)
- Docs MCP, Streamable HTTP, no auth: https://www.salafems.com/developers/mcp
- Store MCP (live store data through curated read + write tools, gated by
the key's scopes, same Bearer key): https://api.salafems.com/ext/mcp/v1The facts in that block are the same ones documented on Getting started, Errors, Writes & idempotency and Pagination — when in doubt, those pages are the authority, and an agent with the MCP server attached can check them itself.