nessie

API

API-ja HTTP që qëndron pas çdo klienti të Nessie dhe si autentikoheni me të.

Çfarë është API-ja

Nessie ka një API të vetme HTTP. Aplikacioni web i administratorit, klientët për macOS dhe iOS dhe daemon-i i ekzekutuesit në distancë janë klientë të zakonshëm të saj, dhe asgjë që ata mund të bëjnë nuk arrihet në ndonjë mënyrë tjetër. Është një API JSON mbi një server Fastify, nuk është GraphQL, dhe nuk ka një nëngrup të veçantë “publik”: sipërfaqja që thërrasin skriptet tuaja është sipërfaqja që thërret vetë produkti.

Nuk ka segment versioni në shteg. Çdo rrugë gjendet nën /api, përveç endpoint-it hyrës MCP POST /mcp dhe disa dokumenteve zbulimi /.well-known/. Përputhshmëria ruhet duke shtuar vetëm fusha të reja, kështu që një klient që injoron atë që nuk e njeh vazhdon të funksionojë.

URL baze

Nessie është i vetë-hostuar, kështu që URL baza është çdo origjinë ku shërben API-n vendosja juaj; operatori e vendos si NESSIE_API_PUBLIC_URL, dhe e njëjta vlerë është ajo që API përdor kur duhet të krijojë një URL absolute të vetën. Në instancën që ne e drejtojmë, kjo është https://api.nessie.works; një instancë zhvillimi e nisur me pnpm dev përgjigjet te http://localhost:5454.

Aplikacioni web i administratorit është një origjinë tjetër nga API, kështu që klientët në shfletues i nënshtrohen një liste të lejuar CORS (NESSIE_CORS_ORIGINS). Kërkesat nga një skript ose server nuk kanë origjinë për t’u kontrolluar dhe nuk preken.

Autentikimi

Çdo kërkesë jashtë një grupi të vogël rrugësh me qëllim publik mbart një token aksesi në header-in standard:

Authorization: Bearer <token>

Token-i është një JWT i nënshkruar, i krijuar nga API. Ka jetë të shkurtër — tridhjetë minuta si parazgjedhje (NESSIE_AUTH_TOKEN_TTL) — dhe është e vetmja kredencial që paraqet një thirrës i zakonshëm. Nessie nuk ka çelësa personalë API me jetë të gjatë. Nëse dëshironi që një skript të thërrasë API-n, ai hyn në të njëjtën mënyrë si shfletuesi i një personi dhe e rinovon token-in sipas nevojës.

Marrja e një token-i

POST /api/auth/session krijon një sesion. Përgjigjet me token-in e aksesit dhe identitetin e thirrësit, dhe vendos token-in e rinovimit si një cookie të veçantë HTTP-only.

Mënyra si provoni kush jeni varet nga mënyra si është vendosur instanca. Një instancë që funksionon në modalitetin local pranon një email dhe fjalëkalim. Te një instancë hosted ose selfHosted, hyrja me fjalëkalim refuzohet plotësisht me 403 PASSWORD_AUTH_DISABLED, dhe e vetmja rrugë hyrjeje është ofruesi i konfiguruar i identitetit — kështu që llogaria nuk mund të krijojë kurrë një rrugë të dytë, më të dobët identiteti pranë tij.

Hyrja përmes një ofruesi identiteti

Hyrja e vetme (SSO) është një shkëmbim kodi autorizimi OAuth me PKCE, në tre hapa. Ofruesi i identitetit të Nessie është UnlikeOtherAI, i cili zotëron gjithashtu strukturën e organizatës dhe të ekipeve që pasqyron një instancë; shkëmbimi më poshtë ka të njëjtën formë për çdo ofrues të konfiguruar.

  1. GET /api/auth/providers liston çfarë pranon kjo instancë, secili me një providerId, një etiketë dhe nëse faqja e hyrjes duhet ta drejtojë automatikisht.
  2. GET /api/auth/providers/:providerId/authorize, me codeChallenge, redirectUri dhe state si parametra query, përgjigjet me { data: { authorizeUrl } }. Dërgojeni personin atje.
  3. Ofruesi e kthen në redirectUri tuaj me një kod. Dërgojeni prapa si POST /api/auth/session me { providerId, code, codeVerifier, redirectUri } — të katër janë të detyrueshme së bashku, dhe një grup i paplotë është 400 EXTERNAL_AUTH_INCOMPLETE.

Rinovimi i një sesioni

Token-i i rinovimit nuk gjendet kurrë në trupin e përgjigjes. Vendoset si një cookie HTTP-only me emrin nessie_refresh, e kufizuar te shtegu /api/auth, me jetë tridhjetëditore si parazgjedhje (NESSIE_AUTH_REFRESH_TOKEN_TTL). Thirrja e POST /api/auth/refresh me atë cookie e konsumon, nxjerr një token aksesi të ri dhe vendos një cookie të re në vend të së vjetrës — token-at rrotullohen, kështu që një token i vjedhur është i përdorshëm vetëm deri në rifreskimin e radhës të klientit të vërtetë.

Një shfletues që ka hyrë në disa llogari mban një cookie të tillë për çdo hyrje, të emëruar nessie_refresh_<slot>, dhe tregon për cilën është kërkesa me header-in x-nessie-account-slot. Një klient që nuk dërgon një header të tillë kërkon për nessie_refresh, që është gjithçka që i nevojitet një klienti me rresht komandash.

Një klient me rresht komandash ka, pra, nevojë për një kavanoz cookie-sh (cookie jar). Me curl kjo është --cookie-jar te thirrja e hyrjes dhe --cookie te rifreskimi, si në shembullin e punuar më poshtë.

Çfarë kontrollohet në çdo kërkesë

Autentikimi nuk është vetëm një kontroll nënshkrimi. Përpara se të ekzekutohet një handler, API verifikon nënshkrimin dhe skadimin e token-it, konfirmon që sesioni nuk është shfuqizuar (si me versionin e token-it në nivel përdoruesi, ashtu edhe me ID-në individuale të sesionit), dhe ngarkon anëtarësimin e thirrësit në organizatën që emëron token-i. Një anëtarësim i çaktivizuar është 403. Kur organizata mbështetet nga një ofrues i jashtëm identiteti, gjendja aktuale e thirrësit rikontrollohet te ai ofrues në vetë kërkesën, jo lexohet nga token-i, kështu që aksesi i hequr andej ndalon menjëherë, jo në skadimin e radhës.

Kredenciale që nuk janë token-a sesioni

Ekzistojnë dy kredenciale të tjera bearer, dhe asnjëra nuk është çelës i përgjithshëm. Një kredencial i pajisjes së zërit pranohet vetëm te rrugët e thirrjeve me zë; një kredencial aksesi agjenti pranohet vetëm te adresa hyrëse MCP. Paraqitja e njërës prej tyre gjetkë refuzohet me një 403 që e emëron kredencialin, dhe nuk riprovohet si një sesion më i dobët — shihni MCP dhe mjetet e lidhura për mënyrën se si lëshohet kredenciali i agjentit.

Mbështjella e përgjigjes

Çdo përgjigje e suksesshme është një objekt me një çelës data. Nuk ka një varg të thjeshtë dhe as një ngarkesë në nivelin e lartë në rrënjë, kështu që një klient nuk duhet kurrë të dallojë sipas formës:

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

Rrugët e listave shtojnë një objekt meta pranë tij. Asgjë tjetër nuk shtohet kurrë në nivelin e lartë.

Dështimet përdorin edhe ato një formë të vetme, pavarësisht nga çfarë i shkaktoi:

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

code është pjesa e lexueshme nga makina dhe është ajo sipas së cilës duhet të veproni; message është për një person. field dhe details janë të pranishme kur dështimi është mjaft specifik për t’u gjetur. Një gabim i patrajtuar i serverit është gjithmonë 500 INTERNAL_ERROR me një mesazh fiks — teksti themelor i gabimit regjistrohet, por nuk kthehet kurrë, sepse mund të ketë marrë të dhëna kërkese gjatë rrugës.

Kodet e gabimeve që do të hasni së pari

StatusiKodiKuptimi
400VALIDATION_ERRORTrupi ose query-ja nuk përputhen me skemën.
401AUTH_REQUIREDNuk ka bearer token, ose header-i ishte i gabuar.
401TOKEN_EXPIREDToken-i i aksesit ka kaluar skadimin. Rifreskoni dhe provoni përsëri.
401TOKEN_REVOKEDSesioni u mbyll me dalje nga llogaria, ose të gjitha sesionet u detyruan të mbyllen.
403ACCOUNT_DEACTIVATEDAnëtarësimi në këtë organizatë është çaktivizuar.
404NOT_FOUNDNuk ka një rrugë të tillë, ose nuk ka një regjistrim të tillë të dukshëm për thirrësin.
429RATE_LIMITEDNjë kovë është e plotë. Lexoni Retry-After.
500INTERNAL_ERRORNjë gabim i patrajtuar i serverit, qëllimisht pa detaje.

Faqëzimi

Listat e adresave faqëzohen me një kursor keyset të pazbërthyeshëm, jo me offset — rreshtat futen ndërkohë që dikush lexon, dhe me një offset faqja e dytë përsëritet pa u njoftuar me një regjistrim që faqja e parë e tregoi tashmë.

Pranohen tre parametra query: cursor, limit dhe direction (forward ose backward). Faqja e parazgjedhur ka 25 rreshta dhe kufiri është 100; një vlerë më e madhe kufizohet, nuk refuzohet. Adresat që renditin ose filtrojnë shtojnë edhe sort, order dhe një q tekst të lirë.

meta e faqes ju tregon ku jeni:

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

nextCursor dhe prevCursor janë të dyja reale: “E mëparshmja” dhe “Tjetra” janë të dy kontrolle që pret çdo person. Ato janë të pazbërthyeshme — serveri kodon brenda tyre çelësin e vet të renditjes, dhe një klient që i analizon ose i ndërton do të prishet sapo një listë të ndryshojë renditjen. total është e pranishme në shumicën e listave dhe mungon aty ku numërimi nuk ka kuptim, si te rezultatet e renditura të kërkimit.

Kufijtë e kërkesave

Kufijtë janë për dritare fikse dhe zbatohen përpara se të lexohet trupi i kërkesës. Një kërkesë e refuzuar është 429 me kodin RATE_LIMITED dhe një header Retry-After në sekonda.

Shumica e kovave lidhen me IP-në e klientit; ato të ndjeshme ndaj kredencialeve — hyrja, rifreskimi, rikonfirmimi i fjalëkalimit, shkrimet e kredencialeve MCP — mbajnë edhe një numërues për llogari dhe refuzojnë kur cilido prej tyre kalon kufirin, kështu që një adresë që provon shumë llogari dhe një llogari e sulmuar nga shumë adresa ndeshen me një mur. Hyrja ka si parazgjedhje 10 përpjekje për IP dhe 5 për adresë emaili çdo dhjetë minuta; postimi i një mesazhi ka si parazgjedhje 60 në minutë; shkrimet nën /api/agents 60 në minutë. Çdo prag është i konfigurueshëm për çdo vendosje, prandaj i merrni këto si vlerat e dërguara, jo si kontratë. Kontrollet e shëndetit dhe rrjedhat në kohë reale janë të përjashtuara.

Në kohë reale

Të pyesësh vazhdimisht një listë (polling) është mënyra e gabuar për të ndjekur një bisedë. Ekzistojnë në vend tre adresa transmetimi:

  • GET /api/events/stream — ngjarjet e dërguara nga serveri (server-sent events) për gjithçka që mund ta shohë personi që ka hyrë.
  • GET /api/threads/:threadId/stream — server-sent events për një fill të vetëm. Respekton Last-Event-ID, kështu që një rilidhje vazhdon, nuk fillon nga e para.
  • GET /api/activity — një WebSocket që mbart feed-in e aktivitetit.

Shfletuesit nuk mund të vendosin header-a në dorëzimin e WebSocket-it, kështu që rruga WebSocket pranon edhe token-in e aksesit si parametër query ?token=. Ky është i vetmi vend ku një token i përket një URL-je; çdo rrugë tjetër e merr në header.

Familjet e burimeve

Ka mbi një mijë adresa, shumë më tepër sesa mund të listohen. Ato grupohen në familje, dhe kur e dini prefiksin e një familjeje, rrugët individuale janë të parashikueshme — një koleksion te prefiksi, një regjistrim nën të, dhe veprimet e emëruara si nënshtigje POST.

FamiljaPrefiksiÇfarë mbulon
Autentikimi/api/authHyrja, rinovimi, dalja, identiteti aktual, sesionet e vetë personit.
Kanalet dhe fijet/api/channels, /api/threadsModeli i bisedës: kanalet mbajnë fijet, fijet mbajnë mesazhet, përgjigjet futen nën një rrënjë.
Agjentët/api/agentsPërkufizimet e agjentëve, lidhjet e kanaleve të tyre, bisedat, detyrat dhe statusi.
Projektet dhe tabelat/api/projects, /api/tasks, /api/iterationsNdjekja e punës: tabelat dhe kolonat nën një projekt, detyrat mbi to, iteracionet mbi ato.
Njohuria/api/knowledge-baseHapësira faqesh dhe skedarësh të versionuar, me komente, lidhje prapa dhe kërkim.
Posta/api/mail, /api/mailbox-connectionsKutitë postare të lidhura, kutitë postare të agjentëve, dhe skicat që një agjent shkroi për t’u miratuar nga një person.
Miratimet/api/approvalsRadha e veprimeve që një agjent nuk mund t’i kryejë vetvetiu, dhe thirrja e vetme që i zgjidh ato.
Auditimi/api/audit-logGjurma e auditimit e organizatës, e lidhur me hash, me një adresë verifikimi. Pronarët dhe administratorët.
Kostoja dhe përdorimi/api/ledger, /api/billingKontabiliteti i token-ave dhe i kostove, buxhetet dhe kohët e ekzekutimit; veçmas, kreditet dhe mbushjet. Pronarët dhe administratorët.
Organizata dhe ekipet/api/organizations, /api/teams, /api/usersAdministrimi i qiramarrësit, i ekipit dhe i anëtarësimit, përfshirë ftesat.
Trigerat/api/triggersOrare dhe webhook-e që nisin punën e agjentëve, me historikun e tyre të ekzekutimit.
Thirrjet dhe zëri/api/calls, /api/voiceThirrjet brenda kanalit, dhe sesioni i asistentit zanor, transkriptimi dhe token-at e pajisjes.
Ekzekutuesit/api/executors, /api/execution-environment-templatesKompjuterët ku punojnë agjentët, mjediset e tyre, dhe kanali i lidhjes së daemon-it.
Ekzekutimet/api/runsEkzekutimet e drejtpërdrejta të agjentëve: anulim, vazhdim, rinisje, lidhje me një ekzekutues.
Ngarkimet/api/uploads, /api/attachmentsNgarkoni një skedar së pari, pastaj lidhni ID-në e kthyer me një mesazh.
Kërkimi/api/messages/searchKërkimi i mesazheve, në gjithë organizatën ose i kufizuar në një kanal.
Sekretet dhe cilësimet/api/secrets, /api/settings/scopedRuajtje kredencialesh vetëm për shkrim, me leje të qarta, dhe vlera konfigurimi të shtresuara.
Mjetet e lidhura/api/mcpServerat MCP që kjo instancë thërret jashtë. Shihni MCP dhe mjetet e lidhura.
Shëndeti/api/healthGjallësia dhe gatishmëria, të dyja publike. /api/health/ready kontrollon edhe bazën e të dhënave.

Të drejtat zbatohen për çdo rrugë, jo për çdo familje. Në përgjithësi: regjistri i auditimit, libri i kostove, rregullat e politikave dhe kontrolli i inferencës janë sipërfaqe për pronarët dhe administratorët e organizatës, ndërsa bisedat, projektet, njohuria dhe miratimet janë të hapura për çdo anëtar dhe ngushtohen sipas asaj që ai anëtar mund të shohë realisht.

Shembuj të punuar

Hyrja dhe mbajtja e cookie-t të rinovimit

Në një instancë me modalitetin local, me fjalëkalim. Token-i i aksesit kthehet në trup; token-i i rinovimit zbret në kavanozin e cookie-ve.

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

Kur token-i skadon, shkëmbeni cookie-n për një të ri:

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

Përgjigjja është e njëjta formë { token, me }, dhe kavanozi i cookie-ve tani mban token-in e rinovimit të rrotulluar.

Listoni kanalet që mund t’i shihni

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

Shtoni ?teamId=… për ta ngushtuar në një ekip, ose ?includeArchived=true për të përfshirë kanalet e arkivuara. defaultThreadId është ai ku postoni tjetrën.

Postoni një mesazh

Një mesazh ka nevojë ose për pak content, ose për të paktën një bashkëngjitje të ngarkuar. Dërgoni një Idempotency-Key dhe një kërkesë e riprovuar zgjidhet në mesazhin që krijoi tentativa e parë, në vend që të postojë një kopje të dytë.

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

Një mesazh i ri është 201:

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

Një riprovim me të njëjtin çelës idempotence përgjigjet me 200 dhe mesazhin origjinal me një pendingAgentInvites bosh, sepse tentativa e parë e ka bërë tashmë njoftimin dhe dërgimin që përshkruan vargu.

Dy refuzime te kjo rrugë meritojnë trajtim të qartë. Përmbajtja mbi kufirin e gjatësisë së bisedës është 413 MESSAGE_TOO_LARGE, dhe përgjigja e duhur është të ngarkoni tekstin si skedar. Përmbajtja që duket strukturisht si kredencial është 422 SECRET_INTERCEPTED — serveri refuzon përpara se mesazhi të ruhet, dërgohet ose regjistrohet, dhe kredenciali i përket /api/secrets.