ZEX API
Open ZEX Get an API key

ZEX API

Build with ZEX

The ZEX API gives your applications the same models you use in ZEX, through an OpenAI-compatible chat completions endpoint. Requests use the credits on your ZEX account.

Base URLhttps://your-zex-domain/api/v1
AuthAuthorization: Bearer zex_sk_…
FormatJSON · Server-Sent Events for streaming

Authentication

Create a key in Settings → API keys. Keys start with zex_sk_ and are shown once — ZEX stores only a hash. You can have up to 10 active keys, rotate a key (the old one stops working immediately) or revoke it at any time. Each key shows when it was last used.

Send the key in the Authorization header. Never put keys in browser code or commit them to source control; call the API from your server.

Authorization: Bearer $ZEX_API_KEY

Quickstart

Replace MODEL_ID with an id from GET /models.

curl https://your-zex-domain/api/v1/chat/completions \
  -H "Authorization: Bearer $ZEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MODEL_ID",
    "messages": [
      {"role": "system", "content": "You are a concise assistant."},
      {"role": "user", "content": "Explain rate limiting in one paragraph."}
    ]
  }'

GET /api/v1/models

Returns the models your key can use right now: enabled, configured on this deployment and included in your plan.

curl https://your-zex-domain/api/v1/models -H "Authorization: Bearer $ZEX_API_KEY"
{
  "object": "list",
  "data": [
    {
      "id": "llama-3.3-70b",
      "object": "model",
      "created": 1790000000,
      "owned_by": "Cloudflare Workers AI",
      "name": "Llama 3.3 70B",
      "context_length": 24000,
      "capabilities": { "vision": false, "documents": false, "streaming": true, "tools": false },
      "pricing": { "inputCreditsPer1k": 0.2, "outputCreditsPer1k": 1 }
    }
  ]
}

Models on this deployment

Live from the public catalog. Availability depends on configured providers and your plan.

Model idNameContextMinimum planCredits / 1K tokens (in / out)
Loading…

POST /api/v1/chat/completions

Creates a model response for a conversation.

Request body

FieldTypeDescription
model requiredstringA model id from GET /api/v1/models.
messages requiredarray1–200 messages. Roles: system, developer (treated as system), user, assistant. content is a string or an array of {"type":"text","text":…} parts; other part types (image_url, …) are not supported and return 400 unsupported_content rather than being dropped. The last message must be from user. Up to 500,000 characters in total.
streambooleanIf true, the response is sent as Server-Sent Events. Default false.
max_tokens / max_completion_tokensinteger1–32000. Use one token-limit field, not both. Clamped to the model’s maximum.
top_pnumber0–1. Supported by OpenAI, native Anthropic and native Gemini adapters.
presence_penalty, frequency_penaltynumber−2–2. OpenAI and native Gemini adapters.
seedintegerSafe integer. OpenAI and native Gemini; best-effort reproducibility, not a determinism guarantee.
stopstring or array1–4 non-empty strings, up to 1024 characters each. OpenAI, native Anthropic and native Gemini.
userstringOptional opaque client ID (max 256 characters). Accepted locally but not stored, logged or forwarded to providers; not authentication.
stream_optionsobjectWith stream: true, supports boolean include_usage.
temperaturenumber0–2. Sampling temperature.

Other OpenAI parameters (tools, response_format, n, …) are not supported. The optional sampling fields above are only enabled for the named adapters; other OpenAI-compatible providers and Workers AI return 400 unsupported_parameter until explicitly supported. Individual upstream models can impose additional restrictions. Fallbacks that cannot honour the supplied fields are excluded. Sending one returns 400 unsupported_parameter — the request is refused rather than silently answered with different semantics. stream must be a real boolean.

Response

{
  "id": "chatcmpl-3f9a0c1e2b7d4a8c9e0f1a2b",
  "object": "chat.completion",
  "created": 1790000000,
  "model": "llama-3.3-70b",
  "choices": [
    { "index": 0, "message": { "role": "assistant", "content": "Rate limiting caps…" }, "finish_reason": "stop" }
  ],
  "usage": { "prompt_tokens": 24, "completion_tokens": 88, "total_tokens": 112 },
  "zex": {
    "credits_charged": 1,
    "provider": "Cloudflare Workers AI",
    "model": "llama-3.3-70b",
    "fallback_used": false,
    "usage_estimated": false,
    "request_id": "3f9a0c1e-2b7d-4a8c-9e0f-1a2b3c4d5e6f"
  }
}

The zex object is a ZEX extension. model is the model that actually answered: if the requested model's provider fails before any text is produced, ZEX retries once and may fall back to the model's configured fallback (fallback_used: true). When a provider does not report token counts, ZEX estimates them (usage_estimated: true).

Streaming

With "stream": true the response is text/event-stream. Each event is a data: line with a chat.completion.chunk. The final chunk has finish_reason: "stop" plus usage and zex; the stream ends with data: [DONE]. For stream_options: {"include_usage": true}, preceding chunks have usage: null and a separate final chunk has choices: [] and aggregate usage. Set include_usage: false to omit usage. Streamed content identifies the actual responding model; terminal zex.fallback_used and zex.requested_model make fallback explicit.

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1790000000,"model":"llama-3.3-70b","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk",…,"choices":[{"index":0,"delta":{"content":"Rate limiting"},"finish_reason":null}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk",…,"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{…},"zex":{…}}

data: [DONE]

If the provider fails mid-stream, an event data: {"error":{"code":"provider_error",…}} is sent before [DONE]. Text already streamed is charged; nothing is charged if no text was produced.

const res = await fetch("https://your-zex-domain/api/v1/chat/completions", {
  method: "POST",
  headers: { "Authorization": `Bearer ${process.env.ZEX_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ model: "MODEL_ID", stream: true, messages: [{ role: "user", content: "Write a haiku." }] })
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  let i;
  while ((i = buffer.indexOf("\n\n")) >= 0) {
    const line = buffer.slice(0, i).trim();
    buffer = buffer.slice(i + 2);
    if (!line.startsWith("data: ")) continue;
    const payload = line.slice(6);
    if (payload === "[DONE]") break;
    const event = JSON.parse(payload);
    if (event.error) throw new Error(event.error.message);
    process.stdout.write(event.choices[0].delta.content ?? "");
  }
}

Errors

Errors use HTTP status codes and a consistent JSON body. Include the request_id (also sent as the X-Request-Id header) when contacting support.

{ "error": { "code": "insufficient_credits", "message": "You do not have enough credits…", "request_id": "3f9a0c1e-…" } }
StatuscodeMeaning
400invalid_request, invalid_jsonThe body is malformed or a parameter is out of range.
401invalid_api_key, revoked_api_keyMissing, unknown or revoked key.
402insufficient_creditsYour balance is too low. Credits renew with your monthly allowance.
403model_not_in_plan, api_disabled, account_suspendedThe model needs a higher plan, the API is disabled, or the account is suspended.
404model_not_foundUnknown or disabled model id.
413payload_too_largeThe request exceeds size limits.
429rate_limitedToo many requests. Wait for Retry-After seconds.
502provider_errorThe provider (and any fallback) failed. No credits are charged if no text was produced; partial output is billable.
503model_unavailable, maintenance, service_unavailableThe model's provider is not configured, ZEX is in maintenance, or a dependency is down.

Rate limits

API requests are limited per account per minute according to your plan. Every response includes:

HeaderDescription
X-RateLimit-LimitRequests allowed in the current window.
X-RateLimit-RemainingRequests left in the window.
X-RateLimit-ResetWhen the window resets (Unix time, seconds).
Retry-AfterOn 429 only: seconds to wait.
PlanAPI requests / minuteMonthly credits
Loading…

Credits & usage

Each successful request is charged max(1, ceil((input_tokens × input_rate + output_tokens × output_rate) / 1000)) credits, using the per-model rates listed above. Failed requests that produce no text are not charged. Charges are idempotent per request and your balance never goes below zero.

See every request, its tokens and cost in Settings → Plan & usage.

Using OpenAI SDKs

Because the endpoint is OpenAI-compatible, the official SDKs work by changing the base URL.

// npm install openai
import OpenAI from "openai";

const client = new OpenAI({ apiKey: process.env.ZEX_API_KEY, baseURL: "https://your-zex-domain/api/v1" });
const completion = await client.chat.completions.create({
  model: "MODEL_ID",
  messages: [{ role: "user", content: "Hello, ZEX" }]
});
console.log(completion.choices[0].message.content);

Data handling

The API does not store your prompts or completions. ZEX records usage metadata only — time, model, token counts, credits, status and latency — so you can audit usage. Keys are stored as SHA-256 hashes. Requests are forwarded to the model's provider to generate the response.