Vercel AI SDK integration
Vercel AI SDK with Plan2API: createOpenAICompatible in TypeScript
The Vercel AI SDK 7 reaches Plan2API through @ai-sdk/openai-compatible: 3 settings in createOpenAICompatible (name, baseURL, apiKey) give generateText, streamText and tools access to Claude, Gemini, GPT and Kimi models through one /v1/chat/completions endpoint.
- Package
- @ai-sdk/
openai-compatible - Factory
- createOpenAICompatible
- baseURL
- https://
api.plan2api.com/ v1 - Endpoint used
- /
v1/ chat/ completions
Updated
AI SDK settings for Plan2API
One createOpenAICompatible provider instance covers every Plan2API model you have connected. The table below lists the exact settings to pass when creating a Vercel AI SDK custom provider.
| Setting | Value |
|---|---|
| Package | @ai-sdk/ |
| name | 'plan2api' (also the providerOptions key) |
| baseURL | https:// |
| apiKey | process.env.PLAN2API_ |
| Model | plan2api('claude/ |
| Endpoint | / |
Set up the provider
- Sign up with a Google account, connect your Claude, ChatGPT, Google AI or Kimi subscription, and copy the
p2a_API key from the dashboard. - Install the core SDK, the OpenAI-compatible provider package and Zod with
npm install ai @ai-sdk/openai-compatible zod(ai7.0.157and@ai-sdk/openai-compatible3.0.81as of October 2026). - Export your key in the environment as
PLAN2API_KEY. - Create the provider with
createOpenAICompatible, settingnameto'plan2api',baseURLto'https://api.plan2api.com/v1'andapiKeytoprocess.env.PLAN2API_KEY. - Run the script as an ES module (for example, by setting
"type": "module"inpackage.json), since the code uses top-levelawait.
The following example calls Claude with generateText, streams Gemini with streamText, and runs a Kimi tool call.
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { generateText, isStepCount, streamText, tool } from 'ai';
import { z } from 'zod';
const plan2api = createOpenAICompatible({
name: 'plan2api',
baseURL: 'https://api.plan2api.com/v1',
apiKey: process.env.PLAN2API_KEY,
});
const { text } = await generateText({
model: plan2api('claude/claude-sonnet-5'),
prompt: 'Explain HTTP 429 in one sentence.',
providerOptions: { plan2api: { reasoningEffort: 'high' } },
});
console.log(text);
const result = streamText({
model: plan2api('gemini/gemini-3.8-flash-high'),
prompt: 'Write a haiku about deploy previews.',
});
for await (const part of result.textStream) {
process.stdout.write(part);
}
console.log();
const { text: answer } = await generateText({
model: plan2api('kimi/k3'),
tools: {
getWeather: tool({
description: 'Get the current weather for a city',
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => ({ city, forecast: 'sunny' }),
}),
},
stopWhen: isStepCount(3),
prompt: 'What is the weather in Berlin?',
});
console.log(answer);Pick a model id
Pass the full model id straight into plan2api('model-id'), slashes included, and the SDK sends that exact string to /v1/chat/completions. The prefix before the slash picks the upstream provider: claude/ (or anthropic/) for Claude models like Claude Sonnet 5 (claude/claude-sonnet-5), openai/ (or codex/) for ChatGPT Codex models such as openai/gpt-6.1-sol, gemini/ (or google/ and antigravity/) for Gemini models like Gemini 3.8 Flash (gemini/gemini-3.8-flash-high), and kimi/ (or moonshotai/) for kimi/k3. GET /v1/models lists every id Plan2API serves, including providers you haven't connected, and Codex ids run on your own ChatGPT plan, so pick one your plan includes. The table below lists the main ids with a note on each.
| Model id | Subscription | Note |
|---|---|---|
| claude/ | Claude Pro or Max | 1M context, no JSON response_ |
| claude/ | Claude Pro or Max | 1M context, up to 128K output |
| gemini/ | Google AI Pro or Ultra | 1,048,576-token context |
| openai/ | ChatGPT (Codex) | Runs on your ChatGPT plan's Codex allowance |
| kimi/ | Kimi Code | Reasoning effort low, high or max |
Streaming, tools and reasoning effort
streamText({ model, prompt })returns right away, andresult.textStreamyields text parts as an async iterable.- Define tools with
tool({ description, inputSchema, execute })using azodschema forinputSchema(such asz.object({ city: z.string() })), which works across all four providers. - Pass
stopWhen: isStepCount(n)so the model can keep going and answer after receiving tool results (stepCountIsis the deprecated name). - Set
providerOptions: { plan2api: { reasoningEffort: 'high' } }to sendreasoning_effortin the request body; the key underproviderOptionsmatches the camelCase providername. Onkimi/k3the accepted values arelow,highandmax, andhighis the default. - Pass
includeUsage: trueincreateOpenAICompatibleto addstream_optionsinclude_usageto streaming requests (the default isfalse). temperatureandtopPcall settings are passed through on Gemini, fixed on Kimi (temperature1,top_p0.95) and ignored on Claude and Codex models.
Use @ai-sdk/openai-compatible, not @ai-sdk/openai
In the @ai-sdk/openai package, the default openai('id') call from createOpenAI targets the OpenAI Responses API (/v1/responses), which Plan2API doesn't serve, even when you configure a custom baseURL. Calling openai.chat('id') switches @ai-sdk/openai back to Chat Completions.
The @ai-sdk/openai-compatible package only calls /chat/completions for chat models (provider('id'), provider.languageModel('id') and provider.chatModel('id')), so it's the simpler fit for an OpenAI-compatible setup in the Vercel AI SDK.
openai('id') from @ai-sdk/openai posts to /v1/responses, which Plan2API doesn't serve, so use createOpenAICompatible or openai.chat('id') instead.
What works and what doesn't
These AI SDK features work through the /v1/chat/completions endpoint:
- Text generation and streaming with
generateTextandstreamText. - Tool and function calling on Claude, Codex, Gemini and Kimi models.
- Image input on Claude, Gemini and Kimi models, plus Codex models that accept images (up to 32 image URLs per request, up to 20 MB and 15 seconds each).
- Structured JSON output on Gemini, Codex and Kimi models; setting
supportsStructuredOutputs: trueon the provider sends aresponse_formatofjson_schemainstead ofjson_object. - Direct calls from browser code because the API sends
Access-Control-Allow-Origin: *CORS headers, though anyp2a_key included in client-side JavaScript is visible to anyone inspecting the page (read the API key security notes before shipping a browser app).
These features aren't supported:
- Legacy text completions through
provider.completionModel(), which posts to/completions. - Embeddings through
provider.embeddingModel(), which posts to/embeddings. - Structured output on Claude models, which rejects any non-text
response_formatwith HTTP400unsupported_parameter.
Troubleshooting the AI SDK with Plan2API
401invalid_api_key:PLAN2API_KEYisn't set in the environment where the code runs, the key doesn't start withp2a_, or it was sent in anx-api-keyheader instead ofAuthorization: Bearer.403no_credentials: your vault has no connected subscription for that model's provider, so connect an account in the dashboard first.400model_not_found: the model id isn't recognized; pass a full prefixed id listed byGET /v1/models.400unsupported_parameter: you requested structured JSON output (a non-textresponse_format) on a Claude model; use a Gemini, Codex or Kimi model for JSON schemas, or ask Claude for JSON in the prompt.- Requests failing at
/v1/responses: the code is callingopenai('id')from@ai-sdk/openai; switch tocreateOpenAICompatiblefrom@ai-sdk/openai-compatibleor callopenai.chat('id'). 429daily_requests: you hit the 51st chat request of the day on the Free plan (resets at 00:00 UTC), and each step of astopWhentool loop is a separate request; wait for theRetry-Afterwindow or check Plan2API plan limits and pricing to upgrade to Pro or Scale, which have no daily request cap.
Once streaming has started, any error arrives as a final server-sent event instead of an HTTP status code, so make sure your code handles stream errors too.
Use the AI subscriptions you already pay for as one OpenAI-compatible API key.
- Free: 1 subscription, 50 requests a day, no card
- Pro: $12 a month
FAQ
How do I use an OpenAI-compatible API with the Vercel AI SDK?
Install @ai-sdk/openai-compatible, create a provider with createOpenAICompatible({ name, baseURL, apiKey }), and pass provider('model-id') to generateText or streamText. The provider sends your apiKey in the Authorization: Bearer header and posts chat requests to {baseURL}/chat/completions.
Why does @ai-sdk/openai fail with Plan2API?
Calling openai('id') in @ai-sdk/openai uses the OpenAI Responses API (/v1/responses), and Plan2API serves Chat Completions (/v1/chat/completions) only. Use createOpenAICompatible from @ai-sdk/openai-compatible instead, or call openai.chat('id').
Can I use my Claude Pro or Max plan in the AI SDK?
Yes, you can connect a Claude Pro or Max subscription to Plan2API and pass claude/ model ids such as claude/claude-sonnet-5 or claude/claude-opus-5-5 to the compatible provider. That lets you run Claude in the AI SDK through /v1/chat/completions without paying per token.
Does structured output work through Plan2API?
Yes, on Gemini, Codex and Kimi models, which accept json_object and json_schema (when supportsStructuredOutputs: true is set on the provider). Claude models don't support it and return HTTP 400 unsupported_parameter for any non-text response_format.
Do embeddings work with the AI SDK and Plan2API?
No. Calling provider.embeddingModel() sends requests to /embeddings, and Plan2API doesn't serve an embeddings endpoint. Use a separate embeddings provider for vectors and keep the Plan2API provider for chat models.
Sources
- AI SDK OpenAI-compatible providersai-sdk.dev
- AI SDK generating textai-sdk.dev
- AI SDK tool callingai-sdk.dev
- openai-compatible chat model (GitHub)github.com
- @ai-sdk/openai provider (GitHub)github.com
- AI SDK stop condition exports, stepCountIs deprecated (GitHub)github.com
- ai on npmnpmjs.com
- @ai-sdk/openai-compatible on npmnpmjs.com