# 04 - API Interaktivni dokumentace je na `/apps//docs`. Tenhle soubor popisuje to, co ze Swaggeru neni videt. ## Endpointy Verejne: | Metoda | Cesta | Popis | | ------ | ------------------- | --------------------------------------- | | GET | `/health` | liveness, nezavisi na databazi | | GET | `/health/ready` | readiness, 503 pri nedostupne databazi | | GET | `/docs` | Swagger UI | | GET | `/openapi.json` | OpenAPI definice | | POST | `/api/auth/login` | prihlaseni, vraci JWT | | POST | `/api/contact` | poptavka z webu | | POST | `/webhook/:token` | prijem dat do automatizace | | POST | `/webhook/ticket/:token` | prijem udalosti do ticketu | | GET | `/webhook/ticket/:token` | napoveda k prijmu | Vyzaduji `Authorization: Bearer `: | Metoda | Cesta | | ------ | --------------------------------------------------- | | GET | `/api/auth/me` | | POST | `/api/auth/logout` | | GET | `/api/dashboard/access` | | GET | `/api/dashboard/widgets` | | GET | `/api/dashboard/layout` | | PUT | `/api/dashboard/layout` | | DELETE | `/api/dashboard/layout` | | GET | `/api/dashboard/summary` | | GET | `/api/dashboard/people` | | GET | `/api/dashboard/people/:id` | | GET | `/api/dashboard/intake` | | POST | `/api/dashboard/intake/regenerate` | | GET | `/api/dashboard/settings/actions/:id/scope` | | GET | `/api/dashboard/notifications` | | POST | `/api/dashboard/notifications/read` | | GET | `/api/dashboard/runs` | | GET | `/api/dashboard/tickets` | | GET | `/api/dashboard/tickets/workload` | | GET | `/api/dashboard/tickets/:id` | | POST | `/api/dashboard/tickets/:id/assign` | | POST | `/api/dashboard/tickets/:id/status` | | POST | `/api/dashboard/tickets/:id/comment` | | POST | `/api/dashboard/tickets/:id/type` | | POST | `/api/dashboard/tickets/:id/tags` | | POST | `/api/dashboard/tickets/:id/group` | | POST | `/api/dashboard/tickets/:id/claim` | | GET | `/api/dashboard/tickets/:id/actions` | | POST | `/api/dashboard/tickets/:id/actions/:actionId` | | GET | `/api/dashboard/invites` | | POST | `/api/dashboard/invites` | | DELETE | `/api/dashboard/invites/:id` | | GET | `/api/invites/:kod` | | POST | `/api/invites/:kod/accept` | | GET | `/api/dashboard/incidents` | | GET | `/api/dashboard/storage` | | GET | `/api/dashboard/services` | | GET | `/api/dashboard/connectors/services` | | GET | `/api/dashboard/connectors` | | POST | `/api/dashboard/connectors` | | GET | `/api/dashboard/connectors/:id` | | PATCH | `/api/dashboard/connectors/:id` | | DELETE | `/api/dashboard/connectors/:id` | | POST | `/api/dashboard/connectors/:id/test` | | GET | `/api/dashboard/scripts` | | GET | `/api/dashboard/scripts/:id` | | PUT | `/api/dashboard/scripts/:id` | | POST | `/api/dashboard/scripts/:id/test` | | POST | `/api/dashboard/scripts/reload` | | GET | `/api/dashboard/stream` | | GET | `/api/dashboard/automations` | | POST | `/api/dashboard/automations` | | GET | `/api/dashboard/automations/:id` | | PUT | `/api/dashboard/automations/:id` | | DELETE | `/api/dashboard/automations/:id` | | POST | `/api/dashboard/automations/:id/webhook/regenerate` | | POST | `/api/dashboard/widget-data` | | GET | `/api/dashboard/widget-data/options` | | GET | `/api/dashboard/settings/catalog` | | GET | `/api/dashboard/settings/features` | | GET | `/api/dashboard/settings/people-overview` | | GET | `/api/dashboard/settings/users-overview` | | PATCH | `/api/dashboard/settings/users/:id/password` | | POST | `/api/admin/impersonate` | | POST | `/api/admin/impersonate/stop` | | GET | `/api/admin/impersonate/candidates` | | GET | `/api/admin/audit` | Sprava zaznamu ma u kazde entity stejnou petici (seznam, detail, vytvoreni, uprava, mazani) na `/api/dashboard/settings/`, protoze ji dela jedna fabrika (`src/routes/crud.ts`): `tenants`, `users`, `roles`, `people`, `groups`, `ticket-types`, `actions`, `widgets`, `features`. ## Format chyb Jednotny pro cele API: ```json { "error": "validation_error", "message": "Zadejte platny e-mail." } ``` | HTTP | `error` | Kdy | | ---- | --------------------- | --------------------------------------- | | 400 | `validation_error` | vstup neprosel schematem | | 401 | `unauthorized` | chybi nebo neplatny token | | 401 | `invalid_credentials` | spatny e-mail nebo heslo | | 403 | `forbidden` | nedostatecna role | | 404 | `not_found` | zaznam nebo endpoint neexistuje | | 409 | ruzne | operace nedava v danem stavu smysl | | 500 | `internal_error` | neodchycena chyba, detail jen mimo produkci | `message` je vzdy cesky a je urcena k zobrazeni uzivateli. ## Autentizace Hesla se hashuji bcryptem, plaintext se nikde neuklada. Login vraci JWT podepsany `JWT_SECRET` s platnosti `JWT_EXPIRES_IN`. Spatne heslo i neexistujici e-mail vraci stejnou odpoved, aby se neprozradilo, ktere ucty existuji. Pokus se loguje bez hesla. Token si drzi klient v `localStorage`. Pro produkci je cilovy stav `httpOnly` cookie se `Secure` a `SameSite` plus CSRF token. ## Zivy stream `GET /api/dashboard/stream` je Server-Sent Events. Po pripojeni posle potvrzeni a poslednich par udalosti, pak uz jen nove. Kazdych 25 sekund jde komentarovy radek, aby spojeni neuspalo proxy. Typy udalosti: `ticket.created`, `ticket.updated`, `ticket.assigned`, `ticket.resolved`, `incident.started`, `incident.updated`, `incident.resolved`, `automation.created`, `automation.updated`, `automation.deleted`, `automation.run`, `webhook.received`. Klient se pripojuje pres fetch s hlavickou `Authorization`, ne pres EventSource. Duvod je v [03-architektura-a-mapa-kodu.md](03-architektura-a-mapa-kodu.md). ## Webhook Verejny endpoint bez prihlaseni. Autorizuje neuhodnutelny token v adrese, 32 znaku z `randomBytes(24)` v base64url. Token generuje **vyhradne server**. Hodnota `webhookToken` poslana klientem se ignoruje, jinak by si sel nastavit predvidatelnou adresu. | Situace | Odpoved | | ----------------------------------- | ------- | | vse v poradku | 202 | | neznamy token | 404 | | automatizace je pozastavena | 409 | | chybi povinny parametr, spatny typ | 400 | Parametry navic se neodmitaji, jen loguji. Odesilatele bezne posilaji i vlastni data a odmitat je by rozbijelo integrace. ```bash curl -X POST https://services.csbot.cz/apps//webhook/ \ -H "Content-Type: application/json" \ -d '{"customer":"Nordis","score":18}' ``` Prototyp pozadavek prijme, zvaliduje a zapocita do metrik, ale strom akci nevykona - runtime neexistuje. ## Firmy a pohledy Prava popisuje [07-firmy-a-prava.md](07-firmy-a-prava.md), tady jen API. Endpointy dashboardu berou `scope` (`all`, `tenant`, `mine`) a `tenantId`. `GET /api/dashboard/access` rekne, co uzivatel smi, aby to klient nedovozoval. Pozadavek na pohled nebo firmu bez opravneni vraci **403 nebo 404**, nikdy tise zuzeny vysledek. Uzivatel nesmi koukat na cizi cisla v domneni, ze jsou spravna. ## Tickety Popis modelu je v [06-tickety.md](06-tickety.md), tady jen to, co se tyka API. `GET /api/dashboard/tickets` bere navic filtry `assignee`, `status`, `channel`. U `assignee` je zvlastni hodnota `unassigned` pro frontu bez resitele. U pohledu `mine` se `assignee` ignoruje, pohled je silnejsi. Neznama hodnota filtru se zaloguje a ignoruje - je lepsi ukazat vic ticketu nez prazdny seznam bez vysvetleni. Odpoved nese vedle `items` jeste `meId`. Klient podle nej pozna, ktere tickety jsou jeho, a jestli ma vubec smysl nabizet filtr "moje". Filtrovani dela **server**, ne klient. Seznam a prehled vytizeni tak nikdy neukazuji jina cisla. Vyhledavaci pole v portalu je jina vec - to jen dohledava v uz nactenem seznamu. `GET /api/dashboard/tickets/:id` vraci navic `trace`, tedy log prubehu vcetne toho, co ktera volana sluzba vratila. `POST /api/dashboard/tickets/:id/assign` s telem `{"assigneeId": null}` vrati ticket do fronty. Neznamy resitel vraci 404, ne tiche odpojeni. `POST /api/dashboard/tickets/:id/claim` je **prevzeti prace**, ne prehozeni: volajici si bere ticket sam a telo je prazdne. Smi to u ticketu bez resitele a u ticketu ve skupine, ve ktere je. Kdyz uz ticket nekdo resi, vraci 409, resp. 403 u cizi skupiny - vzit nekomu rozdelanou praci je jine rozhodnuti a chce to pravo `ticket.assign.others`. Kdo neni vedeny jako resitel, dostane 400. ## Pozvanky do firmy `/api/invites/:kod` je **verejne**, protoze kdo prijde za odkazem, jeste ucet mit nemusi. Autorizuje kod v adrese, proto je nahodny a dlouhy - stejne jako u webhooku. Odpoved je zamerne uzka: nazev firmy, jestli pozvanka plati, a kdyz uz adresu zname, tak ji, at ji clovek nemusi psat. Nic o tom, kdo ve firme je. `POST /api/invites/:kod/accept` s telem `{"name", "email", "password"}`: | Situace | Co se stane | | --- | --- | | ucet neexistuje | zalozi se a pripoji k firme | | ucet existuje, heslo sedi | jen se pripoji k firme | | ucet existuje, heslo nesedi | 401 | | pozvanka je na jinou adresu | 403 | | pozvanka uz byla pouzita nebo vyprsela | 409 s konkretnim duvodem | Overeni hesla u existujiciho uctu neni formalita: bez nej by kdokoliv s odkazem pripojil cizi adresu ke sve firme a videl by jeji data. Sprava pozvanek (`/api/dashboard/invites`) chce pravo `user.manage`. Seznam vraci u kazde pozvanky **celou adresu** vcetne prefixu proxy, aby slo rovnou kopirovat - relativni cesta se do zpravy vlepit neda. ## Akce na ticketu Popis modelu je v [09-navrh-rozsireni.md](09-navrh-rozsireni.md). `GET /api/dashboard/tickets/:id/actions` vraci **jen akce, ktere v teto situaci opravdu jdou spustit**: sedi typ nebo tag, projdou podminky a volajici na ne ma pravo. Klient si nefiltruje nic - jinak by se to pocitalo na dvou mistech a jednou se to rozejde. `POST /api/dashboard/tickets/:id/actions/:actionId` vraci **200 i kdyz akce selhala**. Selhani akce neni chyba API. V odpovedi je `ok`, `summary`, `detail` s celym chybovym hlasenim a `durationMs`. Cely prubeh se zapise do logu ticketu. Vestavene akce (`type`, `tags`, `group`, `assign`, `status`, `comment`) jsou zvlast: meni ticket sam, ne cizi sluzbu, a kazda ma vlastni pravo. ## Prijem udalosti do ticketu Popis modelu je v [18-ticketovaci-system.md](18-ticketovaci-system.md). `POST /webhook/ticket/:token` je **verejny**, autorizuje token firmy v adrese. Vraci 201 kdyz ticket vznikl, 200 kdyz se udalost navesila na existujici: ```json { "ok": true, "created": false, "ticketId": "TK-4822", "externalId": "3", "eventId": "tev_2" } ``` `externalId` je unikatni **v ramci firmy**. Token urcuje firmu, takze dve firmy mohou obe poslat objednavku cislo 3 a nedojde ke smichani. Cislo i retezec jsou tentyz klic. Neznamy `typeId` se zahodi a zaloguje, ticket vznikne bez typu. Odmitnout celou udalost kvuli jednomu poli by znamenalo ztratu dat. ## Fronta behu Popis je v [20-fronta-a-runtime.md](20-fronta-a-runtime.md). `POST /webhook/:token` vraci **202**, ne 200: data jsme prevzali a strom se vykona na pozadi. Vysledek se hleda v `GET /api/dashboard/runs` nebo v logu ticketu. Cekat na cizi sluzbu v requestu nejde - za jeji rychlost nerucime a odesilateli by vyprsel timeout. `GET /webhook/:token` vraci **kontrakt**: co se v tele ceka, na jakych cestach a ukazku. Bez toho by musel ten, kdo webhook zapojuje, hadat. `GET /api/dashboard/runs` ma u kazdeho behu cele chybove hlaseni, pocet pokusu a kdy se to zkusi znovu. ## Prava a navigace `GET /api/dashboard/access` vraci `permissions` (efektivni prava po slouceni roli), `nav` (zalozky, ktere ma volajici videt) a `platformAdmin`. Klient podle toho kresli, ale **nic si nedovozuje** - kdo co smi, rozhoduje server u kazdeho requestu znovu. `GET /api/dashboard/settings/catalog` vraci katalog prav a modulu, aby formular role nemel seznam prav napsany v kodu klienta. ## Prepnuti na jiny ucet `POST /api/admin/impersonate` vraci novy token s narokem `act` (kdo se za koho vydava) a `writes`. Bez `writes` middleware **odmitne cokoliv jineho nez GET** s 403. Kazde prepnuti i ukonceni je v auditu vcetne toho, kdo to byl doopravdy. Svuj puvodni token si klient odklada do `sessionStorage`, server o nem nic nevi. ## Sluzby a konektory Popis modelu je v [12-sluzby-a-konektory.md](12-sluzby-a-konektory.md), tady jen API. Sluzba je to, co umime. Konektor je napojeni jedne firmy vcetne jejich pristupovych udaju. **Hodnoty pristupovych udaju se nikdy nevraci**, jen `filled` a `missing`. V `PATCH` staci poslat jen to, co se meni: prazdny retezec hodnotu smaze, chybejici klic ji nechava. **Sluzba, kterou uzivatel nevidi, se nevraci vubec**, ne se stavem 403. `POST /connectors/:id/test` vraci 200 i pri neuspechu. `checked` rika, co se vlastne overilo - u sluzby bez `verifyPath` jen dostupnost, ne udaje. ## Skripty konektoru Popis modelu je v [11-skripty-konektoru.md](11-skripty-konektoru.md), tady jen API. Cteni smi kazdy prihlaseny, protoze builder potrebuje vedet, co skript umi. Uprava, zkusebni spusteni a vynucene nacteni smi **jen spravce platformy** - uprava skriptu meni chovani vseho, co ho pouziva. `GET /api/dashboard/scripts` vraci vedle manifestu i `problems` s rozbitymi skripty a `connections` se stavem napojeni. **Hodnoty pristupovych udaju se nevraci nikdy**, jen jmena chybejicich environment variables. `PUT /api/dashboard/scripts/:id` kod nejdriv nacte a overi a az pak prepise soubor. Rozbita uprava vraci 400 s `issues` a puvodni skript dal funguje. `POST /api/dashboard/scripts/:id/test` **vola opravdovou sluzbu**. Chyba skriptu neni chyba API, vraci se 200 a popis v `error` vcetne toho, jestli ma smysl zkusit to znovu. ## Pri pridani endpointu Soucasne aktualizovat `src/openapi.ts` a tenhle soubor. Swagger musi odpovidat skutecnemu chovani aplikace, jinak je horsi nez zadny.