Newer
Older
navi-1 / docs / api.md

Navi API Reference

Base URL: http://localhost:8000


REST API

Health

GET /health

Server availability check.

Response 200

{
  "status": "ok",
  "embed": {
    "status": "ok",
    "model": "nomic-embed-text",
    "dimensions": 768
  }
}

GET /health/embed

Embedding model health check. Returns the first vector of a test string to verify the embed backend is responsive.

Response 200

{
  "status": "ok",
  "model": "nomic-embed-text",
  "dimensions": 768
}

Auth

Full auth documentation: docs/auth.md.

GET /auth/login

Redirect to gnexus-auth OAuth authorization endpoint. Sets PKCE + state internally.

Query params

  • return_to — URL to redirect back to after login (default: /)
  • platformbrowser (default) or android (affects redirect after callback)

Response 302 → Location: gnexus-auth /oauth/authorize


GET /auth/callback

OAuth callback. Validates state, exchanges code for tokens, creates DB session.

Query params

  • code — authorization code from gnexus-auth
  • state — state parameter

Response 302

  • Browser → Location: / (with Set-Cookie)
  • Android → Location: /auth/mobile-done?sid=<session_id>

Errors

  • 400 — invalid state, PKCE failure, or token exchange failed
  • 503 — OAuth is not configured (missing gnexus_auth_client_id or gnexus_auth_client_secret)

GET /auth/mobile-done

Bridge page for Android OAuth. Renders HTML that attempts an automatic deep-link back into the native app via Chrome Intent URL (intent://...), and falls back to a manual button for browsers that block automatic navigation to custom schemes (e.g. DuckDuckGo). Styled with the gnexus UI kit design system.

Query params

  • sid — session id that will become the navi_auth_session cookie

Response 200 — HTML page


POST /auth/logout

Logout current user. Deletes DB session and clears cookie.

Response 200

{ "ok": true }

GET /auth/me

Return current authenticated user.

Response 200

{
  "id": "user-uuid",
  "email": "user@example.com",
  "display_name": "User Name",
  "username": "username",
  "first_name": "First",
  "last_name": "Last",
  "phone": "+1234567890",
  "birth_date": "1990-01-01",
  "country": "US",
  "city": "New York",
  "locale": "en-US",
  "avatar_url": "https://...",
  "profile_url": "https://...",
  "role": "admin",
  "permissions": ["navi.sessions.read_all", "navi.memory.read_all"]
}

Errors

  • 401 — not authenticated

GET /auth/status

Check if the user is currently authenticated without returning full profile.

Response 200

{ "authenticated": true }

Profiles & Tools

GET /agents/profiles

List available agent profiles. Non-admin users do not see is_admin_only profiles.

Response 200

[
  {
    "id": "secretary",
    "name": "Personal Secretary",
    "description": "General-purpose assistant",
    "enabled_tools": ["todo", "mcp:navi-web:web_search", "filesystem", "..."],
    "llm_backend": "ollama",
    "model": ["gemma4:31b-cloud", "gemma4:26b-a4b-it-q4_K_M"],
    "temperature": 0.65,
    "top_k": null,
    "top_p": null,
    "max_iterations": 10,
    "iteration_budget_enabled": true,
    "think_enabled": true,
    "subagent_think_enabled": null,
    "mcp_servers": {"gnexus-book": ["read", "write"]}
  }
]

GET /agents/tools

List all registered tools (built-in + user tools).

Response 200

[
  {
    "name": "mcp:navi-web:web_search",
    "description": "Search the web using DuckDuckGo.",
    "parameters": {"type": "object", "properties": {...}, "required": [...]}
  },
  {
    "name": "filesystem",
    "description": "Read, write and list files.",
    "parameters": {"type": "object", "properties": {...}, "required": [...]}
  }
]

GET /agents/prompts

Return the fully resolved system prompt for each profile (persona + profile system_prompt + context provider injections + MCP server instructions).

Response 200

{
  "secretary": "system prompt text...",
  "server_admin": "system prompt text..."
}

GET /agents/mcp_servers

Return all configured MCP servers with their resolved tools per profile.

Response 200

{
  "gnexus-book": {
    "connected": true,
    "tools": [
      {"name": "gnexus-book_list_inventory", "description": "..."}
    ],
    "instructions": "MANDATORY: Before answering ANY question..."
  }
}

Sessions

POST /sessions

Create a new session.

Auth: requires authenticated user.

Request body

{ "profile_id": "secretary" }

Response 201

{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "profile_id": "secretary",
  "created_at": "2026-04-10T18:00:00+00:00"
}

Errors

  • 401 — not authenticated
  • 404 — profile not found

GET /sessions

List all sessions sorted by activity (pinned first).

Auth: requires authenticated user.

Query params | Param | Default | Description | |---|---|---| | limit | 50 | Page size | | offset | 0 | Items to skip | | profile_id | — | Filter by profile |

Response 200 (when pagination params provided)

{
  "items": [
    {
      "session_id": "550e8400-...",
      "profile_id": "secretary",
      "name": "Research task",
      "message_count": 12,
      "preview": "Last 60 chars of the most recent message",
      "pinned": false,
      "created_at": "2026-04-10T15:00:00+00:00",
      "last_active": "2026-04-10T18:00:00+00:00"
    }
  ],
  "limit": 50,
  "offset": 0,
  "has_more": true,
  "next_offset": 50
}

Response 200 (plain list when no pagination params)

[
  {
    "session_id": "550e8400-...",
    "profile_id": "secretary",
    "name": "Research task",
    "message_count": 12,
    "preview": "Last 60 chars of the most recent message",
    "pinned": false,
    "created_at": "2026-04-10T15:00:00+00:00",
    "last_active": "2026-04-10T18:00:00+00:00"
  }
]

name is null until POST /sessions/{id}/generate-name is called.


GET /sessions/{session_id}

Full session with message history (display history — never compressed).

Auth: requires authenticated user (or ownership of the session).

Response 200

{
  "session_id": "550e8400-...",
  "profile_id": "secretary",
  "name": "Research task",
  "context_token_count": 4913,
  "max_context_tokens": 65536,
  "created_at": "...",
  "last_active": "...",
  "messages": [
    {
      "role": "user",
      "content": "Hello",
      "created_at": "2026-04-10T18:00:00+00:00"
    },
    {
      "role": "assistant",
      "content": "Hi. How can I help?",
      "created_at": "2026-04-10T18:00:05+00:00"
    },
    {
      "role": "assistant",
      "tool_calls": [
        {
          "id": "abc123",
          "name": "mcp:navi-web:web_search",
          "arguments": { "query": "..." }
        }
      ]
    },
    {
      "role": "tool",
      "content": "tool result",
      "tool_call_id": "abc123",
      "name": "mcp:navi-web:web_search"
    }
  ]
}

Message fields (role is always present, others by availability):

Field Type Description
role `user\ assistant\ tool\ system` Message author
content `string\ null` Text content
images string[] Base64 images (user/assistant)
tool_calls ToolCall[] Tool invocations (assistant)
tool_call_id string ID of the call this result belongs to (tool)
name string Tool name (tool messages)
thinking `string\ null` LLM reasoning captured during a tool-calling turn
is_plan bool Planning phase output — rendered as a plan card, not text
is_compression bool Marker injected when context compression ran
is_summary bool Summary message replacing compressed history
created_at string (ISO 8601) Creation time
elapsed_seconds `number\ null` Time to complete the turn (final assistant message)
tool_call_count `number\ null` Number of tool calls in the turn
token_count `number\ null` Tokens used in the turn

Errors

  • 404 — session not found

DELETE /sessions/{session_id}

Delete a session and its files.

Response 204 — no body

Errors

  • 404 — session not found

PATCH /sessions/{session_id}/pin

Pin or unpin a session.

Request body

{ "pinned": true }

Response 200

{ "session_id": "...", "pinned": true }

POST /sessions/{session_id}/generate-name

Generate a short display name for a session from its message history. Called automatically by the client after the first exchange. No-op if the session already has a name.

Response 200

{ "name": "Web search for recipes" }

Returns {"name": null} if there are no user messages yet.

Errors

  • 404 — session not found

GET /sessions/{session_id}/context

LLM context (what the model actually sees). May differ from messages — compressed history replaces old turns with a summary. Debug endpoint.

Auth: requires admin role.

Response 200

{
  "session_id": "...",
  "profile_id": "secretary",
  "message_count": 8,
  "total_chars": 4200,
  "context": [ ...same format as messages... ]
}

Errors

  • 403 — not admin

GET /sessions/{session_id}/planning

All planning phase debug logs for the session. Each entry is one planning run.

Auth: requires admin role.

Response 200

{ "session_id": "...", "logs": [ { "phase": "...", "output": "..." }, ... ] }

Errors

  • 403 — not admin

GET /sessions/{session_id}/content

List published session content (artifacts registered via content_publish tool).

Response 200

{
  "content": [
    {
      "id": "...",
      "filename": "report.html",
      "path": "/abs/path/to/content/...",
      "size": 102400,
      "content_type": "text/html",
      "created_at": "2026-04-10T18:00:00+00:00"
    }
  ]
}

Errors

  • 404 — session not found

POST /sessions/{session_id}/files

Upload a file for a session. Call before sending a message to attach the file.

Request: multipart/form-data, field file.

Limits

  • Max size: 200 MB
  • Forbidden extensions: .exe, .dll, .so, .sh, .bat, .cmd, .ps1, .vbs, .bin, .elf, and other executable formats
  • Duplicate filenames get a numeric suffix

Response 201

{
  "name": "report.pdf",
  "size": 102400,
  "path": "/abs/path/to/session_files/550e8400-.../report.pdf",
  "content_type": "application/pdf"
}

Errors

  • 400 — forbidden extension
  • 404 — session not found
  • 413 — file exceeds limit

GET /sessions/{session_id}/files/{filename}

Download or view an uploaded file. Images, PDFs, plain text and HTML are served inline; everything else as an attachment.

Query params

  • download — force attachment download regardless of content type

Response 200 — file bytes

Errors

  • 403 — path traversal attempt
  • 404 — session or file not found

Messages (non-streaming)

POST /sessions/{session_id}/messages

Send a message and receive a response synchronously (no streaming). Blocks until the full agent loop completes.

Request body

{ "content": "How many stars are in the galaxy?" }

Response 200

{ "role": "assistant", "content": "Estimates range from 100 to 400 billion." }

Errors

  • 404 — session not found
  • 500 — agent error or iteration limit exceeded

For production clients prefer WebSocket — it provides streaming, tool progress, and model reasoning.


WebSocket

WS /ws/sessions/{session_id}

Main channel for real-time agent interaction. Supports text streaming, thinking streaming, tool events, file and image attachment.

Connect: if the session is not found, the server closes with code 4004.

On connect: the server immediately sends session_sync (no active run) or starts the reconnect replay flow (run in progress).


Client → Server

All client messages are JSON objects.

Send a message

{
  "type": "message",
  "content": "Message text",
  "images": ["base64string...", "..."],
  "files": [
    { "name": "report.pdf", "size": 102400, "path": "session_files/.../report.pdf" }
  ]
}
Field Required Description
type yes Always "message"
content yes Message text (non-empty)
images no Base64 image list. Both raw base64 and data:image/...;base64,... are accepted — server strips the prefix
files no Files uploaded via POST /sessions/{id}/files. Server appends their paths to the message content

Server → Client

Events arrive in the order they are emitted.

stream_start

{ "type": "stream_start" }

Processing started. Client should block input.


thinking_delta

{ "type": "thinking_delta", "delta": "reasoning fragment..." }

Streaming chunk of model reasoning. Accumulate until thinking_end.


thinking_end

{ "type": "thinking_end" }

Reasoning phase complete. Next will be stream_delta or tool calls.


turn_thinking

{
  "type": "turn_thinking",
  "thinking": "full reasoning text...",
  "is_subagent": false
}

Complete reasoning block from a tool-calling turn. Not streamed — arrives whole. is_subagent: true means this reasoning came from a subagent inside spawn_agent.


planning_status

{
  "type": "planning_status",
  "phase": "analysis",
  "label": "Analysing request...",
  "is_subagent": false
}

Progress update during the planning phase. phase is one of analysis, reflect, plan. is_subagent: true — route into the spawn_agent card, not the top-level UI.


plan_ready

{
  "type": "plan_ready",
  "plan": "1. Step one\n2. Step two\n...",
  "is_subagent": false
}

Planning complete — full step list. Rendered as a collapsible plan card. is_subagent: true — route into the spawn_agent card.


tool_started

{
  "type": "tool_started",
  "tool": "mcp:navi-web:web_search",
  "args": { "query": "weather in moscow" },
  "is_subagent": false
}

Agent started executing a tool. Arrives before execution completes — show a spinner. is_subagent: true — call from a subagent.


tool_call

{
  "type": "tool_call",
  "tool": "mcp:navi-web:web_search",
  "args": { "query": "weather in moscow" },
  "result": "Today +12°C, cloudy.",
  "success": true,
  "is_subagent": false
}

Tool finished. Arrives after tool_started with the same tool and args. success: false — tool returned an error.


stream_delta

{ "type": "stream_delta", "delta": "response fragment..." }

Streaming chunk of the final text response. Accumulate into a string.


stream_end

{
  "type": "stream_end",
  "content": "full response text",
  "context_tokens": 4913,
  "max_context_tokens": 65536,
  "elapsed_seconds": 12.4,
  "tool_call_count": 3,
  "token_count": 1842
}

Agent finished. content is the full accumulated text (duplicates the sum of stream_delta). Client should unblock input.


stream_stopped

{ "type": "stream_stopped" }

Generation was stopped by POST /sessions/{id}/stop.


profile_switched

{
  "type": "profile_switched",
  "profile_id": "server_admin",
  "profile_name": "Server Administrator"
}

Agent switched profile via switch_profile tool. New profile takes effect on the next user message. Client should update the profile indicator. Arrives during the stream — before tool_call for switch_profile.


context_compressed

{
  "type": "context_compressed",
  "messages_before": 42,
  "messages_after": 12,
  "summary": "User asked about..."
}

Context was automatically compressed (triggers at ≥80% of context window). Informational.


heartbeat

{ "type": "heartbeat" }

Keepalive ping sent every 20 s during long silent operations. Client can ignore.


session_sync

{ "type": "session_sync" }

Client must reload session history from GET /sessions/{id}. Sent:

  1. On connect when no run is active (agent may have finished while disconnected).
  2. After a reconnect-replay flow completes (ensures client sees the fully saved response).

replay_start

{ "type": "replay_start", "count": 14 }

About to replay count buffered events from a mid-stream reconnect. Client should suppress cursor animations and in-progress effects during replay.


replay_end

{ "type": "replay_end" }

Replay complete. Live events will follow.


error

{ "type": "error", "message": "Session not found" }

Processing error. Stream may or may not continue after this.


Typical event sequences

Simple question, no tools:

stream_start
thinking_delta × N   (if model has thinking enabled)
thinking_end
stream_delta × N
stream_end

Request with tool calls:

stream_start
turn_thinking         (reasoning before tool selection, if any)
tool_started
tool_call
turn_thinking         (before next tool, if any)
tool_started
tool_call
thinking_delta × N   (final response reasoning)
thinking_end
stream_delta × N
stream_end
context_compressed    (optional, if context was near full)

Request with planning enabled:

stream_start
planning_status       (phase: analysis)
planning_status       (phase: plan)
plan_ready
turn_thinking
tool_started
tool_call
...
stream_end

Request with subagent (spawn_agent):

stream_start
tool_started          (spawn_agent, is_subagent=false)
  turn_thinking       (is_subagent=true)
  planning_status     (is_subagent=true, if subagent has planning)
  plan_ready          (is_subagent=true, if subagent has planning)
  tool_started        (subagent tool, is_subagent=true)
  tool_call           (is_subagent=true)
tool_call             (spawn_agent done, is_subagent=false)
stream_delta × N
stream_end

Reconnect mid-stream:

stream_start
replay_start          {"count": N}
ev_0 ... ev_N-1       (buffered events replayed verbatim)
replay_end
(live events continue)
...
stream_end
session_sync

Profile switch (switch_profile):

stream_start
tool_started          (switch_profile)
profile_switched      (client updates UI here — before tool_call)
tool_call             (switch_profile done)
stream_delta × N
stream_end

Admin

All admin endpoints require admin role or specific permissions.

GET /admin/sessions

All sessions across all users. Supports pagination, search, and sorting.

Query params | Param | Default | Description | |---|---|---| | limit | 50 | Page size | | offset | 0 | Items to skip | | search | — | Filter by session_id, name, user_id or profile_id (case-insensitive) | | sort_by | last_active | last_active, created_at, name, profile_id, user_id, pinned | | sort_order | desc | asc or desc |

Response 200

{
  "total": 128,
  "limit": 50,
  "offset": 0,
  "items": [
    {
      "session_id": "...",
      "profile_id": "secretary",
      "user_id": "user-uuid",
      "name": "Research task",
      "message_count": 12,
      "pinned": false,
      "created_at": "2026-05-04T10:00:00+00:00",
      "last_active": "2026-05-04T10:30:00+00:00"
    }
  ]
}

GET /admin/users

All registered navi_users.

Response 200

[
  {
    "id": "user-uuid",
    "email": "user@example.com",
    "display_name": "User Name",
    "role": "admin",
    "permissions": ["navi.sessions.read_all"],
    "created_at": "...",
    "updated_at": "..."
  }
]

GET /admin/memory

All memory facts (global view). Requires navi.memory.read_all. Supports pagination, search, and sorting.

Query params | Param | Default | Description | |---|---|---| | limit | 50 | Page size | | offset | 0 | Items to skip | | search | — | Filter by key, value or category (case-insensitive) | | sort_by | updated_at | updated_at, category, key, confidence, source | | sort_order | desc | asc or desc | | user_id | — | If set, return only facts for this user instead of global view |

Response 200

{
  "total": 128,
  "limit": 50,
  "offset": 0,
  "items": [
    {
      "id": "...",
      "category": "profile",
      "key": "name",
      "value": "Eugene",
      "source": "conversation",
      "confidence": 90,
      "updated_at": "2026-05-04T10:00:00+00:00"
    }
  ]
}

GET /admin/profiles

All profiles including admin-only ones. Requires navi.profiles.manage.


GET /admin/sessions/{session_id}

Full session details including messages. Bypasses ownership check.

Response 200

{
  "session_id": "...",
  "profile_id": "secretary",
  "user_id": "user-uuid",
  "name": "Research task",
  "messages": [...],
  "context_token_count": 4913,
  "max_context_tokens": 65536,
  "pinned": false,
  "created_at": "...",
  "last_active": "..."
}

Errors

  • 404 — session not found

DELETE /admin/sessions/{session_id}

Delete any session (bypasses ownership). Also deletes session files.

Response 204 — no body

Errors

  • 404 — session not found

GET /admin/users/{user_id}

Single user details.

Response 200

{
  "id": "user-uuid",
  "email": "user@example.com",
  "display_name": "User Name",
  "role": "admin",
  "permissions": ["navi.sessions.read_all"],
  "created_at": "...",
  "updated_at": "..."
}

Errors

  • 404 — user not found

GET /admin/users/{user_id}/sessions

Sessions owned by a specific user.

Response 200

[
  {
    "session_id": "...",
    "profile_id": "secretary",
    "name": "Research task",
    "message_count": 12,
    "pinned": false,
    "created_at": "...",
    "last_active": "..."
  }
]

POST /admin/ollama/clear-blacklists

Manually clear dead-server and dead-model blacklists for the Ollama fallback backend. Useful when a transient failure caused a 5-minute blacklist and you want immediate recovery.

Response 204 — no body


GET /admin/profiles/{profile_id}

Full profile configuration including system prompt.

Response 200

{
  "id": "secretary",
  "name": "Personal Secretary",
  "description": "General-purpose assistant",
  "short_description": "...",
  "full_description": {"specialization": "...", "when_to_use": "...", "key_tools": [...]},
  "system_prompt": "...",
  "subagent_system_prompt": "...",
  "llm_backend": "ollama",
  "model": ["gemma4:31b-cloud", "gemma4:26b-a4b-it-q4_K_M"],
  "temperature": 0.65,
  "top_k": null,
  "top_p": null,
  "num_thread": null,
  "max_iterations": 10,
  "planning_enabled": false,
  "planning_mandatory": false,
  "planning_phase1_enabled": true,
  "planning_phase2_enabled": false,
  "planning_phase3_enabled": true,
  "think_enabled": true,
  "iteration_budget_enabled": true,
  "goal_anchoring_enabled": true,
  "goal_anchoring_interval": 5,
  "anti_stall_enabled": true,
  "anti_stall_threshold": 8,
  "step_validation_enabled": false,
  "adaptive_replan_enabled": false,
  "subagent_tools": [...],
  "subagent_planning_enabled": false,
  "subagent_think_enabled": null,
  "enabled_tools": [...],
  "context_providers": [],
  "is_admin_only": false
}

Errors

  • 404 — profile not found

PUT /admin/profiles/{profile_id}

Update profile configuration on disk and in-memory. Accepts partial updates — only provided fields are modified.

Request body (partial)

{
  "temperature": 0.5,
  "max_iterations": 20,
  "planning_enabled": true
}

Response 200

{ "ok": true }

Errors

  • 400 — invalid profile data
  • 404 — profile not found

PATCH /admin/users/{user_id}/role

Update cached role. Requires admin.

Request body

{ "role": "admin" }

Response 200

{ "ok": true }

PATCH /admin/profiles/{profile_id}/availability

Toggle is_admin_only. Requires navi.profiles.manage.

Request body

{ "is_admin_only": true }

Response 200

{ "ok": true }

Webhooks

POST /webhooks/gnexus-auth

Receive webhooks from gnexus-auth. Verified via HMAC.

Response 200

{ "ok": true }

Errors

  • 400 — invalid payload
  • 503 — OAuth not configured

Files

Client static: GET /static/** — served from client/ directory. Header Cache-Control: no-store.

Session uploaded files: stored in session_files/{session_id}/. Agent accesses them via the filesystem tool. Deleted after 24 h of session inactivity or when the session is deleted.


Error codes

HTTP Reason
400 Forbidden file type
404 Session or profile not found
413 File exceeds 200 MB
500 Internal agent error
WS 4004 Session not found on connect