nessie

API

HTTP API iza svakog Nessie klijenta i način autentifikacije.

Što je API

Nessie ima jedan HTTP API. Web aplikacija za administraciju, klijenti za macOS i iOS te udaljeni executor daemon obični su klijenti tog API-ja, a ništa što oni mogu raditi nije dostupno na drugi način. Riječ je o JSON API-ju nad Fastify poslužiteljem, nije to GraphQL i nema zaseban „javni“ podskup: površina koju pozivaju vaše vlastite skripte jednaka je površini koju poziva sam proizvod.

U putanji nema segmenta verzije. Sve rute nalaze se pod /api, osim ulaznog MCP endpointa na POST /mcp i nekoliko dokumenata za otkrivanje pod /.well-known/. Kompatibilnost se održava tako da se polja samo dodaju, pa klijent koji ignorira ono što ne prepoznaje nastavlja raditi.

Osnovni URL

Nessie se postavlja na vlastitom poslužitelju, pa je osnovni URL adresa (origin) na kojoj vaša instalacija poslužuje API. Operator je postavlja kao NESSIE_API_PUBLIC_URL, a ista vrijednost upotrebljava se kada API mora sam izraditi vlastiti apsolutni URL. Na instanci koju mi pokrećemo to je https://api.nessie.works; razvojna instanca pokrenuta naredbom pnpm dev odgovara na http://localhost:5454.

Web aplikacija za administraciju ima drugi izvor (origin) od API-ja, pa se na klijente u pregledniku primjenjuje popis dopuštenih CORS izvora (NESSIE_CORS_ORIGINS). Zahtjevi iz skripte ili poslužitelja nemaju izvor (origin) koji bi se provjeravao i na njih to ne utječe.

Autentikacija

Svaki zahtjev, osim manjeg skupa namjerno javnih ruta, nosi pristupni token u standardnom zaglavlju:

Authorization: Bearer <token>

Token je potpisani JWT koji izrađuje API. Kratkotrajan je — prema zadanim postavkama trideset minuta (NESSIE_AUTH_TOKEN_TTL) — i jedina je vjerodajnica koju obični pozivatelj ikada predoči. Nessie nema dugotrajne osobne API ključeve. Ako želite da skripta poziva API, ona se prijavljuje na isti način kao preglednik osobe i obnavlja token po potrebi.

Dobivanje tokena

POST /api/auth/session izrađuje sesiju. Odgovara pristupnim tokenom i identitetom pozivatelja te postavlja token za obnavljanje kao zaseban kolačić dostupan samo putem HTTP-a.

Kako dokazujete tko ste, ovisi o tome kako je instanca postavljena. Instanca u načinu rada local prihvaća adresu e-pošte i lozinku. Na instancama hosted ili selfHosted prijava lozinkom odbija se bez iznimke s 403 PASSWORD_AUTH_DISABLED, a jedini ulaz je konfigurirani pružatelj identiteta, pa račun nikada ne može dobiti drugi, slabiji put prijave uz njega.

Prijava putem pružatelja identiteta

Jedinstvena prijava (single sign-on) jest razmjena autorizacijskog koda OAuth-a s PKCE-om u tri koraka. Pružatelj identiteta koji koristi sam Nessie je UnlikeOtherAI, koji ujedno posjeduje strukturu organizacija i timova koju instanca preslikava; razmjena u nastavku ima isti oblik za svakog konfiguriranog pružatelja.

  1. GET /api/auth/providers popisuje što instanca prihvaća, svakog s providerId, oznakom i time treba li stranica za prijavu na njega preusmjeriti automatski.
  2. GET /api/auth/providers/:providerId/authorize, s parametrima upita codeChallenge, redirectUri i state, odgovara s { data: { authorizeUrl } }. Pošaljite osobu tamo.
  3. Pružatelj vraća osobu na vaš redirectUri s kodom. Taj kod pošaljite natrag kao POST /api/auth/session s { providerId, code, codeVerifier, redirectUri } — sva četiri podatka obvezna su zajedno, a nepotpun skup daje 400 EXTERNAL_AUTH_INCOMPLETE.

Obnavljanje sesije

Token za obnavljanje nikada se ne nalazi u tijelu odgovora. Postavlja se kao kolačić dostupan samo putem HTTP-a, naziva nessie_refresh, ograničen na putanju /api/auth, s rokom valjanosti od trideset dana prema zadanim postavkama (NESSIE_AUTH_REFRESH_TOKEN_TTL). Poziv POST /api/auth/refresh s tim kolačićem troši ga, izdaje novi pristupni token i na njegovo mjesto postavlja novi kolačić — tokeni se rotiraju, pa ukraden token vrijedi samo dok se pravi klijent sljedeći put ne obnovi.

Preglednik prijavljen u nekoliko računa čuva po jedan takav kolačić za svaku prijavu, nazvan nessie_refresh_<slot>, a zaglavljem x-nessie-account-slot navodi onaj na koji se zahtjev odnosi. Klijent koji takvo zaglavlje ne šalje odnosi se na nessie_refresh, što je sve što klijentu iz naredbenog retka treba.

Klijentu iz naredbenog retka zato treba spremnik kolačića (cookie jar). Uz curl to je --cookie-jar na pozivu za prijavu i --cookie pri obnavljanju, kao u primjeru u nastavku.

Što se provjerava pri svakom zahtjevu

Autentikacija nije samo provjera potpisa. Prije nego što se pokrene obrada zahtjeva, API provjerava potpis i istek tokena, potvrđuje da sesija nije opozvana (i prema verziji tokena za cijelog korisnika i prema pojedinačnom ID-u sesije) te učitava članstvo pozivatelja u organizaciji koju token navodi. Deaktivirano članstvo daje 403. Kada je organizacija povezana s vanjskim pružateljem identiteta, trenutačni status pozivatelja provjerava se kod tog pružatelja pri samom zahtjevu, a ne čita iz tokena, pa pristup koji je izvana uklonjen prestaje odmah, a ne tek pri sljedećem isteku.

Vjerodajnice koje nisu sesijski tokeni

Postoje još dvije nosive (bearer) vjerodajnice, a nijedna nije ključ opće namjene. Vjerodajnica glasovnog uređaja prihvaća se samo na rutama za glasovne pozive; vjerodajnica pristupa agenta prihvaća se samo na MCP endpointu. Predočenje bilo koje od njih bilo gdje drugdje odbija se s 403 koji imenuje vjerodajnicu, a ne pokušava se ponovno kao slabija sesija — pogledajte MCP i povezani alati za način na koji se vjerodajnica agenta izdaje.

Oblik odgovora

Svaki uspješan odgovor je objekt s ključem data. Nema golog niza ni podatka na najvišoj razini, pa klijent nikada ne mora razlikovati oblike:

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

Endpointi s popisima uz to dodaju objekt meta. Na najvišoj razini ništa drugo se nikada ne dodaje.

I pogreške imaju jedan oblik, bez obzira na to što ih je izazvalo:

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

code je dio koji čita stroj, a vaš kod prema njemu odlučuje; message namijenjen je osobi. field i details prisutni su kada je pogreška dovoljno specifična da se može locirati. Neobrađena greška poslužitelja uvijek je 500 INTERNAL_ERROR s fiksnom porukom — izvorni tekst greške bilježi se, ali se nikada ne vraća, jer je mogao pokupiti podatke iz zahtjeva.

Kodovi grešaka koje ćete prvo susresti

StatusKodZnačenje
400VALIDATION_ERRORTijelo ili upit ne odgovaraju shemi.
401AUTH_REQUIREDNema bearer tokena ili je zaglavlje neispravno.
401TOKEN_EXPIREDPristupni token je istekao. Obnovite sesiju i pokušajte ponovno.
401TOKEN_REVOKEDSesija je odjavljena ili su sve sesije prisilno prekinute.
403ACCOUNT_DEACTIVATEDČlanstvo u ovoj organizaciji je deaktivirano.
404NOT_FOUNDNe postoji takva ruta ili zapis vidljiv ovom pozivatelju.
429RATE_LIMITEDKvota je iskorištena. Pročitajte Retry-After.
500INTERNAL_ERRORNeobrađena greška poslužitelja, namjerno neprozirna.

Paginacija

Endpointi s popisima stranicaju se pomoću neprozirnog keyset kursora, a ne pomaka (offseta) — retci se umeću dok netko čita, a s pomakom bi druga stranica tiho ponovila zapis koji je prva već prikazala.

Prihvaćaju se tri parametra upita: cursor, limit i direction (forward ili backward). Zadana stranica ima 25 stavki, a gornja granica je 100; veća vrijednost se ograničava, a ne odbija. Endpointi koji još i sortiraju ili filtriraju dodaju sort, order i slobodni tekst q.

Polje meta stranice pokazuje gdje se nalazite:

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

nextCursor i prevCursor oboje su stvarni: Prethodna i Sljedeća su kontrole koje osoba očekuje. Neprozirni su — poslužitelj u njih kodira vlastiti ključ sortiranja, a klijent koji ih raščlanjuje ili sastavlja pokvarit će se čim popis promijeni način sortiranja. total je prisutan uz većinu popisa, a izostavlja se tamo gdje brojanje nema smisla, primjerice kod rangiranih rezultata pretrage.

Ograničenja učestalosti

Ograničenja vrijede po fiksnom vremenskom prozoru i primjenjuju se prije nego što se uopće pročita tijelo zahtjeva. Odbijeni zahtjev je 429 s kodom RATE_LIMITED i zaglavljem Retry-After u sekundama.

Većina ograničenja vezana je uz IP adresu klijenta. Ona osjetljiva na vjerodajnice — prijava, obnavljanje, ponovna potvrda lozinkom i upisi MCP vjerodajnica — uz to vode brojač po računu i odbijaju kada bilo koji od njih pukne, pa jedna adresa koja isprobava mnoge račune i jedan račun napadnut s mnogo adresa jednako nailaze na zid. Prijava prema zadanim postavkama dopušta 10 pokušaja po IP adresi i 5 po adresi e-pošte u deset minuta; slanje poruke 60 u minuti; pisanje pod /api/agents 60 u minuti. Svaki prag može se podesiti za pojedinu instalaciju, pa ove vrijednosti shvatite kao isporučene zadane vrijednosti, a ne kao ugovor. Provjere stanja i tokovi u stvarnom vremenu izuzeti su.

Praćenje u stvarnom vremenu

Ponovno dohvaćanje (polling) popisa pogrešan je način za praćenje razgovora. Umjesto toga postoje tri streaming endpointa:

  • GET /api/events/stream — događaji koje šalje poslužitelj za sve što prijavljena osoba može vidjeti.
  • GET /api/threads/:threadId/stream — događaji koje šalje poslužitelj za jednu nit. Poštuje Last-Event-ID, pa ponovno povezivanje nastavlja, a ne kreće ispočetka.
  • GET /api/activity — WebSocket koji prenosi tok aktivnosti.

Preglednici ne mogu postavljati zaglavlja pri uspostavi WebSocket veze, pa WebSocket ruta pristupni token prihvaća i kao parametar upita ?token=. To je jedino mjesto na kojem token pripada u URL-u; sve ostale rute primaju ga u zaglavlju.

Obitelji resursa

Postoji znatno više od tisuću endpointa, što je daleko previše za popis. Grupirani su u obitelji, a kada znate prefiks obitelji, pojedinačne rute su predvidive: zbirka na prefiksu, zapis ispod nje i imenovane radnje kao POST podputanje.

ObiteljPrefiksŠto obuhvaća
Autentikacija/api/authPrijava, obnavljanje, odjava, trenutačni identitet i vlastite sesije osobe.
Kanali i niti/api/channels, /api/threadsModel razgovora: kanali sadrže niti, niti sadrže poruke, a odgovori se ugnježđuju pod korijensku poruku.
Agenti/api/agentsDefinicije agenata, njihova povezivanja s kanalima, razgovori, zadaci i status.
Projekti i ploče/api/projects, /api/tasks, /api/iterationsPraćenje rada: ploče i stupci unutar projekta, zadaci na njima i iteracije nad njima.
Baza znanja/api/knowledge-baseProstori verzioniranih stranica i datoteka, s komentarima, povratnim vezama i pretraživanjem.
E-pošta/api/mail, /api/mailbox-connectionsPovezani poštanski sandučići, sandučići agenata i nacrti koje je agent napisao da ih osoba odobri.
Odobrenja/api/approvalsRed radnji koje agent ne smije poduzeti samostalno i jedan poziv kojim se one rješavaju.
Revizija/api/audit-logRevizijski trag organizacije povezan hash lancem, s endpointom za provjeru. Vlasnici i administratori.
Troškovi i potrošnja/api/ledger, /api/billingObračun tokena i troškova, proračuni i trajanja izvođenja; zasebno krediti i dopune. Vlasnici i administratori.
Organizacija i timovi/api/organizations, /api/teams, /api/usersAdministracija organizacije, timova i članstava, uključujući pozivnice.
Okidači/api/triggersRasporedi i webhookovi koji pokreću rad agenata, s poviješću okidanja.
Pozivi i glas/api/calls, /api/voicePozivi unutar kanala te sesija glasovnog asistenta, transkript i tokeni uređaja.
Executori/api/executors, /api/execution-environment-templatesRačunala na kojima agenti rade, njihova okruženja i kanal za povezivanje daemona.
Izvođenja/api/runsAktivna izvođenja agenata: otkaži, nastavi, ponovno pokreni, veži za executor.
Učitavanja/api/uploads, /api/attachmentsNajprije učitajte datoteku, zatim povežite vraćeni ID s porukom.
Pretraga/api/messages/searchPretraga poruka, za cijelu organizaciju ili ograničena na jedan kanal.
Tajne i postavke/api/secrets, /api/settings/scopedPohrana vjerodajnica samo za upis s izričitim dopuštenjima i slojevite konfiguracijske vrijednosti.
Povezani alati/api/mcpMCP poslužitelji koje ova instanca poziva. Pogledajte MCP i povezani alati.
Stanje sustava/api/healthŽivost i spremnost, obje javne. /api/health/ready također provjerava bazu podataka.

Prava se provode po ruti, a ne po obitelji. Općenito: revizijski dnevnik, knjiga troškova, pravila i upravljačka ravnina za zaključivanje (inference) su površine za vlasnike i administratore organizacije, dok su razgovori, projekti, baza znanja i odobrenja otvoreni svakom članu i sužavaju se onim što taj član zaista može vidjeti.

Primjeri

Prijavite se i sačuvajte kolačić za obnavljanje

Na instanci u načinu local, s lozinkom. Pristupni token vraća se u tijelu odgovora; token za obnavljanje završava u spremniku kolačića.

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

Kada token istekne, zamijenite kolačić novim:

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

Odgovor ima isti oblik { token, me }, a spremnik kolačića sada sadrži rotirani token za obnavljanje.

Popišite kanale koje vidite

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

Dodajte ?teamId=… da suzite na jedan tim ili ?includeArchived=true da uključite arhivirane kanale. defaultThreadId je nit na koju sljedeće objavljujete.

Pošaljite poruku

Poruka treba sadržavati barem nešto od content ili barem jedan učitani privitak. Pošaljite Idempotency-Key i ponovljeni zahtjev razriješit će se u poruku koju je stvorio prvi pokušaj, umjesto da objavi drugi primjerak.

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

Nova poruka je 201:

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

Ponavljanje istog ključa idempotentnosti odgovara s 200 i izvornom porukom te praznim pendingAgentInvites, jer je prvi pokušaj već obavio obavijesti i slanje koje to polje opisuje.

Dva odbijanja na ovoj ruti vrijedi obraditi izričito. Sadržaj koji premašuje ograničenje duljine poruke dobiva 413 MESSAGE_TOO_LARGE, a ispravan odgovor je tekst učitati kao datoteku. Sadržaj koji strukturno izgleda kao vjerodajnica dobiva 422 SECRET_INTERCEPTED — poslužitelj odbija poruku prije nego što se pohrani, pošalje ili zabilježi, a vjerodajnica pripada u /api/secrets.