OrbitDocs packages are coming to npm soon. Until then, run it from the GitHub repo →
AI

Providers and keys

Connect Ask AI to OpenAI, Anthropic, Google or any OpenAI-compatible endpoint, keep the key on your server, and tune the rate limit.

Ask AI calls an LLM with your own key. You choose the provider and model in orbitdocs.config.ts. The key stays in an environment variable on the server that serves the docs.

Choose a provider

orbitdocs.config.ts
ai: {
  provider: 'anthropic',
  model: 'claude-sonnet-5-5',
  apiKeyEnv: 'ANTHROPIC_API_KEY',
},

Create a key in the Anthropic Console.

providerCallsbaseUrl
anthropicAnthropic's APIOptional: a proxy or gateway URL
openaiOpenAI's APIOptional: a proxy or gateway URL
googleGoogle's Gemini APIOptional: a proxy or gateway URL
openai-compatibleAny OpenAI Chat Completions serverRequired

model is passed to the provider as is. Use any model id your provider accepts. A fast, inexpensive model is usually enough: answers come from a few passages, not from the model's own knowledge.

The key variable must be set, even for local models

Ask AI fails with <VAR> is not set (API key for Ask AI) when apiKeyEnv is empty. Servers that need no key, such as Ollama, still need the variable: set it to any value.

Keep the key safe

  • The config holds only the variable name. .orbitdocs/ai.json and the build hold no key.
  • The key is read on the server for each answer and never sent to the browser.
  • To rotate it, change the environment variable and restart the server. No rebuild is needed.

Where to set it depends on what serves the docs:

Served bySet the key in
Nest (mountOrbitDocs)Your Nest app's environment, or pass ai: { env } (below).
Next server modeThe environment of next start: your Vercel project, Docker container or host.
Self-hosted platformThe project's Settings → Environment. See Site environment variables.

Rate limits

Each reader can ask 30 questions per 10 minutes. The 31st gets 429 Too many questions. Try again in a few minutes. Readers are counted by IP address, taken from X-Forwarded-For, then X-Real-IP.

With Nest you can change the limit:

src/main.ts
mountOrbitDocs(app, {
  root: join(__dirname, '../docs/out'),
  path: '/docs',
  ai: { rateLimit: 60, env: process.env },
});

mountOrbitDocs ai options:

  • rateLimit: questions per reader per 10 minutes (default 30).
  • env: where to read apiKeyEnv from (default process.env).
  • ai: false turns Ask AI and MCP off even when the build has an AI index.

In Next server mode the limit is fixed at 30.

Without a proxy, readers share one limit

The limit counts by the X-Forwarded-For or X-Real-IP header. When no proxy sets them, every reader is counted as the same one, and 30 questions per 10 minutes apply to everyone together. Put the docs behind a proxy or load balancer that sets X-Forwarded-For.

Limits are kept in memory per server process. Several instances each keep their own count. Your provider's own limits and spending caps apply on top; set a monthly budget with your provider.

Track questions with onAsk

createAi from @orbitdocs/ai takes an onAsk callback, called for every question with the number of passages found. Zero passages means your docs don't cover the question, which tells you what to write next. The self-hosted platform uses it for its built-in analytics (see Analytics).

mountOrbitDocs and orbitProxy don't expose onAsk. To use it, serve the AI routes with your own handler:

src/docs-ai.ts
import { readFileSync } from 'node:fs';

import { type AiManifest, createAi } from '@orbitdocs/ai';

const manifest = JSON.parse(readFileSync('docs/out/orbitdocs-ai.json', 'utf8')) as AiManifest;

export const ai = createAi({
  manifest,
  rateLimit: 30,
  onAsk: ({ question, sources }) => {
    console.log(JSON.stringify({ event: 'docs_question', question, sources }));
  },
});

// ai.handle(request: Request) answers /_ai/* and /mcp[/<api>] and returns undefined for anything else.

createAi options:

OptionDefault
manifest(required)The AI index from the build (orbitdocs-ai.json).
accessnoneThe access manifest (orbitdocs-access.json), so answers respect private docs.
authnoneThe handler from createAuth in @orbitdocs/auth, to identify readers.
envprocess.envWhere the API key is read.
rateLimit30Questions per reader per 10 minutes.
onAsknone({ question, sources, request }) => void. Errors in it are ignored.
fetchglobal fetchMakes every outbound request: the AI provider and the API MCP's calls to your API. Pass one that limits where requests may go.

Without access and auth, every page is treated as public. Pass both when your docs are private.

createAuth from @orbitdocs/auth takes the same fetch option, for its OpenID Connect, app-session, personalization and audit-webhook requests. The self-hosted platform passes both a fetch that refuses private addresses (see Outbound requests).

Next steps

Last updated on

On this page