# How Open SEO Configures AI/LLM Integration with OpenRouter: A Complete Technical Guide

> Learn how Open SEO configures AI LLM integration with OpenRouter. Discover the three-layer architecture for seamless model connection and usage tracking.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-08-07

---

**Open SEO configures its AI/LLM integration with OpenRouter using a three-layer architecture that reads environment variables through [`runtime-env.ts`](https://github.com/every-app/open-seo/blob/main/runtime-env.ts), constructs a configured model instance via `buildChatAgentModel` in [`openrouter.ts`](https://github.com/every-app/open-seo/blob/main/openrouter.ts), and consumes the model in chat agents with usage tracking and billing.**

The open-source Open SEO repository (`every-app/open-seo`) powers its intelligent chat assistants—including the onboarding "Sam" assistant and the SAM research agent—through OpenRouter's unified API. This integration leverages the official `@openrouter/ai-sdk-provider` package to enable flexible model selection, zero-data-retention policies, and accurate per-request cost tracking.

## Runtime Environment Configuration

The integration begins with secure secret management in [`src/server/lib/runtime-env.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/runtime-env.ts). This module provides typed helpers that validate required environment variables at runtime.

The `getRequiredEnvValue` function throws an error if the OpenRouter API key is missing, while `getOptionalEnvValue` allows the model identifier to fall back to defaults:

```ts
const apiKey = await getRequiredEnvValue("OPENROUTER_API_KEY");
const modelId = await getOptionalEnvValue("OPENROUTER_MODEL");

```

These values are declared in [`src/env.d.ts`](https://github.com/every-app/open-seo/blob/main/src/env.d.ts) for TypeScript support within the Cloudflare Worker environment, ensuring type safety across both production deployments and local development using `process.env`.

## OpenRouter Model Factory

The core configuration lives in [`src/server/lib/openrouter.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/openrouter.ts), which exports `buildChatAgentModel` to instantiate a Vercel AI SDK-compatible `LanguageModelV3` object. This factory function centralizes all OpenRouter-specific settings.

### Default Model and Environment Overrides

By default, Open SEO uses the `minimax/minimax-m3` model. Developers can override this by setting the optional `OPENROUTER_MODEL` environment variable without modifying source code:

```ts
const DEFAULT_CHAT_AGENT_MODEL = "minimax/minimax-m3";

export function buildChatAgentModel(
  apiKey: string,
  modelId?: string,
): LanguageModelV3 {
  return createOpenRouter({ apiKey })(modelId ?? DEFAULT_CHAT_AGENT_MODEL, {
    // Configuration options...
  });
}

```

### Usage Tracking and Cost Calculation

The factory enables `usage: { include: true }` to force OpenRouter to return the exact USD cost of each generation. This metadata is later extracted via `openRouterCostUsd` in [`src/server/lib/chatAgent.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/chatAgent.ts) to debit the organization's credit pool.

### Reasoning and Provider Configuration

The model configuration implements several enterprise-grade policies:

- **Reasoning Channel**: `reasoning: { effort: "medium" }` generates chain-of-thought output that remains separate from the visible user-facing answer
- **Provider Fallbacks**: `order: ["together", "atlas-cloud/fp8"]` specifies priority providers if the primary fails
- **Zero-Data-Retention**: `zdr: true` ensures prompts are never stored on the provider side
- **Fallback Allowance**: `allow_fallbacks: true` prevents hard outages during provider throttling

```ts
return createOpenRouter({ apiKey })(modelId ?? DEFAULT_CHAT_AGENT_MODEL, {
  usage: { include: true },
  reasoning: { effort: "medium" },
  provider: {
    order: ["together", "atlas-cloud/fp8"],
    zdr: true,
    allow_fallbacks: true,
  },
});

```

## Chat Agent Implementation

Both the **OnboardingChatAgent** and **SamChatAgent** consume the OpenRouter integration through `getChatAgentModel`. These agents in [`src/server/features/onboarding/OnboardingChatAgent.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/onboarding/OnboardingChatAgent.ts) and [`src/server/features/sam/SamChatAgent.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/sam/SamChatAgent.ts) stream responses using the Vercel AI SDK's `streamText` function.

After each generation completes, the agents extract billing data from `providerMetadata.openrouter.usage.cost` and track spending:

```ts
const model = await getChatAgentModel();
const result = streamText({
  model,
  system: buildSystemPrompt(project.domain),
  messages: await convertToModelMessages(this.messages),
  maxOutputTokens: 4000,
  onFinish: async (event) => {
    const costUsd = event.steps.reduce(
      (sum, step) => sum + openRouterCostUsd(step.providerMetadata),
      0,
    );
    await trackUsageCreditSpend({ /* billing details */ });
  },
});

```

## Complete Implementation Example

To use the OpenRouter integration in your own feature implementation:

```ts
// Asynchronous initialization (recommended for serverless)
import { getChatAgentModel } from "@/server/lib/openrouter";

async function generateSeoAnalysis(messages: any[]) {
  const model = await getChatAgentModel();
  
  const { text } = await streamText({
    model,
    messages,
    maxOutputTokens: 2000,
  });
  
  return text;
}

// Synchronous variant when credentials are cached
import { buildChatAgentModel } from "@/server/lib/openrouter";

const model = buildChatAgentModel(
  process.env.OPENROUTER_API_KEY!,
  "anthropic/claude-3.5-sonnet" // Optional override
);

```

## Summary

Open SEO's AI/LLM integration with OpenRouter provides a production-ready foundation for conversational AI features:

- **Flexible model selection** via the `OPENROUTER_MODEL` environment variable without code changes
- **Strict privacy controls** through Zero-Data-Retention (`zdr`) and configurable reasoning channels
- **High availability** via provider fallbacks across Together AI and Atlas Cloud
- **Accurate billing** by extracting real-time USD costs from OpenRouter's usage metadata
- **Type-safe configuration** using Cloudflare Worker environment types in [`src/env.d.ts`](https://github.com/every-app/open-seo/blob/main/src/env.d.ts)

## Frequently Asked Questions

### What environment variables are required to configure OpenRouter in Open SEO?

You must set `OPENROUTER_API_KEY` as a required variable in your Cloudflare Worker environment or `.env` file. Optionally, set `OPENROUTER_MODEL` to override the default `minimax/minimax-m3` model with any slug supported by OpenRouter.

### How does Open SEO handle OpenRouter outages or rate limiting?

The configuration in [`src/server/lib/openrouter.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/openrouter.ts) sets `allow_fallbacks: true` and defines a provider `order` array that falls back to `together` and `atlas-cloud/fp8` if the primary provider fails. This ensures chat agents remain operational during individual provider outages.

### How is usage billing calculated for AI-generated content?

OpenRouter returns exact USD costs in the response metadata when `usage: { include: true }` is configured. The `openRouterCostUsd` helper in [`src/server/lib/chatAgent.ts`](https://github.com/every-app/open-seo/blob/main/src/server/lib/chatAgent.ts) aggregates costs across streaming steps, and the `trackUsageCreditSpend` function debits the organization's credit pool accordingly.

### Can I change the AI model without redeploying the application?

Yes. Because `buildChatAgentModel` accepts an optional `modelId` parameter that defaults to the `OPENROUTER_MODEL` environment variable, you can switch models instantly by updating the environment variable. If no override is provided, the system falls back to `minimax/minimax-m3`.