nessie

API

HTTP API-то на кое се потпираат сите клиенти на Nessie и како да се автентицирате со него.

Што е API

Nessie има еден HTTP API. Админ веб-апликацијата, клиентите за macOS и iOS и далечинскиот демон на извршителот се обични клиенти на него, и ништо што тие можат да направат не е достапно на друг начин. Тоа е JSON API над Fastify сервер, не е GraphQL и нема посебно „јавно“ подмножество: површината што ја користат вашите сопствени скрипти е истата површина што ја користи производот.

Во патеката нема сегмент за верзија. Сите рути се под /api, освен влезната MCP крајна точка POST /mcp и неколку документи за откривање под /.well-known/. Компатибилноста се одржува со тоа што секогаш само се додаваат полиња, па клиент што ги игнорира непознатите полиња продолжува да работи.

Основна адреса

Nessie се хостира сам, па основната адреса е изворот (origin) на кој вашето распоредување го служи API-то — операторот ја поставува како NESSIE_API_PUBLIC_URL, а истата вредност ја користи API-то кога треба да изгради сопствена апсолутна адреса. На инстанцата што ја управуваме, таа е https://api.nessie.works; развојната инстанца стартувана со pnpm dev одговара на http://localhost:5454.

Админ веб-апликацијата е друг извор од API-то, па клиентите во прелистувачот се подложени на CORS листа на дозволени (NESSIE_CORS_ORIGINS). Барањата од скрипта или од сервер немаат извор што треба да се провери и не се засегнати.

Автентикација

Секое барање, освен мал број намерно јавни рути, носи токен за пристап во стандардното заглавие:

Authorization: Bearer <token>

Токенот е потпишан JWT што го издава API-то. Краткотраен е — стандардно триесет минути (NESSIE_AUTH_TOKEN_TTL) — и е единствената акредитација што обичниот повикувач некогаш ја прикажува. Nessie нема долготрајни персонални API клучеви. Ако сакате скрипта да го повикува API-то, таа се најавува на ист начин како прелистувачот на човекот и го обновува својот токен додека работи.

Добивање токен

POST /api/auth/session создава сесија. Одговара со токенот за пристап и идентитетот на повикувачот и го поставува токенот за обновување како посебно HTTP-only колаче.

Начинот на кој ќе докажете кој сте зависи од тоа како е распоредена инстанцата. Инстанца што работи во режимот local прифаќа е-пошта и лозинка. Кај инстанца hosted или selfHosted најавата со лозинка е директно одбиена со 403 PASSWORD_AUTH_DISABLED, а единствениот пат е конфигурираниот провајдер за идентитет — така сметката никогаш не може да добие втор, послаб пат за идентитет покрај него.

Најава преку провајдер за идентитет

Единствената најава (SSO) е размена на код за авторизација преку OAuth со PKCE, во три чекори. Сопствениот провајдер за идентитет на Nessie е UnlikeOtherAI, кој ја поседува и структурата на организацијата и тимовите што ја огледува инстанцата; размената подолу има иста форма за секој конфигуриран провајдер.

  1. GET /api/auth/providers ги наведува провајдерите што ги прифаќа оваа инстанца, секој со providerId, ознака и тоа дали страницата за најава треба автоматски да пренасочи кон него.
  2. GET /api/auth/providers/:providerId/authorize, со codeChallenge, redirectUri и state како параметри во адресата, одговара со { data: { authorizeUrl } }. Пренасочете го лицето таму.
  3. Провајдерот го враќа лицето на вашиот redirectUri со код. Испратете го тоа назад како POST /api/auth/session со { providerId, code, codeVerifier, redirectUri } — сите четири се задолжителни заедно, а нецелосен збир дава 400 EXTERNAL_AUTH_INCOMPLETE.

Обновување на сесија

Токенот за обновување никогаш не е во телото на одговорот. Се поставува како HTTP-only колаче со име nessie_refresh, ограничено на патеката /api/auth, со стандардна важност од триесет дена (NESSIE_AUTH_REFRESH_TOKEN_TTL). Повикот на POST /api/auth/refresh со тоа колаче го троши, издава нов токен за пристап и на негово место поставува ново колаче — токените се ротираат, па украден токен е валиден само до следното обновување на вистинскиот клиент.

Прелистувач најавен на повеќе сметки чува по едно такво колаче за секоја најава, со име nessie_refresh_<slot>, и со заглавието x-nessie-account-slot ја наведува оној на кого се однесува барањето. Клиент што не испраќа такво заглавие се однесува на nessie_refresh, што е сè што му треба на клиент од командна линија.

Затоа на клиентот од командна линија му треба складиште за колачиња. Со curl тоа е --cookie-jar при повикот за најава и --cookie при обновувањето, како во примерот подолу.

Што се проверува при секое барање

Автентикацијата не е само проверка на потпис. Пред да се изврши обработувачот, API-то го проверува потписот и рокот на токенот, потврдува дека сесијата не е поништена (и со верзијата на токенот за целиот корисник и со поединечниот ид на сесијата) и го вчитува членството на повикувачот во организацијата што ја наведува токенот. Деактивирано членство е 403. Кога организацијата се потпира на надворешен провајдер за идентитет, моменталниот статус на повикувачот се проверува повторно кај тој провајдер при барањето, а не се чита од токенот, па пристапот укинат однадвор престанува веднаш, а не при следното истекување.

Акредитиви што не се токени за сесија

Постојат уште два bearer акредитиви, а ниту еден не е клуч за општа намена. Акредитивот на гласовен уред се прифаќа само на рутите за гласовни повици; акредитивот за пристап на агент се прифаќа само на MCP крајната точка. Прикажување на кој било од нив на друго место се одбива со 403 што го именува акредитивот, а не се обидува повторно како послаба сесија — видете MCP и поврзани алатки за тоа како се издава акредитивот на агентот.

Формат на одговорот

Секој успешен одговор е објект со клуч data. Нема гола низа и нема податоци на највисокото ниво, па клиентот никогаш не мора да прави разлика според обликот:

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

Крајните точки со списоци додаваат објект meta покрај него. На највисокото ниво никогаш не се додава ништо друго.

И грешките користат еден облик, без оглед на тоа што ги предизвикало:

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

code е делот што го чита машината и според кој треба да се одлучува; message е за човекот. field и details се присутни кога грешката е доволно специфична за да се лоцира. Необработена серверска грешка е секогаш 500 INTERNAL_ERROR со фиксна порака — основниот текст на грешката се запишува во дневникот, никогаш не се враќа, бидејќи може да содржи податоци од барањето.

Кодови на грешки што прво ќе ги сретнете

СтатусКодЗначење
400VALIDATION_ERRORТелото или барањето не одговараат на шемата.
401AUTH_REQUIREDНема bearer токен, или заглавието е неисправно.
401TOKEN_EXPIREDТокенот за пристап е истечен. Обновете го и обидете се повторно.
401TOKEN_REVOKEDСесијата е одјавена, или сите сесии се принудно прекинати.
403ACCOUNT_DEACTIVATEDЧленството во оваа организација е деактивирано.
404NOT_FOUNDНема таква рута, или нема таков запис видлив за повикувачот.
429RATE_LIMITEDКофата е полна. Прочитајте Retry-After.
500INTERNAL_ERRORНеобработена серверска грешка, намерно нејасна.

Страничење

Крајните точки со списоци се страничат со непрозирен keyset курсор, а не со офсет — редови се вметнуваат додека некој чита, а со офсет втората страница тивко повторува запис што првата веќе го прикажала.

Прифаќаат три параметри: cursor, limit и direction (forward или backward). Стандардната страница е 25, а горната граница е 100; поголема вредност се сведува на границата, а не се одбива. Крајните точки што уште и подредуваат или филтрираат додаваат sort, order и слободно текстуално q.

meta на страницата ви кажува каде сте:

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

nextCursor и prevCursor се и двете вистински: Претходна и Следна се контроли што човекот ги очекува. Тие се непрозирни — серверот во нив го кодира сопствениот клуч за подредување, а клиент што ќе ги расчлени или составува ќе се скрши во моментот кога списокот ќе промени начин на подредување. total е присутен кај повеќето списоци и се изоставува таму каде бројењето нема смисла, како кај рангирани резултати од пребарување.

Ограничувања на бројот барања

Ограничувањата важат по фиксен прозорец и се применуваат пред дури и да се прочита телото на барањето. Одбиено барање е 429 со код RATE_LIMITED и заглавие Retry-After во секунди.

Повеќето кофи се врзани за IP адресата на клиентот; оние осетливи на акредитиви — најава, обновување, повторно докажување на лозинка, запишување MCP акредитиви — покрај тоа водат бројач по сметка и одбиваат кога било кој ќе се активира, па и една адреса што распрсне многу сметки и една сметка нападната од многу адреси наидуваат на ѕид. Најавата стандардно дозволува 10 обиди по IP и 5 по адреса на е-пошта во десет минути; објавувањето порака стандардно е 60 во минута; запишувањата под /api/agents се 60 во минута. Секој праг може да се конфигурира по распоредување, затоа овие вредности земете ги како испорачани стандардни вредности, а не како договор. Проверките на здравјето и стриминзите во живо се изземени.

Реално време

Повторното прашување на список не е добар начин да се следи разговор. Наместо тоа постојат три крајни точки за стриминг:

  • GET /api/events/stream — server-sent events за сè што најавеното лице може да види.
  • GET /api/threads/:threadId/stream — server-sent events за една нишка. Го почитува Last-Event-ID, па повторното поврзување продолжува, а не започнува од почеток.
  • GET /api/activity — WebSocket што ја носи листата на активности.

Прелистувачите не можат да поставуваат заглавија при WebSocket ракување, па WebSocket рутата го прифаќа токенот за пристап и како параметар ?token= во адресата. Тоа е единственото место каде токенот припаѓа во адреса; сите други рути го примаат во заглавието.

Семејства на ресурси

Има над илјада крајни точки, што е премногу за да се набројат. Групирани се во семејства, а штом го знаете префиксот на семејството, поединечните рути се предвидливи — колекција на префиксот, запис под неа и именувани дејства како подпатеки POST.

СемејствоПрефиксШто опфаќа
Автентикација/api/authНајава, обновување, одјава, тековниот идентитет, сопствените сесии на лицето.
Канали и нишки/api/channels, /api/threadsМоделот на разговор: каналите содржат нишки, нишките содржат пораки, одговорите се гнездат под корен.
Агенти/api/agentsДефиниции на агенти, нивните врски со канали, разговори, задачи и статус.
Проекти и табли/api/projects, /api/tasks, /api/iterationsСледење на работа: табли и колони под еден проект, задачи на нив и итерации над нив.
Знаење/api/knowledge-baseПростори со верзионирани страници и датотеки, со коментари, повратни врски и пребарување.
Пошта/api/mail, /api/mailbox-connectionsПоврзани поштенски сандачиња, поштенски сандачиња на агенти и нацрти што агент ги напишал за лице да ги одобри.
Одобренија/api/approvalsРедот на дејства што агент не смее да ги преземе самостојно и единствениот повик со кој се решаваат.
Ревизија/api/audit-logРевизискиот запис на организацијата, поврзан со хеш-синџир, со крајна точка за проверка. Сопственици и администратори.
Трошоци и користење/api/ledger, /api/billingЕвиденција на токени и трошоци, буџети и времиња на извршување; посебно, кредити и полнења. Сопственици и администратори.
Организација и тимови/api/organizations, /api/teams, /api/usersАдминистрација на тенантот, тимовите и членствата, вклучително и поканите.
Активатори/api/triggersРаспореди и webhook-и што ја започнуваат работата на агентите, со нивната историја на активирања.
Повици и глас/api/calls, /api/voiceПовици во каналот, и сесијата, транскриптот и токените на уредот на гласовниот асистент.
Извршители/api/executors, /api/execution-environment-templatesМашините на кои работат агентите, нивните средини и каналот за спарување на демонот.
Извршувања/api/runsАгентски извршувања во живо: откажување, продолжување, рестарт, врзување за извршител.
Качувања/api/uploads, /api/attachmentsПрво качете датотека, па врзете го вратениот ид со порака.
Пребарување/api/messages/searchПребарување на пораки, на ниво на организација или ограничено на еден канал.
Тајни и поставки/api/secrets, /api/settings/scopedСкладиште за акредитиви само за запишување со експлицитни дозволи, и слоевити конфигурациски вредности.
Поврзани алатки/api/mcpMCP сервери на кои оваа инстанца им се јавува. Видете MCP и поврзани алатки.
Здравје/api/healthЖивост и подготвеност, двете јавни. /api/health/ready ја проверува и базата на податоци.

Правата се применуваат по рута, а не по семејство. Во основа: ревизискиот дневник, книгата на трошоците, правилата на политиката и контролната рамнина на инференцијата се површини за сопствениците и администраторите на организацијата, додека разговорите, проектите, знаењето и одобренијата се отворени за секој член и се стеснуваат според тоа што тој член навистина може да го види.

Работни примери

Најавете се и зачувајте го колачето за обновување

На инстанца во режим local, со лозинка. Токенот за пристап се враќа во телото; токенот за обновување завршува во складиштето за колачиња.

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

Кога токенот ќе истече, разменете го колачето за нов:

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

Одговорот има ист облик { token, me }, а складиштето за колачиња сега го држи ротираниот токен за обновување.

Наведете ги каналите што ги гледате

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

Додајте ?teamId=… за да се стеснат на еден тим, или ?includeArchived=true за да се вклучат архивираните канали. defaultThreadId е оној каде следно ќе објавите.

Објавете порака

Пораката треба или некоја content, или барем еден качен прилог. Испратете Idempotency-Key, па повторено барање се решава во пораката што ја создал првиот обид, наместо да објави втора копија.

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

Нова порака е 201:

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

Повторување со истиот клуч за идемпотентност одговара со 200 со оригиналната порака и празно pendingAgentInvites, бидејќи првиот обид веќе ги направил известувањето и испраќањето што ги опишува низата.

Две одбивања на оваа рута заслужуваат експлицитна обработка. Содржина над ограничувањето на должината на разговорот е 413 MESSAGE_TOO_LARGE, а правилниот одговор е текстот да се качи како датотека. Содржина што структурно личи на акредитив е 422 SECRET_INTERCEPTED — серверот одбива пред пораката да се зачува, испрати или запише во дневник, а акредитивот припаѓа во /api/secrets.