Sinatra Docs
Self-hosting

5. Supply LLM keys

Anthropic, OpenAI, OpenRouter, or any combination. How Sinatra picks which one to use per request.

The agent's intelligence comes from large language models. Sinatra is the orchestration around them — it doesn't run any model itself. You bring the keys.

Supported providers

  • Anthropic — bare model names (opus, sonnet, haiku) route here directly. Set ANTHROPIC_API_KEY.
  • OpenAI — accessed through OpenRouter using openai/... slugs (openai/gpt-5, openai/gpt-4o, openai/o3-mini, etc.). Set OPENROUTER_API_KEY.
  • OpenRouter — any model OpenRouter exposes, by slug (anthropic/claude-opus-4-7, meta-llama/llama-3.1-405b-instruct, google/gemini-2.5-pro, etc.). Set OPENROUTER_API_KEY.

You only need one to get going. Anthropic is the path of least resistance if you already have a Claude key. Reach for an OpenAI or OpenRouter key when you want to use a non-Anthropic model.

Direct OpenAI (OPENAI_API_KEY against api.openai.com) is not a v1 router feature — OpenAI models are served through OpenRouter today. Practically: you sign up at openrouter.ai, drop in an OpenRouter key, and use openai/<model> slugs in .sinatrarc.

How routing works

The model router reads the per-repo .sinatrarc to decide:

  • A bare name like opusAnthropic direct (mapped to the latest Claude model id at call time).
  • A slug like anthropic/claude-opus-4-7, openai/gpt-5, or meta-llama/llama-3.1-405b-instructOpenRouter.
  • model_provider: 'openrouter' in .sinatrarc forces OpenRouter even for bare names (useful if you want Claude through OpenRouter for unified billing).

Default model is sonnet if nothing is specified.

Examples

# .sinatrarc — pin Claude Opus via direct Anthropic
agent:
  model: opus
# .sinatrarc — pin OpenAI's GPT-5 via OpenRouter
agent:
  model: openai/gpt-5
# .sinatrarc — Claude through OpenRouter for unified billing
agent:
  model: opus
  model_provider: openrouter

Where keys live

In a multi-tenant deploy, per-tenant model credentials live in the database (tenantModelCredential table), envelope-encrypted with a Cloud KMS data key bound to the tenant ID as AAD. There's no global env var that holds these in production — each tenant supplies their own through the install flow.

For single-tenant evaluation with SANDBOX_PROVIDER=docker, the worker shells out to the host claude CLI for classifier calls and copies host claude/opencode credentials into the container for agent execution, so you don't need to seed a tenantModelCredential row at all. Just run claude login on the host.

For single-tenant production with SANDBOX_PROVIDER=daytona, you'll seed exactly one credential row per tenant. See the seeding script in docs/debugging.md (Stage 5) in the contributor docs.

What you set in env

In a single-tenant evaluation or self-host where you want to skip the DB seeding step:

SINATRA_LOCAL_DEV_KMS_KEY=<base64 32 bytes>     # local KMS for envelope-encrypted creds

Generate a random KMS key:

node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

In production you'd wire SINATRA_KMS_KEY to a real Cloud KMS resource instead.

Continue to Populate .env.production