nessie

API

A HTTP API, amely minden Nessie-kliens mögött van, és az, hogyan hitelesítheted magad vele szemben.

Mi az API

A Nessie-nek egyetlen HTTP API-ja van. Az admin webalkalmazás, a macOS- és iOS-kliensek és a távoli executor-démon mind ugyanennek az API-nak a hagyományos kliensei, és amit csak tesznek, azt máshogy nem lehet elérni. Ez egy JSON API, amely Fastify szerveren fut, nem GraphQL, és nincs külön „nyilvános” részhalmaza: amelyet a saját szkriptjeid hívnak, az ugyanaz a felület, amelyet a termék is hív.

Az útvonalban nincs verziószegmens. Minden útvonal a /api alatt van, kivéve a bejövő MCP-végpontot, a POST /mcp-t, és néhány /.well-known/ felfedező dokumentumot. A kompatibilitást úgy tartjuk fenn, hogy csak mezőket adunk hozzá, így az a kliens, amely figyelmen kívül hagyja az általa nem ismert dolgokat, továbbra is működik.

Alap-URL

A Nessie saját üzemeltetésű, ezért az alap-URL az az origin, amelyen a telepítésed kiszolgálja az API-t. Az üzemeltető a NESSIE_API_PUBLIC_URL értékét állítja be, és ugyanezt használja az API akkor is, amikor saját abszolút URL-t kell létrehoznia. A nálunk futó példányon ez a https://api.nessie.works; a pnpm dev paranccsal indított fejlesztői példány a http://localhost:5454 címen válaszol.

Az admin webalkalmazás más origin, mint az API, ezért a böngészős kliensekre CORS-engedélyezési lista (NESSIE_CORS_ORIGINS) vonatkozik. Szkriptből vagy szerverről érkező kérésnek nincs ellenőrizendő origin-je, ezért ezeket nem érinti.

Hitelesítés

A néhány szándékosan nyilvános útvonaltól eltekintve minden kérés szabványos fejlécben viszi a hozzáférési tokent:

Authorization: Bearer <token>

A token az API által kiállított, aláírt JWT. Rövid életű — alapértelmezetten harminc perc (NESSIE_AUTH_TOKEN_TTL) —, és ez az egyetlen hitelesítő adat, amelyet egy átlagos hívó valaha is bemutat. A Nessie-nek nincsenek hosszú életű személyes API-kulcsai. Ha azt szeretnéd, hogy egy szkript hívja az API-t, ugyanúgy jelentkezzen be, ahogyan a személy böngészője teszi, és közben frissítse a tokenjét.

Token megszerzése

A POST /api/auth/session munkamenetet hoz létre. Válaszként visszaadja a hozzáférési tokent és a hívó identitását, a megújító tokent pedig külön, csak HTTP-s sütiben állítja be.

Az, hogy hogyan igazolod magad, attól függ, hogyan van telepítve a példány. A local módban futó példány e-mail címet és jelszót fogad el. hosted vagy selfHosted példányon a jelszavas bejelentkezést a rendszer egyszerűen elutasítja, a 403 PASSWORD_AUTH_DISABLED válasszal, és az egyetlen bejutási út a beállított identitásszolgáltató — így a fióknak sosem nőhet ki mellette második, gyengébb identitási útja.

Bejelentkezés identitásszolgáltatón keresztül

Az egyszeri bejelentkezés (SSO) egy OAuth engedélyezési kódcsere PKCE-vel, három lépésben. A Nessie saját identitásszolgáltatója az UnlikeOtherAI, amely a példány által tükrözött szervezeti és csapatstruktúrát is birtokolja; az alábbi csere alakja bármely beállított szolgáltatónál ugyanaz.

  1. A GET /api/auth/providers felsorolja, mely szolgáltatókat fogadja el ez a példány; mindegyiknél megadja a providerId-t, a címkét, és azt, hogy a bejelentkezési oldal automatikusan átirányít-e oda.
  2. A GET /api/auth/providers/:providerId/authorize végpont a codeChallenge, a redirectUri és a state lekérdezési paraméterekkel dolgozik, és a { data: { authorizeUrl } } választ adja. Küldd oda a személyt.
  3. A szolgáltató egy kóddal visszaküldi a személyt a redirectUri-jukra. Ezt küldd vissza POST /api/auth/session-ként a { providerId, code, codeVerifier, redirectUri } tartalommal — mind a négy értéket együtt kell megadni, a hiányos készlet pedig 400 EXTERNAL_AUTH_INCOMPLETE hibát ad.

Munkamenet megújítása

A megújító token soha nincs a válasz törzsében. nessie_refresh néven HTTP-s sütiként állítják be, a /api/auth útvonalra korlátozva, alapértelmezetten harminc napos élettartammal (NESSIE_AUTH_REFRESH_TOKEN_TTL). A POST /api/auth/refresh hívás ezzel a sütivel felhasználja a tokent, új hozzáférési tokent ad ki, és új sütit állít a régi helyére — a tokenek forognak, így az ellopott token csak addig érvényes, amíg az igazi kliens legközelebb nem frissít.

Több fiókba bejelentkezett böngésző minden bejelentkezéshez tart egy ilyen sütit, nessie_refresh_<slot> néven, és az x-nessie-account-slot fejléccel nevezi meg, hogy a kérés melyikre vonatkozik. Az a kliens, amely nem küld ilyen fejlécet, a nessie_refresh-re vonatkozik, ami egy parancssori kliens számára elegendő.

A parancssori kliensnek ezért sütitároló (cookie jar) kell. A curl-lel ez a bejelentkező hívás --cookie-jar, a frissítésé pedig --cookie kapcsolóval jár, ahogyan az alábbi kidolgozott példában látható.

Mit ellenőriz minden kérés

A hitelesítés nem csak aláírás-ellenőrzés. Mielőtt a kezelő lefut, az API ellenőrzi a token aláírását és lejáratát, megerősíti, hogy a munkamenetet nem vonták-e vissza (mind a felhasználó-szintű tokenverzió, mind az egyedi munkamenet-azonosító alapján), és betölti a hívó tagságát a tokenben megnevezett szervezetben. A deaktivált tagság 403-at ad. Ha a szervezet külső identitásszolgáltatóra épül, a hívó aktuális állapotát a kérésnél ellenőrzik az adott szolgáltatónál, ahelyett hogy a token tartalmából olvasnák, így a felsőbb szinten visszavont hozzáférés azonnal megszűnik, nem csak a következő lejáratkor.

Nem munkamenet-tokenek

Két másik hozzáférési hitelesítő adat létezik, és egyik sem általános célú kulcs. Egy hangeszköz-hitelesítő adatot csak a hanghívási útvonalak fogadnak el, egy ügynök-hozzáférési hitelesítő adatot pedig csak az MCP-végpont. Másutt bemutatva mindkettő 403-as hibát kap, amely megnevezi a hitelesítő adatot, és nem próbálják gyengébb munkamenetként újra — lásd: MCP és csatlakoztatott eszközök, ott olvasható, hogyan adják ki az ügynök-hozzáférési hitelesítő adatot.

A válasz szerkezete

Minden sikeres válasz egy data kulcsot tartalmazó objektum. Nincs csupasz tömb, és nincs felső szintű adat a gyökérben, így a kliensnek sosem kell alak szerint döntenie:

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

A listavégpontok ezen kívül egy meta objektumot adnak hozzá. A felső szinten soha nem kerül más.

A hibák is egyetlen alakot követnek, bármi okozta is őket:

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

A code a gépi olvasható rész, és ez alapján érdemes elágaznod; a message embernek szól. A field és a details akkor szerepel, ha a hiba elég konkrét a helymeghatározáshoz. Egy kezeletlen szerverhiba mindig 500 INTERNAL_ERROR rögzített üzenettel érkezik — a mögöttes hibaszöveg naplózódik, de sosem kerül vissza, mert útközben a kérés adatait is felszedhette.

Az első hibakódok, amelyekkel találkozni fogsz

ÁllapotKódJelentés
400VALIDATION_ERRORA törzs vagy a lekérdezés nem felelt meg a sémának.
401AUTH_REQUIREDNincs hordozó (bearer) token, vagy a fejléc sérült.
401TOKEN_EXPIREDA hozzáférési token lejárt. Frissíts, és próbáld újra.
401TOKEN_REVOKEDA munkamenetet kijelentkeztették, vagy minden munkamenetet kényszerítve megszüntettek.
403ACCOUNT_DEACTIVATEDEnnek a szervezetnek a tagsága deaktiválva lett.
404NOT_FOUNDNincs ilyen útvonal, vagy nincs ilyen rekord, amelyet ez a hívó láthat.
429RATE_LIMITEDEgy keret megtelt. Olvasd ki a Retry-After fejlécet.
500INTERNAL_ERRORKezeletlen szerverhiba, szándékosan általános szöveggel.

Lapozás

A listavégpontok átlátszatlan, kulcsalapú (keyset) kurzorral lapoznak, nem eltolásos (offset) módon — a sorok akkor is beszúródnak, amikor valaki olvassa őket, és eltolással a második oldal csendben megismétel egy rekordot, amelyet az első oldal már megmutatott.

Három lekérdezési paramétert fogadnak el: cursor, limit és direction (forward vagy backward). Az alapértelmezett oldalméret 25, a felső határ 100; a nagyobb értéket levágják, nem utasítják el. A rendezést vagy szűrést is támogató végpontok még a sort, order és a szabadszöveges q paramétert adják hozzá.

Az oldal meta része megmondja, hol tartasz:

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

A nextCursor és a prevCursor egyaránt valódi: az Előző és a Következő is olyan gomb, amelyet a személy vár. Átlátszatlanok — a szerver saját rendezési kulcsát kódolja beléjük, és az a kliens, amely megfejti vagy összerakja őket, el fog törni, amint a lista megváltoztatja a rendezését. A total a legtöbb listán megvan, és ott hagyják ki, ahol a számlálás nem értelmes, például rangsorolt keresési eredményeknél.

Sebességkorlátok

A korlátok rögzített ablakokon belül érvényesek, és még a kérés törzsének beolvasása előtt érvényesülnek. Az elutasított kérés 429 válasz, RATE_LIMITED kóddal és másodpercben megadott Retry-After fejléccel.

A legtöbb keret a kliens IP-címéhez kötött. A hitelesítésre érzékeny keretek — bejelentkezés, megújítás, jelszó-újraigazolás, MCP-hitelesítő adat írása — emellett fiókonkénti számlálót is tartanak, és már akkor elutasítanak, ha bármelyik átlépődik, így az egy címről sok fiókot támadó, és az egy fiókot sok címről támadó kísérlet is falba ütközik. A bejelentkezés alapértelmezésként IP-címenként 10, e-mail-címenként 5 próbálkozást enged tíz percenként; üzenetküldés percenként 60; a /api/agents alatti írások percenként 60. Minden küszöb telepítésenként állítható, ezért ezeket a szállított alapértelmezésként kezeld, nem szerződésként. Az állapotellenőrzések és a valós idejű adatfolyamok mentesek.

Valós idejű

Egy beszélgetés követése listalekérdezéssel rossz módszer. Ehelyett három adatfolyam-végpont van:

  • GET /api/events/stream — szerveroldali események (SSE) mindarról, amit a bejelentkezett személy láthat.
  • GET /api/threads/:threadId/stream — szerveroldali események egyetlen szálhoz. Tiszteletben tartja a Last-Event-ID fejlécet, így az újracsatlakozás folytatódik, nem indul újra.
  • GET /api/activity — WebSocket, amely az aktivitási hírfolyamot viszi.

A böngészők nem tudnak fejlécet beállítani a WebSocket-kézfogásnál, ezért a WebSocket-útvonal a hozzáférési tokent ?token= lekérdezési paraméterként is elfogadja. Ez az egyetlen hely, ahol token szerepelhet az URL-ben; minden más útvonal fejlécet fogad el.

Az erőforráscsaládok

Jóval több mint ezer végpont van, ami túl sok ahhoz, hogy felsoroljuk őket. Családokba rendeződnek, és ha ismered egy család előtagját, az egyes útvonalak kiszámíthatók — egy gyűjtemény az előtagon, egy rekord alatta, a megnevezett műveletek pedig POST alútvonalként.

CsaládElőtagMit fed le
Hitelesítés/api/authBejelentkezés, megújítás, kijelentkezés, az aktuális identitás, és egy személy saját munkamenetei.
Csatornák és szálak/api/channels, /api/threadsA beszélgetési modell: a csatornák szálakat tartalmaznak, a szálak üzeneteket, a válaszok pedig a gyökerük alá ágaznak.
Ügynökök/api/agentsÜgynök-definíciók, csatornakötéseik, beszélgetéseik, teendőik és állapotuk.
Projektek és táblák/api/projects, /api/tasks, /api/iterationsMunkakövetés: táblák és oszlopok egy projekt alatt, a rajtuk lévő feladatok, és ezek fölött az iterációk.
Tudásbázis/api/knowledge-baseVerziózott oldalak és fájlok terei, megjegyzésekkel, visszautalásokkal és kereséssel.
Levelezés/api/mail, /api/mailbox-connectionsCsatlakoztatott postafiókok, ügynök-postafiókok, és azok a piszkozatok, amelyeket egy ügynök írt, hogy a személy jóváhagyja.
Jóváhagyások/api/approvalsAzoknak a műveleteknek a sora, amelyeket egy ügynök nem tehet meg egyoldalúan, és az egyetlen hívás, amely ezeket megoldja.
Audit/api/audit-logA szervezet hash-láncolt naplózási nyoma, ellenőrző végponttal. Tulajdonosok és adminisztrátorok.
Költség és használat/api/ledger, /api/billingToken- és költségelszámolás, költségkeretek és futási idők; külön a kreditek és feltöltések. Tulajdonosok és adminisztrátorok.
Szervezet és csapatok/api/organizations, /api/teams, /api/usersBérlő-, csapat- és tagságkezelés, a meghívásokkal együtt.
Triggerek/api/triggersÜtemezések és webhook-ok, amelyek ügynöki munkát indítanak, a lefutási előzményeikkel együtt.
Hívások és hang/api/calls, /api/voiceCsatornán belüli hívás, valamint a hangasszisztens munkamenete, átirata és eszközhitelesítő adatai.
Executorok/api/executors, /api/execution-environment-templatesAzok a gépek, amelyeken az ügynökök dolgoznak, a hozzájuk tartozó környezetek, és a démon-párosítási csatorna.
Futások/api/runsÉlő ügynökfutások: megszakítás, folytatás, újraindítás, kötés egy executorhoz.
Feltöltések/api/uploads, /api/attachmentsElőbb tölts fel egy fájlt, majd kapcsold a visszaadott azonosítót egy üzenethez.
Keresés/api/messages/searchÜzenetkeresés, szervezetszintűen vagy egyetlen csatornára szűkítve.
Titkok és beállítások/api/secrets, /api/settings/scopedCsak írható hitelesítő adat-tárolás explicit jogosultságokkal, és rétegzett konfigurációs értékek.
Csatlakoztatott eszközök/api/mcpMCP-szerverek, amelyeket ez a példány hív. Lásd: MCP és csatlakoztatott eszközök.
Állapotellenőrzés/api/healthÉlő állapot és készenlét, mindkettő nyilvános. A /api/health/ready az adatbázist is ellenőrzi.

A jogosultságokat útvonalanként érvényesítik, nem családonként. Nagyjából: az audit napló, a költségkönyvelés, a házirend-szabályok és az inferenciavezérlés a szervezet tulajdonosainak és adminisztrátorainak felületei; a beszélgetések, a projektek, a tudásbázis és a jóváhagyások bármely tag számára nyitottak, és arra szűkítve, amit az adott tag ténylegesen láthat.

Kidolgozott példák

Bejelentkezés, és a megújító süti megtartása

Egy local módú példányon, jelszóval. A hozzáférési token a válasz törzsében érkezik; a megújító token a sütitárolóba kerül.

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
      }
    }
  }
}

Amikor a token lejár, a sütivel kérj újat:

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

A válasz ugyanaz a { token, me } alakú, a sütitároló pedig már a forgatott megújító tokent tartalmazza.

Listázd a látható csatornáidat

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
    }
  ]
}

Add hozzá a ?teamId=… paramétert, hogy egy csapatra szűkítsd, vagy a ?includeArchived=true paramétert, hogy az archivált csatornák is megjelenjenek. A defaultThreadId az, ahová legközelebb posztolsz.

Üzenet küldése

Egy üzenethez kell legalább némi content, vagy legalább egy feltöltött melléklet. Küldj Idempotency-Key fejlécet, és az újrapróbált kérés az első próbálkozás által létrehozott üzenetre oldódik fel, nem küld második példányt.

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."}'

Az új üzenetre 201-es választ kapsz:

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

Ugyanazzal az idempotencia-kulccsal érkező újrapróbálkozás 200-zal válaszol az eredeti üzenettel és egy üres pendingAgentInvites tömbbel, mert az első próbálkozás már elvégezte azt az értesítést és kiküldést, amelyet a tömb leír.

Ennél az útvonalnál két elutasítást érdemes külön kezelni. A csevegés hosszkorlátját meghaladó tartalom 413 MESSAGE_TOO_LARGE választ kap, és a helyes válasz az, hogy a szöveget fájlként töltsd fel. A hitelesítő adatra hasonlító tartalom 422 SECRET_INTERCEPTED választ kap — a szerver az üzenet tárolása, kiküldése vagy naplózása előtt elutasítja, a hitelesítő adatnak pedig a /api/secrets végponton van a helye.