# Vercel AI SDK

> Connect the Vercel AI SDK to MirAPI with createOpenAICompatible and stream deepseek-chat responses over standard SSE.

The Vercel AI SDK's `createOpenAICompatible` adapter connects to any OpenAI-compatible endpoint, including MirAPI, and `streamText` consumes the SSE stream. You only change the base URL, the key, and the model.

## What you need

| Element | Value |
|---|---|
| Base URL | `https://api.mirapi.ai/v1` |
| API key | `MIRAPI_API_KEY` |
| Model | `deepseek-chat` |

## Install

Install the core SDK and the OpenAI-compatible adapter:

```bash
npm install ai @ai-sdk/openai-compatible
```

## Configure

Then create the provider pointing at MirAPI:

```ts
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { streamText } from "ai";

const mirapi = createOpenAICompatible({
  name: "mirapi",
  baseURL: "https://api.mirapi.ai/v1",
  apiKey: process.env.MIRAPI_API_KEY!,
});
```

The adapter options are:

| Option | Purpose |
|---|---|
| `name` | Provider label used to build model handles such as `mirapi("deepseek-chat")` |
| `baseURL` | `https://api.mirapi.ai/v1` — the adapter appends `/chat/completions` itself |
| `apiKey` | Your MirAPI key, typically from `process.env.MIRAPI_API_KEY` |

:::note
Keep the `/v1` in `baseURL`. The adapter builds the full path, so pointing it at `https://api.mirapi.ai` alone would miss the `/v1` segment.
:::

## Verify with a message

```ts
const result = streamText({
  model: mirapi("deepseek-chat"),
  prompt: "Reply with exactly: connected to MirAPI",
});

for await (const textPart of result.textStream) {
  process.stdout.write(textPart);
}
```

The streamed output should end with `connected to MirAPI`.

## Tool calling

Pass `tools` in the `streamText` options using the OpenAI `tools`/`tool_choice` format. The upstream model must support tool calling — check the capability flags in the [catalogue](/docs/models).

```ts
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { streamText } from "ai";
import { z } from "zod";

const mirapi = createOpenAICompatible({
  name: "mirapi",
  baseURL: "https://api.mirapi.ai/v1",
  apiKey: process.env.MIRAPI_API_KEY!,
});

const result = streamText({
  model: mirapi("deepseek-chat"),
  prompt: "What is the weather in Paris?",
  tools: {
    getWeather: {
      description: "Get the current weather for a city",
      parameters: z.object({
        city: z.string().describe("City name"),
      }),
      execute: async ({ city }) => `Sunny, 18°C in ${city}`,
    },
  },
});

for await (const textPart of result.textStream) {
  process.stdout.write(textPart);
}
```

## Streaming

Streaming is standard SSE — `data:` lines terminated by `data: [DONE]` — and `streamText` maps it to text parts without special flags. With reasoning models, the reasoning content arrives before the answer. Non-streaming `usage` reports token counts only, never a dollar amount.

## Protocol variants

MirAPI speaks three protocols natively, and one key covers all of them:

| Protocol | Base URL | Authentication |
|---|---|---|
| OpenAI-compatible | `https://api.mirapi.ai/v1` | `Authorization: Bearer` |
| Anthropic | `https://api.mirapi.ai` | `x-api-key` or `Authorization: Bearer` |
| Gemini | `https://api.mirapi.ai` or `https://api.mirapi.ai/v1beta` | `x-goog-api-key` or `?key=` |

`createOpenAICompatible` uses the OpenAI protocol. The Anthropic protocol has its own SDK, covered in [Anthropic SDK](/docs/integrations/anthropic-sdk); the Gemini protocol is documented in [Configuration](/docs/configuration).

## Errors and troubleshooting

| Status | Meaning | What to do |
|---|---|---|
| `401` | Key missing, invalid, or unknown | Fix your credentials |
| `403` | Key valid but rejected (balance, whitelist, quota, IP) | Resolve the cause, then retry |
| `413` | Payload too large | Reduce the request size |
| `429` | Rate limited | Back off; no `Retry-After` header is sent |
| `500` | Server error | Safe to retry idempotent requests |

A 403 with a valid key means the model is not whitelisted or the balance is exhausted — top up and retry. Every error message ends with a request ID; include it in support tickets. See [Errors](/docs/api-reference/errors).

## Billing

Chat is billed per token, in three tiers per 1M tokens (input, output, cache read). Reasoning tokens are billed as output, and cache reads cost less than fresh input. The balance is prepaid and never expires; when it runs out, requests return 403 and recover immediately after you top up. Reconcile cost in the console billing log or `GET /api/log/token` — see [Billing](/docs/billing).

## Troubleshooting

- **401?** The key is missing, invalid, or unknown — verify it and retry.
- **403 on a request that used to work?** Check the balance and the key constraints (whitelist, quota, IP). Topping up restores the key immediately.
- **429?** Back off with jittered exponential delay (1s → 2s → 4s, capped around 30s); no `Retry-After` header is sent.
- **Model not found?** Use the exact `id` from `GET /v1/models` and drop any `vendor/` prefix.

## Related links

- [Quickstart](/docs/quickstart)
- [OpenAI compatibility](/docs/openai-compat)
- [Anthropic SDK](/docs/integrations/anthropic-sdk)
- [Configuration](/docs/configuration)
- [Models](/docs/models)
- [Billing](/docs/billing)
- [Errors](/docs/api-reference/errors)