Client SDKs

TypeScript

The official openai package works unchanged; only the base URL and the model ids differ.

Setting up the client#

lib/upfyn.ts
import OpenAI from "openai";
 
export const client = new OpenAI({
baseURL: "https://ai.upfyn.com/v1",
apiKey: process.env.UPFYN_API_KEY,
timeout: _000,
maxRetries: 0, // retry yourself: the SDK would also retry a 402, which cannot succeed
});

Server-side only

Never construct this in the browser. A key in a client bundle is a key anyone can read and spend. Put it behind a route on your own server — the example further down does that.

A basic call#

One turn
const response = await client.chat.completions.create({
model: "upfyn-yuva",
messages: [
{ role: "system", content: "You answer in one short paragraph." },
{ role: "user", content: "Why is my invoice higher this month?" },
],
max_tokens: 400,
});
 
console.log(response.choices[0].message.content);
 
// Upfyn extras exist at runtime but are absent from the OpenAI types.
const { credits_used } = response as unknown as { credits_used: number };

The OpenAI types do not know about credits_used, so TypeScript needs a cast. The field is present at runtime.

Streaming#

for await
const stream = await client.chat.completions.create({
model: "upfyn-yuva",
messages: [{ role: "user", content: "Draft a status update." }],
stream: true,
stream_options: { include_usage: true },
});
 
let text = "";
for await (const chunk of stream) {
const delta = chunk.choices?.[0]?.delta?.content;
if (delta) {
text += delta;
process.stdout.write(delta);
}
}
  • Use optional chaining — the final usage chunk has an empty choices array.
  • The [DONE] terminator is handled for you by the SDK.

Streaming to a browser#

Next.js route handler
// app/api/chat/route.ts — stream through to the browser without exposing the key.
import { client } from "@/lib/upfyn";
 
export async function POST(req: Request) {
const { messages } = await req.json();
 
const stream = await client.chat.completions.create({
model: "upfyn-yuva",
messages,
stream: true,
});
 
const encoder = new TextEncoder();
return new Response(
new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
const delta = chunk.choices?.[0]?.delta?.content;
if (delta) controller.enqueue(encoder.encode(delta));
}
controller.close();
},
}),
{ headers: { "Content-Type": "text/plain; charset=utf-8" } },
);
}

Errors#

APIError
import { APIError } from "openai";
 
try {
await client.chat.completions.create({ /* ... */ });
} catch (err) {
if (err instanceof APIError) {
if (err.status === 402) throw new OutOfCredits(err.message);
if (err.status === 429) {
const wait = Number(err.headers?.["retry-after"] ?? 5);
// back off, then retry
}
}
throw err;
}

Which codes are worth retrying is covered in Errors.

Upfyn-only parameters

The SDK forwards unknown keys in the body; the cast is only to satisfy the types.

Extra fields
await client.chat.completions.create({
model: "upfyn-yuva",
messages,
persist_session: true,
upfyn_tag: "support",
} as never);