Client integration

Point any OpenAI SDK at Cordy Gateway — authentication, streaming, tool calling, embeddings, routing headers, and the product extras.

Cordy Gateway speaks the OpenAI API. Any client or SDK that can target a custom base_url works without code changes beyond the URL and key. This page shows the common integration patterns end to end.

Connect

Set two things on your client:

  • Base URL: https://<host>:8002/v1 (use http:// only for local no-TLS trials).
  • API key: a wnx_-prefixed key, sent as a bearer token.

Authentication accepts either header:

Authorization: Bearer wnx_<key>

or

X-API-Key: wnx_<key>

The standard OpenAI SDKs use the Authorization: Bearer form automatically. API keys must be sent in a header — keys in the URL query string are not accepted.

Python

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["CORDY_API_KEY"],   # wnx_...
    base_url=os.environ["CORDY_BASE_URL"],  # https://<host>:8002/v1
)

JavaScript / TypeScript

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.CORDY_API_KEY,   // wnx_...
  baseURL: process.env.CORDY_BASE_URL, // https://<host>:8002/v1
});

Throughout, model is the public model slug configured in the Admin, not a vendor model name.

Chat completions

Non-streaming

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "You are a concise assistant."},
        {"role": "user", "content": "What is Cordy Gateway in one line?"},
    ],
    temperature=0.7,
    max_tokens=256,
)
print(resp.choices[0].message.content)
const resp = await client.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [
    { role: "system", content: "You are a concise assistant." },
    { role: "user", content: "What is Cordy Gateway in one line?" },
  ],
  temperature: 0.7,
  max_tokens: 256,
});
console.log(resp.choices[0].message.content);

Every chat request must contain at least one user message; system-only or assistant-only histories are rejected with 400.

Streaming (SSE)

Set stream=True to receive server-sent events as they are produced. The stream ends with a [DONE] sentinel.

stream = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Stream a short poem about routing."}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)
print()
const stream = await client.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "Stream a short poem about routing." }],
  stream: true,
});
for await (const chunk of stream) {
  const delta = chunk.choices[0]?.delta?.content;
  if (delta) process.stdout.write(delta);
}

If the upstream stream closes without a finish reason, the gateway emits an in-band stream_interrupted error event before [DONE], so clients can tell a clean finish from a truncation. Routing headers (X-Channel-Id, X-Provider, X-Failover-Count) are present on the streaming response as well as non-streaming.

Raw SSE with curl:

curl -N "$CORDY_BASE_URL/chat/completions" \
  -H "Authorization: Bearer $CORDY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o-mini","stream":true,"messages":[{"role":"user","content":"hi"}]}'

Tool / function calling

Tool calling uses the standard OpenAI tools and tool_choice fields. If you pass tool_choice, you must also pass a non-empty tools list.

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather for a city.",
            "parameters": {
                "type": "object",
                "properties": {"city": {"type": "string"}},
                "required": ["city"],
            },
        },
    }
]

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "What's the weather in Paris?"}],
    tools=tools,
    tool_choice="auto",
)

msg = resp.choices[0].message
if msg.tool_calls:
    call = msg.tool_calls[0]
    print(call.function.name, call.function.arguments)
const tools = [
  {
    type: "function",
    function: {
      name: "get_weather",
      description: "Get the current weather for a city.",
      parameters: {
        type: "object",
        properties: { city: { type: "string" } },
        required: ["city"],
      },
    },
  },
];

const resp = await client.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "What's the weather in Paris?" }],
  tools,
  tool_choice: "auto",
});

const msg = resp.choices[0].message;
if (msg.tool_calls) {
  const call = msg.tool_calls[0];
  console.log(call.function.name, call.function.arguments);
}

You can round-trip the assistant message back into messages (the canonical multi-turn pattern), append your tool result as a role: "tool" message, and call again to let the model use the result.

Embeddings

emb = client.embeddings.create(
    model="text-embedding-3-small",
    input=["first document", "second document"],
)
print(len(emb.data), "vectors")
print(len(emb.data[0].embedding), "dimensions")
const emb = await client.embeddings.create({
  model: "text-embedding-3-small",
  input: ["first document", "second document"],
});
console.log(emb.data.length, "vectors");

input accepts a single string or an array of strings. Empty input is rejected with 400.

Text only

The gateway is text-only today. A chat message content may be a plain string, or an array of {"type": "text", "text": "..."} parts (which are concatenated). Any non-text part — image_url, audio, and similar multimodal types — is rejected with a clear 400 error. Do not send images or audio.

Product extras

Beyond the OpenAI-standard endpoints, the gateway exposes read-only product endpoints that your client can call with the same key (they require the balance scope).

Balance and quota

curl -sS "$CORDY_BASE_URL/balance" -H "Authorization: Bearer $CORDY_API_KEY"

Returns the key's token quota (quota_limit, quota_used, quota_remaining) and the owning user's prepaid credit_balance_usd.

Subscription

curl -sS "$CORDY_BASE_URL/subscriptions" -H "Authorization: Bearer $CORDY_API_KEY"

Returns the user's tier, plan, subscription status, and feature summary.

Package catalog

curl -sS "$CORDY_BASE_URL/packages" -H "Authorization: Bearer $CORDY_API_KEY"

Returns the read-only catalog of platform packages (tiers, quota limits, pricing).

Usage analytics

curl -sS "$CORDY_BASE_URL/account/analytics?period_days=30" \
  -H "Authorization: Bearer $CORDY_API_KEY"

Returns the authenticated user's own usage, latency, and cost aggregated over a trailing window. period_days accepts 1365 (default 30); out-of-range values are clamped. Note: this endpoint is available only under the /v1 prefix.

Request headers

You can influence routing and reliability per request:

HeaderValuesEffect
X-Routing-Strategyweighted, cheapest, fastest, region_awareOverrides the routing strategy for this request.
X-Regiona region code, e.g. us-eastSets the caller region for region_aware routing.
Idempotency-Keyany unique stringReplay-protects a POST: a repeat within the TTL returns the stored response instead of calling upstream again. X-Idempotency-Key is also accepted.

Example — cheapest routing with an idempotency key:

curl -sS "$CORDY_BASE_URL/chat/completions" \
  -H "Authorization: Bearer $CORDY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Routing-Strategy: cheapest" \
  -H "Idempotency-Key: order-4711-attempt-1" \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'

Passing custom headers with the OpenAI SDKs:

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "hi"}],
    extra_headers={"X-Routing-Strategy": "fastest"},
)
const resp = await client.chat.completions.create(
  { model: "gpt-4o-mini", messages: [{ role: "user", content: "hi" }] },
  { headers: { "X-Routing-Strategy": "fastest" } },
);

Response headers

Successful responses include observability headers you can log or surface:

HeaderMeaning
X-Channel-IdThe channel that served the request.
X-ProviderThe upstream provider used.
X-Failover-CountHow many channels were tried before success.
X-Routing-Overhead-MsTime spent in routing, excluding the upstream call.
X-Idempotency-Statushit when a stored response was replayed; skipped when idempotency did not apply.

Handling errors

Errors use the OpenAI-style envelope: {"error": {"type", "message", "code", ...}}. The ones your client should handle explicitly:

  • 402 insufficient_credits — the account is out of prepaid credit. Stop and top up rather than retrying; a retry will fail the same way.
  • 429 with code: quota_exceeded (monthly token quota) or a rate-limit code — back off and retry later, or raise the limit.
  • 401 — the key is missing, invalid, revoked, inactive, or expired. Do not retry with the same key.
  • 403 scope_denied — the key lacks the scope for that endpoint. Issue a key with the right scope.
  • 503 auth_service_unavailable — a transient auth-infrastructure issue. Honour Retry-After and retry.

See the API reference for the complete error table.

A note on Cordy's client apps

Cordy's own end-user client apps are separate products. They can optionally be pointed at a Cordy Gateway deployment as their backend, but they are not required — any OpenAI-compatible client works against the gateway directly, as shown above.

Next steps

  • API reference — full endpoint, scope, and error tables.
  • Providers — how models and routing are configured on the server side.