Conventions
Formats
JSON with snake_case fields; ids are UUIDs; times are ISO 8601 in UTC (2026-10-05T12:00:00Z), dates 2026-10-05.
Values from a fixed list come as {"code": "in_progress", "name": "In Progress"}: the code never changes, the
name follows Accept-Language (tr gives Turkish, anything else English).
Pages
Lists answer {"data": [...], "next_cursor": "…"}. Ask for the next page with cursor=<next_cursor>; the last page has
next_cursor: null. limit is 1–200 (default 50). Keep the other parameters the same while paging.
- Python
- Node.js
def every(path, token, **params):
"""every item of a list, page by page"""
cursor = None
while True:
query = {**params, "limit": 200, **({"cursor": cursor} if cursor else {})}
page = requests.get(f"{API}{path}", params=query, timeout=30,
headers={"Authorization": f"Bearer {token}"}).json()
yield from page["data"]
cursor = page["next_cursor"]
if not cursor:
return
async function* every(path, token, params = {}) {
let cursor = null;
do {
const query = new URLSearchParams({...params, limit: '200', ...(cursor ? {cursor} : {})});
const page = await (await fetch(`${API}${path}?${query}`, {headers: {authorization: `Bearer ${token}`}})).json();
yield* page.data;
cursor = page.next_cursor;
} while (cursor);
}
Errors
Errors answer {"error": {"code": "…", "message": "…"}} (invalid_body also has details with the fields).
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_parameter | A query parameter doesn't fit (a time, an id, limit, a cursor). |
| 401 | invalid_token | No token, an unknown, expired or revoked one — get a new token. |
| 403 | insufficient_scope | The token lacks the scope this endpoint needs. |
| 403 | forbidden | The integration's permissions don't allow this (on this record, now). |
| 403 | access_denied | The integration can't be used now (turned off, the plan, the organization). |
| 403 | read_only | The organization is read-only (unpaid plan): only answering work orders goes on. |
| 403 | plan_limit | The plan doesn't allow this (e.g. creating work orders). |
| 404 | not_found | No such record, or the integration may not read it. |
| 409 | external_id_taken | Another record of the organization has this external_id. |
| 409 | request_in_progress | The first request with this Idempotency-Key is still running. |
| 422 | invalid_body | The body doesn't fit (details lists the fields). |
| 422 | rule_violation | A rule of kommuns refused it (e.g. a required question unanswered) — the message says which. |
| 422 | idempotency_key_reused | The Idempotency-Key was used for a different request. |
| 429 | rate_limited | Over the per-minute limit; wait Retry-After seconds. |
Idempotency
POST, PATCH and DELETE accept an Idempotency-Key header (1–200 characters, e.g. a UUID or your own record's
number). The same key with the same request within 24 hours returns the first answer again, marked
Idempotent-Replayed: true, and creates nothing new — so a retry after a timeout is safe.
Your own numbers: external_id
Inventory nodes, work orders and feedback can carry your system's own number (up to 128 characters, unique within the
organization). Inventory numbers are entered in kommuns on the node's General tab ("External system number");
work orders and feedback get theirs when you create them. Find a record by it with ?external_id=.