API
HTTP-API-et bak hver Nessie-klient, og hvordan du autentiserer deg mot det.
Hva API-et er
Nessie har ett HTTP-API. Admin-webappen, macOS- og iOS-klientene og den eksterne executor-demonen er alle vanlige klienter av det, og det de kan gjøre, kan ikke nås på noen annen måte. Det er et JSON-API over en Fastify-server. Det er ikke GraphQL, og det finnes ingen egen «offentlig» delmengde: flaten egne skript kaller, er den produktet kaller.
Stien har ikke noe versjonssegment. Alle ruter ligger under /api, bortsett fra det innkommende MCP-endepunktet POST /mcp og en håndfull /.well-known/-dokumenter for oppdagelse. Kompatibilitet ivaretas ved bare å legge til felt, slik at en klient som ignorerer det den ikke gjenkjenner, fortsetter å fungere.
Basis-URL
Nessie kjøres på egen infrastruktur, så basis-URL-en er den opprinnelsen driftsmiljøet ditt serverer API-et fra. Driftsansvarlig setter den som NESSIE_API_PUBLIC_URL, og den samme verdien er det API-et bruker når det må lage en egen absolutt URL. På instansen vi kjører, er det https://api.nessie.works. En utviklingsinstans startet med pnpm dev svarer på http://localhost:5454.
Admin-webappen har en annen opprinnelse enn API-et, så nettleserklienter er underlagt en CORS-tillatelsesliste (NESSIE_CORS_ORIGINS). Forespørsler fra et skript eller en server har ingen opprinnelse å sjekke, og påvirkes ikke.
Autentisering
Alle forespørsler utenfor et lite sett med bevisst offentlige ruter bærer et tilgangstoken i standard HTTP-header:
Authorization: Bearer <token>Tokenet er en signert JWT utstedt av API-et. Det er kortlevd: tretti minutter som standard (NESSIE_AUTH_TOKEN_TTL). Det er den eneste påloggingsopplysningen en vanlig kaller noen gang presenterer. Nessie har ingen langlivede personlige API-nøkler. Hvis du vil at et skript skal kalle API-et, logger det inn på samme måte som nettleseren til en person og fornyer tokenet underveis.
Få et token
POST /api/auth/session oppretter en økt. Svaret inneholder tilgangstokenet og identiteten til kalleren, og fornyelsestokenet settes som en egen HTTP-only-informasjonskapsel.
Hvordan du beviser hvem du er, avhenger av hvordan installasjonen er satt opp. En installasjon som kjører i local-modus, godtar e-post og passord. På en hosted- eller selfHosted-installasjon blir innlogging med passord avvist rett ut med 403 PASSWORD_AUTH_DISABLED, og eneste vei inn er den konfigurerte identitetsleverandøren. Da kan kontoen aldri få en annen, svakere identitetsvei ved siden av den.
Logge inn via en identitetsleverandør
Single sign-on er en OAuth-utveksling med autorisasjonskode og PKCE, i tre steg. Nessies egen identitetsleverandør er UnlikeOtherAI, som også eier organisasjons- og teamstrukturen en installasjon speiler. Utvekslingen under har samme form for alle konfigurerte leverandører.
GET /api/auth/providerslister hva denne installasjonen godtar, med enproviderId, en etikett og om innloggingssiden skal sende brukeren dit automatisk.GET /api/auth/providers/:providerId/authorizemedcodeChallenge,redirectUriogstatesom query-parametere, svarer med{ data: { authorizeUrl } }. Send personen dit.- Leverandøren sender personen tilbake til
redirectUrimed en kode. Send den tilbake somPOST /api/auth/sessionmed{ providerId, code, codeVerifier, redirectUri }. Alle fire feltene må være med samtidig, og et ufullstendig sett gir400 EXTERNAL_AUTH_INCOMPLETE.
Fornye en økt
Fornyelsestokenet står aldri i et svar. Det settes som en HTTP-only-informasjonskapsel med navnet nessie_refresh, begrenset til stien /api/auth, med en levetid på tretti dager som standard (NESSIE_AUTH_REFRESH_TOKEN_TTL). Et kall til POST /api/auth/refresh med den informasjonskapselen bruker den opp, utsteder et nytt tilgangstoken og setter en ny informasjonskapsel i stedet. Tokenene roteres, så et stjålet token gjelder bare til den ekte klienten fornyer neste gang.
En nettleser som er logget inn på flere kontoer, har én slik informasjonskapsel per innlogging, med navnet nessie_refresh_<slot>, og oppgir hvilken en forespørsel gjelder, med headeren x-nessie-account-slot. En klient som ikke sender en slik header, gjelder nessie_refresh, og det er alt en kommandolinjeklient trenger.
En kommandolinjeklient trenger derfor en informasjonskapselfil (cookie jar). Med curl er det --cookie-jar på innloggingskallet og --cookie ved fornyelsen, som i eksemplet nedenfor.
Hva som kontrolleres på hver forespørsel
Autentisering er ikke bare en signatursjekk. Før en behandler kjører, verifiserer API-et tokenets signatur og utløpstid, bekrefter at økten ikke er tilbakekalt (både etter tokenversjonen for hele brukeren og etter den enkelte økt-ID-en), og laster kallerens medlemskap i organisasjonen tokenet oppgir. Et deaktivert medlemskap gir en 403. Når organisasjonen har en ekstern identitetsleverandør, sjekkes kallerens nåværende status hos den leverandøren på forespørselen, i stedet for å leses fra tokenet. Tilgang som er fjernet hos leverandøren, slutter dermed å virke med en gang, ikke først ved neste utløp.
Påloggingsopplysninger som ikke er øktokener
Det finnes to andre bearer-påloggingsopplysninger, og ingen av dem er en nøkkel til generell bruk. En stemmeenhets-påloggingsopplysning godtas bare på stemmesamtalerutene. En agenttilgangs-påloggingsopplysning godtas bare på MCP-endepunktet. Å presentere en av dem andre steder avvises med en 403 som nevner påloggingsopplysningen, og den prøves ikke på nytt som en svakere økt. Se MCP og tilkoblede verktøy for hvordan agentpåloggingsopplysningen utstedes.
Svarformatet
Hvert vellykket svar er et objekt med en data-nøkkel. Det finnes ingen ren liste og ingen nyttelast på øverste nivå, så en klient aldri trenger å skille på form:
{
"data": { "id": "3f1c…", "label": "engineering" }
}Listeendepunkter legger til et meta-objekt ved siden av. Ingen andre felt legges til på øverste nivå.
Feil bruker også ett format, uansett hva som forårsaket dem:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "A message needs content or at least one attachment",
"field": "body",
"details": { "formErrors": [], "fieldErrors": {} }
}
}code er den maskinlesbare delen, og det er den du skal forgrene på. message er for et menneske. field og details er med når feilen er konkret nok til å peke ut. En ubehandlet serverfeil er alltid 500 INTERNAL_ERROR med en fast melding. Den underliggende feilteksten logges, men returneres aldri, fordi den kan ha fanget opp forespørselsdata på vei ut.
Feilkoder du møter først
| Status | Kode | Betydning |
|---|---|---|
| 400 | VALIDATION_ERROR | Innholdet eller spørringen samsvarte ikke med skjemaet. |
| 401 | AUTH_REQUIRED | Mangler bearer-token, eller headeren er feilformatert. |
| 401 | TOKEN_EXPIRED | Tilgangstokenet har utløpt. Forny det og prøv på nytt. |
| 401 | TOKEN_REVOKED | Økten ble logget ut, eller alle økter ble tvunget ut. |
| 403 | ACCOUNT_DEACTIVATED | Medlemskapet i denne organisasjonen er deaktivert. |
| 404 | NOT_FOUND | Ingen slik rute, eller ingen slik post som er synlig for kalleren. |
| 429 | RATE_LIMITED | Grensen er nådd. Les Retry-After. |
| 500 | INTERNAL_ERROR | En ubehandlet serverfeil, bevisst uten detaljer. |
Paginering
Listeendepunkter pagineres med en ugjennomsiktig markør (cursor) basert på nøkkel, ikke med en forskyvning (offset). Rader settes inn mens noen leser, og med en forskyvning gjentar andre side stille en post som første side allerede viste.
Tre query-parametere godtas: cursor, limit og direction (forward eller backward). Standardsiden har 25 poster, og taket er 100. En større verdi begrenses i stedet for å avvises. Endepunkter som også sorterer eller filtrerer, legger til sort, order og et fritekstfelt q.
meta på siden forteller deg hvor du er:
{
"data": [ … ],
"meta": {
"hasMore": true,
"nextCursor": "2026-09-12T08:31:44.005Z|8b2f…",
"prevCursor": null,
"total": 134
}
}nextCursor og prevCursor er begge ekte: Forrige og Neste er to kontroller en person forventer. De er ugjennomsiktige. Serveren koder sin egen sorteringsnøkkel inn i dem, og en klient som tolker eller bygger en av dem, går i stykker i det øyeblikket en liste endrer rekkefølge. total finnes på de fleste lister, og utelates der telling ikke gir mening, for eksempel i rangerte søkeresultater.
Hastighetsbegrensninger
Grensene gjelder per fast tidsvindu og håndheves før forespørselens innhold i det hele tatt leses. En avvist forespørsel er 429 med koden RATE_LIMITED og en Retry-After-header i sekunder.
De fleste tellegrensene er knyttet til klientens IP-adresse. De som er følsomme for påloggingsopplysninger, nemlig innlogging, fornyelse, ny bekreftelse av passord og skriving av MCP-påloggingsopplysninger, har i tillegg en teller per konto, og avviser når begge utløses. Dermed møter både én adresse som sprer forsøk over mange kontoer, og én konto som angripes fra mange adresser, en vegg. Innlogging har som standard 10 forsøk per IP og 5 per e-postadresse per ti minutter. Å sende en melding har som standard 60 per minutt, og skriving under /api/agents har 60 per minutt. Alle terskler kan konfigureres per driftsmiljø, så regn disse som leveringsstandardene, ikke som en avtale. Helsesjekker og sanntidsstrømmene er unntatt.
Sanntid
Å spørre en liste om og om igjen er feil måte å følge en samtale på. Det finnes tre strømmeendepunkter i stedet:
GET /api/events/stream— server-sendte hendelser for alt den innloggede personen kan se.GET /api/threads/:threadId/stream— server-sendte hendelser for én tråd. Den respektererLast-Event-ID, så en ny tilkobling fortsetter i stedet for å starte på nytt.GET /api/activity— en WebSocket som bærer aktivitetsstrømmen.
Nettlesere kan ikke sette headere i et WebSocket-håndtrykk, så WebSocket-ruten godtar også tilgangstokenet som en ?token=-query-parameter. Det er det eneste stedet et token hører hjemme i en URL. Alle andre ruter tar det i headeren.
Ressursfamiliene
Det finnes godt over tusen endepunkter, altfor mange til å liste opp. De er gruppert i familier, og når du kjenner en familie sitt prefiks, er de enkelte rutene forutsigbare: en samling under prefikset, en post under den, og navngitte handlinger som POST-understier.
| Familie | Prefiks | Hva den dekker |
|---|---|---|
| Innlogging | /api/auth | Innlogging, fornyelse, utlogging, gjeldende identitet og en persons egne økter. |
| Kanaler og tråder | /api/channels, /api/threads | Samtalemodellen: kanaler har tråder, tråder har meldinger, og svar ligger under en rotmelding. |
| Agenter | /api/agents | Agentdefinisjoner, kanalkoblingene deres, samtaler, gjøremål og status. |
| Prosjekter og tavler | /api/projects, /api/tasks, /api/iterations | Oppgavesporing: tavler og kolonner under et prosjekt, oppgaver på dem, og iterasjoner over disse. |
| Kunnskap | /api/knowledge-base | Områder med versjonerte sider og filer, med kommentarer, tilbakelenker og søk. |
| E-post | /api/mail, /api/mailbox-connections | Tilkoblede postkasser, agentpostkasser og utkast en agent har skrevet for at en person skal godkjenne. |
| Godkjenninger | /api/approvals | Køen av handlinger en agent ikke kan utføre på egen hånd, og det ene kallet som avgjør dem. |
| Revisjon | /api/audit-log | Organisasjonens hash-kjedede revisjonslogg, med et endepunkt for verifisering. Eiere og administratorer. |
| Kostnad og bruk | /api/ledger, /api/billing | Regnskap for tokens og kostnader, budsjetter og kjøretider; i tillegg kreditt og påfyll. Eiere og administratorer. |
| Organisasjon og team | /api/organizations, /api/teams, /api/users | Administrasjon av leietaker, team og medlemskap, inkludert invitasjoner. |
| Utløsere | /api/triggers | Tidsplaner og webhooks som starter agentarbeid, med historikken over utløsningene. |
| Samtaler og stemme | /api/calls, /api/voice | Samtaler i kanalen, og stemmeassistentens økt, transkripsjon og enhetstokener. |
| Executorer | /api/executors, /api/execution-environment-templates | Maskinene agentene arbeider på, deres miljøer, og paringskanalen for demonen. |
| Kjøringer | /api/runs | Aktive agentkjøringer: avbryt, fortsett, start på nytt, knytt til en executor. |
| Opplastinger | /api/uploads, /api/attachments | Last opp en fil først, og knytt deretter den returnerte ID-en til en melding. |
| Søk | /api/messages/search | Søk i meldinger, for hele organisasjonen eller avgrenset til én kanal. |
| Hemmeligheter og innstillinger | /api/secrets, /api/settings/scoped | Lagring av påloggingsopplysninger som bare kan skrives til, med eksplisitte tilganger, og lagdelte konfigurasjonsverdier. |
| Tilkoblede verktøy | /api/mcp | MCP-servere denne installasjonen kaller ut til. Se MCP og tilkoblede verktøy. |
| Helsesjekk | /api/health | Aktivitets- og klarsjekk, begge offentlige. /api/health/ready sjekker også databasen. |
Rettigheter håndheves per rute, ikke per familie. I hovedtrekk: revisjonsloggen, kostnadsregnskapet, regelsettene og kontrollplanet for inferens er flater for organisasjonens eiere og administratorer, mens samtaler, prosjekter, kunnskap og godkjenninger er åpne for alle medlemmer og begrenses av det det enkelte medlemmet faktisk kan se.
Eksempler
Logg inn og behold fornyelsesinformasjonskapselen
På en installasjon i local-modus, med passord. Tilgangstokenet kommer tilbake i svaret, og fornyelsestokenet havner i informasjonskapselfilen.
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
}
}
}
}Når tokenet utløper, bytter du informasjonskapselen mot et nytt:
curl -sS -X POST https://api.example.com/api/auth/refresh \
--cookie nessie.cookies --cookie-jar nessie.cookiesSvaret har samme { token, me }-form, og informasjonskapselfilen inneholder nå det roterte fornyelsestokenet.
List kanalene du kan se
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
}
]
}Legg til ?teamId=… for å avgrense til ett team, eller ?includeArchived=true for å ta med arkiverte kanaler. defaultThreadId er tråden du poster til neste gang.
Send en melding
En melding trenger enten noe content eller minst ett opplastet vedlegg. Send en Idempotency-Key, så løser et nytt forsøk seg til meldingen det første forsøket opprettet, i stedet for å legge inn en ny kopi.
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."}'En ny melding er 201:
{
"data": {
"message": {
"id": "d70e…",
"threadId": "c41a…",
"role": "user",
"content": "Deploying the migration now.",
"createdAt": "2026-09-16T09:04:11.882Z"
},
"pendingAgentInvites": []
}
}Et nytt forsøk med samme Idempotency-Key svarer med 200, den opprinnelige meldingen og en tom pendingAgentInvites, fordi første forsøk allerede sto for varslingen og utsendelsen som matrisen beskriver.
To avvisninger på denne ruten er verdt å håndtere eksplisitt. Innhold som overskrider lengdegrensen i chatten, gir 413 MESSAGE_TOO_LARGE, og riktig svar er å laste opp teksten som en fil i stedet. Innhold som strukturelt ser ut som en påloggingsopplysning, gir 422 SECRET_INTERCEPTED. Serveren avviser meldingen før den lagres, sendes eller logges, og påloggingsopplysningen hører hjemme i /api/secrets.