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.

Административното уеб приложение е на различен origin от API-то, затова браузър клиентите подлежат на CORS списък с разрешени (NESSIE_CORS_ORIGINS). Заявките от скрипт или сървър нямат origin, който да се проверява, и не са засегнати.

Удостоверяване

Всяка заявка извън малък набор от умишлено публични маршрути носи токен за достъп в стандартния header:

Authorization: Bearer <token>

Токенът е подписан JWT, издаден от API-то. Той е краткотраен — по подразбиране тридесет минути (NESSIE_AUTH_TOKEN_TTL) — и е единственото удостоверение, което обикновеният извикващ някога представя. Nessie няма дълготрайни лични API ключове. Ако искате скрипт да извиква API-то, той влиза по същия начин, по който влиза браузърът на човек, и подновява токена си, докато работи.

Получаване на токен

POST /api/auth/session създава сесия. Отговаря с токена за достъп и самоличността на извикващия и задава токена за подновяване като отделна бисквитка само за HTTP.

Как доказвате самоличността си зависи от това как е внедрена инстанцията. Инстанция, работеща в режим local, приема имейл и парола. В инстанция hosted или selfHosted влизането с парола е отказано изцяло с 403 PASSWORD_AUTH_DISABLED, а единственият път е конфигурираният доставчик на идентичност — така акаунтът никога не получава втори, по-слаб път за самоличност до себе си.

Вход през доставчик на идентичност

Единният вход (SSO) е размяна на код за оторизация с 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 с име nessie_refresh, ограничена до пътя /api/auth, с живот по подразбиране тридесет дни (NESSIE_AUTH_REFRESH_TOKEN_TTL). Извикването на POST /api/auth/refresh с тази бисквитка го консумира, издава нов токен за достъп и задава нова бисквитка на мястото на старата — токените се ротират, така че откраднатият важи само докато истинският клиент не се поднови следващия път.

Браузър, влязъл в няколко акаунта, пази по една такава бисквитка за всеки вход, наречена nessie_refresh_<slot>, и с хедъра x-nessie-account-slot посочва коя от тях се отнася до заявката. Клиент, който не изпраща такъв хедър, се отнася до nessie_refresh, което е всичко, от което се нуждае клиент от командния ред.

Клиентът от командния ред затова се нуждае от cookie jar. С 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 маршрутът приема и токена за достъп като query параметър ?token=. Това е единственото място, където токенът принадлежи в URL; всеки друг маршрут го приема в хедър.

Семействата ресурси

Има над хиляда крайни точки, което е далеч твърде много, за да се изброят. Те се групират в семейства, а когато знаете префикса на семейството, отделните маршрути са предвидими — колекция на префикса, запис под нея и именувани действия като 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Графици и уебхукове, които стартират работа на агенти, с историята на задействанията им.
Обаждания и глас/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, с парола. Токенът за достъп идва в тялото; токенът за подновяване попада в cookie jar.

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 }, а cookie jar вече съдържа ротирания токен за подновяване.

Списък на каналите, които виждате

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.