API
API-ul HTTP din spatele fiecărui client Nessie și cum vă autentificați în fața lui.
Ce este API-ul
Nessie are un singur API HTTP. Aplicația web de administrare, clienții pentru macOS și iOS și demonul executorului la distanță sunt toți clienți obișnuiți ai acestuia, iar nimic din ce pot face ei nu este accesibil altfel. Este un API JSON peste un server Fastify, nu GraphQL, și nu are un subset separat „public”: suprafața pe care o apelează propriile dvs. scripturi este cea pe care o apelează produsul.
Calea nu conține niciun segment de versiune. Fiecare rută se află sub /api, cu excepția endpoint-ului MCP de intrare, POST /mcp, și a câtorva documente de descoperire din /.well-known/. Compatibilitatea se asigură prin adăugarea doar a câmpurilor noi, astfel încât un client care ignoră ce nu recunoaște continuă să funcționeze.
URL de bază
Nessie se găzduiește pe propriile servere, deci URL-ul de bază este originea pe care o servește instalarea dvs. pentru API. Operatorul o setează ca NESSIE_API_PUBLIC_URL, iar aceeași valoare este cea pe care o folosește API-ul când trebuie să construiască un URL absolut propriu. Pe instanța pe care o rulăm noi, aceasta este https://api.nessie.works; o instanță de dezvoltare pornită cu pnpm dev răspunde la http://localhost:5454.
Aplicația web de administrare are altă origine decât API-ul, așa că clienții din browser sunt supuși unei liste permise CORS (NESSIE_CORS_ORIGINS). Cererile dintr-un script sau de pe un server nu au o origine de verificat și nu sunt afectate.
Autentificarea
Fiecare cerere în afara unui set restrâns de rute deliberat publice poartă un token de acces în antetul standard:
Authorization: Bearer <token>Token-ul este un JWT semnat, emis de API. Are o durată scurtă — implicit treizeci de minute (NESSIE_AUTH_TOKEN_TTL) — și este singura credențială pe care o prezintă un apelant obișnuit. Nessie nu are chei personale API de lungă durată. Dacă vreți ca un script să apeleze API-ul, acesta se autentifică în același mod ca browserul unei persoane și își reînnoiește token-ul pe parcurs.
Obținerea unui token
POST /api/auth/session creează o sesiune. Răspunde cu token-ul de acces și cu identitatea apelantului și setează token-ul de reînnoire ca pe un cookie separat, HTTP-only.
Felul în care vă dovediți identitatea depinde de modul în care este instalată instanța. O instanță care rulează în modul local acceptă e-mail și parolă. Pe o instanță hosted sau selfHosted, autentificarea cu parolă este refuzată direct, cu 403 PASSWORD_AUTH_DISABLED, iar singura cale de intrare este furnizorul de identitate configurat — astfel încât un cont nu poate căpăta o a doua identitate, mai slabă, alături de aceasta.
Autentificarea prin furnizorul de identitate
Autentificarea unică este un schimb de cod de autorizare OAuth cu PKCE, în trei pași. Furnizorul de identitate al Nessie este UnlikeOtherAI, care deține și structura de organizații și echipe pe care o oglindește o instanță; schimbul de mai jos are aceeași formă pentru orice furnizor configurat.
GET /api/auth/providerslistează ce acceptă instanța, fiecare cu unproviderId, o etichetă și indicația dacă pagina de autentificare trebuie să redirecționeze automat spre el.GET /api/auth/providers/:providerId/authorize, cucodeChallenge,redirectUrișistateca parametri de interogare, răspunde cu{ data: { authorizeUrl } }. Trimiteți persoana acolo.- Furnizorul o redirecționează înapoi la
redirectUricu un cod. Trimiteți-l caPOST /api/auth/sessioncu{ providerId, code, codeVerifier, redirectUri }— toate patru sunt obligatorii împreună, iar un set incomplet produce400 EXTERNAL_AUTH_INCOMPLETE.
Reînnoirea unei sesiuni
Token-ul de reînnoire nu apare niciodată într-un corp de răspuns. Este setat ca un cookie HTTP-only numit nessie_refresh, limitat la calea /api/auth, cu o durată de treizeci de zile implicit (NESSIE_AUTH_REFRESH_TOKEN_TTL). Un apel la POST /api/auth/refresh cu acest cookie consumă cookie-ul, emite un token de acces nou și setează în locul lui un cookie nou — token-urile se rotesc, astfel încât unul furat este valabil doar până când clientul real se reînnoiește următoarea dată.
Un browser conectat la mai multe conturi păstrează câte un astfel de cookie pentru fiecare autentificare, numit nessie_refresh_<slot>, și indică prin antetul x-nessie-account-slot la care dintre ele se referă o cerere. Un client care nu trimite acest antet se referă la nessie_refresh, ceea ce este suficient pentru un client de linie de comandă.
Un client de linie de comandă are, așadar, nevoie de un fișier de cookie-uri (cookie jar). Cu curl, acesta este --cookie-jar la apelul de autentificare și --cookie la reînnoire, cum se vede în exemplul lucrat de mai jos.
Ce se verifică la fiecare cerere
Autentificarea nu înseamnă doar verificarea semnăturii. Înainte ca un handler să ruleze, API-ul verifică semnătura și expirarea token-ului, confirmă că sesiunea nu a fost revocată (atât prin versiunea de token la nivel de utilizator, cât și prin identificatorul individual al sesiunii) și încarcă apartenența apelantului la organizația numită de token. O apartenență dezactivată primește un 403. Când organizația este susținută de un furnizor de identitate extern, starea curentă a apelantului este reverificată la acel furnizor, la fiecare cerere, și nu este citită din token; astfel, accesul retras din amonte încetează imediat, nu la următoarea expirare.
Credențiale care nu sunt token-uri de sesiune
Mai există două alte credențiale de tip bearer, iar niciuna nu este o cheie cu scop general. O credențială de dispozitiv vocal este acceptată doar pe rutele de apel vocal; o credențială de acces pentru agenți este acceptată doar pe endpoint-ul MCP. Prezentarea oricăreia dintre ele în altă parte este refuzată cu un 403 care o numește, și nu este reîncercată ca o sesiune mai slabă — consultați MCP și instrumentele conectate pentru cum se emite credențiala agentului.
Anvelopa răspunsului
Fiecare răspuns reușit este un obiect cu o cheie data. Nu există niciun tablou simplu și nicio sarcină utilă la rădăcină, astfel încât un client nu trebuie să deosebească după formă:
{
"data": { "id": "3f1c…", "label": "engineering" }
}Endpoint-urile de listă adaugă alături un obiect meta. Nimic altceva nu se adaugă la nivelul superior.
Erorile au și ele o singură formă, indiferent de ce le-a produs:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "A message needs content or at least one attachment",
"field": "body",
"details": { "formErrors": [], "fieldErrors": {} }
}
}code este partea citită de mașină și pe ea ar trebui să se ramifice clientul; message este pentru o persoană. field și details apar când eroarea este suficient de specifică pentru a fi localizată. O eroare de server netratată este întotdeauna 500 INTERNAL_ERROR, cu un mesaj fix — textul erorii de bază este înregistrat în jurnal, dar niciodată returnat, pentru că ar fi putut prelua date din cerere pe drum.
Codurile de eroare pe care le veți întâlni primele
| Stare | Cod | Semnificație |
|---|---|---|
| 400 | VALIDATION_ERROR | Corpul sau interogarea nu corespund schemei. |
| 401 | AUTH_REQUIRED | Lipsește token-ul bearer sau antetul este malformat. |
| 401 | TOKEN_EXPIRED | Token-ul de acces a depășit data de expirare. Reînnoiți-l și reîncercați. |
| 401 | TOKEN_REVOKED | Sesiunea a fost închisă sau toate sesiunile au fost forțate să se închidă. |
| 403 | ACCOUNT_DEACTIVATED | Apartenența la această organizație a fost dezactivată. |
| 404 | NOT_FOUND | Nu există o astfel de rută sau nicio înregistrare vizibilă pentru acest apelant. |
| 429 | RATE_LIMITED | O cotă este epuizată. Citiți Retry-After. |
| 500 | INTERNAL_ERROR | O eroare de server netratată, intenționat opacă. |
Paginare
Endpoint-urile de listă paginează printr-un cursor keyset opac, nu printr-un offset — rândurile se inserează în timp ce cineva citește, iar cu un offset a doua pagină repetă în tăcere o înregistrare pe care prima pagină o arătase deja.
Sunt acceptați trei parametri de interogare: cursor, limit și direction (forward sau backward). Pagina implicită are 25 de elemente, iar plafonul este 100; o valoare mai mare este limitată, nu respinsă. Endpoint-urile care mai și sortează sau filtrează adaugă sort, order și un text liber q.
meta al paginii vă spune unde vă aflați:
{
"data": [ … ],
"meta": {
"hasMore": true,
"nextCursor": "2026-09-12T08:31:44.005Z|8b2f…",
"prevCursor": null,
"total": 134
}
}nextCursor și prevCursor sunt ambele reale: Anterior și Următor sunt ambele comenzi pe care o persoană le așteaptă. Sunt opace — serverul își codifică în ele propria cheie de sortare, iar un client care le analizează sau le construiește se va strica în clipa în care o listă își schimbă ordinea. total este prezent pe majoritatea listelor și omis acolo unde numărarea nu are sens, cum sunt rezultatele de căutare ordonate după relevanță.
Limite de rată
Limitele sunt pe ferestre fixe și sunt aplicate înainte ca corpul cererii să fie citit. O cerere respinsă primește 429 cu codul RATE_LIMITED și un antet Retry-After în secunde.
Majoritatea limitelor se bazează pe adresa IP a clientului; cele sensibile la credențiale — autentificarea, reînnoirea, reconfirmarea parolei, scrierile de credențiale MCP — păstrează și un contor pe cont și resping cererea când se atinge oricare dintre ele, astfel încât atât o adresă care încearcă multe conturi, cât și un cont atacat de pe multe adrese dau de un perete. Autentificarea are implicit 10 încercări pe IP și 5 pe adresă de e-mail la zece minute; trimiterea unui mesaj are implicit 60 pe minut; scrierile sub /api/agents au 60 pe minut. Fiecare prag este configurabil pe instalare, așa că tratați aceste valori ca valori implicite livrate, nu ca un contract. Verificările de sănătate și fluxurile în timp real sunt exceptate.
Timp real
Interogarea periodică a unei liste este modul greșit de a urmări o conversație. În schimb există trei endpoint-uri de streaming:
GET /api/events/stream— evenimente trimise de server pentru tot ce poate vedea persoana autentificată.GET /api/threads/:threadId/stream— evenimente trimise de server pentru un singur fir. RespectăLast-Event-ID, astfel încât o reconectare reia, nu repornește.GET /api/activity— un WebSocket care transportă fluxul de activitate.
Browserele nu pot seta antete la handshake-ul WebSocket, așa că ruta WebSocket acceptă și token-ul de acces ca parametru de interogare ?token=. Acesta este singurul loc în care un token are ce căuta într-un URL; orice altă rută îl primește în antet.
Familiile de resurse
Există mult peste o mie de endpoint-uri, mult prea multe ca să fie enumerate. Ele se grupează în familii, iar odată ce cunoașteți prefixul unei familii, rutele individuale sunt previzibile — o colecție la prefix, o înregistrare sub ea și acțiuni numite, ca sub-căi POST.
| Familia | Prefixul | Ce acoperă |
|---|---|---|
| Autentificare | /api/auth | Autentificarea, reînnoirea, deconectarea, identitatea curentă, sesiunile proprii ale unei persoane. |
| Canale și fire | /api/channels, /api/threads | Modelul conversațional: canalele conțin fire, firele conțin mesaje, iar răspunsurile se cuibăresc sub un mesaj rădăcină. |
| Agenți | /api/agents | Definițiile agenților, legăturile lor cu canalele, conversațiile, sarcinile și starea. |
| Proiecte și panouri | /api/projects, /api/tasks, /api/iterations | Urmărirea lucrului: panouri și coloane sub un proiect, sarcini pe ele, iterații peste acestea. |
| Cunoștințe | /api/knowledge-base | Spații de pagini și fișiere versionate, cu comentarii, legături inverse și căutare. |
/api/mail, /api/mailbox-connections | Căsuțele de e-mail conectate, căsuțele de e-mail ale agenților și ciornele pe care un agent le-a scris pentru ca o persoană să le aprobe. | |
| Aprobări | /api/approvals | Coada acțiunilor pe care un agent nu le poate face singur și apelul unic care le rezolvă. |
| Audit | /api/audit-log | Jurnalul de audit al organizației, cu lanț de hash-uri, și un endpoint de verificare. Proprietari și administratori. |
| Costuri și utilizare | /api/ledger, /api/billing | Contorizarea token-urilor și a costurilor, bugetele și duratele rulărilor; separat, creditele și reîncărcările. Proprietari și administratori. |
| Organizație și echipe | /api/organizations, /api/teams, /api/users | Administrarea organizației, a echipelor și a apartenențelor, inclusiv invitațiile. |
| Declanșatoare | /api/triggers | Programări și webhook-uri care pornesc munca agenților, cu istoricul declanșărilor. |
| Apeluri și voce | /api/calls, /api/voice | Apelurile în canal și sesiunea, transcrierea și token-urile de dispozitiv ale asistentului vocal. |
| Executori | /api/executors, /api/execution-environment-templates | Mașinile pe care lucrează agenții, mediile lor și canalul de asociere al demonului. |
| Rulări | /api/runs | Rulările agenților în desfășurare: anulare, continuare, repornire, legare de un executor. |
| Încărcări | /api/uploads, /api/attachments | Încărcați mai întâi un fișier, apoi legați identificatorul returnat de un mesaj. |
| Căutare | /api/messages/search | Căutarea în mesaje, la nivelul întregii organizații sau limitată la un singur canal. |
| Secrete și setări | /api/secrets, /api/settings/scoped | Stocarea credențialelor doar pentru scriere, cu permisiuni explicite, și valori de configurare pe niveluri. |
| Instrumente conectate | /api/mcp | Serverele MCP la care apelează această instanță. Consultați MCP și instrumentele conectate. |
| Sănătate | /api/health | Verificarea vitalității și a disponibilității, ambele publice. /api/health/ready verifică și baza de date. |
Drepturile se aplică pe rută, nu pe familie. În linii mari: jurnalul de audit, registrul de costuri, regulile de politică și planul de control al inferenței sunt suprafețe pentru proprietarii și administratorii organizației, în timp ce conversațiile, proiectele, cunoștințele și aprobările sunt deschise oricărui membru și restrânse la ceea ce poate vedea efectiv acel membru.
Exemple lucrate
Autentificare și păstrarea cookie-ului de reînnoire
Pe o instanță în modul local, cu parolă. Token-ul de acces vine în corp; token-ul de reînnoire ajunge în fișierul de cookie-uri.
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
}
}
}
}Când token-ul expiră, schimbați cookie-ul pe unul nou:
curl -sS -X POST https://api.example.com/api/auth/refresh \
--cookie nessie.cookies --cookie-jar nessie.cookiesRăspunsul are aceeași formă { token, me }, iar fișierul de cookie-uri conține acum token-ul de reînnoire rotit.
Enumerarea canalelor pe care le puteți vedea
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
}
]
}Adăugați ?teamId=… ca să restrângeți la o singură echipă sau ?includeArchived=true ca să includeți canalele arhivate. defaultThreadId este firul la care postați următorul mesaj.
Postarea unui mesaj
Un mesaj are nevoie fie de un content, fie de cel puțin un atașament încărcat. Trimiteți un Idempotency-Key, iar o cerere reîncercată se rezolvă în mesajul creat de prima încercare, fără să posteze o a doua copie.
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."}'Un mesaj nou primește 201:
{
"data": {
"message": {
"id": "d70e…",
"threadId": "c41a…",
"role": "user",
"content": "Deploying the migration now.",
"createdAt": "2026-09-16T09:04:11.882Z"
},
"pendingAgentInvites": []
}
}O reluare cu aceeași cheie de idempotență răspunde cu 200, cu mesajul original și cu lista goală pendingAgentInvites, pentru că prima încercare a făcut deja alertarea și expedierea pe care le descrie lista.
Două refuzuri ale acestei rute merită tratate explicit. Un conținut care depășește limita de lungime a chatului primește 413 MESSAGE_TOO_LARGE, iar răspunsul corect este să încărcați textul ca fișier. Un conținut care arată structural ca o credențială primește 422 SECRET_INTERCEPTED — serverul refuză înainte ca mesajul să fie stocat, expediat sau înregistrat, iar credențiala aparține la /api/secrets.