Overview
API reference
Every endpoint of the Panlytics API, its request, its answer and its errors, with the base URL, authentication and conventions they share.
Base URL and authentication
Every endpoint is under one base URL:
https://panlytics.net/apiSend the key in the Authorization header of every call, as a bearer token:
Authorization: Bearer pan_...A call with no key answers 401 with the code api.key_missing. A wrong, ended or revoked key answers 401 with api.key_refused. Keys and rotation explains how keys are made and how they end.
Conventions
- JSON in and out. Bodies are JSON, except a report upload, which is
multipart/form-data. Three lists also answer CSV, and one run answers as server-sent events. - Ids are whole numbers, except a run's id, which is a UUID.
- Times are UTC, in ISO 8601, such as
2026-10-25T14:07:00.000Z. - Unknown fields are refused. A query parameter or a body field the endpoint does not know answers
400 api.invalid, with the field's name indetail. - Refusals are JSON with a
codeto branch on. See Errors. - 120 calls a minute per key. See Rate limits.
What a key can see
Every id is looked up in two steps, always in this order:
- Is it in the key's company? If not, the answer is
404 not_found.said, the same for an id that never existed and an id of another company. An id tells a key nothing about other companies. - May the key's maker do this to it? If not, the answer is
403 refused, and the refusal is written to the company's security log, as it is for a person.
So inside its own company a key may learn that a shop exists and that it may not open it, which its maker sees from the shop list in the app too.
Pages
Every list takes cursor and limit and answers one page:
{
"data": [],
"nextCursor": "eyJvIjo1MH0",
"total": 412
}limitis 1 to 200, and 50 when you leave it out.- Send
nextCursorback ascursorfor the next page. It isnullon the last page. totalis how many rows the whole list has.- Treat the cursor as an opaque string. A cursor the API did not make answers
400 api.invalid.
CSV
GET /v1/categories/{id}/reorder-points, /order-list and /stop-stocking also answer ?format=csv: the whole list as one file, with the columns of the app's own download. Paging is ignored then. A CSV needs the export ability in that category, and every CSV is written to the company's audit log.
Idempotency and retries
A program on a bad connection cannot tell "the shop was not opened" from "the shop was opened and the answer was lost". The Idempotency-Key header answers that.
- Every
POSTthat creates something takes it:POST /v1/shops,POST /v1/shops/{id}/reports,POST /v1/shops/{id}/suppliersandPOST /v1/chat. - Send a new random string, 1 to 255 characters, with each new request, and the same string when you retry that request.
- The same key with the same request within 24 hours answers with the first answer again, with the header
Idempotent-Replayed: true, and does nothing twice. - The same key with a different request answers
409 api.idempotency_conflict. - The same key while the first is still running answers
409 api.idempotency_busywithRetry-After: 5. - A refusal is not kept. Every endpoint refuses before it writes anything, so after a
4xxthe same key can be sent again once the cause is fixed. - A fault is kept, with its
500. A fault of ours may come after the write, so running the request again could make the thing twice. For 24 hours the same key answers the same500. Check what exists first (list the shops, the reports or the suppliers), then send the request with a new key if it is still missing. - Before a stored answer is sent again, the key's permission is checked again. A key whose role lost the ability since the first call gets
403 refused, not the stored answer. - A key is remembered for the key's maker in the company, not for one API key, so a retry with a new API key is still caught.
Changing a category safely
PATCH /v1/categories/{id} takes the updatedAt you read from GET /v1/categories/{id}, in the body or as an If-Unmodified-Since header. When somebody changed the category after that time, the change is refused with 409 api.stale: read it again and send your change over the new version. Without either time the change is made over whatever is there.
Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/me | Who this key acts as, which company and shops it reaches, what it may do, and when it ends. |
GET /v1/organisation | The company, the shops this key reaches, and its people where the key's maker may see them. |
GET /v1/shops | The shops this key reaches, one page at a time. |
POST /v1/shops | Open a new shop in the company. |
GET /v1/shops/{id} | One shop. |
GET /v1/shops/{id}/categories | The categories of one shop, each with its newest report. |
POST /v1/shops/{id}/reports | Upload a report: the first of a new category, or the next report of one. |
GET /v1/categories/{id} | One category, with the settings its figures are worked out with. |
PATCH /v1/categories/{id} | Rename a category, change the settings its figures use, or both. |
GET /v1/categories/{id}/reorder-points | Per product: the reorder point, what to order when it is reached, and the days of stock left. |
GET /v1/categories/{id}/order-list | What to order now: every product at or under its reorder point. |
GET /v1/categories/{id}/stop-stocking | The products worth no longer stocking, most money tied up first. |
POST /v1/categories/{id}/recompute | Work a category's summary figures out again and answer with them. |
GET /v1/shops/{id}/suppliers | Who delivers to one shop, and the days a delivery takes from each. |
POST /v1/shops/{id}/suppliers | Add a supplier to one shop. |
PATCH /v1/suppliers/{id} | Rename a supplier or change the days a delivery takes from it. |
DELETE /v1/suppliers/{id} | Remove a supplier from its shop. |
PUT /v1/suppliers/{id}/categories | Set which categories of its shop a supplier delivers. |
POST /v1/chat | Ask Panlytics a question about a shop. The answer comes back as a run. |
GET /v1/runs/{id} | One run: its state, its answer, and the proposals that wait for a person. |
GET /v1/runs/{id}/stream | Follow one run live, as server-sent events. |
POST /v1/agent-sign-in-links | A one-time link that signs you in to the app as the person who made the key, in a browser profile nobody else is signed in to. |
Machine-readable
- /developers/openapi.json describes every endpoint as OpenAPI 3.1, for code generators and for tools like a ChatGPT action.
- /developers/llms.txt lists every page of these docs for an AI agent.