OpenRouter

Lightweight, dependency-free, in-memory OpenRouter API fake. OpenRouter is OpenAI-compatible, so this works with the official openai SDK pointed at it. All output is deterministic (hash-derived) and SSE streaming is supported.

Default port: 4861

Quick start

import { OpenrouterServer } from "./services/openrouter/src/server.js";

const server = new OpenrouterServer(4861);
await server.start();
// ... run your app/tests ...
await server.stop();

Point the openai SDK at it:

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: "sk-or-parlel",
  baseURL: "http://127.0.0.1:4861/api/v1",
});

const res = await client.chat.completions.create({
  model: "anthropic/claude-3.5-sonnet",
  messages: [{ role: "user", content: "hello" }],
});
// res.choices[0].message.content => deterministic text; res.provider => routing field

Access via MCP / preview URL

Implemented operations

Service & inspection operations (parlel extensions)

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatus
chat.completions (non-stream + SSE stream)✅ Supported
embeddings✅ Supported
models list with routing/provider field✅ Supported
Deterministic, reproducible output✅ Supported
Real model inference✓ By design — Deterministic stub output — repeatable assertions, no API spend
Provider preferences / fallbacks / transformsprovider echoed from model prefix only
Credits / cost accounting✓ By design — Not enforced
Tool/function calling, vision◐ Accepted, not specially handled

Error codes & shapes

Errors use the OpenAI envelope: { "error": { "message", "type", "code" } }.

StatusWhen
401missing/invalid Authorization (except /models)
400missing model/messages/input or bad JSON
404unknown endpoint

Manifest

See services/openrouter/manifest.json:

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

OPENROUTER_API_KEY=sk-or-parlel
OPENROUTER_BASE_URL=http://parlel-bridge:4861/api/v1
<!-- parlel:testenv:end -->