> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.astropods.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.astropods.com/_mcp/server.

# Chat completions

The OpenAI-compatible API lives under the **`/v1`** path, so set your client's base URL to `${ASTRO_GATEWAY_URL}/v1`. Everything else is a standard OpenAI-style request: pass one of the [supported models](/ai-gateway/models) as `model` and authenticate with `ASTRO_GATEWAY_API_KEY` as a bearer token.

**Append `/v1` to `ASTRO_GATEWAY_URL`.** The env var is the host only (e.g. `https://aig.example`); the OpenAI-compatible endpoints are served under `/v1` (`/v1/chat/completions`, `/v1/embeddings`, `/v1/responses`). Pointing a client at the bare host returns `404`.

**`OpenAI SDK (Python)`**

```python title="OpenAI SDK (Python)"
from openai import OpenAI
import os

client = OpenAI(
    api_key=os.environ["ASTRO_GATEWAY_API_KEY"],
    base_url=f"{os.environ['ASTRO_GATEWAY_URL']}/v1",
)

response = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)
```

**`OpenAI SDK (TypeScript)`**

```typescript title="OpenAI SDK (TypeScript)"
import OpenAI from "openai";

const client = new OpenAI({
    apiKey: process.env.ASTRO_GATEWAY_API_KEY!,
    baseURL: `${process.env.ASTRO_GATEWAY_URL}/v1`,
});

const response = await client.chat.completions.create({
    model: "claude-sonnet-4-6",
    messages: [{ role: "user", content: "Hello" }],
});
console.log(response.choices[0].message.content);
```

**`Mastra`**

```typescript title="Mastra"
import { createOpenAI } from "@ai-sdk/openai";   // v2 or later (see note below)
import { Agent } from "@mastra/core/agent";

const gateway = createOpenAI({
    apiKey: process.env.ASTRO_GATEWAY_API_KEY,
    baseURL: `${process.env.ASTRO_GATEWAY_URL}/v1`,
});

export const agent = new Agent({
    name: "my-agent",
    instructions: "You are a helpful assistant.",
    model: gateway("claude-sonnet-4-6"),
});
```

**`LangChain (Python)`**

```python title="LangChain (Python)"
from langchain_openai import ChatOpenAI
import os

llm = ChatOpenAI(
    api_key=os.environ["ASTRO_GATEWAY_API_KEY"],
    base_url=f"{os.environ['ASTRO_GATEWAY_URL']}/v1",
    model="claude-sonnet-4-6",
)

response = llm.invoke("Hello")
```

**`LangChain (TypeScript)`**

```typescript title="LangChain (TypeScript)"
import { ChatOpenAI } from "@langchain/openai";

const llm = new ChatOpenAI({
    apiKey: process.env.ASTRO_GATEWAY_API_KEY,
    model: "claude-sonnet-4-6",
    configuration: {
        baseURL: `${process.env.ASTRO_GATEWAY_URL}/v1`,
    },
});

const response = await llm.invoke("Hello");
```

**`curl`**

```bash title="curl"
curl "$ASTRO_GATEWAY_URL/v1/chat/completions" \
  -H "Authorization: Bearer $ASTRO_GATEWAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-6",
    "messages": [{"role": "user", "content": "Hello"}]
  }'
```

For Mastra: the AI-SDK provider returned by `createOpenAI` is what `Agent.model:` accepts, so the rest of your `Agent` / `Workflow` / `Tool` definitions stay the same.

**Mastra needs an AI SDK v5+ model.** Add `@ai-sdk/openai` at **v2 or later** to your own `package.json`. Version 1.x produces an AI SDK v4 model (`specificationVersion: "v1"`) that Mastra's `stream()` rejects with `AGENT_STREAM_V1_MODEL_NOT_SUPPORTED`. This can be newer than the `@ai-sdk/openai` version Mastra pins internally, so install it explicitly rather than relying on the transitive one.

## Streaming

Set `stream: true` to receive tokens incrementally as they're generated.

**`Python`**

```python title="Python"
stream = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=[{"role": "user", "content": "Write a haiku about the sea."}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)
```

**`TypeScript`**

```typescript title="TypeScript"
const stream = await client.chat.completions.create({
    model: "claude-sonnet-4-6",
    messages: [{ role: "user", content: "Write a haiku about the sea." }],
    stream: true,
});
for await (const chunk of stream) {
    process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
```

## Structured output

Prompting for a JSON object and parsing it (with a fallback for the occasional non-JSON reply) is a portable way to get structured output that works regardless of which structured-output features a given model exposes through the gateway.

**`Python`**

```python title="Python"
import json

response = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=[
        {"role": "system", "content": "Reply with only a JSON object: {\"sentiment\": \"positive|negative|neutral\"}."},
        {"role": "user", "content": "I love this product."},
    ],
)
raw = response.choices[0].message.content
try:
    result = json.loads(raw)
except json.JSONDecodeError:
    result = {"sentiment": "unknown"}
```

## Next steps

* [Supported models](/ai-gateway/models): every model id the gateway accepts
* [Vision](/ai-gateway/vision): send images alongside text
* [Web search](/ai-gateway/web-search): ground answers in current information