# Testing OmniRoute locally: verify your AI gateway actually works before you trust it

> Source: <https://dev.to/ahmed_nafies_3a55c907115c/testing-omniroute-locally-verify-your-ai-gateway-actually-works-before-you-trust-it-1pde>
> Published: 2026-08-25 18:11:45+00:00

OmniRoute is a local AI gateway: it puts one OpenAI-compatible endpoint (`http://localhost:20128/v1`

) in front of hundreds of providers, then routes requests between them with automatic fallback. Install it, point your coding tool at `auto`

, and it "just works."

But "it starts" and "it works" are not the same thing. A gateway that answers `Hello!`

but falls over on streaming, tool calls, or a provider outage is decorative, not functional. This article is a checklist for proving a fresh local install actually does its job before you wire a coding agent into it.

```
npm install -g omniroute
omniroute
```

The server boots on port `20128`

, the dashboard opens at `http://localhost:20128`

, and the API base URL is `http://localhost:20128/v1`

.

If you use pnpm, add the native build flags:

`pnpm add -g omniroute@latest --allow-build=better-sqlite3 --allow-build=@swc/core`

.

A fresh install is zero-config: the free providers (OpenCode Free, Felo) are pre-wired into the `auto`

combo, so a brand-new server answers out of the box with no API key.

`omniroute doctor`

)
OmniRoute ships a health checker that runs without starting the server:

```
omniroute doctor
omniroute doctor --json        # machine-readable output
omniroute doctor --no-liveness # skip live checks
```

Treat this as the first gate. If `doctor`

reports a broken runtime, native module, or DB issue, fix it before touching the network.

Other useful CLI probes:

```
omniroute status            # offline dashboard: version, DB, tools, config
omniroute providers list    # what's actually connected
omniroute providers validate
```

A connected provider should show up in `/v1/models`

. If you've created an API key on the **Endpoints** page, query with it:

```
curl http://localhost:20128/v1/models \
  -H "Authorization: Bearer YOUR_KEY"
```

A non-empty list means the gateway can see providers. But don't stop here — a model list is not a working request.

The fastest end-to-end proof:

```
curl http://localhost:20128/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"auto","messages":[{"role":"user","content":"Hello!"}]}'
```

You can also call a specific free backend directly (e.g. `oc/...`

for OpenCode Free) to isolate one provider, then graduate to `auto`

and let the router pick.

Each response carries an `X-OmniRoute-Decision`

header naming the strategy, provider, and latency that served it — use it to confirm routing is doing what you expect.

Many tools depend on streaming. Send the same request with `"stream": true`

:

```
curl -N http://localhost:20128/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"auto","stream":true,"messages":[{"role":"user","content":"Count to ten."}]}'
```

You should see `data:`

chunks arriving incrementally rather than one blob. A gateway that only works in non-streaming mode will break most agent CLIs.

Coding agents live on tool/function calling. Verify it round-trips:

```
curl http://localhost:20128/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "auto",
    "messages": [{"role": "user", "content": "What is the weather in Lisbon?"}],
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get the current weather",
        "parameters": {
          "type": "object",
          "properties": {"city": {"type": "string"}},
          "required": ["city"]
        }
      }
    }]
  }'
```

A valid reply should include a `tool_calls`

block with correctly structured arguments, not a plain-text answer pretending to be a tool call.

The reason you run a gateway is resilience. To prove it:

`X-OmniRoute-Decision`

header to confirm the primary provider is serving.The request should still succeed, now routed to the next provider — silently, with no error surfaced to your client. If it 500s instead, your fallback chain isn't wired the way you think.

Once the API surface checks out, connect an actual agent:

```
Base URL: http://localhost:20128/v1
API Key:  [copy from the Endpoints page]
Model:    auto
```

Or let OmniRoute write the config for you, per tool:

```
omniroute setup-codex      # ~/.codex/<name>.config.toml profiles
omniroute setup-claude     # ~/.claude/profiles/<name>/settings.json
omniroute setup-opencode   # opencode.json
omniroute setup-cursor     # prints Cursor's in-app steps
```

Even simpler — launch a CLI through the gateway with no config written:

```
omniroute run claude   --model auto
omniroute run codex    --model auto
```

Then do a real task (edit a file, run a tool) rather than a hello-world. A finished task proves the whole pipeline.

| Check | Pass condition |
|---|---|
`omniroute doctor` |
No failing checks |
`curl /v1/models` |
Providers listed |
| Plain chat | Valid completion + `X-OmniRoute-Decision` header |
| Streaming | Incremental `data:` chunks |
| Tool calling | Structured `tool_calls` in the response |
| Fallback | Request survives a dead primary provider |
| Real agent task | A coding CLI completes an actual edit |

`doctor`

complains about a native module`STREAM_IDLE_TIMEOUT_MS`

.Ten minutes of this checklist beats an hour of debugging why your agent silently "can't reach the model" in the middle of real work.
