# How to Add a Custom AI Provider to OmniRoute: A Step-by-Step Guide for the Registry-Based Architecture

> Learn how to add a custom AI provider to OmniRoute with this step by step guide. Explore registry based architecture and custom executors for non standard APIs.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-08-13

---

**You add a custom AI provider to OmniRoute by creating a provider module with a `RegistryEntry`, registering it in the providers index, and optionally implementing custom executors or translators for non-standard APIs.**

OmniRoute routes requests to AI back-ends through a centralized **Provider Registry** that serves as the single source of truth for all provider configuration. This article walks through the exact implementation steps based on the OmniRoute source code in `diegosouzapw/OmniRoute`, covering the registry system in [`open-sse/config/providerRegistry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/config/providerRegistry.ts) and the module structure used throughout the codebase.

## Understanding the Provider Registry Architecture

The registry system in OmniRoute is built around a lazy-loaded `REGISTRY` object that maps provider IDs to their full configuration. According to the source code, this registry is imported and re-exported from [`open-sse/config/providers/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/config/providers/index.ts):

```ts
export { REGISTRY } from "./providers/index.ts";
import { REGISTRY } from "./providers/index.ts";

```

*(source: [providerRegistry.ts line 11-13](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/config/providerRegistry.ts#L11-L13))*

The registry powers helper functions such as `getRegistryEntry()`, `getProviderCategory()`, and `generateModels()` that the routing, combo-logic, and resilience layers consume. When you add a custom AI provider, you integrate directly into this system so your back-end participates fully in OmniRoute's request pipeline.

## Step 1: Create the Provider Module with RegistryEntry

Every provider in OmniRoute is defined by a `RegistryEntry` object that specifies its identity, authentication, endpoint configuration, and available models. Create a new directory under `open-sse/config/providers/` with your provider's identifier.

**Minimal provider definition** ([`open-sse/config/providers/mycustom/provider.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/config/providers/mycustom/provider.ts)):

```ts
import { RegistryEntry } from "../../../config/providerRegistry.ts";

export const myCustomProvider: RegistryEntry = {
  id: "mycustom",
  format: "openai",
  baseUrl: "https://api.mycustom.ai/v1",
  authType: "apikey",                     // or "oauth"
  models: [
    { id: "mycustom/gpt-4", name: "GPT-4", maxTokens: 8192 },
    { id: "mycustom/claude", name: "Claude", maxTokens: 10000 },
  ],
  // Optional OAuth block (remove if authType is "apikey")
  // oauth: {
  //   clientIdEnv: "MYCUSTOM_CLIENT_ID",
  //   clientSecretEnv: "MYCUSTOM_CLIENT_SECRET",
  //   tokenUrl: "https://auth.mycustom.ai/token",
  // },
};

```

The `RegistryEntry` shape includes:
- **id**: Unique provider identifier used in routing
- **format**: Expected request/response format ("openai" or custom)
- **baseUrl**: Root endpoint for the provider's API
- **authType**: Either `"apikey"` or `"oauth"`
- **models**: Array of model definitions with IDs, display names, and token limits
- **oauth**: Optional credential configuration for OAuth flows

## Step 2: Register the Module in the Providers Index

Add your new module to [`open-sse/config/providers/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/config/providers/index.ts) to include it in the aggregated `REGISTRY` object:

```ts
export * from "./mycustom/provider.ts";

```

This single export statement makes your provider discoverable throughout OmniRoute. The index file builds the runtime maps that power `getRegistryEntry()` and related utilities.

## Step 3: Optionally Implement a Custom Executor

Most providers work with OmniRoute's generic `HttpExecutor`. Only implement a custom executor if your provider requires non-standard request handling such as multipart uploads, custom streaming protocols, or special header injection.

**Custom executor skeleton** ([`open-sse/executors/mycustomExecutor.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/executors/mycustomExecutor.ts)):

```ts
import { BaseExecutor } from "./BaseExecutor.ts";

export class MyCustomExecutor extends BaseExecutor {
  async execute(request: any): Promise<any> {
    // custom fetch logic, headers, streaming, etc.
    return super.execute(request);
  }
}

```

Extend `BaseExecutor` and override `execute()` to inject provider-specific behavior while maintaining compatibility with OmniRoute's request lifecycle.

## Step 4: Optionally Implement a Translator

If your provider's request or response schema differs from the OpenAI-compatible format, add a translator under `open-sse/translator/`. Translators convert between the provider's native format and OmniRoute's internal `ChatRequest` / `ChatResponse` shapes.

**Translator skeleton** ([`open-sse/translator/mycustomTranslator.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/translator/mycustomTranslator.ts)):

```ts
import { Translator } from "./BaseTranslator.ts";

export class MyCustomTranslator extends Translator {
  toProvider(request) { /* map OmniRoute request → provider format */ }
  fromProvider(response) { /* map provider response → OmniRoute format */ }
}

```

## Step 5: Configure OAuth Credentials via Environment Variables

For OAuth-based providers, the registry automatically resolves credentials from environment variables. In [`providerRegistry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/providerRegistry.ts) lines 63-71, the OAuth resolution logic reads from the `oauth` section of your `RegistryEntry`:

```ts
if (entry.oauth) {
  if (entry.oauth.clientIdEnv) {
    p.clientId = process.env[entry.oauth.clientIdEnv] || entry.oauth.clientIdDefault;
  }
  if (entry.oauth.clientSecretEnv) {
    p.clientSecret =
      process.env[entry.oauth.clientSecretEnv] || entry.oauth.clientSecretDefault;
  }
  // …
}

```

*(source: [providerRegistry.ts line 63-71](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/config/providerRegistry.ts#L63-L71))*

Define `clientIdEnv` and `clientSecretEnv` with the environment variable names you want operators to use, and optionally provide `clientIdDefault` / `clientSecretDefault` fallback values.

## Step 6: Verify Registry Helper Functions

Once registered, your provider automatically populates the helper functions in [`providerRegistry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/providerRegistry.ts). The `getRegistryEntry()` function resolves providers by ID or alias:

```ts
export function getRegistryEntry(provider: string): RegistryEntry | null {
  ensureByAliasPopulated();
  return REGISTRY[provider] || _byAlias.get(provider) || null;
}

```

*(source: [providerRegistry.ts line 77-81](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/config/providerRegistry.ts#L77-L81))*

Similarly, `getProviderCategory()` classifies your provider for routing decisions:

```ts
export function getProviderCategory(provider: string): "oauth" | "apikey" {
  const entry = getRegistryEntry(provider);
  if (!entry) return "apikey";
  return entry.authType === "apikey" ? "apikey" : "oauth";
}

```

*(source: [providerRegistry.ts line 71-76](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/config/providerRegistry.ts#L71-L76))*

These functions ensure your custom AI provider integrates with OmniRoute's combo routing, circuit-breaker resilience, and cooldown logic without additional configuration.

## Step 7: Add Unit Tests

OmniRoute requires test coverage for all production code changes. Create tests in `tests/unit/` that verify registration, model discovery, and any custom logic:

```ts
import { getRegistryEntry } from "@/open-sse/config/providerRegistry";

test("mycustom provider is registered", () => {
  const entry = getRegistryEntry("mycustom");
  expect(entry).not.toBeNull();
  expect(entry?.models?.length).toBeGreaterThan(0);
});

```

Cover both success paths and edge cases such as missing OAuth credentials or invalid model IDs.

## Step 8: Update Documentation

Add your provider to the generated provider reference at [`docs/reference/PROVIDER_REFERENCE.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/docs/reference/PROVIDER_REFERENCE.md) using the standard link format. This makes your back-end discoverable to operators and automated tooling.

## Key Files Reference

| File | Purpose |
|------|---------|
| [`open-sse/config/providerRegistry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/config/providerRegistry.ts) | Core registry logic, lazy-loaded helper maps, OAuth env resolution |
| [`open-sse/config/providers/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/config/providers/index.ts) | Aggregates all provider modules into the `REGISTRY` object |
| `open-sse/config/providers/<your-provider>/provider.ts` | Your custom `RegistryEntry` definition |
| `open-sse/executors/<your-provider>Executor.ts` | Optional: custom executor for non-standard request handling |
| `open-sse/translator/<your-provider>Translator.ts` | Optional: translator for non-OpenAI schema |
| `tests/unit/<your-provider>-provider.test.ts` | Required: unit tests for registration and functionality |

## Summary

- **The Provider Registry** in [`open-sse/config/providerRegistry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/config/providerRegistry.ts) is the single source of truth for all AI back-ends in OmniRoute
- Create a `RegistryEntry` in `open-sse/config/providers/<id>/provider.ts` with your provider's ID, URL, auth type, and models
- Register the module by exporting it from [`open-sse/config/providers/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/config/providers/index.ts)
- Implement **custom executors** only for non-standard HTTP handling, and **custom translators** only for non-OpenAI request/response formats
- OAuth credentials resolve automatically from environment variables defined in the `oauth` block
- Helper functions like `getRegistryEntry()` and `getProviderCategory()` expose your provider to routing and resilience layers
- Unit tests and documentation updates are required for production acceptance

## Frequently Asked Questions

### What is the minimum code required to add a custom AI provider to OmniRoute?

The minimum implementation requires two files: a provider module with a `RegistryEntry` export, and an index export. Create [`open-sse/config/providers/mycustom/provider.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/config/providers/mycustom/provider.ts) defining your `RegistryEntry`, then add `export * from "./mycustom/provider.ts"` to [`open-sse/config/providers/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/config/providers/index.ts). This registers your provider for routing through the standard HTTP executor with OpenAI-compatible format.

### When do I need a custom executor versus using the default HttpExecutor?

You need a custom executor only when your provider requires non-standard request handling that the generic `HttpExecutor` cannot accommodate. This includes multipart upload protocols, custom streaming implementations, special header injection, or request signing requirements. Most providers work with the default executor and require only a `RegistryEntry` configuration.

### How does OmniRoute handle authentication for custom providers?

OmniRoute supports two authentication types defined in the `authType` field: `"apikey"` for API key authentication passed in headers, and `"oauth"` for OAuth 2.0 flows. For OAuth providers, the `oauth` block in your `RegistryEntry` specifies environment variable names (`clientIdEnv`, `clientSecretEnv`) that the registry resolves at runtime according to the logic in [`providerRegistry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/providerRegistry.ts) lines 63-71.

### Can I add a provider that uses a completely non-OpenAI API format?

Yes. Implement a custom translator in `open-sse/translator/` that extends the base `Translator` class. Your translator must implement `toProvider()` to map OmniRoute's internal `ChatRequest` to your provider's native format, and `fromProvider()` to map the response back to OmniRoute's `ChatResponse` shape. Register the translator in your provider configuration so the routing layer applies it automatically.