API
L’API HTTP que hi ha darrere de cada client de Nessie i com t’hi autentiques.
Què és l’API
Nessie té una sola API HTTP. El client web d’administració, els clients de macOS i iOS i el dimoni de l’executor remot són clients ordinaris seus, i res del que poden fer no és accessible d’una altra manera. És una API JSON sobre un servidor Fastify, no és GraphQL, i no té cap subconjunt «públic» a part: la superfície que crides des dels teus propis scripts és la mateixa que utilitza el producte.
No hi ha cap segment de versió al camí. Totes les rutes són sota /api, tret de l’endpoint MCP d’entrada POST /mcp i d’uns quants documents de descobriment /.well-known/. La compatibilitat es gestiona afegint només camps nous, de manera que un client que ignora el que no reconeix continua funcionant.
URL base
Nessie s’allotja en un servidor propi, de manera que la URL base és l’origen on el teu desplegament serveix l’API: l’operador la defineix com a NESSIE_API_PUBLIC_URL, i és el mateix valor que l’API utilitza quan ha de generar una URL absoluta pròpia. A la instància que executem nosaltres, és https://api.nessie.works; una instància de desenvolupament iniciada amb pnpm dev respon a http://localhost:5454.
L’aplicació web d’administració és un origen diferent del de l’API, de manera que els clients del navegador estan subjectes a una llista permesa de CORS (NESSIE_CORS_ORIGINS). Les sol·licituds d’un script o d’un servidor no tenen cap origen que comprovar i no hi estan afectades.
Autenticació
Cada sol·licitud fora d’un petit conjunt de rutes deliberadament públiques porta un token d’accés a la capçalera estàndard:
Authorization: Bearer <token>El token és un JWT signat que emet l’API. És de vida curta —trenta minuts per defecte (NESSIE_AUTH_TOKEN_TTL)— i és l’única credencial que presenta un client ordinari. Nessie no té claus d’API personals de llarga durada. Si vols que un script cridi l’API, inicia la sessió de la mateixa manera que ho fa el navegador d’una persona i renova el token a mesura que avança.
Obtenir un token
POST /api/auth/session crea una sessió. Respon amb el token d’accés i la identitat de qui fa la crida, i defineix el token de renovació com una cookie HTTP-only independent.
La manera de demostrar qui ets depèn de com s’hagi desplegat la instància. Una instància que s’executa en mode local accepta un correu electrònic i una contrasenya. En una instància hosted o selfHosted, l’inici de sessió amb contrasenya es rebutja directament amb 403 PASSWORD_AUTH_DISABLED, i l’única manera d’entrar és el proveïdor d’identitat configurat; així, el compte no pot tenir mai un segon camí d’identitat, més feble, al costat.
Inici de sessió amb un proveïdor d’identitat
L’inici de sessió únic (SSO) és un intercanvi de codi d’autorització OAuth amb PKCE, en tres passos. El proveïdor d’identitat propi de Nessie és UnlikeOtherAI, que també és el propietari de l’estructura d’organitzacions i equips que reflecteix una instància; l’intercanvi següent té la mateixa forma per a qualsevol proveïdor configurat.
GET /api/auth/providersenumera els proveïdors que accepta aquesta instància, cadascun amb unproviderId, una etiqueta i si la pàgina d’inici de sessió ha de redirigir-hi automàticament.GET /api/auth/providers/:providerId/authorize, ambcodeChallenge,redirectUriistatecom a paràmetres de consulta, respon amb{ data: { authorizeUrl } }. Envia la persona allà.- El proveïdor torna la persona a la teva
redirectUriamb un codi. Envia’l de nou com aPOST /api/auth/sessionamb{ providerId, code, codeVerifier, redirectUri }: els quatre camps són obligatoris junts, i un conjunt incomplet és un400 EXTERNAL_AUTH_INCOMPLETE.
Renovar una sessió
El token de renovació mai no és al cos d’una resposta. Es defineix com una cookie HTTP-only anomenada nessie_refresh, amb abast a la ruta /api/auth i una durada de trenta dies per defecte (NESSIE_AUTH_REFRESH_TOKEN_TTL). Si crides POST /api/auth/refresh amb aquesta cookie, la consumeix, emet un token d’accés nou i defineix una cookie nova al seu lloc. Els tokens canvien en cada renovació, de manera que un token robat només és vàlid fins que el client real es renova.
Un navegador amb la sessió iniciada en diversos comptes guarda una cookie d’aquest tipus per a cada inici de sessió, anomenada nessie_refresh_<slot>, i indica a quina es refereix una sol·licitud amb la capçalera x-nessie-account-slot. Un client que no envia aquesta capçalera es refereix a nessie_refresh, que és tot el que necessita un client de línia d’ordres.
Un client de línia d’ordres necessita, doncs, un magatzem de cookies. Amb curl, això vol dir --cookie-jar a la crida d’inici de sessió i --cookie a la renovació, tal com es veu a l’exemple pràctic de més avall.
Què es comprova a cada sol·licitud
L’autenticació no és només una comprovació de signatura. Abans que s’executi un controlador, l’API verifica la signatura i la caducitat del token, confirma que la sessió no s’ha revocat (tant per la versió de token de tot l’usuari com per l’identificador individual de la sessió) i carrega la pertinença de qui fa la crida a l’organització que indica el token. Una pertinença desactivada és un 403. Quan l’organització es basa en un proveïdor d’identitat extern, la situació actual de qui fa la crida es torna a comprovar amb aquest proveïdor en la sol·licitud, en lloc de llegir-la del token; així, un accés retirat en origen deixa de funcionar a l’instant i no només en la següent caducitat.
Credencials que no són tokens de sessió
Existeixen dues altres credencials de tipus bearer, i cap no és una clau d’ús general. Una credencial de dispositiu de veu només s’accepta a les rutes de trucades de veu, i una credencial d’accés d’agent només s’accepta a l’endpoint MCP. Presentar-ne qualsevol en un altre lloc es rebutja amb un 403 que n’indica el nom, i no es reintenta com una sessió més feble. Consulta MCP i eines connectades per saber com s’emet la credencial de l’agent.
L’envoltori de resposta
Cada resposta correcta és un objecte amb una clau data. No hi ha cap matriu sola ni cap càrrega útil a l’arrel, de manera que un client mai no ha de distingir per la forma:
{
"data": { "id": "3f1c…", "label": "engineering" }
}Els endpoints de llista afegeixen un objecte meta al costat. No s’hi afegeix res més al nivell superior.
Les fallades també fan servir una sola forma, sigui quina sigui la causa:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "A message needs content or at least one attachment",
"field": "body",
"details": { "formErrors": [], "fieldErrors": {} }
}
}code és la part llegible per una màquina i és la que has d’utilitzar per decidir; message és per a una persona. field i details hi són quan la fallada és prou concreta per localitzar-la. Un error de servidor no gestionat és sempre 500 INTERNAL_ERROR amb un missatge fix: el text de l’error original es registra, però mai no es retorna, perquè pot haver capturat dades de la sol·licitud pel camí.
Codis d’error que trobaràs primer
| Estat | Codi | Significat |
|---|---|---|
| 400 | VALIDATION_ERROR | El cos o la consulta no coincideixen amb l’esquema. |
| 401 | AUTH_REQUIRED | Falta el token bearer, o la capçalera està malformada. |
| 401 | TOKEN_EXPIRED | El token d’accés ha caducat. Renova’l i torna-ho a provar. |
| 401 | TOKEN_REVOKED | La sessió s’ha tancat, o s’han expulsat totes les sessions. |
| 403 | ACCOUNT_DEACTIVATED | La pertinença a aquesta organització s’ha desactivat. |
| 404 | NOT_FOUND | No existeix la ruta, o no hi ha cap registre visible per a qui fa la crida. |
| 429 | RATE_LIMITED | S’ha esgotat un límit. Llegeix Retry-After. |
| 500 | INTERNAL_ERROR | Un error de servidor no gestionat, deliberadament opac. |
Paginació
Els endpoints de llista es paginen amb un cursor de claus opac, no amb un desplaçament: les files s’insereixen mentre algú les llegeix, i amb un desplaçament la segona pàgina repeteix sense avisar un registre que ja mostrava la primera.
S’accepten tres paràmetres de consulta: cursor, limit i direction (forward o backward). La pàgina per defecte té 25 elements i el màxim és 100; un valor més gran es limita en lloc de rebutjar-se. Els endpoints que també ordenen o filtren hi afegeixen sort, order i un q de text lliure.
El meta de la pàgina indica on ets:
{
"data": [ … ],
"meta": {
"hasMore": true,
"nextCursor": "2026-09-12T08:31:44.005Z|8b2f…",
"prevCursor": null,
"total": 134
}
}nextCursor i prevCursor són tots dos reals: Anterior i Següent són controls que una persona espera. Són opacs —el servidor hi codifica la seva pròpia clau d’ordenació—, i un client que n’analitza o en construeix un es trencarà en el moment en què una llista canviï la seva ordenació. total hi és a la majoria de llistes i s’omet on comptar no té sentit, com en els resultats de cerca classificats.
Límits de peticions
Els límits són per finestra fixa i s’apliquen abans que es llegeixi el cos de la sol·licitud. Una sol·licitud rebutjada és un 429 amb el codi RATE_LIMITED i una capçalera Retry-After en segons.
La majoria de límits es calculen per l’adreça IP del client; els sensibles a les credencials —l’inici de sessió, la renovació, la reverificació de la contrasenya i les escriptures de credencials MCP— mantenen també un comptador per compte i rebutgen si qualsevol dels dos s’activa, de manera que tant una adreça que prova molts comptes com un compte atacat des de moltes adreces topen amb un mur. L’inici de sessió té per defecte 10 intents per IP i 5 per adreça de correu electrònic cada deu minuts; publicar un missatge, 60 per minut; i les escriptures sota /api/agents, 60 per minut. Cada llindar es pot configurar per desplegament, de manera que has de tractar aquests valors com a predeterminats del producte i no com un contracte. Les comprovacions de salut i els fluxos en temps real estan exempts.
Temps real
Consultar una llista de manera repetida és la manera equivocada de seguir una conversa. En canvi, hi ha tres endpoints de flux:
GET /api/events/stream— esdeveniments enviats pel servidor de tot el que la persona que ha iniciat la sessió pot veure.GET /api/threads/:threadId/stream— esdeveniments enviats pel servidor per a un sol fil. RespectaLast-Event-ID, de manera que en reconnectar es reprèn en lloc de tornar a començar.GET /api/activity— un WebSocket que transporta el canal d’activitat.
Els navegadors no poden definir capçaleres en el handshake d’un WebSocket, de manera que la ruta WebSocket també accepta el token d’accés com a paràmetre de consulta ?token=. És l’únic lloc on un token pertany a una URL; totes les altres rutes el reben a la capçalera.
Les famílies de recursos
Hi ha molt més de mil endpoints, massa per enumerar-los. S’agrupen en famílies, i un cop saps el prefix d’una família, les rutes individuals són previsibles: una col·lecció sota el prefix, un registre a sota i les accions amb nom com a subrutes POST.
| Família | Prefix | Què inclou |
|---|---|---|
| Autenticació | /api/auth | Inici de sessió, renovació, tancament de sessió, la identitat actual i les sessions pròpies d’una persona. |
| Canals i fils | /api/channels, /api/threads | El model de converses: els canals contenen fils, els fils contenen missatges, i les respostes s’agrupen sota un missatge arrel. |
| Agents | /api/agents | Definicions d’agents, les seves vinculacions de canal, les converses, les tasques pendents i l’estat. |
| Projectes i taulers | /api/projects, /api/tasks, /api/iterations | Seguiment del treball: taulers i columnes sota un projecte, tasques a sobre i iteracions sobre aquestes. |
| Coneixement | /api/knowledge-base | Espais de pàgines i fitxers versionats, amb comentaris, retroenllaços i cerca. |
| Correu | /api/mail, /api/mailbox-connections | Bústies connectades, bústies d’agents i esborranys que un agent ha escrit perquè una persona els aprovi. |
| Aprovacions | /api/approvals | La cua d’accions que un agent no pot fer per iniciativa pròpia, i la única crida que les resol. |
| Auditoria | /api/audit-log | El registre d’auditoria de l’organització, amb encadenament de hashes i un endpoint de verificació. Propietaris i administradors. |
| Cost i ús | /api/ledger, /api/billing | Comptabilitat de tokens i costos, pressupostos i temps d’execució; per separat, crèdits i recàrregues. Propietaris i administradors. |
| Organització i equips | /api/organizations, /api/teams, /api/users | Administració de l’organització, dels equips i de les pertinences, incloses les invitacions. |
| Disparadors | /api/triggers | Programacions i webhooks que inicien el treball dels agents, amb el seu historial d’activacions. |
| Trucades i veu | /api/calls, /api/voice | Les trucades dins d’un canal, i la sessió, la transcripció i els tokens de dispositiu de l’assistent de veu. |
| Executors | /api/executors, /api/execution-environment-templates | Les màquines on treballen els agents, els seus entorns i el canal d’emparellament del dimoni. |
| Execucions | /api/runs | Execucions d’agents en curs: cancel·lar, continuar, reiniciar, vincular a un executor. |
| Pujades | /api/uploads, /api/attachments | Puja primer un fitxer i després enllaça l’identificador retornat a un missatge. |
| Cerca | /api/messages/search | Cerca de missatges, a tota l’organització o limitada a un canal. |
| Secrets i configuració | /api/secrets, /api/settings/scoped | Emmagatzematge de credencials només d’escriptura amb concessions explícites, i valors de configuració en capes. |
| Eines connectades | /api/mcp | Servidors MCP als quals aquesta instància es connecta. Consulta MCP i eines connectades. |
| Salut | /api/health | Vitalitat i preparació, totes dues públiques. /api/health/ready també comprova la base de dades. |
Els drets s’apliquen per ruta, no per família. En termes generals: el registre d’auditoria, el registre de costos, les regles de política i el pla de control d’inferència són superfícies per als propietaris i administradors de l’organització, mentre que les converses, els projectes, el coneixement i les aprovacions estan oberts a qualsevol membre i es limiten a allò que aquell membre pot veure realment.
Exemples pràctics
Iniciar sessió i guardar la cookie de renovació
En una instància en mode local, amb contrasenya. El token d’accés torna al cos; el token de renovació arriba al magatzem de cookies.
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
}
}
}
}Quan caduqui el token, intercanvia la cookie per una de nova:
curl -sS -X POST https://api.example.com/api/auth/refresh \
--cookie nessie.cookies --cookie-jar nessie.cookiesLa resposta té la mateixa forma { token, me }, i el magatzem de cookies ara conté el token de renovació rotat.
Llista els canals que pots veure
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
}
]
}Afegeix ?teamId=… per limitar-ho a un equip, o ?includeArchived=true per incloure els canals arxivats. El defaultThreadId és on publiques el següent missatge.
Publicar un missatge
Un missatge necessita algun content o almenys un fitxer adjunt pujat. Envia una Idempotency-Key i una sol·licitud reintentada es resol amb el missatge que va crear el primer intent, en lloc de publicar una còpia més.
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 missatge nou és un 201:
{
"data": {
"message": {
"id": "d70e…",
"threadId": "c41a…",
"role": "user",
"content": "Deploying the migration now.",
"createdAt": "2026-09-16T09:04:11.882Z"
},
"pendingAgentInvites": []
}
}Una repetició amb la mateixa clau d’idempotència respon amb un 200, amb el missatge original i un pendingAgentInvites buit, perquè el primer intent ja va fer les alertes i l’enviament que descriu la matriu.
Hi ha dos rebutjos en aquesta ruta que val la pena gestionar explícitament. El contingut que supera el límit de longitud del xat és un 413 MESSAGE_TOO_LARGE, i la resposta adequada és pujar el text com a fitxer. El contingut que estructuralment sembla una credencial és un 422 SECRET_INTERCEPTED: el servidor el rebutja abans que es guardi, s’enviï o es registri, i la credencial pertany a /api/secrets.