English · Español
Website · Documentation · Home · Changelog
The FastAPI service connects the PWA and CLI to RAG, memory, voice, and local administration. Its default managed URL is https://localhost:3333; the URL depends on TRINAXAI_HOST, TRINAXAI_PORT, and TLS. Live schema endpoints on a running instance are /docs (Swagger UI), /redoc (ReDoc), and /openapi.json (OpenAPI JSON).
Managed installations prefer HTTPS. Use the public CA printed by trinaxai network with API clients; direct HTTP is only a loopback development fallback.
LAN clients must enter through the PWA gateway on port 3334, trust the public
CA on their device, and must not connect directly to port 3333 or Ollama
11434. For a symptom-first recovery path, see Troubleshooting.
Protected low-risk endpoints allow a direct loopback request, a scoped
paired-device credential (X-TrinaxAI-Device-Token), or the administrator
credential (X-Admin-Token). Host-only scopes (system and agent_yolo)
first require verified original loopback provenance; no credential and no legacy
LAN setting can override that requirement. The elevated scopes index, agent
and modify are granted to a specific paired device by the localhost operator;
an admin token sent over the network does not unlock them. The local PWA gateway strips
client identity headers and signs the original peer, method and path with a
fresh, single-use HMAC assertion. FastAPI accepts that assertion only from
loopback or an explicitly configured private runtime peer that also proves the
shared key; Forwarded and X-Forwarded-For are never identity.
Chat, STT, and TTS use separate monotonic token buckets per verified IP. General capacity is 30 and an empty bucket refills over 60 seconds.
curl -k https://localhost:3333/health
curl -k -H "X-Admin-Token: $TOKEN" https://localhost:3333/v1/memory
curl -k -H "X-TrinaxAI-Device-Token: $DEVICE_TOKEN" https://localhost:3333/v1/memoryDevice scopes are chat, read_private, web, index, agent, and
modify; default pairing grants chat and read_private. Requests for
system or agent_yolo are rejected. Records written before per-device grants
existed keep their stored list but their elevated scopes (index, agent,
modify) are ignored until the host grants them again. An admin token can
authenticate protected reads but cannot turn a LAN peer into localhost. An invalid supplied credential is never bypassed merely
because its transport peer is loopback.
| Method and path | Access | Purpose |
|---|---|---|
POST /v1/chat/completions, /v1/research |
chat + rate limited |
JSON/SSE chat and research. |
POST /v1/agent, /v1/agent/approve, /v1/agent/cancel, GET /v1/agent/browse |
agent (loopback or granted device) |
Workspace agent stream, approval/cancellation, and registered-root browsing. yolo: true additionally needs loopback (agent_yolo). |
GET/POST /v1/voice/* |
chat + rate limited |
Speech recognition and synthesis. |
POST /documents/extract |
LAN/VPN or chat + rate limited |
Stateless temporary document extraction. |
GET /v1/sources/*, GET /v1/memory/*, POST /v1/memory/context, GET /v1/stats |
read_private |
Private indexed and user data. |
DELETE /v1/sources/*, /v1/watch/*, collection mutations |
index |
Source, watcher and collection administration. A device may only watch folders inside the configured indexing roots. |
Memory writes (POST/PATCH/DELETE /v1/memory*) |
modify |
Edit private memory. |
POST /v1/usage |
chat |
Local usage accounting. |
GET /health, GET /ready, GET /resources |
Public | Liveness, model-provider readiness, and RAM data. |
GET/PUT/DELETE /v1/settings/web-search* |
modify |
Host-local web-search provider settings and credential reset. |
GET/PUT /app-state / DELETE /app-state |
read_private / loopback (system) |
Versioned shared PWA state / factory reset. |
POST /attachments, GET /attachments/{id} / DELETE /attachments/{id} / POST /attachments/{id}/open |
read_private / modify / loopback (system) |
Store/retrieve, delete, or open host-backed chat attachments. |
GET /collections / mutations |
read_private / index |
Collection metadata and administration. |
/system/index* / other /system/* |
index / loopback only |
Indexing versus lifecycle/reload/self-test. |
/v1/pairing/* |
Mixed | One-time device pairing and revocation. |
Every API error returns a safe error object (and the same canonical fields in
detail):
category, internal code, user-facing message, recovery, and retryable.
Categories are internet_unavailable, external_service_unavailable,
ai_model_unavailable, model_loading_failed, tool_timeout,
permission_denied, authentication_failed, resource_exhausted,
memory_limit_reached, gpu_unavailable, file_not_found,
document_unreadable, invalid_input, unsupported_format,
network_timeout, internal_server_error, and unknown_error. Raw exception
details stay in server-side developer logs; request_id is returned for support.
Safe idempotent browser/CLI reads retry once when retryable is true.
Older RAG responses may identify an empty or missing collection with the legacy
codes collection_empty or collection_not_found. The PWA maps both to its
Open indexing action. API clients should use category, code,
recovery, and retryable rather than matching localized message text.
Create codes only from a real loopback peer. Admin credentials from LAN do not qualify:
POST /v1/pairing/start
{"scopes":["chat","read_private"],"ttl_seconds":300,"device_ttl_days":null}The clear code is returned once. A LAN/VPN client claims it with
POST /v1/pairing/claim {"code":"ABCD-EFGH","device_name":"Phone"}.
Claim attempts are limited to five per client per five minutes. The returned
response sets an HttpOnly; SameSite=Strict cookie scoped to /api and
contains device metadata but no bearer. Only keyed hashes are persisted. Codes
expire after 60–900 seconds and are single-use.
POST /v1/pairing/new-device {"device_name":"Phone"} is the first-run
alternative for a LAN/VPN browser. It issues a revocable chat/web-only
cookie, so the device can chat and search the web without importing shared
history, RAG knowledge, memory, or host files.
The PWA sends that cookie automatically and does not persist newly claimed
device tokens in localStorage or sessionStorage. The CLI continues to use
an older bearer only in the X-TrinaxAI-Device-Token header (for example via
TRINAXAI_DEVICE_TOKEN); GET /v1/pairing/me never copies that header into a
response cookie. The registry and hashing secret are separate atomic
mode-0600 files. Pairing authenticates a device/capability; it is not a
multi-user account or authorization delegation system.
GET /v1/pairing/me and DELETE /v1/pairing/me accept the cookie; the legacy
header remains supported for CLI clients and for the explicit PWA migration, after which the PWA removes the legacy stored value. GET /v1/pairing/devices,
PATCH /v1/pairing/devices/{id} and DELETE /v1/pairing/devices/{id} are
loopback-only operations. Revocation takes effect for FastAPI and the Ollama
gateway.
PATCH /v1/pairing/devices/{id} replaces one active device's permissions:
PATCH /v1/pairing/devices/0123456789abcdef01234567
{"scopes":["chat","read_private","agent"]}The list must be non-empty and contain only the six grantable scopes; system
and agent_yolo return 422, an unknown or revoked device returns 404, and a
device or remote admin token cannot call it (403). The change applies to the
next request, in FastAPI and in the PWA gateway.
Basically compatible with OpenAI chat completions:
{
"model": null,
"messages": [{"role": "user", "content": "How does authorization work?"}],
"stream": true,
"collections": ["default"],
"mode": "knowledge",
"think": true,
"keep_alive": "10m",
"aggressive_quant": false
}messages accepts 1–100 system, user, or assistant objects, requires a
user message, limits each message to 100,000 characters and the conversation to
200,000 characters. A null/empty model enables routing; an Ollama model name is also accepted. Up to 50 collection IDs
are accepted. mode is one of:
auto: classify whether indexed evidence is needed;knowledge: always retrieve and answer only from retrieved evidence, or return an explicit no-index/no-relevant-evidence response;model: do not retrieve, even when the wording resembles a document query.
In automatic conversational routing, the client selects the capability before calling its endpoint: a direct public lookup such as Search who TrinaxCode is uses web search; a question about the user's own projects such as What Python programs have I made? uses knowledge; and complex multi-source research uses /v1/research. Agent execution is never inferred from wording: enter Agent mode explicitly before calling /v1/agent. route_model() only selects the model and does not select web, RAG, research, or Agent.
think is an explicit client preference. false disables provider reasoning;
true permits it only for analytical or complex tasks; omitted uses
TRINAXAI_THINKING_MODE. Simple turns never need the reasoning channel.
With stream=false, the result is an OpenAI-shaped chat.completion JSON (with choices and trinaxai.sources) plus
trinaxai metadata (mode, rag_used, abstained, collections, result count,
request ID and sources) and an explicitly estimated usage. abstained is
true for deterministic no-index, empty-collection, or no-relevant-evidence
responses, and for a clear model refusal to answer from the supplied context.
With stream=true, the response is text/event-stream and SSE emits
the plan, content deltas, sources, retrieval metadata, estimated usage, timing,
post-stream quality heuristics, and finally [DONE]. These heuristics detect
likely omissions or malformed output; they are not a compiler, type checker,
browser test or proof of correctness. A missing index produces an informative
response without sources rather than an HTTP failure.
Possible SSE events:
data: {"trinaxai":{"model":"...","project":null,"phase":"retrieving"}}
data: {"choices":[{"delta":{"content":"text"}}]}
data: {"trinaxai_sources":[{"file":"...","snippet":"...","score":0.8}]}
data: [DONE]
GET|PUT|DELETE /v1/settings/web-search reads, updates or resets host-local search settings. POST /v1/settings/web-search/test tests the selected provider from the backend, and DELETE /v1/settings/web-search/credentials/brave explicitly removes the managed Brave key. Every route is host-only and requires verified loopback provenance. Secret values are write-only and never serialized.
{
"query": "Compare persistence mechanisms",
"collections": ["default"],
"depth": 2,
"think": true,
"model": null,
"keep_alive": "10m",
"aggressive_quant": false
}depth is clamped to 1–3 and POST /v1/research returns JSON fields answer,
sub_questions, sources, passes, model, web_search, web_provider and
search_query. Web research searches first, then attempts to read a bounded set
of pages. Each web source reports content_scope: "full_page" when bounded page
text was extracted or "snippet_only" with fetch_error when only the search
excerpt was available. URL fetches reject credentials, non-HTTP schemes,
private/loopback/link-local destinations and unsafe redirects, resolve once and
connect to the validated public IP to limit SSRF/DNS-rebinding exposure, and cap
redirects, bytes, text and time. full_page still means bounded extracted text,
not a complete archival copy.
POST /v1/research/preflight accepts the same request shape and checks Ollama,
the selected model, local collections, and web-provider readiness without
running the full research task.
If a web provider is unavailable, the research response is degraded rather than an empty error: it explains the classified reason, returns no fabricated web sources, and labels any fallback answer as general local-model knowledge.
GET /v1/sources?collection=default
GET /v1/sources/{collection}/{file}/chunks?source_id=ID&limit=50&offset=0&q=text
DELETE /v1/sources/{collection}/{file}?source_id=ID
DELETE /v1/sources/{collection}File paths may contain slashes and must be URL-encoded. A collection can contain
the same relative path from several independently synchronized roots; use the
source_id returned by the source list to address one root without deleting its
namesake. Chunk limits are clamped to 1–500. Deleting all sources from the
default collection is rejected.
GET /collections
POST /collections {"name":"Documentation"}
PATCH /collections/{collection_id} {"name":"New name"}
DELETE /collections/{collection_id}Collection listing returns {ok, collections}. The default collection cannot be deleted. Removing collection metadata and clearing its indexed sources are distinct operations.
POST /system/index-upload receives multipart/form-data:
| Field | Type / default |
|---|---|
files |
One or more files; required. |
label |
Text; import. |
collection_id |
Text; default. |
embed_model |
Optional text. |
aggressive_quant |
Boolean; false. |
watch_id |
Optional text for synchronized imports. |
The response contains the job_id, local path, saved/skipped files, bytes, and collection while indexing continues in the background:
GET /system/index-jobs/{job_id}
POST /system/index-jobs/{job_id}/cancel
POST /system/index-jobs/{job_id}/retry
DELETE /system/index-imports {"path":"...","collection_id":"..."}Job status persists across frontend reconnects and reports phase, elapsed time, recent activity, page/chunk/batch counters, and whether progress is exact. Failed or cancelled jobs can be retried while their uploaded source remains available. The delete operation only accepts safe paths inside managed local imports.
GET /v1/memory
POST /v1/memory {"text":"...","tags":["style"],"kind":"preference","provenance":"manual","expires_at":null}
PATCH /v1/memory/{memory_id} {"text":"...","kind":"decision","clear_expiration":true}
DELETE /v1/memory/{memory_id}
POST /v1/memory/context {"query":"current turn","max_entries":8}
POST /v1/memory/refresh {"scope":null}
GET /v1/memory/summary
POST /v1/watch/start {"paths":["/path"],"collection":"default"}
POST /v1/watch/stop
GET /v1/watch/status
POST /v1/usage {"engine":"ollama","model":"...","est_tokens":100}
GET /v1/statsMemory kinds are fact, preference, decision, and note; provenance is
manual or inferred, and expired entries are excluded. /context returns
only active entries relevant to the query. PWA, CLI, and backend delimit those
entries as untrusted data rather than instructions. The global summary is a
human-facing overview only and is never injected into a turn. The PWA confirms
deletion and exposes edit, provenance, kind, and expiry controls. Its local
tc-project-memory scratchpad is not prompt context.
Creating, editing, and deleting memories updates the summary immediately from the recent active entries. POST /v1/memory/refresh synthesizes a summary with the model only when explicitly requested.
The watcher requires watchdog and existing directories. Usage data never leaves local storage.
GET /app-staterequires authorization and returns{ok, schema_version:2, revision, values}with an ETag of the form"trinaxai-app-state-v2-N";If-None-Matchmay return304.PUT /app-statesendsschema_version:2, a stabledevice_id, thebase_revision, and orderedset/deleteoperations. The server applies the batch atomically only when the base revision matches. A stale writer receives409plus the current revision/values and must merge/retry.If-Matchmay carry the same revision.- Legacy
{"values":{...}}is accepted only with optimistic concurrency (or against a pristine revision-zero store); otherwise the API returns428. DELETE /app-staterequires authorization plusX-TrinaxAI-Confirm: reset-app-stateand advances the revision so an offline pre-reset client cannot silently restore deleted state.
The default state limit is 6 MiB (TRINAXAI_APP_STATE_MAX_BYTES).
POST /attachments accepts one authorized multipart upload and stores it under
storage/chat_attachments/ so synchronized conversations can open it in
another authorized browser. It returns the ID, name, size, MIME type, and a
server: storage key. GET and DELETE require the same authorization and are
rate limited. POST /attachments/{attachment_id}/open asks the host OS to open
the stored file with its default application and is also authorized/rate limited.
Unknown response types are downloads with nosniff. Defaults are 512 MiB per
file, 4 GiB total, and 1,000 retained files. Chat history stores the attachment
reference, not a second persistent copy of the full extracted text.
POST /v1/agent streams tool-use events over SSE; dangerous calls pause at an
approval_request until POST /v1/agent/approve accepts or denies it.
The model decides whether to answer directly or call one or more tools, orders
dependent calls, and synthesizes their results. Web search, deep research,
memory, indexed-document search, and collection discovery are available by
default. The web_search, deep_research, and knowledge_search request
booleans can restrict availability; setting one never forces its execution.
POST /v1/agent/cancel stops a running session owned by the same identity. Approvals
must include both the session_id from the stream's start event and
the approval_id, and must use the same authenticated identity that opened the stream.
Requested workspaces must be descendants of TRINAXAI_AGENT_WORKSPACE_ROOTS (configured
index roots and the repository are the fallback), and filesystem roots are
rejected. HTTP yolo is off by default and, even when enabled, only works over a
real loopback transport with the agent_yolo capability. Remote agent callers
always approve dangerous actions individually.
File tools reject symlink/path escapes. On Linux, shell commands require
networkless bubblewrap and expose only the workspace as writable; on unsupported
hosts terminal execution fails closed unless the operator explicitly accepts
full user-level access with TRINAXAI_AGENT_ALLOW_UNSANDBOXED_COMMANDS=1.
POST /documents/extract accepts one multipart file and returns {ok, name, text, chars, truncated}. PDF, DOCX, and PPTX have specialized extraction; text formats are decoded directly. Extraction does not index or persist the document, so an unauthenticated peer may use it from the local network or VPN. Public-network callers still require the chat scope. Limits are documented in CONFIGURATION.md.
GET /v1/voice/capabilities
POST /v1/voice/stt multipart: file, lang=en
POST /v1/voice/tts {"text":"Hello","lang":"en"}TTS returns audio bytes with the detected content type. STT/TTS return 501 if no suitable local backend is installed.
| Endpoint | Result |
|---|---|
POST /system/shutdown |
Stop AI while leaving the PWA available. |
POST /system/startup |
Start AI services. |
POST /system/stop-all |
Stop all services; only loopback recovery remains at https://localhost:3334 and LAN access stays closed until local start. |
POST /system/reload |
Reload the persisted index in memory. |
POST /system/self-test |
Check Ollama, embeddings, and RAG/index state. |
GET /health |
Models, active/detected profile, hardware, recommendations, collections, index state, and feature flags. |
GET /ready |
Same status payload; returns 503 until Ollama responds. |
GET /resources |
RAM and detected VRAM values in bytes, plus the hardware snapshot. |
FastAPI errors use {"detail": {...}, "error": {...}, "request_id": "..."}.
detail may also include a safe legacy code or field hint for client
compatibility; it never contains raw exception text. Retryable responses include
Retry-After: 1. The most relevant statuses are 400 (invalid input), 403
(authorization), 404, 409 (confirmation required), 413 (limit), 422
(validation/extraction), 429, 500, 501, and 503. See Troubleshooting and
configuration for recovery, limits, and network settings.
