nessie

API

HTTP-rajapinta, jonka kautta jokainen Nessie-asiakas toimii, ja ohje sen käyttöön kirjautumiseen.

Mitä API on

Nessiessä on yksi HTTP-rajapinta. Hallintasovellus, macOS- ja iOS-asiakkaat sekä etäsuorittimen daemon ovat kaikki tavallisia tämän rajapinnan asiakkaita, eikä niiden tekemiseen ole mitään muuta reittiä. Kyseessä on JSON-rajapinta, joka toimii Fastify-palvelimen päällä. Se ei ole GraphQL, eikä sillä ole erillistä ”julkista” osajoukkoa: rajapinta, jota omat skriptisi kutsuvat, on sama, jota tuote itse kutsuu.

Polussa ei ole versiosegmenttiä. Jokainen reitti on polun /api alla, paitsi sisääntuleva MCP-päätepiste POST /mcp ja muutama /.well-known/-löytöasiakirja. Yhteensopivuus hoidetaan lisäämällä vain kenttiä, joten asiakas, joka ohittaa tuntemattomat kentät, toimii edelleen.

Perus-URL

Nessie on itse isännöitävä, joten perus-URL on se origin, jossa käyttöönottosi palvelee rajapintaa. Ylläpitäjä asettaa sen muuttujaan NESSIE_API_PUBLIC_URL, ja sama arvo on se, jota API käyttää, kun sen pitää luoda oma absoluuttinen URL. Ylläpitämässämme instanssissa se on https://api.nessie.works. Komennolla pnpm dev käynnistetty kehitysinstanssi vastaa osoitteessa http://localhost:5454.

Hallintasovellus on eri origin kuin rajapinta, joten selainasiakkaisiin sovelletaan CORS-sallittujen listaa (NESSIE_CORS_ORIGINS). Skriptien tai palvelimien pyynnöillä ei ole originia, jota tarkistettaisiin, joten ne eivät kuulu tämän piiriin.

Todentaminen

Jokainen pyyntö, paitsi harvat tarkoituksella julkiset reitit, kantaa käyttöoikeustunnusta tavallisessa otsakkeessa:

Authorization: Bearer <token>

Tunnus on API:n allekirjoittama JWT. Se on lyhytikäinen: oletuksena kolmekymmentä minuuttia (NESSIE_AUTH_TOKEN_TTL). Se on ainoa tunniste, jonka tavallinen kutsuja koskaan esittää. Nessiessä ei ole pitkäikäisiä henkilökohtaisia API-avaimia. Jos haluat skriptin kutsuvan rajapintaa, se kirjautuu samalla tavalla kuin henkilön selain ja uusii tunnuksensa matkan varrella.

Tunnuksen hankkiminen

POST /api/auth/session luo istunnon. Se vastaa käyttöoikeustunnuksella ja kutsujan henkilöllisyydellä ja asettaa uusimistunnuksen erilliseen HTTP-only-evästeeseen.

Miten todistat henkilöllisyytesi, riippuu siitä, miten instanssi on otettu käyttöön. local-tilassa toimiva instanssi hyväksyy sähköpostiosoitteen ja salasanan. Instanssilla, joka on hosted- tai selfHosted-tilassa, salasanakirjautuminen evätään suoraan vastauksella 403 PASSWORD_AUTH_DISABLED, ja ainoa sisäänkäynti on määritetty identiteettitarjoaja. Tilillä ei siis voi olla toista, heikompaa tunnistautumistapaa sen rinnalla.

Kirjautuminen identiteettitarjoajan kautta

Yhden kirjautumisen (SSO) toteutus on OAuth-valtuutuskoodin vaihto PKCE:n kanssa, ja se tapahtuu kolmessa vaiheessa. Nessien oma identiteettitarjoaja on UnlikeOtherAI, joka omistaa myös organisaatio- ja tiimirakenteen, jota instanssi peilaa. Alla oleva vaihto on muodoltaan sama mille tahansa määritetylle tarjoajalle.

  1. GET /api/auth/providers listaa tämän instanssin hyväksymät tarjoajat. Jokaisella on providerId, nimi ja tieto siitä, pitääkö kirjautumissivun ohjata siihen automaattisesti.
  2. GET /api/auth/providers/:providerId/authorize, jolle annetaan kyselyparametreina codeChallenge, redirectUri ja state, vastaa muodossa { data: { authorizeUrl } }. Ohjaa henkilö sinne.
  3. Tarjoaja ohjaa henkilön takaisin redirectUri-osoitteeseesi koodin kanssa. Lähetä koodi takaisin pyynnöllä POST /api/auth/session, jonka runko on { providerId, code, codeVerifier, redirectUri }. Kaikki neljä kenttää vaaditaan yhdessä, ja puutteellinen joukko antaa vastauksen 400 EXTERNAL_AUTH_INCOMPLETE.

Istunnon uusiminen

Uusimistunnus ei ole koskaan vastauksen rungossa. Se asetetaan HTTP-only-evästeeseen nimeltä nessie_refresh, jonka polku on /api/auth ja elinikä oletuksena kolmekymmentä päivää (NESSIE_AUTH_REFRESH_TOKEN_TTL). Kun kutsut POST /api/auth/refresh tuon evästeen kanssa, eväste kulutetaan, käyttöön annetaan uusi käyttöoikeustunnus ja tilalle asetetaan uusi eväste. Tunnukset vaihtuvat jokaisella uusimisella, joten varastettu tunnus on käyttökelpoinen vain siihen asti, kunnes oikea asiakas uusii istunnon seuraavan kerran.

Selain, johon on kirjautunut useilla tileillä, säilyttää yhden tällaisen evästeen kullekin kirjautumiselle nimeltä nessie_refresh_<slot>, ja pyynnön kohde ilmoitetaan otsakkeella x-nessie-account-slot. Asiakas, joka ei lähetä tätä otsaketta, käsittelee evästettä nessie_refresh, mikä riittää komentoriviasiakkaalle.

Komentoriviasiakas tarvitsee siis evästetiedoston. curl-komennolla se tarkoittaa valitsinta --cookie-jar kirjautumispyynnössä ja valitsinta --cookie uusimispyynnössä, kuten alla olevassa esimerkissä.

Mitä tarkistetaan jokaisessa pyynnössä

Todennus ei ole pelkkä allekirjoituksen tarkistus. Ennen kuin käsittelijä suoritetaan, API tarkistaa tunnuksen allekirjoituksen ja voimassaoloajan, varmistaa, ettei istuntoa ole peruttu (sekä koko käyttäjän tunnusversion että yksittäisen istuntotunnuksen osalta), ja lataa kutsujan jäsenyyden organisaatiossa, jonka tunnus nimeää. Käytöstä poistettu jäsenyys tuottaa vastauksen 403. Kun organisaatio on sidottu ulkoiseen identiteettitarjoajaan, kutsujan nykyinen tila tarkistetaan kyseisen tarjoajan kanssa pyynnön yhteydessä eikä lueta tunnuksesta. Näin ylävirrassa poistettu käyttöoikeus lakkaa toimimasta heti eikä vasta seuraavan vanhenemisen yhteydessä.

Muut kuin istuntotunnukset

Muita bearer-tunnisteita on kaksi, eikä kumpikaan ole yleiskäyttöinen avain. Äänilaitteen tunnistetta hyväksytään vain äänipuheluiden reiteillä, ja agentin tunnistetta vain MCP-päätepisteessä. Jos jompaakumpaa esitetään muualla, pyyntö hylätään vastauksella 403, jossa mainitaan tunniste, eikä sitä yritetä uudelleen heikompana istuntona. Katso, miten agentin tunniste myönnetään, kohdasta MCP ja yhdistetyt työkalut.

Vastauksen rakenne

Jokainen onnistunut vastaus on olio, jossa on avain data. Juuressa ei ole paljasta taulukkoa eikä ylimmän tason hyötykuormaa, joten asiakkaan ei tarvitse koskaan erotella muotoja:

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

Listaus-päätepisteet lisäävät rinnalle olion meta. Ylimmälle tasolle ei lisätä mitään muuta.

Virheissä on myös yksi muoto, riippumatta siitä, mikä ne aiheutti:

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

code on koneluettava osa, ja sen perusteella kannattaa haarautua. message on tarkoitettu ihmiselle. field ja details ovat mukana, kun virhe on riittävän tarkka paikantamaan. Käsittelemätön palvelinvirhe on aina 500 INTERNAL_ERROR kiinteällä viestillä. Alkuperäinen virheteksti kirjataan lokiin, mutta sitä ei palauteta, koska se on voinut kerätä pyynnön dataa matkallaan.

Ensimmäiset vastaan tulevat virhekoodit

TilaKoodiMerkitys
400VALIDATION_ERRORRunko tai kysely ei vastannut skeemaa.
401AUTH_REQUIREDBearer-tunnusta ei ole, tai otsake on virheellinen.
401TOKEN_EXPIREDKäyttöoikeustunnus on vanhentunut. Uusi istunto ja yritä uudelleen.
401TOKEN_REVOKEDIstunto on suljettu tai kaikki istunnot on pakotettu sulkeutumaan.
403ACCOUNT_DEACTIVATEDTämän organisaation jäsenyys on poistettu käytöstä.
404NOT_FOUNDReittiä ei ole, tai kutsujalle näkyvää tietuetta ei ole.
429RATE_LIMITEDRaja on täynnä. Lue Retry-After.
500INTERNAL_ERRORKäsittelemätön palvelinvirhe, joka on tarkoituksella epämääräinen.

Sivutus

Listaus-päätepisteet sivutetaan läpinäkymättömällä avainkursorilla, ei offsetilla. Rivejä lisätään lukemisen aikana, ja offsetilla toinen sivu toistaisi hiljaa tietueen, jonka ensimmäinen sivu jo näytti.

Kolme kyselyparametria hyväksytään: cursor, limit ja direction (forward tai backward). Oletussivun koko on 25 ja enimmäismäärä 100. Suurempi arvo rajataan eikä hylätä. Päätepisteet, jotka myös järjestävät tai suodattavat, lisäävät parametrit sort, order ja vapaamuotoisen haun q.

Sivun meta kertoo, missä kohdassa olet:

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

nextCursor ja prevCursor ovat molemmat todellisia: Edellinen ja Seuraava ovat molemmat painikkeita, joita ihmiset odottavat. Kursorit ovat läpinäkymättömiä. Palvelin koodaa niihin oman lajittelöavaimensa, ja asiakas, joka jäsentää tai rakentaa niitä, hajoaa heti, kun listan järjestys muuttuu. total on mukana useimmissa listoissa. Se jätetään pois, kun laskeminen ei ole mielekästä, kuten järjestetyissä hakutuloksissa.

Pyyntörajat

Rajat ovat kiinteitä aikaikkunoita, ja ne otetaan käyttöön ennen kuin pyynnön runkoa edes luetaan. Hylätty pyyntö on 429 koodilla RATE_LIMITED ja Retry-After-otsakkeella sekunteina.

Useimmat rajat perustuvat asiakkaan IP-osoitteeseen. Tunnistautumiseen liittyvillä rajoilla, eli kirjautumisella, uusimisella, salasanan uudelleentodistuksella ja MCP-tunnisteiden kirjoituksella, on lisäksi tilikohtainen laskuri, ja pyyntö hylätään, kun kumpi tahansa raja täyttyy. Näin yksi osoite, joka yrittää monelle tilille, ja yksi tili, jota hyökätään monesta osoitteesta, törmäävät molemmat seinään. Kirjautuminen sallii oletuksena 10 yritystä IP-osoitetta kohden ja 5 yritystä sähköpostiosoitetta kohden kymmenessä minuutissa. Viestin lähettäminen sallii oletuksena 60 viestiä minuutissa, ja kirjoitukset polun /api/agents alla 60 minuutissa. Jokainen raja on määritettävissä käyttöönottokohtaisesti, joten pidä näitä oletusarvoina eikä sopimuksena. Terveystarkistukset ja reaaliaikaiset virrat ovat vapautettuja.

Reaaliaikaisuus

Listan kyseleminen toistuvasti ei ole oikea tapa seurata keskustelua. Sen sijaan on kolme suoratoistavaa päätepistettä:

  • GET /api/events/stream — palvelimen lähettämät tapahtumat kaikelle, minkä kirjautunut henkilö voi nähdä.
  • GET /api/threads/:threadId/stream — palvelimen lähettämät tapahtumat yhdelle ketjulle. Se kunnioittaa Last-Event-ID-otsaketta, joten uudelleenyhdistäminen jatkaa eikä aloita alusta.
  • GET /api/activity — WebSocket, joka kuljettaa aktiivisuussyötettä.

Selaimet eivät voi asettaa otsakkeita WebSocket-kättelyyn, joten WebSocket-reitti hyväksyy käyttöoikeustunnuksen myös kyselyparametrina ?token=. Se on ainoa paikka, jossa tunnus saa olla URL-osoitteessa. Kaikki muut reitit ottavat sen otsakkeessa.

Resurssiperheet

Päätepisteitä on selvästi yli tuhat, joten niitä ei ole järkevää luetella. Ne ryhmittyvät perheisiin, ja kun tiedät perheen etuliitteen, yksittäiset reitit ovat ennustettavia: kokoelma etuliitteessä, tietue sen alla ja nimetyt toiminnot POST-alipolkuina.

PerheEtuliiteMitä se kattaa
Todennus/api/authKirjautuminen, uusiminen, uloskirjautuminen, nykyinen identiteetti ja henkilön omat istunnot.
Kanavat ja ketjut/api/channels, /api/threadsKeskustelumalli: kanavat sisältävät ketjuja, ketjut sisältävät viestejä ja vastaukset sisäkkäin juuren alla.
Agentit/api/agentsAgenttien määritykset, niiden kanavasidonnat, keskustelut, tehtävälistat ja tila.
Projektit ja taulut/api/projects, /api/tasks, /api/iterationsTyönseuranta: taulut ja sarakkeet projektin alla, niillä olevat tehtävät ja niiden päällä olevat iteraatiot.
Tietämys/api/knowledge-baseVersioitujen sivujen ja tiedostojen tilat, kommentit, takalinkit ja haku.
Posti/api/mail, /api/mailbox-connectionsYhdistetyt postilaatikot, agenttien postilaatikot ja luonnokset, jotka agentti kirjoitti henkilön hyväksyttäväksi.
Hyväksynnät/api/approvalsJono toimista, joita agentti ei saa tehdä yksin, ja yksi kutsu, joka ratkaisee ne.
Tarkastusloki/api/audit-logOrganisaation hash-ketjutettu tarkastusloki vahvistusrajapintoineen. Omistajat ja ylläpitäjät.
Kustannukset ja käyttö/api/ledger, /api/billingToken- ja kustannuskirjanpito, budjetit ja ajoajat sekä erikseen krediitit ja täydennykset. Omistajat ja ylläpitäjät.
Organisaatio ja tiimit/api/organizations, /api/teams, /api/usersOrganisaation, tiimien ja jäsenyyksien hallinta, kutsut mukaan lukien.
Triggerit/api/triggersAikataulut ja webhookit, jotka käynnistävät agenttityötä, sekä niiden laukaisuhistoria.
Puhelut ja ääni/api/calls, /api/voiceKanavan sisäiset puhelut sekä äänenavustajan istunto, litterointi ja laitetunnisteet.
Suorittimet/api/executors, /api/execution-environment-templatesKoneet, joilla agentit työskentelevät, niiden ympäristöt ja daemonin parituskanava.
Ajot/api/runsKäynnissä olevat agenttiajot: peruutus, jatkaminen, uudelleenkäynnistys ja sitominen suorittimeen.
Lataukset/api/uploads, /api/attachmentsLataa tiedosto ensin ja liitä palautettu tunnus sitten viestiin.
Haku/api/messages/searchViestihaku, koko organisaatiossa tai yhteen kanavaan rajattuna.
Salaisuudet ja asetukset/api/secrets, /api/settings/scopedVain kirjoitettava salaisuuksien tallennus eksplisiittisillä oikeuksilla ja kerroksittaiset asetusarvot.
Yhdistetyt työkalut/api/mcpMCP-palvelimet, joihin tämä instanssi ottaa yhteyden. Katso MCP ja yhdistetyt työkalut.
Terveys/api/healthElinkelpoisuus ja valmius, molemmat julkisia. /api/health/ready tarkistaa myös tietokannan.

Oikeudet tarkistetaan reittikohtaisesti, ei perheittäin. Karkeasti sanottuna tarkastusloki, kustannusrekisteri, käytäntösäännöt ja päättelyn hallintapinta ovat organisaation omistajien ja ylläpitäjien pintoja. Keskustelut, projektit, tietämys ja hyväksynnät ovat taas avoinna kaikille jäsenille, ja ne rajautuvat sen mukaan, mitä kyseinen jäsen todella näkee.

Esimerkkejä

Kirjaudu sisään ja säilytä uusimisevästeet

local-tilassa olevassa instanssissa salasanalla. Käyttöoikeustunnus tulee vastauksen rungossa, ja uusimistunniste menee evästetiedostoon.

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

Kun tunnus vanhenee, vaihda eväste uuteen:

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

Vastaus on sama { token, me }-muoto, ja evästetiedostossa on nyt uusittu uusimistunniste.

Listaa kanavat, jotka näet

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

Lisää ?teamId=… rajataksesi yhteen tiimiin tai ?includeArchived=true sisällyttääksesi arkistoidut kanavat. defaultThreadId on se ketju, johon lähetät seuraavaksi.

Lähetä viesti

Viestissä on oltava joko content-sisältöä tai vähintään yksi ladattu liite. Lähetä Idempotency-Key, niin uudelleen lähetetty pyyntö ratkeaa samaan viestiin, jonka ensimmäinen yritys loi, eikä lähetä toista kopiota.

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

Uusi viesti on 201:

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

Saman idempotenssiavaimen toisto vastaa 200, alkuperäisellä viestillä ja tyhjällä pendingAgentInvites-taulukolla, koska ensimmäinen yritys on jo hoitanut taulukon kuvaamat ilmoitukset ja lähetyksen.

Kaksi tämän reitin virhevastausta kannattaa käsitellä erikseen. Chatin pituusrajan ylittävä sisältö on 413 MESSAGE_TOO_LARGE, ja oikea vastaus on ladata teksti tiedostona. Sisältö, joka rakenteellisesti näyttää tunnukselta, on 422 SECRET_INTERCEPTED. Palvelin hylkää sen ennen kuin viesti tallennetaan, välitetään tai kirjataan lokiin, ja tunnus kuuluu tallennettavaksi osoitteeseen /api/secrets.