# OpenAI Node SDK

The official [OpenAI Node SDK](https://github.com/openai/openai-node) talks to
Hyphen with one changed option. Every framework on the TypeScript pages is
built on this, so it is worth knowing what the layer underneath looks like.

```bash
npm install openai
export HYPHEN_API_KEY="sk-..."
```

*Setup guide: this config comes from the OpenAI Node SDK's official
documentation and has not been run end to end against the gateway.
Corrections to [support@hyphen-solution.com](mailto:support@hyphen-solution.com).*

## Minimal

```javascript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.hyphen-solution.com/v1",
  apiKey: process.env.HYPHEN_API_KEY,
});

const resp = await client.chat.completions.create({
  model: "minimax-m3",
  messages: [{ role: "user", content: "Explain a Bloom filter in three sentences." }],
  max_tokens: 2000,
});

console.log(resp.choices[0].message.content);
console.log(resp.usage);
```

Save as `app.mjs` and run `node app.mjs`. That is the whole integration.

## Streaming

```javascript
const stream = await client.chat.completions.create({
  model: "minimax-m3",
  messages: [{ role: "user", content: "Count to ten slowly." }],
  max_tokens: 2000,
  stream: true,
});

for await (const chunk of stream) {
  const delta = chunk.choices[0]?.delta?.content;
  if (delta) process.stdout.write(delta);
}
```

The `?.` chain matters. The final usage chunk can arrive with an empty
`choices` array.

## Tool calling

```javascript
const tools = [
  {
    type: "function",
    function: {
      name: "get_stock_price",
      description: "Latest closing price for a ticker.",
      parameters: {
        type: "object",
        properties: { ticker: { type: "string" } },
        required: ["ticker"],
      },
    },
  },
];

function getStockPrice({ ticker }) {
  return { ticker, price: 184.21, currency: "USD" };
}

const messages = [{ role: "user", content: "What is AAPL trading at?" }];

const first = await client.chat.completions.create({
  model: "minimax-m3",
  messages,
  tools,
  max_tokens: 4000,
});

messages.push(first.choices[0].message);

for (const call of first.choices[0].message.tool_calls ?? []) {
  messages.push({
    role: "tool",
    tool_call_id: call.id,
    content: JSON.stringify(getStockPrice(JSON.parse(call.function.arguments))),
  });
}

const second = await client.chat.completions.create({
  model: "minimax-m3",
  messages,
  tools,
  max_tokens: 4000,
});

console.log(second.choices[0].message.content);
```

Full walkthrough of this pattern, including the Anthropic surface, on
[Tool calling](/recipes/tool-calling).

## TypeScript types

The SDK ships its own types and they work unchanged. Model IDs are plain
strings, so a Hyphen model name type-checks fine:

```typescript
import OpenAI from "openai";
import type { ChatCompletionMessageParam } from "openai/resources/chat/completions";

const client = new OpenAI({
  baseURL: "https://api.hyphen-solution.com/v1",
  apiKey: process.env.HYPHEN_API_KEY,
});

const messages: ChatCompletionMessageParam[] = [
  { role: "system", content: "You are terse." },
  { role: "user", content: "Define idempotent." },
];

const resp = await client.chat.completions.create({
  model: "minimax-m3",
  messages,
  max_tokens: 2000,
});
```

## Options worth setting

```javascript
const client = new OpenAI({
  baseURL: "https://api.hyphen-solution.com/v1",
  apiKey: process.env.HYPHEN_API_KEY,
  maxRetries: 0,     // handle 429 yourself, see below
  timeout: 120_000,  // reasoning models can take a while on hard prompts
});
```

Turn off `maxRetries` if you are doing your own 429 handling. Left on, the SDK
silently retries budget 429s that will never succeed, which just adds latency
and hides the real error. See [Handling the 429 cap](/recipes/handling-429).

## The Python equivalent

Identical, with `base_url` instead of `baseURL`:

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.hyphen-solution.com/v1",
    api_key=os.environ["HYPHEN_API_KEY"],
)

resp = client.chat.completions.create(
    model="minimax-m3",
    messages=[{"role": "user", "content": "Explain a Bloom filter in three sentences."}],
    max_tokens=2000,
)
print(resp.choices[0].message.content)
```

## Gotchas

- **`max_tokens` must be generous.** 2000 for chat, 4000 for tools. The
  M-series burn budget on reasoning before emitting anything, and a tight cap
  returns an empty string. See
  [Choosing a model](/choosing-a-model#the-max_tokens-gotcha).
- **`response_format` does nothing.** JSON mode is not supported on the
  M-series. Use a tool schema instead, per
  [Structured JSON output](/recipes/structured-output).
- **No `client.embeddings`.** The catalog has no embedding models.
- **`client.responses`** works too. Hyphen serves the Responses API at
  `/v1/responses`, which is what Codex CLI uses.

## Related

- [Vercel AI SDK](/build/vercel-ai-sdk): the framework most TypeScript apps use instead.
- [Recipes](/recipes/streaming): streaming, tools, agent loops, 429 handling.
- [API Reference](/api): every endpoint and parameter.
