Skip to content
← All posts
Guides

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-flash and deepseek-v4-pro (1M context), kimi-k2.6, kimi-k2.7-code, glm-5.2 (262K) and glm-4.7-flash (131K) — full list with prices on /models.

Python — official openai SDK

Works with openai ≥ 1.0 (pip install openai):

python
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:

typescript
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.

python
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):

python
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):

typescript
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:

json
{
  "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_tokens on 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"} with temperature: 0 — a full hit bills 10% of the normal price.
  • Read usage on every response. The math behind it is in Per-token billing you can audit.