API
Interfejs HTTP, na którym opierają się wszystkie klienty Nessie, i sposób uwierzytelniania się w nim.
Czym jest API
Nessie ma jedno API HTTP. Aplikacja internetowa panelu administracyjnego, aplikacje dla macOS i iOS oraz zdalny demon executora są zwykłymi aplikacjami klienckimi tego API, a nic z tego, co mogą zrobić, nie jest dostępne w inny sposób. To API JSON działające na serwerze Fastify. Nie jest to GraphQL i nie ma osobnego podzbioru „publicznego”: powierzchnia, którą wywołują twoje skrypty, jest tą samą, którą wywołuje produkt.
Ścieżka nie zawiera segmentu wersji. Każda trasa znajduje się pod /api, z wyjątkiem przychodzącego punktu MCP POST /mcp i kilku dokumentów wykrywania w /.well-known/. Zgodność zapewnia się, dodając wyłącznie nowe pola, dzięki czemu klient, który ignoruje nierozpoznane dane, nadal działa.
Adres bazowy
Nessie działa na własnym serwerze, więc adresem bazowym jest ten origin, pod którym twoje wdrożenie udostępnia API. Operator ustawia go jako NESSIE_API_PUBLIC_URL, a ta sama wartość służy API, gdy musi samo zbudować bezwzględny adres URL. Na instancji, którą prowadzimy, jest to https://api.nessie.works; instancja deweloperska uruchomiona poleceniem pnpm dev odpowiada pod adresem http://localhost:5454.
Panel administracyjny to inny origin niż API, więc aplikacje działające w przeglądarce podlegają liście dozwolonych źródeł CORS (NESSIE_CORS_ORIGINS). Żądania ze skryptu lub serwera nie mają origin do sprawdzenia i nie są tym objęte.
Uwierzytelnianie
Każde żądanie poza małym zestawem celowo publicznych tras zawiera token dostępu w standardowym nagłówku:
Authorization: Bearer <token>Token jest podpisanym JWT wystawianym przez API. Jest krótkotrwały — domyślnie trzydzieści minut (NESSIE_AUTH_TOKEN_TTL) — i jest jedynym poświadczeniem, jakie zwykły wywołujący kiedykolwiek przedstawia. Nessie nie ma długotrwałych osobistych kluczy API. Jeśli chcesz, aby skrypt wywoływał API, loguje się tak samo jak przeglądarka danej osoby i odnawia token w miarę potrzeby.
Uzyskiwanie tokenu
POST /api/auth/session tworzy sesję. Odpowiada tokenem dostępu i tożsamością wywołującego oraz ustawia token odnowienia jako osobny plik cookie HTTP-only.
To, jak udowodnisz swoją tożsamość, zależy od sposobu wdrożenia instancji. Instancja działająca w trybie local przyjmuje e-mail i hasło. Na instancji hosted lub selfHosted logowanie hasłem jest całkowicie odrzucane z kodem 403 PASSWORD_AUTH_DISABLED, a jedynym wejściem jest skonfigurowany dostawca tożsamości — dzięki temu konto nigdy nie zyska drugiej, słabszej ścieżki tożsamości obok niego.
Logowanie przez dostawcę tożsamości
Logowanie jednokrotne (SSO) to wymiana kodu autoryzacji OAuth z PKCE, w trzech krokach. Dostawcą tożsamości Nessie jest UnlikeOtherAI, który jest też właścicielem struktury organizacji i zespołów, jaką odzwierciedla instancja; poniższa wymiana ma ten sam kształt dla każdego skonfigurowanego dostawcy.
GET /api/auth/providerswylicza dostawców akceptowanych przez tę instancję, każdego zproviderId, etykietą i informacją, czy strona logowania ma automatycznie przekierowywać do niego.GET /api/auth/providers/:providerId/authorizez parametrami zapytaniacodeChallenge,redirectUriistateodpowiada{ data: { authorizeUrl } }. Wyślij tam osobę.- Dostawca odsyła ją do twojego
redirectUriz kodem. Wyślij go z powrotem jakoPOST /api/auth/sessionz{ providerId, code, codeVerifier, redirectUri }— wszystkie cztery pola są wymagane razem, a niekompletny zestaw daje400 EXTERNAL_AUTH_INCOMPLETE.
Odnawianie sesji
Token odnowienia nigdy nie znajduje się w treści odpowiedzi. Jest ustawiany jako plik cookie HTTP-only o nazwie nessie_refresh, ograniczony do ścieżki /api/auth, z domyślnym czasem życia trzydziestu dni (NESSIE_AUTH_REFRESH_TOKEN_TTL). Wywołanie POST /api/auth/refresh z tym plikiem cookie zużywa go, wydaje nowy token dostępu i ustawia w jego miejsce nowy plik cookie — tokeny się rotują, więc skradziony działa tylko do chwili, gdy prawdziwy klient się odświeży.
Przeglądarka zalogowana na kilka kont przechowuje po jednym takim pliku cookie na każde logowanie, o nazwie nessie_refresh_<slot>, i wskazuje ten, którego dotyczy żądanie, nagłówkiem x-nessie-account-slot. Klient, który nie wysyła tego nagłówka, dotyczy nessie_refresh, czyli wystarcza klientowi wiersza poleceń.
Klient wiersza poleceń potrzebuje więc zbiornika plików cookie. W przypadku curl jest to --cookie-jar przy wywołaniu logowania i --cookie przy odświeżaniu, jak w poniższym przykładzie.
Co jest sprawdzane przy każdym żądaniu
Uwierzytelnianie to nie tylko sprawdzenie podpisu. Zanim uruchomi się procedura obsługi, API weryfikuje podpis i termin ważności tokenu, potwierdza, że sesja nie została unieważniona (zarówno przez wersję tokenu dla całego użytkownika, jak i przez identyfikator poszczególnej sesji), i wczytuje członkostwo wywołującego w organizacji nazwanej w tokenie. Dezaktywowane członkostwo daje 403. Gdy organizacja jest oparta na zewnętrznym dostawcy tożsamości, aktualny status wywołującego jest ponownie sprawdzany u tego dostawcy przy żądaniu, a nie odczytywany z tokenu, więc dostęp odebrany u źródła przestaje działać natychmiast, a nie dopiero przy następnym wygaśnięciu.
Poświadczenia, które nie są tokenami sesji
Istnieją dwa inne poświadczenia typu bearer i żadne z nich nie jest kluczem ogólnego przeznaczenia. Poświadczenie urządzenia głosowego jest przyjmowane tylko na trasach połączeń głosowych, a poświadczenie dostępu agenta — tylko na punkcie MCP. Przedstawienie któregokolwiek z nich gdziekolwiek indziej jest odrzucane kodem 403 z nazwą poświadczenia, a nie ponawiane jako słabsza sesja — zobacz MCP i połączone narzędzia, jak wydaje się poświadczenie agenta.
Format odpowiedzi
Każda udana odpowiedź jest obiektem z kluczem data. Nie ma gołej tablicy ani ładunku na najwyższym poziomie, więc klient nigdy nie musi rozpoznawać kształtu odpowiedzi:
{
"data": { "id": "3f1c…", "label": "engineering" }
}Odpowiedzi z listami dodają obok niego obiekt meta. Na najwyższym poziomie nie dodaje się niczego innego.
Błędy mają jeden kształt, niezależnie od tego, co je spowodowało:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "A message needs content or at least one attachment",
"field": "body",
"details": { "formErrors": [], "fieldErrors": {} }
}
}code to część czytelna dla maszyn i według niej należy rozgałęziać kod; message jest dla człowieka. field i details są obecne, gdy błąd jest na tyle konkretny, by go zlokalizować. Nieobsłużony błąd serwera to zawsze 500 INTERNAL_ERROR ze stałym komunikatem — treść błędu trafia do logów, nigdy do odpowiedzi, ponieważ mogła po drodze zebrać dane żądania.
Kody błędów, które spotkasz najpierw
| Status | Kod | Znaczenie |
|---|---|---|
| 400 | VALIDATION_ERROR | Treść lub zapytanie nie zgodziły się ze schematem. |
| 401 | AUTH_REQUIRED | Brak tokenu typu bearer lub nagłówek jest błędny. |
| 401 | TOKEN_EXPIRED | Token dostępu wygasł. Odśwież go i ponów żądanie. |
| 401 | TOKEN_REVOKED | Sesja została wylogowana albo wszystkie sesje zostały wymuszone na wylogowanie. |
| 403 | ACCOUNT_DEACTIVATED | Członkostwo w tej organizacji zostało dezaktywowane. |
| 404 | NOT_FOUND | Nie ma takiej trasy lub nie ma rekordu widocznego dla tego wywołującego. |
| 429 | RATE_LIMITED | Pula jest pełna. Odczytaj Retry-After. |
| 500 | INTERNAL_ERROR | Nieobsłużony błąd serwera, celowo nieczytelny. |
Stronicowanie
Listy stronicują się za pomocą nieprzejrzystego kursora klucza, a nie przesunięcia — wiersze są wstawiane, gdy ktoś czyta, a przy przesunięciu druga strona po cichu powtarza rekord, który pokazała pierwsza.
Akceptowane są trzy parametry zapytania: cursor, limit i direction (forward lub backward). Domyślna strona ma 25 pozycji, a maksimum to 100; większa wartość jest obcinana, a nie odrzucana. Punkty końcowe, które również sortują lub filtrują, dodają sort, order i wolnotekstowe q.
meta strony mówi, gdzie jesteś:
{
"data": [ … ],
"meta": {
"hasMore": true,
"nextCursor": "2026-09-12T08:31:44.005Z|8b2f…",
"prevCursor": null,
"total": 134
}
}nextCursor i prevCursor są oba prawdziwe: Wstecz i Dalej to dwa elementy sterujące, których człowiek się spodziewa. Są nieprzejrzyste — serwer koduje w nich własny klucz sortowania, a klient, który je parsuje lub konstruuje, przestanie działać, gdy lista zmieni kolejność. total jest obecne na większości list i pomijane tam, gdzie liczenie nie ma sensu, na przykład w rankingowych wynikach wyszukiwania.
Limity żądań
Limity działają w stałych oknach i są egzekwowane, zanim w ogóle odczytane zostanie ciało żądania. Odrzucone żądanie to 429 z kodem RATE_LIMITED i nagłówkiem Retry-After podanym w sekundach.
Większość pul opiera się na adresie IP klienta; te wrażliwe na poświadczenia — logowanie, odświeżanie, ponowne potwierdzenie hasła, zapisy poświadczeń MCP — dodatkowo prowadzą licznik na konto i odrzucają, gdy zadziała którykolwiek, dzięki czemu zarówno jeden adres rozsyłający próby na wiele kont, jak i jedno konto atakowane z wielu adresów trafiają na mur. Logowanie domyślnie dopuszcza 10 prób na IP i 5 na adres e-mail w ciągu dziesięciu minut; wysłanie wiadomości domyślnie 60 na minutę; zapisy pod /api/agents — 60 na minutę. Każdy próg jest konfigurowalny w danym wdrożeniu, więc traktuj te wartości jako domyślne wartości fabryczne, a nie zobowiązanie. Kontrole stanu i strumienie w czasie rzeczywistym są wyłączone z limitów.
Czas rzeczywisty
Odpytywanie listy to zły sposób na śledzenie rozmowy. Zamiast tego istnieją trzy punkty strumieniowe:
GET /api/events/stream— zdarzenia wysyłane przez serwer (SSE) dla wszystkiego, co zalogowana osoba może zobaczyć.GET /api/threads/:threadId/stream— SSE dla jednego wątku. RespektujeLast-Event-ID, więc ponowne połączenie wznawia strumień, a nie zaczyna od nowa.GET /api/activity— WebSocket przenoszący kanał aktywności.
Przeglądarki nie mogą ustawiać nagłówków przy uzgadnianiu WebSocket, więc trasa WebSocket akceptuje też token dostępu jako parametr zapytania ?token=. To jedyne miejsce, w którym token należy do adresu URL; każda inna trasa przyjmuje go w nagłówku.
Rodziny zasobów
Punktów końcowych jest znacznie ponad tysiąc, zbyt wiele, by je wymienić. Grupują się w rodziny, a gdy znasz prefiks rodziny, poszczególne trasy są przewidywalne — kolekcja pod prefiksem, rekord pod nią i nazwane akcje jako podścieżki POST.
| Rodzina | Prefiks | Co obejmuje |
|---|---|---|
| Uwierzytelnianie | /api/auth | Logowanie, odnawianie, wylogowanie, bieżąca tożsamość i własne sesje osoby. |
| Kanały i wątki | /api/channels, /api/threads | Model rozmów: kanały zawierają wątki, wątki zawierają wiadomości, a odpowiedzi zagnieżdżają się pod wiadomością nadrzędną. |
| Agenci | /api/agents | Definicje agentów, ich powiązania z kanałami, rozmowy, listy zadań i status. |
| Projekty i tablice | /api/projects, /api/tasks, /api/iterations | Śledzenie pracy: tablice i kolumny w projekcie, zadania na nich, iteracje nad nimi. |
| Wiedza | /api/knowledge-base | Przestrzenie wersjonowanych stron i plików, z komentarzami, linkami zwrotnymi i wyszukiwaniem. |
| Poczta | /api/mail, /api/mailbox-connections | Podłączone skrzynki pocztowe, skrzynki agentów i szkice napisane przez agenta do zatwierdzenia przez osobę. |
| Zatwierdzenia | /api/approvals | Kolejka działań, których agent nie może podjąć samodzielnie, i jedno wywołanie, które je rozstrzyga. |
| Audyt | /api/audit-log | Łańcuchowo haszowany dziennik audytu organizacji z punktem weryfikacji. Właściciele i administratorzy. |
| Koszty i zużycie | /api/ledger, /api/billing | Rozliczanie tokenów i kosztów, budżety i czasy uruchomień; osobno kredyty i doładowania. Właściciele i administratorzy. |
| Organizacja i zespoły | /api/organizations, /api/teams, /api/users | Administracja najemcą, zespołami i członkostwem, w tym zaproszeniami. |
| Wyzwalacze | /api/triggers | Harmonogramy i webhooki uruchamiające pracę agentów, wraz z historią ich uruchomień. |
| Połączenia i głos | /api/calls, /api/voice | Połączenia w kanale oraz sesja asystenta głosowego, transkrypt i tokeny urządzeń. |
| Executory | /api/executors, /api/execution-environment-templates | Maszyny, na których pracują agenci, ich środowiska i kanał parowania demona. |
| Uruchomienia | /api/runs | Trwające uruchomienia agentów: anulowanie, kontynuacja, ponowne uruchomienie, powiązanie z executorem. |
| Przesyłanie plików | /api/uploads, /api/attachments | Najpierw prześlij plik, potem połącz zwrócony identyfikator z wiadomością. |
| Wyszukiwanie | /api/messages/search | Wyszukiwanie wiadomości w całej organizacji lub ograniczone do jednego kanału. |
| Sekrety i ustawienia | /api/secrets, /api/settings/scoped | Magazyn poświadczeń tylko do zapisu z jawnymi uprawnieniami oraz warstwowe wartości konfiguracji. |
| Połączone narzędzia | /api/mcp | Serwery MCP, do których ta instancja się łączy. Zobacz MCP i połączone narzędzia. |
| Kontrola stanu | /api/health | Żywotność i gotowość, obie publiczne. /api/health/ready sprawdza też bazę danych. |
Uprawnienia egzekwowane są na poziomie trasy, a nie rodziny. Ogólnie: dziennik audytu, rejestr kosztów, reguły polityk i płaszczyzna sterowania wnioskowaniem są zasobami dla właścicieli i administratorów organizacji, a rozmowy, projekty, wiedza i zatwierdzenia są otwarte dla każdego członka i zawężane do tego, co ten członek faktycznie widzi.
Przykłady krok po kroku
Zaloguj się i zachowaj plik cookie odnowienia
Na instancji w trybie local, z hasłem. Token dostępu wraca w treści; token odnowienia trafia do zbiornika plików cookie.
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
}
}
}
}Gdy token wygaśnie, wymień plik cookie na nowy:
curl -sS -X POST https://api.example.com/api/auth/refresh \
--cookie nessie.cookies --cookie-jar nessie.cookiesOdpowiedź ma ten sam kształt { token, me }, a zbiornik plików cookie zawiera już obrócony token odnowienia.
Wypisz kanały, które widzisz
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
}
]
}Dodaj ?teamId=…, aby zawęzić wynik do jednego zespołu, albo ?includeArchived=true, aby uwzględnić zarchiwizowane kanały. defaultThreadId to wątek, do którego trafi następna wysłana wiadomość.
Wyślij wiadomość
Wiadomość potrzebuje albo treści content, albo przynajmniej jednego przesłanego załącznika. Wyślij nagłówek Idempotency-Key, a ponowione żądanie zwróci wiadomość utworzoną przez pierwszą próbę, zamiast wysyłać drugą kopię.
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."}'Nowa wiadomość to 201:
{
"data": {
"message": {
"id": "d70e…",
"threadId": "c41a…",
"role": "user",
"content": "Deploying the migration now.",
"createdAt": "2026-09-16T09:04:11.882Z"
},
"pendingAgentInvites": []
}
}Powtórzenie z tym samym kluczem idempotentności daje 200 z oryginalną wiadomością i pustą tablicą pendingAgentInvites, ponieważ pierwsza próba już wykonała powiadomienia i wysyłkę, które ta tablica opisuje.
Dwie odmowy na tej trasie warto obsłużyć jawnie. Treść przekraczająca limit długości czatu daje 413 MESSAGE_TOO_LARGE i właściwą odpowiedzią jest przesłanie tekstu jako pliku. Treść strukturalnie przypominająca poświadczenie daje 422 SECRET_INTERCEPTED — serwer odrzuca ją przed zapisaniem, wysłaniem lub zalogowaniem wiadomości, a poświadczenie należy umieścić w /api/secrets.