nessie

API

HTTP API, na kterém stojí každý klient Nessie, a jak se k němu ověřit.

Co je API

Nessie má jedno HTTP API. Webová aplikace pro správu, klienti pro macOS a iOS i vzdálený démon executoru jsou všechny běžní klienti tohoto API a nic, co dokážou, nelze dosáhnout jinou cestou. Je to JSON API nad serverem Fastify, není to GraphQL a nemá žádnou oddělenou „veřejnou“ podmnožinu: rozhraní, které volají vaše vlastní skripty, je totéž rozhraní, které volá produkt.

V cestě není žádný segment verze. Každá trasa je pod /api, kromě příchozího koncového bodu MCP POST /mcp a několika dokumentů pro objevování na /.well-known/. Kompatibilitu držíme tak, že se pole pouze přidávají, takže klient, který neznámá pole ignoruje, dál funguje.

Základní URL

Nessie provozujete na vlastní infrastruktuře, takže základní URL je ten původ (origin), na kterém vaše nasazení obsluhuje API. Provozovatel jej nastaví jako NESSIE_API_PUBLIC_URL a stejná hodnota slouží API, kdykoli potřebuje vytvořit vlastní absolutní URL. Na instanci, kterou provozujeme my, je to https://api.nessie.works; vývojová instance spuštěná příkazem pnpm dev odpovídá na http://localhost:5454.

Webová aplikace pro správu je jiný původ než API, takže prohlížečoví klienti podléhají seznamu povolených CORS původů (NESSIE_CORS_ORIGINS). Požadavky ze skriptu nebo ze serveru žádný původ nemají, takže je tento seznam neovlivňuje.

Autentizace

Každý požadavek mimo malou sadu záměrně veřejných tras nese přístupový token ve standardní hlavičce:

Authorization: Bearer <token>

Token je podepsaný JWT, který vydává API. Je krátkodobý, ve výchozím nastavení třicet minut (NESSIE_AUTH_TOKEN_TTL), a je jediným přihlašovacím údajem, který běžný volající kdy předkládá. Nessie nepoužívá dlouhodobé osobní klíče API. Pokud chcete, aby skript volal API, přihlásí se stejně jako prohlížeč dané osoby a průběžně si svůj token obnovuje.

Získání tokenu

POST /api/auth/session vytvoří relaci. Odpoví přístupovým tokenem a identitou volajícího a obnovovací token nastaví jako samostatnou cookie s příznakem HTTP-only.

Jak se prokazujete, závisí na tom, jak je instance nasazená. Instance běžící v režimu local přijímá e-mail a heslo. Na instanci hosted nebo selfHosted je přihlášení heslem rovnou odmítnuto s 403 PASSWORD_AUTH_DISABLED a jedinou cestou dovnitř je nakonfigurovaný poskytovatel identity. Účet tak nikdy nezíská druhou, slabší cestu k identitě vedle něj.

Přihlášení přes poskytovatele identity

Single sign-on je výměna autorizačního kódu OAuth s PKCE ve třech krocích. Vlastním poskytovatelem identity Nessie je UnlikeOtherAI, který také spravuje strukturu organizací a týmů, kterou instance zrcadlí; výměna níže má stejný tvar u kteréhokoli nakonfigurovaného poskytovatele.

  1. GET /api/auth/providers vypíše, co tato instance přijímá; u každého je uvedeno providerId, popisek a to, zda má přihlašovací stránka přesměrovat na něj automaticky.
  2. GET /api/auth/providers/:providerId/authorize s parametry dotazu codeChallenge, redirectUri a state odpoví hodnotou { data: { authorizeUrl } }. Pošlete tam danou osobu.
  3. Poskytovatel vrátí osobu na váš redirectUri s kódem. Ten pošlete zpět jako POST /api/auth/session s { providerId, code, codeVerifier, redirectUri } — všechny čtyři hodnoty jsou vyžadovány společně a neúplná sada vrátí 400 EXTERNAL_AUTH_INCOMPLETE.

Obnovení relace

Obnovovací token se nikdy neobjeví v těle odpovědi. Je nastaven jako cookie s příznakem HTTP-only s názvem nessie_refresh, omezený na cestu /api/auth, s výchozí platností třicet dní (NESSIE_AUTH_REFRESH_TOKEN_TTL). Volání POST /api/auth/refresh s touto cookie jej spotřebuje, vydá nový přístupový token a místo něj nastaví novou cookie. Tokeny se rotují, takže ukradený token platí jen do chvíle, kdy se skutečný klient příště obnoví.

Prohlížeč, ve kterém je přihlášeno více účtů, si drží jednu takovou cookie pro každé přihlášení, pojmenovanou nessie_refresh_<slot>, a požadavek, který se týká konkrétního účtu, označí hlavičkou x-nessie-account-slot. Klient, který takovou hlavičku neposílá, se týká nessie_refresh, což je vše, co potřebuje klient z příkazové řádky.

Klient z příkazové řádky tedy potřebuje cookie jar. U curl je to --cookie-jar při přihlášení a --cookie při obnovení, jak ukazuje příklad níže.

Co se kontroluje u každého požadavku

Autentizace není jen kontrola podpisu. Před spuštěním obslužné funkce API ověří podpis a expiraci tokenu, potvrdí, že relace nebyla odvolána (jak podle verze tokenu platné pro celého uživatele, tak podle ID jednotlivé relace), a načte členství volajícího v organizaci, kterou token uvádí. Deaktivované členství vrací 403. Pokud je organizace napojena na externího poskytovatele identity, aktuální stav volajícího se u něj ověřuje přímo při požadavku a nečte se z tokenu. Přístup odebraný u poskytovatele tak přestane fungovat okamžitě, a ne až při dalším vypršení platnosti.

Přihlašovací údaje, které nejsou tokeny relace

Existují dva další přihlašovací údaje typu bearer a ani jeden z nich není univerzální klíč. Přihlašovací údaj hlasového zařízení se přijímá jen na trasách hovorů s hlasem a přístupový údaj agenta jen na koncovém bodu MCP. Předložení kteréhokoli z nich kdekoli jinde je odmítnuto s 403, které pojmenuje dotyčný přihlašovací údaj, a nezkouší se znovu jako slabší relace. Jak se přístupový údaj agenta vydává, je popsáno v MCP a připojené nástroje.

Obálka odpovědi

Každá úspěšná odpověď je objekt s klíčem data. Neexistuje holé pole ani obsah na nejvyšší úrovni, takže klient nikdy nemusí rozlišovat podle tvaru:

{
  "data": { "id": "3f1c…", "label": "engineering" }
}

Seznamové koncové body přidávají vedle něj objekt meta. Na nejvyšší úrovni se nepřidává nic jiného.

Chyby mají také jeden tvar, bez ohledu na to, co je způsobilo:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "A message needs content or at least one attachment",
    "field": "body",
    "details": { "formErrors": [], "fieldErrors": {} }
  }
}

code je strojově čitelná část a podle ní by se měl klient větvit; message je určena člověku. field a details jsou přítomny, když je chyba dost konkrétní, aby se dala lokalizovat. Neošetřená chyba serveru je vždy 500 INTERNAL_ERROR s pevnou zprávou. Původní text chyby se zaloguje, ale nikdy se nevrátí, protože mohl při cestě ven zachytit data z požadavku.

Kódy chyb, se kterými se setkáte nejdřív

StavKódVýznam
400VALIDATION_ERRORTělo nebo dotaz neodpovídají schématu.
401AUTH_REQUIREDChybí přístupový token nebo je hlavička poškozená.
401TOKEN_EXPIREDPřístupový token je po expiraci. Obnovte jej a zopakujte požadavek.
401TOKEN_REVOKEDRelace byla odhlášena nebo byly všechny relace násilně ukončeny.
403ACCOUNT_DEACTIVATEDČlenství v této organizaci bylo deaktivováno.
404NOT_FOUNDTaková trasa neexistuje, nebo volající nevidí žádný takový záznam.
429RATE_LIMITEDKvóta je vyčerpaná. Přečtěte si hlavičku Retry-After.
500INTERNAL_ERRORNeošetřená chyba serveru, záměrně neprůhledná.

Stránkování

Seznamové koncové body stránkují pomocí neprůhledného kurzoru podle klíče (keyset), ne pomocí offsetu. Řádky se vkládají, zatímco je někdo čte, a s offsetem by druhá stránka tiše zopakovala záznam, který už ukázala první.

Přijímají se tři parametry dotazu: cursor, limit a direction (forward nebo backward). Výchozí stránka má 25 položek a horní strop je 100; vyšší hodnota se ořízne, místo aby byla odmítnuta. Koncové body, které také řadí nebo filtrují, přidávají sort, order a volný text q.

meta stránky vám řekne, kde se nacházíte:

{
  "data": [ … ],
  "meta": {
    "hasMore": true,
    "nextCursor": "2026-09-12T08:31:44.005Z|8b2f…",
    "prevCursor": null,
    "total": 134
  }
}

nextCursor a prevCursor jsou obě skutečné: Předchozí a Další jsou ovládací prvky, které člověk očekává. Jsou neprůhledné — server do nich zakóduje vlastní řadicí klíč a klient, který jeden z nich rozebírá nebo sestavuje, se rozbije ve chvíli, kdy seznam změní pořadí. total je přítomen u většiny seznamů a vynechán tam, kde počítání nedává smysl, například u výsledků hledání seřazených podle relevance.

Limity požadavků

Limity platí pro pevná časová okna a vynucují se dřív, než se vůbec přečte tělo požadavku. Zamítnutý požadavek dostane 429 s kódem RATE_LIMITED a hlavičkou Retry-After v sekundách.

Většina kvót je vázána na IP adresu klienta. Ty citlivé na přihlašovací údaje — přihlášení, obnovení, opakované ověření heslem a zápisy přihlašovacích údajů MCP — navíc vedou počítadlo pro každý účet a odmítnou, když překročí limit kterýkoli z nich. Jedna adresa rozesílající pokusy na mnoho účtů i jeden účet napadený z mnoha adres tak narazí na zeď. Přihlášení má výchozí hodnoty 10 pokusů na IP adresu a 5 na e-mailovou adresu za deset minut; odeslání zprávy 60 za minutu; zápisy pod /api/agents 60 za minutu. Každý práh lze nastavit pro konkrétní nasazení, takže tyto hodnoty berte jako výchozí, nikoli jako závazek. Kontroly stavu (health) a streamy v reálném čase jsou vyjmuty.

Realtime

Opakované dotazování seznamu je špatný způsob, jak sledovat konverzaci. Místo toho existují tři streamovací koncové body:

  • GET /api/events/stream — události odesílané serverem (SSE) pro vše, co přihlášená osoba vidí.
  • GET /api/threads/:threadId/stream — SSE pro jedno vlákno. Respektuje Last-Event-ID, takže se opětovné připojení obnoví, místo aby začalo znovu.
  • GET /api/activity — WebSocket, který přenáší kanál aktivity.

Prohlížeče nemohou nastavovat hlavičky při handshaku WebSocketu, proto trasa WebSocketu přijímá přístupový token také jako query parametr ?token=. Je to jediné místo, kam token v URL patří; každá další trasa jej bere z hlavičky.

Rodiny zdrojů

Koncových bodů je dobře přes tisíc, což je na výčet příliš mnoho. Seskupují se do rodin, a jakmile znáte předponu rodiny, jsou jednotlivé trasy předvídatelné: kolekce na předponě, záznam pod ní a pojmenované akce jako podcesty POST.

RodinaPředponaCo pokrývá
Autentizace/api/authPřihlášení, obnovení, odhlášení, aktuální identita a vlastní relace osoby.
Kanály a vlákna/api/channels, /api/threadsModel konverzace: kanály obsahují vlákna, vlákna obsahují zprávy a odpovědi se vnořují pod svůj kořen.
Agenti/api/agentsDefinice agentů, jejich vazby na kanály, konverzace, úkoly a stav.
Projekty a nástěnky/api/projects, /api/tasks, /api/iterationsSledování práce: nástěnky a sloupce pod projektem, úkoly na nich a iterace nad nimi.
Znalosti/api/knowledge-baseProstory verzovaných stránek a souborů s komentáři, zpětnými odkazy a hledáním.
E-mail/api/mail, /api/mailbox-connectionsPropojené schránky, schránky agentů a koncepty, které agent připravil pro schválení osobou.
Schválení/api/approvalsFronta akcí, které agent nesmí podniknout jednostranně, a jediné volání, které je vyřeší.
Audit/api/audit-logHashovaně řetězená auditní stopa organizace s ověřovacím koncovým bodem. Pro vlastníky a správce.
Náklady a využití/api/ledger, /api/billingÚčetnictví tokenů a nákladů, rozpočty a časy běhů; odděleně kredity a dobíjení. Pro vlastníky a správce.
Organizace a týmy/api/organizations, /api/teams, /api/usersSpráva organizace, týmů a členství, včetně pozvánek.
Spouštěče/api/triggersPlány a webhooky, které spouštějí práci agentů, s historií jejich spuštění.
Hovory a hlas/api/calls, /api/voiceHovory v kanálu a relace hlasového asistenta, jeho přepisy a tokeny zařízení.
Executory/api/executors, /api/execution-environment-templatesStroje, na kterých agenti pracují, jejich prostředí a kanál párování démona.
Běhy/api/runsŽivé běhy agentů: zrušit, pokračovat, restartovat, navázat na executor.
Nahrávání/api/uploads, /api/attachmentsNejdřív nahrajte soubor, pak k zprávě připojte vrácené ID.
Hledání/api/messages/searchHledání ve zprávách, napříč organizací nebo omezené na jeden kanál.
Tajemství a nastavení/api/secrets, /api/settings/scopedÚložiště přihlašovacích údajů pouze pro zápis s výslovnými oprávněními a vrstvené konfigurační hodnoty.
Připojené nástroje/api/mcpServery MCP, které tato instance volá. Viz MCP a připojené nástroje.
Stav/api/healthKontrola živosti a připravenosti, obě veřejné. /api/health/ready kontroluje i databázi.

Práva se vynucují na úrovni jednotlivé trasy, ne rodiny. Obecně: auditní protokol, účetní kniha nákladů, pravidla zásad a řídicí vrstva inference jsou povrchy pro vlastníky a správce organizace, zatímco konverzace, projekty, znalosti a schválení jsou otevřené každému členovi a omezené tím, co dotyčný člen skutečně vidí.

Příklady

Přihlášení a uložení obnovovací cookie

Na instanci v režimu local s heslem. Přístupový token se vrátí v těle odpovědi; obnovovací token skončí v cookie jar.

curl -sS https://api.example.com/api/auth/session \
  --cookie-jar nessie.cookies \
  -H 'Content-Type: application/json' \
  -d '{"email":"ada@example.com","password":"…"}'
{
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
    "me": {
      "user": {
        "id": "1d4e…",
        "email": "ada@example.com",
        "displayName": "Ada Lovelace",
        "roleIds": ["owner"],
        "superAdmin": false
      },
      "session": {
        "sessionId": "9c07…",
        "issuedAt": "2026-09-16T09:00:00.000Z",
        "expiresAt": "2026-09-16T09:30:00.000Z"
      },
      "context": {
        "organizationId": "5a81…",
        "projectId": "7b12…",
        "teamId": "2e60…",
        "channelId": null,
        "bootstrapMode": false
      },
      "auth": {
        "providerId": "local",
        "providerType": "local-bootstrap",
        "autoRedirectToSso": false
      }
    }
  }
}

Když token vyprší, vyměňte cookie za nový:

curl -sS -X POST https://api.example.com/api/auth/refresh \
  --cookie nessie.cookies --cookie-jar nessie.cookies

Odpověď má stejný tvar { token, me } a cookie jar nyní drží nový obnovovací token.

Výpis kanálů, které vidíte

curl -sS https://api.example.com/api/channels \
  -H "Authorization: Bearer $TOKEN"
{
  "data": [
    {
      "id": "8b2f…",
      "label": "engineering",
      "type": "standard",
      "visibility": "public",
      "projectId": "…",
      "projectName": "Platform",
      "teamId": "…",
      "defaultThreadId": "c41a…",
      "unreadCount": 3,
      "lastMessageAt": "2026-09-15T18:22:07.441Z",
      "viewerCanManage": true
    }
  ]
}

Přidejte ?teamId=… pro omezení na jeden tým, nebo ?includeArchived=true, aby byly zahrnuty archivované kanály. defaultThreadId je místo, kam příště odešlete zprávu.

Odeslání zprávy

Zpráva potřebuje buď nějaký content, nebo alespoň jednu nahranou přílohu. Pošlete Idempotency-Key a opakovaný požadavek se vyřeší na zprávu, kterou vytvořil první pokus, místo aby vznikla druhá kopie.

curl -sS https://api.example.com/api/threads/c41a…/messages \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 0f9c2d16-8f4a-4c1e-9a77-6b0d4f2e1a53' \
  -d '{"content":"Deploying the migration now."}'

Nově vytvořená zpráva má stav 201:

{
  "data": {
    "message": {
      "id": "d70e…",
      "threadId": "c41a…",
      "role": "user",
      "content": "Deploying the migration now.",
      "createdAt": "2026-09-16T09:04:11.882Z"
    },
    "pendingAgentInvites": []
  }
}

Opakování se stejným klíčem idempotence odpoví 200 s původní zprávou a prázdným polem pendingAgentInvites, protože první pokus už provedl upozornění a odeslání, která toto pole popisuje.

Dvě odmítnutí na této trase stojí za výslovné ošetření. Obsah delší než limit délky zprávy vrací 413 MESSAGE_TOO_LARGE a správná reakce je nahrát text jako soubor. Obsah, který strukturálně vypadá jako přihlašovací údaj, vrací 422 SECRET_INTERCEPTED. Server odmítne zprávu dřív, než je uložena, odeslána nebo zalogována, a přihlašovací údaj patří do /api/secrets.