Ship with the OpenAI SDK in 5 minutes
4 min read
The gateway speaks the OpenAI wire format — same endpoints, same request shape, same SSE framing. If your code already runs against api.openai.com, it runs against CLF AI Gateway after two changes: the base URL and the key. Below are working setups for the two official SDKs, LangChain, and the Vercel AI SDK, plus streaming and the errors you will actually meet.
Before you start
- Create a key at app.clfaigateway.dev. It is prefixed
sk-gw-and shown exactly once — store it immediately. - Base URL:
https://api.clfaigateway.dev/v1 - Model IDs:
deepseek-v4-flashanddeepseek-v4-pro(1M context),kimi-k2.6,kimi-k2.7-code,glm-5.2(262K) andglm-4.7-flash(131K) — full list with prices on /models.
Python — official openai SDK
Works with openai ≥ 1.0 (pip install openai):
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.clfaigateway.dev/v1",
api_key=os.environ["CLF_API_KEY"], # sk-gw-...
)
resp = client.chat.completions.create(
model="kimi-k2.6",
messages=[{"role": "user", "content": "Say hello in five words."}],
max_tokens=256,
)
print(resp.choices[0].message.content)max_tokens is optional but worth the habit: it doubles as your hard spend cap per request.
Node / TypeScript — official openai SDK
npm install openai:
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://api.clfaigateway.dev/v1',
apiKey: process.env.CLF_API_KEY, // sk-gw-...
});
const resp = await client.chat.completions.create({
model: 'kimi-k2.6',
messages: [{ role: 'user', content: 'Say hello in five words.' }],
max_tokens: 256,
});
console.log(resp.choices[0].message.content);Streaming
Streaming is standard SSE through the SDKs. Three gateway-specific notes: pass stream_options: {"include_usage": true} so the final chunk carries the authoritative token counts (the ones billing uses); handle finish_reason: "error", because a mid-stream upstream failure arrives inside the stream body — the HTTP status was already sent as 200; and Kimi/GLM are reasoning models, so reasoning_content streams before the visible content.
stream = client.chat.completions.create(
model="glm-5.2",
messages=[{"role": "user", "content": "Explain SSE in one sentence."}],
stream=True,
stream_options={"include_usage": True},
)
usage = None
for chunk in stream:
if chunk.usage is not None:
usage = chunk.usage # final chunk = the token counts you are billed on
for choice in chunk.choices:
thinking = getattr(choice.delta, "reasoning_content", None)
if thinking:
print(thinking, end="")
if choice.delta.content:
print(choice.delta.content, end="")
if choice.finish_reason == "error":
raise RuntimeError("stream failed mid-flight; only delivered tokens are billed")
print(usage)LangChain
langchain-openai takes a custom base URL directly (pip install langchain-openai):
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="glm-4.7-flash",
base_url="https://api.clfaigateway.dev/v1",
api_key="sk-gw-...",
)
print(llm.invoke("Summarize SSE in one sentence.").content)From here ChatOpenAI behaves normally in chains, agents and .stream() — the gateway is just another OpenAI-shaped endpoint to it.
Vercel AI SDK
Use the OpenAI-compatible provider (npm install ai @ai-sdk/openai-compatible):
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { streamText } from 'ai';
const clf = createOpenAICompatible({
name: 'clf-ai-gateway',
baseURL: 'https://api.clfaigateway.dev/v1',
apiKey: process.env.CLF_API_KEY,
});
const result = streamText({
model: clf('kimi-k2.6'),
prompt: 'Write a two-line haiku about prepaid credits.',
});
for await (const text of result.textStream) {
process.stdout.write(text);
}generateText, streamText and the useChat UI hooks work unchanged.
The two errors you will actually hit
401 invalid_api_key
The key is missing, mistyped, or revoked. Check the Authorization: Bearer sk-gw-... header, and that you copied the full key when it was shown. SDKs surface this as their standard authentication error class.
402 insufficient_credits
Your prepaid balance cannot cover the request’s hold estimate. The body tells you exactly how short you are:
{
"error": {
"type": "insufficient_credits",
"code": "insufficient_credits",
"message": "Insufficient credits. Available: $0.0041. This request requires an estimated hold of $0.0269. Top up at https://app.clfaigateway.dev/billing/topup",
"metadata": {
"available_nano": 4100000,
"required_estimate_nano": 26927600,
"topup_url": "https://app.clfaigateway.dev/billing/topup"
}
}
}Top up (VietQR confirms in about 15 seconds; international cards work too) and retry — nothing to code around beyond surfacing the message. Also worth knowing: 404 model_not_found means the model ID is not in the open list (see /models), and 429 rpm_exceeded comes with a retry-after header that the official SDKs already honor with automatic backoff.
Defaults that save money
- Set
max_tokenson every production call — disconnecting mid-stream does not cancel tokens already generated. - Put stable content (system prompt, tools, few-shot examples) at the front of the conversation: repeated prefixes bill at the cached-input rate, roughly 6× cheaper on Kimi models.
- For identical deterministic calls, add
"cache": {"mode": "on"}withtemperature: 0— a full hit bills 10% of the normal price. - Read
usageon every response. The math behind it is in Per-token billing you can audit.