Skip to main content
Source Code: src/gaia/api/
Import: from gaia.api.openai_server import app

Detailed Spec: spec/api-server

7.1 OpenAI-Compatible API

Purpose: Expose GAIA agents as OpenAI-compatible REST API.

What a request delivers to the agent

Each request gets a fresh agent, loaded with the conversation the client sent:
  • History — every message before the final one becomes the agent’s conversation history. The final message must be from the user, or the request fails with 400.
  • System instructions — system and developer messages are added after the agent’s own system prompt; they don’t replace it.
  • Content parts — content may be a string or an array of parts. Text parts are joined with newlines. Other part types (image_url, input_audio, file) fail with a 400 that names the type.
  • Sampling — temperature, top_p, and max_tokens apply to every model call the agent makes while answering, so max_tokens caps each call, not the whole answer. Omit them to keep the agent’s defaults.

CORS

Browser (cross-origin) access is limited to localhost / 127.0.0.1 origins by default. Non-browser clients (the OpenAI SDK, curl, VSCode) are unaffected — CORS only gates requests made from web pages. To let a web app on another origin call the API, list its origin in GAIA_API_CORS_ORIGINS (comma-separated):
Setting the variable to * opens the API to all origins but disables credentialed requests — wildcard origins combined with credentials are forbidden by the Fetch spec and would let any website the user visits call the local API.

Authentication

POST /v1/chat/completions runs the agent loop, so a caller that reaches it can make the agent read files and call tools. Set GAIA_API_KEY to require a key:
Browser requests from an origin outside the CORS allowlist are refused with 403 before the agent runs, whether or not a key is set. The /v1/<agent>/* relay is stricter: it is disabled (503) until GAIA_API_KEY is set. Agents served with --api through the framework’s generic server (gaia.agents.base.server.AgentServer) apply the same policy to every route except /health.
The VSCode extension sends a fixed key (Bearer gaia). It keeps working while GAIA_API_KEY is unset; once you set a key, the extension’s requests get 401.

High-risk tools are refused

Tools that mutate the machine — run_shell_command, write_file, edit_file and the rest of TOOLS_REQUIRING_CONFIRMATION, plus anything an agent adds via CONFIRMATION_REQUIRED_TOOLS — require explicit user approval. That approval is GAIA’s backstop against prompt injection: a document, email, or web page the agent reads can talk the model into a destructive tool call the user never asked for. The OpenAI-compatible API is a one-shot request/response surface with no channel to ask the caller “allow this?”, so it refuses those tools rather than auto-approving them — the tool returns {"status": "denied", "error": "…requires explicit user approval…"} instead of executing. The refusal is not silent to the caller either: streaming clients get a tool_confirm_denied event and non-streaming responses are prefixed with the same message, naming the tool and what to do instead:
Run requests that need these tools through the Agent UI (gaia chat --ui), which is the surface with a real confirmation handshake (permission_request + POST /api/chat/confirm-tool).
For a trusted, single-user, localhost-only server you can opt out:
This disables the approval requirement for every high-risk tool, so any prompt injection that reaches the agent can run shell commands and write files. gaia api start prints a warning when it is set, and every approval taken this way is logged with the tool name. Never set it on a shared or network-exposed host. See Output Handlers.

7.2 Custom API Agent

Import: from gaia.agents.base.api_agent import ApiAgent ApiAgent is a mixin that already subclasses the core Agent; you only need to inherit from ApiAgent:

Registering new agents with the server

The API server exposes a static registry of agent models at src/gaia/api/agent_registry.py, not a runtime register() API. AGENT_MODELS ships one entry, gaia, the flagship agent. To expose another model, append an entry to AGENT_MODELS:
The server imports class_name lazily on the first request. init_params are passed to the agent’s constructor; gaia api start --debug, --show-prompts, --streaming, and --step-through inject GAIA_API_* env-vars that the registry merges into init_params at startup.
Runtime plug-in discovery is on the roadmap (see src/gaia/agents/registry.py), but the OpenAI-compatible server still requires editing AGENT_MODELS and restarting the process.

7.3 SSE Streaming

Import: from gaia.api.sse_handler import SSEOutputHandler

Events emitted

The API-layer SSEOutputHandler emits a strict subset used for OpenAI-style chat completions: chunk (streamed token deltas) and answer (final message). The Agent UI uses a richer handler in src/gaia/ui/sse_handler.py that emits additional events — tool_start, tool_end, tool_result, thinking, processing, error. When wiring SSE through the Agent UI routers you will see both surfaces.

Disconnects and failures

If the client disconnects mid-stream, the agent is told to stop and quits at its next step instead of finishing the turn. If the agent fails after streaming has started, the stream ends with an {"error": {"message": ..., "type": "server_error"}} chunk and then data: [DONE], since the HTTP status has already been sent.
The stop takes effect at the agent’s next step or tool, so a model call that is already in flight runs to completion and keeps its worker thread until it returns. Agent runs execute on the default thread pool, so a client that repeatedly starts and cancels long requests can keep those workers busy and delay unrelated requests.

Confirmation-gated tools are denied on this surface

This stream is one-way — the client has no channel to answer a permission request — so tools in the agent’s confirmation_required_tools() set (shell, file mutation, email send/delete) are denied rather than executed, and the reason is streamed as a tool_confirm_denied event. See High-risk tools are refused above for the full contract and the unattended-approval opt-out.