{"openapi":"3.1.0","info":{"title":"Panlytics API","version":"1","description":"Read the figures of the shops an API key reaches, upload reports, manage suppliers and ask the assistant. A key acts as the person who made it, fenced: it can never manage people, roles, billing or keys, and it can never approve what the assistant proposes."},"jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","servers":[{"url":"https://panlytics.net/api"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"An API key made in Settings, Developer. It lives at most five hours."}}},"paths":{"/v1/me":{"get":{"operationId":"me","summary":"Who this key acts as, which company and shops it reaches, what it may do, and when it ends.","description":"The first call to make. It answers with the person who made the key, the company, the shops and categories the key reaches, the abilities it holds after the fence, and the moment it ends.","tags":["me"],"parameters":[],"responses":{"200":{"description":"Who this key acts as, which company and shops it reaches, what it may do, and when it ends.","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"number","description":"The maker's id."},"username":{"type":"string","description":"The maker's username."},"name":{"description":"The maker's full name, or null.","type":["string","null"]},"email":{"type":"string","description":"The maker's email address."}},"required":["id","username","name","email"],"additionalProperties":false,"description":"The person who made the key. The key acts as them, inside the fence."},"organization":{"type":"object","properties":{"id":{"type":"number","description":"The company's id."},"name":{"description":"The company's name.","type":"string"}},"required":["id"],"additionalProperties":false,"description":"The company the key works in."},"role":{"description":"\"CEO\", or the name of the maker's role in that company.","type":["string","null"]},"reach":{"type":"object","properties":{"allShops":{"type":"boolean","description":"True when the role covers every shop, including shops opened later."},"shops":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number","description":"The shop's id."},"name":{"type":"string","description":"The shop's name."}},"required":["id","name"],"additionalProperties":false},"description":"Every shop the key reaches."},"categoryScoped":{"type":"boolean","description":"True when the role covers chosen categories of one shop rather than whole shops."},"categories":{"anyOf":[{"type":"array","items":{"type":"number"}},{"type":"null"}],"description":"The category ids a category-scoped role covers. Null otherwise."}},"required":["allShops","shops","categoryScoped","categories"],"additionalProperties":false,"description":"Which shops and categories the key reaches."},"abilities":{"type":"array","items":{"type":"string"},"description":"What the key may do after the fence, by id: figures, sheet, export, exceptions, agents, suppliers, brain, shops."},"fenced":{"type":"boolean","description":"Always true for a key: people, roles, the audit log and the developer settings stay out of its reach."},"key":{"type":"object","properties":{"id":{"type":"number","description":"The key's own id, as the Developer pages and the audit log show it."},"endsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the key stops working, in UTC. Shortening the company's period moves it sooner."}},"required":["id","endsAt"],"additionalProperties":false,"description":"The key itself."}},"required":["user","organization","role","reach","abilities","fenced","key"],"additionalProperties":false},"example":{"user":{"id":7,"username":"anna","name":"Anna Virtanen","email":"anna@example.com"},"organization":{"id":3,"name":"Example Oy"},"role":"Manager","reach":{"allShops":false,"shops":[{"id":12,"name":"Harbour"}],"categoryScoped":false,"categories":null},"abilities":["figures","sheet","export","agents"],"fenced":true,"key":{"id":21,"endsAt":"2026-10-25T15:00:00.000Z"}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}}},"/v1/organisation":{"get":{"operationId":"organisation.get","summary":"The company, the shops this key reaches, and its people where the key's maker may see them.","description":"People are listed by name and role name only, and only when the person who made the key may see people in the app. A key never reads an email address or a phone number here. Needs the figures ability.","tags":["organisation"],"parameters":[],"responses":{"200":{"description":"The company, the shops this key reaches, and its people where the key's maker may see them.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The company's id."},"name":{"type":"string","description":"The company's name."},"country":{"description":"The country the company is in, or null.","type":["string","null"]},"currency":{"type":"string","description":"The company's currency, as a three-letter code."},"shops":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The shop's id."},"name":{"type":"string","description":"What the shop is called, as people in the company say it."},"branch":{"type":"string","description":"The branch name, or empty."},"city":{"type":"string","description":"The city the shop is in."},"country":{"type":"string","description":"The country the shop is in, or empty."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the shop was made, in UTC."}},"required":["id","name","branch","city","country","createdAt"],"additionalProperties":false},"description":"The shops this key reaches."},"people":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The person's name."},"role":{"type":"string","description":"The name of their role."}},"required":["name","role"],"additionalProperties":false}},{"type":"null"}],"description":"Names and role names only, and only when the person who made the key may see people in the app. Never an email address or a phone number. Null otherwise."}},"required":["id","name","country","currency","shops","people"],"additionalProperties":false},"example":{"id":3,"name":"Example Oy","country":"Finland","currency":"EUR","shops":[{"id":12,"name":"Harbour","branch":"","city":"Helsinki","country":"Finland","createdAt":"2026-10-01T08:00:00.000Z"}],"people":[{"name":"Anna Virtanen","role":"Manager"}]}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}}},"/v1/shops":{"get":{"operationId":"shops.list","summary":"The shops this key reaches, one page at a time.","description":"Every open shop of the company that the key's maker may see, oldest first. Needs the figures ability.","tags":["shops"],"parameters":[{"name":"cursor","in":"query","required":false,"description":"The `nextCursor` of the page before. Leave it out for the first page. Treat it as an opaque string.","schema":{"type":"string","maxLength":200}},{"name":"limit","in":"query","required":false,"description":"How many rows on one page: 1 to 200, 50 when left out.","schema":{"default":50,"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"The shops this key reaches, one page at a time.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The shop's id."},"name":{"type":"string","description":"What the shop is called, as people in the company say it."},"branch":{"type":"string","description":"The branch name, or empty."},"city":{"type":"string","description":"The city the shop is in."},"country":{"type":"string","description":"The country the shop is in, or empty."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the shop was made, in UTC."}},"required":["id","name","branch","city","country","createdAt"],"additionalProperties":false},"description":"The rows of this page."},"nextCursor":{"description":"Send it as `cursor` for the next page. Null on the last page.","type":["string","null"]},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many rows the whole list has."}},"required":["data","nextCursor","total"],"additionalProperties":false},"example":{"data":[{"id":12,"name":"Harbour","branch":"","city":"Helsinki","country":"Finland","createdAt":"2026-10-01T08:00:00.000Z"}],"nextCursor":null,"total":1}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}},"post":{"operationId":"shops.create","summary":"Open a new shop in the company.","description":"Needs the shops ability over every shop of the company, the rule the app keeps: a person limited to some shops cannot open one outside them. Send an Idempotency-Key header so a retry never opens the shop twice. Needs the shops ability.","tags":["shops"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any string up to 255 characters, new for each new request. The same key with the same body within 24 hours answers with the first answer again and creates nothing; the same key with another body is refused with 409.","schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"Open a new shop in the company.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The shop's id."},"name":{"type":"string","description":"What the shop is called, as people in the company say it."},"branch":{"type":"string","description":"The branch name, or empty."},"city":{"type":"string","description":"The city the shop is in."},"country":{"type":"string","description":"The country the shop is in, or empty."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the shop was made, in UTC."}},"required":["id","name","branch","city","country","createdAt"],"additionalProperties":false},"example":{"id":12,"name":"Harbour","branch":"","city":"Helsinki","country":"Finland","createdAt":"2026-10-01T08:00:00.000Z"}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you. `refused.demo`: This is a public demo, and that part is switched off.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh","refused.demo"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"409":{"description":"`api.idempotency_conflict`: That Idempotency-Key was already used for a different request. `api.idempotency_busy`: The first request with that Idempotency-Key is still running.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.idempotency_conflict","api.idempotency_busy"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"What the shop is called, as people in the company say it."},"city":{"type":"string","minLength":1,"maxLength":120,"description":"The city the shop is in."},"branch":{"description":"The branch name, when the company has several shops of one name.","type":"string","maxLength":120},"country":{"description":"The country the shop is in.","type":"string","maxLength":120}},"required":["name","city"],"additionalProperties":false},"example":{"name":"Harbour","city":"Helsinki"}}}}}},"/v1/shops/{id}":{"get":{"operationId":"shops.get","summary":"One shop.","description":"Answers 404 when the company has no open shop with that id, and 403 when it has one the key does not reach. Needs the figures ability.","tags":["shops"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}}],"responses":{"200":{"description":"One shop.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The shop's id."},"name":{"type":"string","description":"What the shop is called, as people in the company say it."},"branch":{"type":"string","description":"The branch name, or empty."},"city":{"type":"string","description":"The city the shop is in."},"country":{"type":"string","description":"The country the shop is in, or empty."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the shop was made, in UTC."}},"required":["id","name","branch","city","country","createdAt"],"additionalProperties":false},"example":{"id":12,"name":"Harbour","branch":"","city":"Helsinki","country":"Finland","createdAt":"2026-10-01T08:00:00.000Z"}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}}},"/v1/shops/{id}/categories":{"get":{"operationId":"shops.categories","summary":"The categories of one shop, each with its newest report.","description":"A key whose maker is limited to chosen categories sees only those. Needs the figures ability.","tags":["shops"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}},{"name":"cursor","in":"query","required":false,"description":"The `nextCursor` of the page before. Leave it out for the first page. Treat it as an opaque string.","schema":{"type":"string","maxLength":200}},{"name":"limit","in":"query","required":false,"description":"How many rows on one page: 1 to 200, 50 when left out.","schema":{"default":50,"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"The categories of one shop, each with its newest report.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The category's id."},"shopId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The shop the category belongs to."},"name":{"type":"string","description":"The category's name, as people in the company say it."},"reportId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The newest report of this category. Its figures are the ones every endpoint answers with."},"products":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many products the newest report holds."},"uploadedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the newest report was uploaded, in UTC."},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the newest report or its settings last changed. Send it back as `updatedAt` on PATCH."}},"required":["id","shopId","name","reportId","products","uploadedAt","updatedAt"],"additionalProperties":false},"description":"The rows of this page."},"nextCursor":{"description":"Send it as `cursor` for the next page. Null on the last page.","type":["string","null"]},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many rows the whole list has."}},"required":["data","nextCursor","total"],"additionalProperties":false},"example":{"data":[{"id":40,"shopId":12,"name":"Beverages","reportId":311,"products":412,"uploadedAt":"2026-10-20T07:12:00.000Z","updatedAt":"2026-10-20T07:12:00.000Z"}],"nextCursor":null,"total":1}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}}},"/v1/categories/{id}":{"get":{"operationId":"categories.get","summary":"One category, with the settings its figures are worked out with.","description":"The settings are the newest report's. Keep `updatedAt` to change them with PATCH. Needs the figures ability.","tags":["categories"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}}],"responses":{"200":{"description":"One category, with the settings its figures are worked out with.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The category's id."},"shopId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The shop the category belongs to."},"name":{"type":"string","description":"The category's name, as people in the company say it."},"reportId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The newest report of this category. Its figures are the ones every endpoint answers with."},"products":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many products the newest report holds."},"uploadedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the newest report was uploaded, in UTC."},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the newest report or its settings last changed. Send it back as `updatedAt` on PATCH."},"settings":{"type":"object","properties":{"periodDays":{"type":"number","description":"How many days the newest report covers."},"delTimeDs":{"type":"number","description":"Days until the next delivery, as the report's file says it."},"targetSto":{"type":"number","description":"How many times a year the stock should sell through, used to class products by sales."},"targetRoce":{"type":"number","description":"The return wanted on the money tied up in stock, used to class products by margin."},"co2PerWastePc":{"type":"number","description":"The CO2e of one wasted item, in kg."},"carbonTaxPerKgCo2":{"type":"number","description":"What one kg of CO2e costs, in euros."},"labourCostPerHour":{"type":"number","description":"What an hour of staff time costs, in euros."},"wasteHandlingMinPerPc":{"type":"number","description":"Minutes of staff time to handle one wasted item."},"orderEveryDs":{"type":"number","description":"Days between orders. 0 keeps what the report's file says."},"leadTimeDs":{"type":"number","description":"The days a delivery takes, for every product of the category. 0 keeps what the report's file says."},"shelfLifeDs":{"type":"number","description":"How many days a product keeps, for every product of the category. 0 keeps what the report's file says."},"demandNoisePhi":{"type":"number","description":"How much daily sales jump around: their variance divided by their mean. 1 is an even pace; groceries run near 3.3, which is used when this is 0."},"shelvingEurPerPc":{"type":"number","description":"What it costs to put one piece on the shelf, in euros. A wasted piece costs it twice."}},"required":["periodDays","delTimeDs","targetSto","targetRoce","co2PerWastePc","carbonTaxPerKgCo2","labourCostPerHour","wasteHandlingMinPerPc","orderEveryDs","leadTimeDs","shelfLifeDs","demandNoisePhi","shelvingEurPerPc"],"additionalProperties":false,"description":"The settings the category's figures are worked out with."}},"required":["id","shopId","name","reportId","products","uploadedAt","updatedAt","settings"],"additionalProperties":false},"example":{"id":40,"shopId":12,"name":"Beverages","reportId":311,"products":412,"uploadedAt":"2026-10-20T07:12:00.000Z","updatedAt":"2026-10-20T07:12:00.000Z","settings":{"periodDays":30,"delTimeDs":2,"targetSto":12,"targetRoce":0.3,"co2PerWastePc":0.5,"carbonTaxPerKgCo2":0,"labourCostPerHour":25,"wasteHandlingMinPerPc":1,"orderEveryDs":7,"leadTimeDs":2,"shelfLifeDs":0,"demandNoisePhi":1,"shelvingEurPerPc":0}}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"409":{"description":"`report.none`: There is no report open for this shop.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["report.none"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}},"patch":{"operationId":"categories.update","summary":"Rename a category, change the settings its figures use, or both.","description":"A new name needs the shops ability; settings need the sheet ability. Send the `updatedAt` you read, or the same time in an If-Unmodified-Since header, and the change is refused with 409 when somebody changed the category after it. After a change the figures are worked out again at once, as they are in the app. Needs the sheet ability, and shops where the description says so.","tags":["categories"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}},{"name":"If-Unmodified-Since","in":"header","required":false,"description":"The category's `updatedAt` as you read it. The change is refused with 409 when the category changed after it.","schema":{"type":"string"}}],"responses":{"200":{"description":"Rename a category, change the settings its figures use, or both.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The category's id."},"shopId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The shop the category belongs to."},"name":{"type":"string","description":"The category's name, as people in the company say it."},"reportId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The newest report of this category. Its figures are the ones every endpoint answers with."},"products":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many products the newest report holds."},"uploadedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the newest report was uploaded, in UTC."},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the newest report or its settings last changed. Send it back as `updatedAt` on PATCH."},"settings":{"type":"object","properties":{"periodDays":{"type":"number","description":"How many days the newest report covers."},"delTimeDs":{"type":"number","description":"Days until the next delivery, as the report's file says it."},"targetSto":{"type":"number","description":"How many times a year the stock should sell through, used to class products by sales."},"targetRoce":{"type":"number","description":"The return wanted on the money tied up in stock, used to class products by margin."},"co2PerWastePc":{"type":"number","description":"The CO2e of one wasted item, in kg."},"carbonTaxPerKgCo2":{"type":"number","description":"What one kg of CO2e costs, in euros."},"labourCostPerHour":{"type":"number","description":"What an hour of staff time costs, in euros."},"wasteHandlingMinPerPc":{"type":"number","description":"Minutes of staff time to handle one wasted item."},"orderEveryDs":{"type":"number","description":"Days between orders. 0 keeps what the report's file says."},"leadTimeDs":{"type":"number","description":"The days a delivery takes, for every product of the category. 0 keeps what the report's file says."},"shelfLifeDs":{"type":"number","description":"How many days a product keeps, for every product of the category. 0 keeps what the report's file says."},"demandNoisePhi":{"type":"number","description":"How much daily sales jump around: their variance divided by their mean. 1 is an even pace; groceries run near 3.3, which is used when this is 0."},"shelvingEurPerPc":{"type":"number","description":"What it costs to put one piece on the shelf, in euros. A wasted piece costs it twice."}},"required":["periodDays","delTimeDs","targetSto","targetRoce","co2PerWastePc","carbonTaxPerKgCo2","labourCostPerHour","wasteHandlingMinPerPc","orderEveryDs","leadTimeDs","shelfLifeDs","demandNoisePhi","shelvingEurPerPc"],"additionalProperties":false,"description":"The settings the category's figures are worked out with."}},"required":["id","shopId","name","reportId","products","uploadedAt","updatedAt","settings"],"additionalProperties":false},"example":{"id":40,"shopId":12,"name":"Beverages","reportId":311,"products":412,"uploadedAt":"2026-10-20T07:12:00.000Z","updatedAt":"2026-10-25T09:30:00.000Z","settings":{"periodDays":30,"delTimeDs":2,"targetSto":12,"targetRoce":0.3,"co2PerWastePc":0.5,"carbonTaxPerKgCo2":0,"labourCostPerHour":25,"wasteHandlingMinPerPc":1,"orderEveryDs":7,"leadTimeDs":3,"shelfLifeDs":0,"demandNoisePhi":1,"shelvingEurPerPc":0}}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you. `refused.demo`: This is a public demo, and that part is switched off.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh","refused.demo"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"409":{"description":"`report.none`: There is no report open for this shop. `api.stale`: This was changed after the time you sent.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["report.none","api.stale"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"A new name, as people in the company say it.","type":"string","minLength":1,"maxLength":120},"settings":{"description":"Only the settings to change. Every value is a number of zero or more; periodDays is at least 1.","type":"object","properties":{"periodDays":{"description":"How many days the newest report covers.","type":"number","minimum":0},"delTimeDs":{"description":"Days until the next delivery, as the report's file says it.","type":"number","minimum":0},"targetSto":{"description":"How many times a year the stock should sell through, used to class products by sales.","type":"number","minimum":0},"targetRoce":{"description":"The return wanted on the money tied up in stock, used to class products by margin.","type":"number","minimum":0},"co2PerWastePc":{"description":"The CO2e of one wasted item, in kg.","type":"number","minimum":0},"carbonTaxPerKgCo2":{"description":"What one kg of CO2e costs, in euros.","type":"number","minimum":0},"labourCostPerHour":{"description":"What an hour of staff time costs, in euros.","type":"number","minimum":0},"wasteHandlingMinPerPc":{"description":"Minutes of staff time to handle one wasted item.","type":"number","minimum":0},"orderEveryDs":{"description":"Days between orders. 0 keeps what the report's file says.","type":"number","minimum":0},"leadTimeDs":{"description":"The days a delivery takes, for every product of the category. 0 keeps what the report's file says.","type":"number","minimum":0},"shelfLifeDs":{"description":"How many days a product keeps, for every product of the category. 0 keeps what the report's file says.","type":"number","minimum":0},"demandNoisePhi":{"description":"How much daily sales jump around: their variance divided by their mean. 1 is an even pace; groceries run near 3.3, which is used when this is 0.","type":"number","minimum":0},"shelvingEurPerPc":{"description":"What it costs to put one piece on the shelf, in euros. A wasted piece costs it twice.","type":"number","minimum":0}},"additionalProperties":false},"updatedAt":{"description":"The `updatedAt` you read. The change is refused with 409 when the category changed after it. The If-Unmodified-Since header does the same.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"additionalProperties":false},"example":{"settings":{"orderEveryDs":7,"leadTimeDs":3},"updatedAt":"2026-10-20T07:12:00.000Z"}}}}}},"/v1/shops/{id}/reports":{"post":{"operationId":"reports.upload","summary":"Upload a report: the first of a new category, or the next report of one.","description":"Multipart form data with a `file` (.csv or .xlsx, at most 8 MB) and either `category`, the name of a new category, with `periodDays`, or `categoryId` for the next report of a category. The file goes through exactly the checks and the pipeline the app's own upload does, and the upload is written to the audit log. Demo accounts cannot upload. Send an Idempotency-Key header so a retry never files the report twice. Needs the sheet ability.","tags":["shops"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Any string up to 255 characters, new for each new request. The same key with the same body within 24 hours answers with the first answer again and creates nothing; the same key with another body is refused with 409.","schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"Upload a report: the first of a new category, or the next report of one.","content":{"application/json":{"schema":{"type":"object","properties":{"reportId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The new report's id."},"categoryId":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"The category the report was filed under."},"shopId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The shop the report belongs to."},"newCategory":{"type":"boolean","description":"True when this report started a new category."},"rows":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many product rows were read from the file."},"deduplicated":{"type":"boolean","description":"True when this exact file was uploaded before. It was worked out again and stored once."}},"required":["reportId","categoryId","shopId","newCategory","rows","deduplicated"],"additionalProperties":false},"example":{"reportId":311,"categoryId":40,"shopId":12,"newCategory":true,"rows":412,"deduplicated":false}}}},"400":{"description":"`api.invalid`: The field is missing or not valid. `upload.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid","upload.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you. `refused.demo`: This is a public demo, and that part is switched off.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh","refused.demo"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"409":{"description":"`report.none`: There is no report open for this shop. `api.idempotency_conflict`: That Idempotency-Key was already used for a different request. `api.idempotency_busy`: The first request with that Idempotency-Key is still running.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["report.none","api.idempotency_conflict","api.idempotency_busy"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"413":{"description":"`upload.too_large`: That file is too big to upload.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["upload.too_large"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"422":{"description":"`form.field`:  `upload.unreadable`: That file could not be read.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["form.field","upload.unreadable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}},"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","description":"The report, as .csv or .xlsx."},"category":{"description":"The name of a NEW category this report starts.","type":"string","minLength":1,"maxLength":120},"categoryId":{"description":"The category this report is the next report of.","type":"integer","exclusiveMinimum":0,"maximum":2147483647},"periodDays":{"description":"How many days the report covers. Needed for a new category; a next report keeps its category's period when left out.","type":"integer","minimum":1,"maximum":3650}}}}}}}},"/v1/categories/{id}/reorder-points":{"get":{"operationId":"categories.reorderPoints","summary":"Per product: the reorder point, what to order when it is reached, and the days of stock left.","description":"The products whose reorder point should change, worked out from the category's newest report and sorted by the sales they lose first. `?format=csv` answers the whole list with the columns of the app's download; that needs the export ability and is written to the audit log. Needs the figures ability, and export where the description says so.","tags":["categories"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}},{"name":"cursor","in":"query","required":false,"description":"The `nextCursor` of the page before. Leave it out for the first page. Treat it as an opaque string.","schema":{"type":"string","maxLength":200}},{"name":"limit","in":"query","required":false,"description":"How many rows on one page: 1 to 200, 50 when left out.","schema":{"default":50,"type":"integer","minimum":1,"maximum":200}},{"name":"format","in":"query","required":false,"description":"`csv` answers the whole list as one CSV file, with the columns the app's own download has. Paging is ignored then.","schema":{"default":"json","type":"string","enum":["json","csv"]}}],"responses":{"200":{"description":"Per product: the reorder point, what to order when it is reached, and the days of stock left.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"ean":{"type":"string","description":"The product's id in the shop's own system, usually its barcode (EAN)."},"name":{"type":"string","description":"The product's name."},"class":{"type":"string","description":"The sales class, A to D."},"reorderPointNow":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The reorder point the report gave: the level that triggers a new order today."},"newReorderPoint":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The reorder point Panlytics works out."},"change":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The new reorder point minus the one the report gave."},"orderPcs":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How much to order when the level is reached, in whole pieces."},"packSize":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many pieces come in one pack."},"stockPcs":{"type":"number","description":"What the shop holds now, across its locations."},"daysOfStockLeft":{"description":"Days of stock left at the pace the report measured. Null when the product sold nothing.","type":["number","null"]},"salesEur":{"type":"number","description":"Sales over the report's period, in euros."},"marginEur":{"type":"number","description":"Profit on each sale, summed over the report."},"reason":{"type":"string","description":"Why the level changes, in one sentence."},"keptByHand":{"type":"boolean","description":"True when a person fixed this level as an exception."}},"required":["ean","name","class","reorderPointNow","newReorderPoint","change","orderPcs","packSize","stockPcs","daysOfStockLeft","salesEur","marginEur","reason","keptByHand"],"additionalProperties":false},"description":"The rows of this page."},"nextCursor":{"description":"Send it as `cursor` for the next page. Null on the last page.","type":["string","null"]},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many rows the whole list has."}},"required":["data","nextCursor","total"],"additionalProperties":false},"example":{"data":[{"ean":"6410000000017","name":"Sparkling water 0.5 l","class":"A","reorderPointNow":12,"newReorderPoint":18,"change":6,"orderPcs":24,"packSize":24,"stockPcs":9,"daysOfStockLeft":1.5,"salesEur":1840.5,"marginEur":610.2,"reason":"Ran out on 3 days and lost sales.","keptByHand":false}],"nextCursor":null,"total":1}},"text/csv":{"schema":{"type":"string"}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you. `refused.demo`: This is a public demo, and that part is switched off.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh","refused.demo"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"409":{"description":"`report.none`: There is no report open for this shop.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["report.none"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}}},"/v1/categories/{id}/order-list":{"get":{"operationId":"categories.orderList","summary":"What to order now: every product at or under its reorder point.","description":"The same list as Order now in the app, best sellers first. `?format=csv` answers the whole list with the columns of the app's download; that needs the export ability and is written to the audit log. Needs the figures ability, and export where the description says so.","tags":["categories"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}},{"name":"cursor","in":"query","required":false,"description":"The `nextCursor` of the page before. Leave it out for the first page. Treat it as an opaque string.","schema":{"type":"string","maxLength":200}},{"name":"limit","in":"query","required":false,"description":"How many rows on one page: 1 to 200, 50 when left out.","schema":{"default":50,"type":"integer","minimum":1,"maximum":200}},{"name":"format","in":"query","required":false,"description":"`csv` answers the whole list as one CSV file, with the columns the app's own download has. Paging is ignored then.","schema":{"default":"json","type":"string","enum":["json","csv"]}}],"responses":{"200":{"description":"What to order now: every product at or under its reorder point.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"ean":{"type":"string","description":"The product's id in the shop's own system, usually its barcode (EAN)."},"name":{"type":"string","description":"The product's name."},"class":{"type":"string","description":"The sales class, A to D."},"orderPcs":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How much to order, in whole pieces."},"packSize":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many pieces come in one pack."},"orderValueEur":{"type":"number","description":"What the order costs, in euros."},"stockPcs":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"What the shop holds now, in pieces."},"reorderPoint":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The level the stock is at or under."},"salesEur":{"type":"number","description":"Sales over the report's period, in euros."},"marginEur":{"type":"number","description":"Profit on the sales over the report's period, in euros."}},"required":["ean","name","class","orderPcs","packSize","orderValueEur","stockPcs","reorderPoint","salesEur","marginEur"],"additionalProperties":false},"description":"The rows of this page."},"nextCursor":{"description":"Send it as `cursor` for the next page. Null on the last page.","type":["string","null"]},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many rows the whole list has."}},"required":["data","nextCursor","total"],"additionalProperties":false},"example":{"data":[{"ean":"6410000000017","name":"Sparkling water 0.5 l","class":"A","orderPcs":48,"packSize":24,"orderValueEur":19.2,"stockPcs":6,"reorderPoint":18,"salesEur":1840.5,"marginEur":610.2}],"nextCursor":null,"total":1}},"text/csv":{"schema":{"type":"string"}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you. `refused.demo`: This is a public demo, and that part is switched off.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh","refused.demo"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"409":{"description":"`report.none`: There is no report open for this shop.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["report.none"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}}},"/v1/categories/{id}/stop-stocking":{"get":{"operationId":"categories.stopStocking","summary":"The products worth no longer stocking, most money tied up first.","description":"A product a person marked Never stop stocking is not on the list. `?format=csv` answers the whole list with the columns of the app's download; that needs the export ability and is written to the audit log. Needs the figures ability, and export where the description says so.","tags":["categories"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}},{"name":"cursor","in":"query","required":false,"description":"The `nextCursor` of the page before. Leave it out for the first page. Treat it as an opaque string.","schema":{"type":"string","maxLength":200}},{"name":"limit","in":"query","required":false,"description":"How many rows on one page: 1 to 200, 50 when left out.","schema":{"default":50,"type":"integer","minimum":1,"maximum":200}},{"name":"format","in":"query","required":false,"description":"`csv` answers the whole list as one CSV file, with the columns the app's own download has. Paging is ignored then.","schema":{"default":"json","type":"string","enum":["json","csv"]}}],"responses":{"200":{"description":"The products worth no longer stocking, most money tied up first.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"ean":{"type":"string","description":"The product's id in the shop's own system, usually its barcode (EAN)."},"name":{"type":"string","description":"The product's name."},"salesEur":{"type":"number","description":"Sales over the report's period, in euros."},"marginEur":{"type":"number","description":"Profit on the sales over the report's period, in euros."},"wasteEur":{"type":"number","description":"What was thrown away over the report's period, in euros."},"stockEur":{"type":"number","description":"The money tied up in its stock now."}},"required":["ean","name","salesEur","marginEur","wasteEur","stockEur"],"additionalProperties":false},"description":"The rows of this page."},"nextCursor":{"description":"Send it as `cursor` for the next page. Null on the last page.","type":["string","null"]},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many rows the whole list has."}},"required":["data","nextCursor","total"],"additionalProperties":false},"example":{"data":[{"ean":"6410000000093","name":"Tonic water 1 l","salesEur":12.4,"marginEur":3.1,"wasteEur":8.9,"stockEur":41.6}],"nextCursor":null,"total":1}},"text/csv":{"schema":{"type":"string"}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you. `refused.demo`: This is a public demo, and that part is switched off.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh","refused.demo"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"409":{"description":"`report.none`: There is no report open for this shop.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["report.none"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}}},"/v1/categories/{id}/recompute":{"post":{"operationId":"categories.recompute","summary":"Work a category's summary figures out again and answer with them.","description":"You rarely need this. Every figure the API answers with is worked out when you read it, from the newest report, its settings, the suppliers and the exceptions as they are at that moment. This call refreshes the stored summary the app's shop page shows and forgets this report's cached model, then answers with the fresh summary. Needs the figures ability.","tags":["categories"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}}],"responses":{"200":{"description":"Work a category's summary figures out again and answer with them.","content":{"application/json":{"schema":{"type":"object","properties":{"categoryId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The category the figures are for."},"reportId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The report the figures were worked out from."},"salesEur":{"type":"number","description":"Sales over the report's period, in euros."},"missedSalesEur":{"type":"number","description":"Sales you missed: what people came for while the shelf was empty."},"wasteEur":{"type":"number","description":"What was thrown away over the report's period, in euros."},"computedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When these figures were worked out, in UTC."}},"required":["categoryId","reportId","salesEur","missedSalesEur","wasteEur","computedAt"],"additionalProperties":false},"example":{"categoryId":40,"reportId":311,"salesEur":48210.5,"missedSalesEur":1310.2,"wasteEur":420.75,"computedAt":"2026-10-25T09:31:00.000Z"}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"409":{"description":"`report.none`: There is no report open for this shop.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["report.none"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}}},"/v1/shops/{id}/suppliers":{"get":{"operationId":"suppliers.list","summary":"Who delivers to one shop, and the days a delivery takes from each.","description":"A key whose maker is limited to chosen categories sees no suppliers, as in the app. Needs the figures ability.","tags":["shops"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}},{"name":"cursor","in":"query","required":false,"description":"The `nextCursor` of the page before. Leave it out for the first page. Treat it as an opaque string.","schema":{"type":"string","maxLength":200}},{"name":"limit","in":"query","required":false,"description":"How many rows on one page: 1 to 200, 50 when left out.","schema":{"default":50,"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Who delivers to one shop, and the days a delivery takes from each.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The supplier's id."},"shopId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The shop the supplier delivers to."},"name":{"type":"string","description":"The supplier's name."},"leadTimeDays":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The days a delivery takes from this supplier."},"categoryIds":{"type":"array","items":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"description":"The categories this supplier delivers."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the supplier was added, in UTC."},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the supplier last changed, in UTC."}},"required":["id","shopId","name","leadTimeDays","categoryIds","createdAt","updatedAt"],"additionalProperties":false},"description":"The rows of this page."},"nextCursor":{"description":"Send it as `cursor` for the next page. Null on the last page.","type":["string","null"]},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many rows the whole list has."}},"required":["data","nextCursor","total"],"additionalProperties":false},"example":{"data":[{"id":5,"shopId":12,"name":"Example Wholesale","leadTimeDays":2,"categoryIds":[40],"createdAt":"2026-10-01T08:00:00.000Z","updatedAt":"2026-10-01T08:00:00.000Z"}],"nextCursor":null,"total":1}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}},"post":{"operationId":"suppliers.create","summary":"Add a supplier to one shop.","description":"The days a delivery takes from this supplier replace the category's own setting once the supplier is given that category. A name may be used once per shop. Send an Idempotency-Key header so a retry never adds the supplier twice. Needs the suppliers ability.","tags":["shops"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Any string up to 255 characters, new for each new request. The same key with the same body within 24 hours answers with the first answer again and creates nothing; the same key with another body is refused with 409.","schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"Add a supplier to one shop.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The supplier's id."},"shopId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The shop the supplier delivers to."},"name":{"type":"string","description":"The supplier's name."},"leadTimeDays":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The days a delivery takes from this supplier."},"categoryIds":{"type":"array","items":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"description":"The categories this supplier delivers."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the supplier was added, in UTC."},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the supplier last changed, in UTC."}},"required":["id","shopId","name","leadTimeDays","categoryIds","createdAt","updatedAt"],"additionalProperties":false},"example":{"id":5,"shopId":12,"name":"Example Wholesale","leadTimeDays":2,"categoryIds":[],"createdAt":"2026-10-25T09:00:00.000Z","updatedAt":"2026-10-25T09:00:00.000Z"}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you. `refused.demo`: This is a public demo, and that part is switched off.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh","refused.demo"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"409":{"description":"`conflict.said`:  `api.idempotency_conflict`: That Idempotency-Key was already used for a different request. `api.idempotency_busy`: The first request with that Idempotency-Key is still running.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["conflict.said","api.idempotency_conflict","api.idempotency_busy"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"The supplier's name, unique in the shop."},"leadTimeDays":{"type":"integer","minimum":0,"maximum":365,"description":"The days a delivery takes, 0 to 365."}},"required":["name","leadTimeDays"],"additionalProperties":false},"example":{"name":"Example Wholesale","leadTimeDays":2}}}}}},"/v1/suppliers/{id}":{"patch":{"operationId":"suppliers.update","summary":"Rename a supplier or change the days a delivery takes from it.","description":"Send only what changes. The figures of the categories it delivers change at once. Needs the suppliers ability.","tags":["suppliers"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}}],"responses":{"200":{"description":"Rename a supplier or change the days a delivery takes from it.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The supplier's id."},"shopId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The shop the supplier delivers to."},"name":{"type":"string","description":"The supplier's name."},"leadTimeDays":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The days a delivery takes from this supplier."},"categoryIds":{"type":"array","items":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"description":"The categories this supplier delivers."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the supplier was added, in UTC."},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the supplier last changed, in UTC."}},"required":["id","shopId","name","leadTimeDays","categoryIds","createdAt","updatedAt"],"additionalProperties":false},"example":{"id":5,"shopId":12,"name":"Example Wholesale","leadTimeDays":3,"categoryIds":[40],"createdAt":"2026-10-01T08:00:00.000Z","updatedAt":"2026-10-25T09:00:00.000Z"}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you. `refused.demo`: This is a public demo, and that part is switched off.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh","refused.demo"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"409":{"description":"`conflict.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["conflict.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"The supplier's name, unique in the shop."},"leadTimeDays":{"type":"integer","minimum":0,"maximum":365,"description":"The days a delivery takes, 0 to 365."}},"additionalProperties":false},"example":{"leadTimeDays":3}}}}},"delete":{"operationId":"suppliers.delete","summary":"Remove a supplier from its shop.","description":"The categories it delivered go back to their own settings for the days a delivery takes. Needs the suppliers ability.","tags":["suppliers"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}}],"responses":{"200":{"description":"Remove a supplier from its shop.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean","const":true,"description":"Always true."},"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The id of what was removed."}},"required":["deleted","id"],"additionalProperties":false},"example":{"deleted":true,"id":5}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you. `refused.demo`: This is a public demo, and that part is switched off.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh","refused.demo"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}}},"/v1/suppliers/{id}/categories":{"put":{"operationId":"suppliers.categories","summary":"Set which categories of its shop a supplier delivers.","description":"The list replaces the one before. Every id must be a category of the supplier's own shop. Needs the suppliers ability.","tags":["suppliers"],"parameters":[{"name":"id","in":"path","required":true,"description":"The id in the path.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}}],"responses":{"200":{"description":"Set which categories of its shop a supplier delivers.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The supplier's id."},"shopId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The shop the supplier delivers to."},"name":{"type":"string","description":"The supplier's name."},"leadTimeDays":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The days a delivery takes from this supplier."},"categoryIds":{"type":"array","items":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"description":"The categories this supplier delivers."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the supplier was added, in UTC."},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the supplier last changed, in UTC."}},"required":["id","shopId","name","leadTimeDays","categoryIds","createdAt","updatedAt"],"additionalProperties":false},"example":{"id":5,"shopId":12,"name":"Example Wholesale","leadTimeDays":2,"categoryIds":[40,41],"createdAt":"2026-10-01T08:00:00.000Z","updatedAt":"2026-10-25T09:00:00.000Z"}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you. `refused.demo`: This is a public demo, and that part is switched off.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh","refused.demo"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"categoryIds":{"maxItems":2000,"type":"array","items":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647},"description":"Every category this supplier delivers. The list replaces the one before."}},"required":["categoryIds"],"additionalProperties":false},"example":{"categoryIds":[40,41]}}}}}},"/v1/chat":{"post":{"operationId":"chat.start","summary":"Ask Panlytics a question about a shop. The answer comes back as a run.","description":"Starts the same assistant the app has, counted against the shop's daily AI budget and the limit of 30 runs an hour per person. Read the run with GET /v1/runs/{id}, or follow it with GET /v1/runs/{id}/stream. Anything the assistant proposes to do waits for a person to approve it in the app: a key can never approve it. Send an Idempotency-Key header so a retry never starts a second run. Needs the agents ability.","tags":["chat"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any string up to 255 characters, new for each new request. The same key with the same body within 24 hours answers with the first answer again and creates nothing; the same key with another body is refused with 409.","schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"Ask Panlytics a question about a shop. The answer comes back as a run.","content":{"application/json":{"schema":{"type":"object","properties":{"runId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The run's id. Read it with GET /v1/runs/{id}."},"threadId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The conversation the run belongs to. Send it as `threadId` to ask a follow-up."},"status":{"type":"string","const":"queued","description":"Always queued: the run has been handed to the engine."}},"required":["runId","threadId","status"],"additionalProperties":false},"example":{"runId":"6f1c2b9e-2d4f-4f7e-9a51-0c3b8e7d1a20","threadId":"0b9e4a3c-71d2-4c55-8f0e-3a6d2b1c9e47","status":"queued"}}}},"400":{"description":"`api.invalid`: The field is missing or not valid. `bad_request.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid","bad_request.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you. `refused.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh","refused.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"409":{"description":"`report.none`: There is no report open for this shop. `store.none`: No shop is open in this session. `conflict.said`:  `api.idempotency_conflict`: That Idempotency-Key was already used for a different request. `api.idempotency_busy`: The first request with that Idempotency-Key is still running.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["report.none","store.none","conflict.said","api.idempotency_conflict","api.idempotency_busy"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute. `api.ai_budget`: This shop has used today's assistant budget. `net.rate_limited`: That was asked for too many times.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited","api.ai_budget","net.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet. `agent.unreachable`: The assistant is not answering right now.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable","agent.unreachable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shopId":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647,"description":"The shop the question is about. Its daily AI budget pays for the run."},"prompt":{"type":"string","minLength":1,"maxLength":4000,"description":"The question, in plain words."},"categoryId":{"description":"A category of that shop, to ask about its newest report.","type":"integer","exclusiveMinimum":0,"maximum":2147483647},"threadId":{"description":"A conversation to continue: any conversation in the company that the key's maker may open.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["shopId","prompt"],"additionalProperties":false},"example":{"shopId":12,"prompt":"Which drinks ran out most often last month?"}}}}}},"/v1/runs/{id}":{"get":{"operationId":"runs.get","summary":"One run: its state, its answer, and the proposals that wait for a person.","description":"A run this key's maker started, in the app or with any key. Poll it every few seconds until the status is done, failed or cancelled. Needs the agents ability.","tags":["runs"],"parameters":[{"name":"id","in":"path","required":true,"description":"The run's id, as POST /v1/chat answered it.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}],"responses":{"200":{"description":"One run: its state, its answer, and the proposals that wait for a person.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The run's id."},"threadId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The conversation the run belongs to."},"status":{"type":"string","enum":["queued","running","awaiting_approval","done","failed","cancelled"],"description":"Where the run stands. done, failed and cancelled are final."},"answer":{"description":"The assistant's answer. Null until it has one.","type":["string","null"]},"error":{"anyOf":[{"type":"object","properties":{"code":{"type":"string","description":"A stable code from the Errors page."},"error":{"type":"string","description":"The code's sentence."}},"required":["code","error"],"additionalProperties":false},{"type":"null"}],"description":"Why the run did not finish, as a code and its sentence."},"steps":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many steps the run has taken."},"costEur":{"type":"number","description":"What the run has spent of the shop's AI budget, in euros."},"startedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the run started, in UTC."},"endedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"A time in ISO 8601, in UTC."},{"type":"null"}],"description":"When the run ended, in UTC. Null while it runs."},"waitingForPerson":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The proposal's id."},"tool":{"type":"string","description":"What the assistant proposes to do."},"reason":{"description":"Why, in the assistant's words, or null.","type":["string","null"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When it was proposed, in UTC."}},"required":["id","tool","reason","createdAt"],"additionalProperties":false},"description":"Proposals that wait for a person to approve them in the app. A key can never approve them."}},"required":["id","threadId","status","answer","error","steps","costEur","startedAt","endedAt","waitingForPerson"],"additionalProperties":false},"example":{"id":"6f1c2b9e-2d4f-4f7e-9a51-0c3b8e7d1a20","threadId":"0b9e4a3c-71d2-4c55-8f0e-3a6d2b1c9e47","status":"done","answer":"Sparkling water ran out on 3 days.","error":null,"steps":2,"costEur":0.004,"startedAt":"2026-10-25T09:00:00.000Z","endedAt":"2026-10-25T09:00:09.000Z","waitingForPerson":[]}}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}}},"/v1/runs/{id}/stream":{"get":{"operationId":"runs.stream","summary":"Follow one run live, as server-sent events.","description":"The same events the app's own conversation reads: `state` first, then `delta` for text as it is written, `event` for each step, and `ended` last. Each event is one JSON object on a `data:` line. Reconnect with `?from=` set to the last offset you saw and nothing is missed. The stream closes when the run ends or the key ends; a key that is revoked stops its runs too, within one 15 second ping. One key keeps at most 3 streams open at once: a fourth answers 429 with Retry-After. Needs the agents ability.","tags":["runs"],"parameters":[{"name":"id","in":"path","required":true,"description":"The run's id, as POST /v1/chat answered it.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"from","in":"query","required":false,"description":"The offset to start from.","schema":{"type":"integer","minimum":0,"maximum":9007199254740991}}],"responses":{"200":{"description":"Follow one run live, as server-sent events.","content":{"text/event-stream":{"schema":{"type":"string"},"example":"data: {\"t\":\"state\",\"status\":\"running\",\"code\":null}\n\n"}}},"400":{"description":"`api.invalid`: The field is missing or not valid.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.invalid"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"404":{"description":"`not_found.said`: ","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["not_found.said"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute. `net.rate_limited`: That was asked for too many times.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited","net.rate_limited"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}}},"/v1/agent-sign-in-links":{"post":{"operationId":"agentSignInLinks.create","summary":"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.","description":"Works only when the CEO has switched on letting an AI sign in to the app. The link works once, within 60 seconds, and only when pasted into the address bar: a link followed from another site is refused. The session it opens is marked as an AI's, never reaches the company's security, billing, data, people, roles, audit log or AI budgets, never approves anything, and ends when the key ends. Six links an hour per person.","tags":["agent-sign-in-links"],"parameters":[],"responses":{"201":{"description":"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.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"The link. Open it once, within 60 seconds, by pasting it into the address bar of a browser profile nobody else is signed in to."},"expiresAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"When the link stops working, in UTC: 60 seconds after it was made."}},"required":["url","expiresAt"],"additionalProperties":false},"example":{"url":"https://panlytics.net/agent-sign-in#t=Q2xhdWRlLWV4YW1wbGUtdG9rZW4tbm90LXJlYWwtMDAw","expiresAt":"2026-10-25T09:01:00.000Z"}}}},"401":{"description":"`api.key_missing`: This call carried no API key. `api.key_refused`: That API key is not a live key. `api.wrong_key_kind`: This key does not open these endpoints.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.key_missing","api.key_refused","api.wrong_key_kind"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"403":{"description":"`refused`: You do not have permission to do that. `auth.not_fresh`: This step needs you to prove it is you. `agent.sign_in_off`: Letting an AI sign in to the app is switched off for this company.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["refused","auth.not_fresh","agent.sign_in_off"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"429":{"description":"`api.rate_limited`: This key made too many calls in one minute. `agent.links_limit`: This key's maker has asked for six sign-in links in the last hour.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["api.rate_limited","agent.links_limit"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"500":{"description":"`server`: Something went wrong on our side.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["server"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}},"503":{"description":"`developer.unavailable`: API keys are not set up on this deployment yet.","content":{"application/json":{"schema":{"type":"object","required":["code","error","requestId"],"properties":{"code":{"type":"string","description":"The stable code. Read this, not the sentence.","enum":["developer.unavailable"]},"error":{"type":"string","description":"The code's sentence, for a person reading along."},"requestId":{"type":["string","null"],"description":"Set when the fault is ours; quote it when you report it."},"detail":{"type":["string","null"],"description":"For `api.invalid`, the field that was refused."}}}}}}}}}}}