Blog

What OpenAI-compatible means in practice

Compatibility is a promise about your code, not a marketing label. Exactly which endpoints, events and behaviours Zarx implements, what it does not, and how to verify it in an afternoon.

“OpenAI-compatible” is on many product pages and means different things on each of them. For some, it means a chat endpoint that takes a messages array. For others, it means the official SDK works if you squint. We would rather be precise, because the phrase is a promise about your code, and you will find out quickly whether it was kept.

Here is what it means on Zarx, and how to check.

The one-line change

The promise, stated as a test: take an application written against api.openai.com, change the base URL and the API key, change the model id to one from our catalog, and the application keeps working.

from openai import OpenAI

client = OpenAI(
    base_url="https://api.zarx.ai/v1",
    api_key=ZARX_API_KEY,
)

response = client.responses.create(
    model="glm-5.2",
    input="Summarise this incident report in three bullet points.",
)
print(response.output_text)

The base URL shown is the design target for early access; partners receive their endpoint and keys when onboarded. Everything else in the snippet is the unmodified openai SDK.

Endpoints

Zarx is the gateway NPAW runs in production for its own products, opened to design partners. The endpoints below exist and are documented; we do not list anything that is not implemented.

  • POST /v1/responses is the primary endpoint. It implements the OpenAI Responses API with identical request and response shapes: string or chat-array input, instructions, tools, text.format including strict JSON schema, metadata, store, truncation, and previous_response_id for server-side conversation chaining.
  • POST /v1/chat/completions serves the Chat Completions shape (choices, message, usage with prompt, completion and total tokens) for the models we run ourselves, for code that has not moved to Responses yet.
  • GET /v1/models lists the models available to your key, including the reasoning effort levels each one supports.
  • GET, POST .../cancel and DELETE on /v1/responses/:id, and GET /v1/responses/:id/input_items, behave as documented by OpenAI.
  • POST /v1/batches with POST /v1/files (purpose: "batch") gives you the asynchronous Batch API for the models we run ourselves.

Streaming

Set "stream": true and read text/event-stream. Every event is the official OpenAI Responses event: the same event: name, the same data: JSON with the same type, and a monotonically increasing sequence_number. The sequence for a plain text reply is response.created, response.in_progress, response.output_item.added, response.content_part.added, a run of response.output_text.delta, then response.output_text.done, response.content_part.done, response.output_item.done and finally response.completed with the full usage. Errors end the stream with response.failed.

Chunks are relayed as the model produces them; nothing is buffered between the GPU and your client. Streaming code written against api.openai.com runs unchanged.

curl -N https://api.zarx.ai/v1/responses \
  -H "Authorization: Bearer $ZARX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kimi-k2.6",
    "input": "List the questions a bank should ask an inference provider about data residency.",
    "stream": true
  }'

Tools, structure, retries, errors

Function calling works the way you expect: declare tools of type function, control selection with tool_choice and parallel_tool_calls, receive the call, reply with the result. Structured output with text.format = { type: "json_schema", schema, strict } validates the model’s answer against your schema, which is what a downstream system needs when it cannot tolerate prose.

An Idempotency-Key header makes retries safe: the same key returns the same response id. Errors come back in the OpenAI envelope, { error: { message, type, code, param } }, with the usual HTTP status codes, and the official SDKs raise the exceptions you already handle.

What is different, and what is missing

Some things are deliberately different, and a few are not there yet. We would rather you read them here than discover them in staging.

  • Authentication. Send your key as Authorization: Bearer <key>, exactly as with OpenAI. The gateway also accepts an X-API-Key header if your HTTP client prefers it.
  • Model ids are ours, not OpenAI’s. GET /v1/models is the source of truth.
  • No hosted tools. There is no web_search, file_search or code_interpreter. Use a function tool and your own backend; it keeps your data on your side anyway.
  • No background mode. For long requests use an Idempotency-Key and retry.
  • Usage accounting is per key. Every request is recorded with its model and token counts and rolled up per key and model by hour and by day. Limits (requests per minute, tokens per minute, daily tokens, monthly spend) can be set per key, per model or per account, and a hard limit returns an OpenAI-shaped 429 with Retry-After.

How to verify it in an afternoon

  1. Point your existing integration tests at the Zarx base URL with a Zarx key and a model from GET /v1/models.
  2. Run the streaming path and assert on event names, not on timing.
  3. Run one structured-output request with strict: true and validate the JSON against your schema.
  4. Send the same request twice with one Idempotency-Key and check you get the same response id.
  5. Read the usage in response.completed and compare it with what your account shows.

If any step fails, that is a bug on our side or a gap in this list, and we want to hear about it. Request access and we will get you a key.

Run inference in your jurisdiction.

Early access is open to a small group of design partners.