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, който също притежава структурата на организациите и екипите, която инстанцията отразява; размяната по-долу има същата форма за всеки конфигуриран доставчик.
GET /api/auth/providersпоказва какво приема тази инстанция, като за всеки доставчик имаproviderId, етикет и дали страницата за вход трябва автоматично да пренасочва към него.GET /api/auth/providers/:providerId/authorizeс параметритеcodeChallenge,redirectUriиstateв заявката отговаря с{ data: { authorizeUrl } }. Изпратете човека там.- Доставчикът връща човека към вашия
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 с фиксирано съобщение — текстът на основната грешка се записва в журнала, никога не се връща, защото може да е уловил данни от заявката по пътя си навън.
Кодове на грешки, които ще срещнете първи
| Статус | Код | Значение |
|---|---|---|
| 400 | VALIDATION_ERROR | Тялото или заявката не съответстват на схемата. |
| 401 | AUTH_REQUIRED | Няма bearer токен или хедърът е невалиден. |
| 401 | TOKEN_EXPIRED | Токенът за достъп е изтекъл. Подновете и опитайте отново. |
| 401 | TOKEN_REVOKED | Сесията е прекратена при излизане или всички сесии са били принудително прекратени. |
| 403 | ACCOUNT_DEACTIVATED | Членството в тази организация е деактивирано. |
| 404 | NOT_FOUND | Няма такъв маршрут или няма такъв запис, видим за този извикващ. |
| 429 | RATE_LIMITED | Кофата за лимити е пълна. Прочетете Retry-After. |
| 500 | INTERNAL_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/mcp | MCP сървърите, към които тази инстанция се обръща. Вижте 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.