---
url: https://docs.freetheai.org/sdks/openai-javascript.md
description: >-
  Use the official OpenAI Node.js SDK with FreeTheAI from JavaScript or
  TypeScript - baseURL, chat completions, streaming, Responses, and error
  handling.
---

# OpenAI JavaScript and TypeScript SDK

The official [`openai`](https://www.npmjs.com/package/openai) package works with FreeTheAI in Node.js, Deno, Bun, and edge runtimes. Only `baseURL` and the key change. Keep the key on a server: never ship it in browser code.

## Install and configure

```bash
npm install openai
export FTAI_KEY="ftai_your_key_here"
```

::: code-group

```ts [TypeScript]
import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: 'https://api.freetheai.org/v1',
  apiKey: process.env.FTAI_KEY ?? '',
})
```

```js [JavaScript (ESM)]
import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: 'https://api.freetheai.org/v1',
  apiKey: process.env.FTAI_KEY,
})
```

```js [JavaScript (CommonJS)]
const OpenAI = require('openai')

const client = new OpenAI({
  baseURL: 'https://api.freetheai.org/v1',
  apiKey: process.env.FTAI_KEY,
})
```

:::

The SDK also reads `OPENAI_BASE_URL` and `OPENAI_API_KEY` from the environment.

## Chat completion

```ts
const reply = await client.chat.completions.create({
  model: 'fta/kimi/k3',
  messages: [{ role: 'user', content: 'Write a haiku about the sea.' }],
})
console.log(reply.choices[0].message.content)
```

## Streaming

```ts
const stream = await client.chat.completions.create({
  model: 'fta/zai/glm-5.3',
  messages: [{ role: 'user', content: 'Tell me a short story.' }],
  stream: true,
})
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? '')
}
```

### Stream to a browser

A minimal server route that keeps the key on the server and streams plain text to the page (works in any runtime with the Fetch API, such as Next.js route handlers, Hono, or Deno):

```ts
import OpenAI from 'openai'

const client = new OpenAI({ baseURL: 'https://api.freetheai.org/v1', apiKey: process.env.FTAI_KEY })

export async function POST(request: Request): Promise<Response> {
  const { prompt } = (await request.json()) as { prompt: string }
  const stream = await client.chat.completions.create({
    model: 'fta/zai/glm-5.3',
    messages: [{ role: 'user', content: prompt }],
    stream: true,
  })
  const encoder = new TextEncoder()
  const body = new ReadableStream<Uint8Array>({
    async start(controller) {
      for await (const chunk of stream) {
        const text = chunk.choices[0]?.delta?.content
        if (text) controller.enqueue(encoder.encode(text))
      }
      controller.close()
    },
  })
  return new Response(body, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } })
}
```

## Responses API

```ts
const response = await client.responses.create({
  model: 'fta/zai/glm-5.3',
  input: 'Summarize the plot of Hamlet in two sentences.',
})
console.log(response.output_text)
```

## Tool calls

See [Tool calls](/concepts/tool-calls) for a complete TypeScript loop.

## Errors

```ts
import OpenAI from 'openai'

try {
  await client.chat.completions.create({
    model: 'fta/zai/glm-5.3',
    messages: [{ role: 'user', content: 'Hi' }],
  })
} catch (err) {
  if (err instanceof OpenAI.APIError) {
    // err.status: 401, 403, 404, 429, 502, 503, ...
    // err.error: FreeTheAI's error object; its message is safe to show to people
    const message = (err.error as { message?: string } | undefined)?.message ?? err.message
    console.error(err.status, message, err.headers?.get('x-error-id'))
  } else {
    throw err
  }
}
```

Every message and its fix: [Errors](/errors).
