OpenAI

Lightweight, dependency-free, in-memory OpenAI REST API fake for testing code that uses the real openai Node.js/Python SDK (and the language-agnostic OpenAI REST API). All generated content is deterministic — text and embedding vectors are derived from a hash of the input so tests are repeatable.

Default port: 4747

Quick start

Start the server:

import { OpenaiServer } from "./services/openai/src/server.js";

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

Point the real openai client at it via baseURL:

import OpenAI from "openai";

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

const completion = await client.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Hello!" }],
});
// completion.choices[0].message.content => deterministic text for that prompt

Implemented operations

All /v1/* routes require an Authorization: Bearer <key> header (any non-empty bearer token is accepted, matching how a local test key behaves). State is in-memory and ephemeral.

Service & inspection operations (parlel extensions)

SDK usage example

from openai import OpenAI

client = OpenAI(api_key="sk-parlel", base_url="http://127.0.0.1:4747/v1")

stream = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Stream me"}],
    stream=True,
)
for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="")

Access via MCP / preview URL

HTTP services are auto-exposed at the Daytona preview URL. Send requests to the preview URL with the x-daytona-preview-token header for authentication. The OpenAI surface is mounted under /v1, so set OPENAI_BASE_URL to <preview-url>/v1.

Error codes & shapes

Every error uses the real OpenAI envelope: { "error": { "message", "type", "param", "code" } } (all four keys always present; param and code may be null).

ScenarioStatustypecodeparam
Missing Authorization header401invalid_request_errornullnull
Malformed credential (not a Bearer token)401invalid_request_errorinvalid_api_keynull
Missing required field (messages, model, input, prompt)400invalid_request_errornullthe field name
Retrieve unknown model404invalid_request_errormodel_not_foundnull
Unknown /v1 route (or non-/v1 path)404invalid_request_errorunknown_urlnull
Malformed JSON body400invalid_request_errornullnull

Any non-empty Bearer token is accepted (the emulator does not validate real secrets), so a missing/malformed Authorization header is the only auth failure you will see.

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 (+ streaming SSE)✅ Supported
chat.completions usage token details (prompt_tokens_details, completion_tokens_details)✅ Supported
completions (legacy)✅ Supported
embeddings (deterministic vectors, custom dims)✅ Supported
models list✅ Supported
models retrieve (404 model_not_found for unknown ids)✅ Supported
images.generations (url / b64_json, revised_prompt)✅ Supported
moderations (13 categories, category_applied_input_types, omni-moderation-latest default)✅ Supported
Error envelope ({ error: { message, type, param, code } }) incl. missing-key vs invalid-key 401✅ Supported
Unknown-route 404 (code: "unknown_url")✅ Supported
Request inspection✅ Supported (parlel extension)
Real model inference / quality✓ By design — Deterministic stub output — repeatable assertions, no API spend
tools / function calling execution◐ Accepted, not executed (returns text)
Vision / audio / file uploads⟳ Roadmap
List stored chat completions (GET /v1/chat/completions)⟳ Roadmap
Token counts◐ Approximate word-based, not real BPE
Bearer-token validity / org scoping✓ By design — Any non-empty credential is accepted — no real secrets needed
HTTP 405 on method mismatch✓ By design — Falls through to a valid JSON 404 envelope
Rate limiting (429) / quota✓ By design — Never throttles — local tests run at full speed, zero cost

Manifest

See services/openai/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.

OPENAI_API_KEY=sk-parlel
OPENAI_BASE_URL=http://parlel-bridge:4747/v1
<!-- parlel:testenv:end -->