You want one API key and one URL, and you still want to pick the provider per request. Vercel AI Gateway sits in front of many model providers. This package talks to that public OpenAI-compatible API.
Install @tanstack/ai-vercel-gateway. Then call vercelGatewayText, vercelGatewayEmbedding, or vercelGatewayImage.
npm install @tanstack/ai-vercel-gatewaySet AI_GATEWAY_API_KEY. If that key is not set, the adapter uses VERCEL_OIDC_TOKEN.
export AI_GATEWAY_API_KEY="..."You can also pass the key to a create* factory:
import { createVercelGatewayText } from "@tanstack/ai-vercel-gateway"
const adapter = createVercelGatewayText(
"anthropic/claude-opus-5",
process.env.AI_GATEWAY_API_KEY!,
)The default adapter uses the OpenAI Responses API at https://ai-gateway.vercel.sh/v1. Model ids use the creator/model form.
Server. An endpoint that streams the reply over SSE:
import { chat, toServerSentEventsResponse } from "@tanstack/ai"
import { vercelGatewayText } from "@tanstack/ai-vercel-gateway"
export async function POST(request: Request) {
const { messages } = await request.json()
const stream = chat({
adapter: vercelGatewayText("anthropic/claude-opus-5"),
messages,
})
return toServerSentEventsResponse(stream)
}Client. The same useChat hook as every other provider:
import { useState } from "react"
import { fetchServerSentEvents, useChat } from "@tanstack/ai-react"
export function Chat() {
const [input, setInput] = useState("")
const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
})
return (
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}</strong>
{message.parts.map((part, index) =>
part.type === "text" ? <p key={index}>{part.content}</p> : null,
)}
</div>
))}
<form
onSubmit={(event) => {
event.preventDefault()
if (!input.trim() || isLoading) return
sendMessage(input)
setInput("")
}}
>
<input value={input} onChange={(event) => setInput(event.target.value)} />
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
</div>
)
}Pass { api: "chat" } when the model must talk to Chat Completions. The default is Responses.
import { chat } from "@tanstack/ai"
import { vercelGatewayText } from "@tanstack/ai-vercel-gateway"
const stream = chat({
adapter: vercelGatewayText("openai/gpt-5.5", { api: "chat" }),
messages: [{ role: "user", content: "Hello" }],
})api: "responses" is the same as the default. api: "chat-completions" is the same as api: "chat".
Put Gateway routing on modelOptions.gateway. The adapter sends those fields as providerOptions.gateway. Do not put gateway at the top level of the request body.
import { chat } from "@tanstack/ai"
import { vercelGatewayText } from "@tanstack/ai-vercel-gateway"
const stream = chat({
adapter: vercelGatewayText("anthropic/claude-opus-5"),
messages: [{ role: "user", content: "Hello" }],
modelOptions: {
gateway: {
order: ["anthropic", "openai"],
only: ["anthropic"],
sort: "cost",
models: ["anthropic/claude-opus-5", "openai/gpt-5.5"],
caching: "auto",
disallowPromptTraining: true,
},
},
})order is the provider try list. only limits which providers can run. sort picks cost, time to first token, or tokens per second. models is the fallback model list.
order and only accept catalog provider ids such as "anthropic". models accepts catalog chat model ids. Each chat model also has its own modelOptions keys and input types from the catalog. A text-only model does not accept image parts. A model without temperature in the catalog does not accept temperature.
import { embed } from "@tanstack/ai"
import { vercelGatewayEmbedding } from "@tanstack/ai-vercel-gateway"
const result = await embed({
adapter: vercelGatewayEmbedding("openai/text-embedding-3-small"),
input: "a red guitar",
})
console.log(result.embeddings[0]?.vector)Image generation is text-to-image only. The adapter calls POST /v1/images/generations. Image edits are not in this package yet.
import { generateImage } from "@tanstack/ai"
import { vercelGatewayImage } from "@tanstack/ai-vercel-gateway"
const result = await generateImage({
adapter: vercelGatewayImage("openai/gpt-image-1"),
prompt: "a red guitar",
})import { summarize } from "@tanstack/ai"
import { vercelGatewaySummarize } from "@tanstack/ai-vercel-gateway"
const result = await summarize({
adapter: vercelGatewaySummarize("anthropic/claude-opus-5"),
text: "The Fender Stratocaster is a versatile electric guitar.",
stream: false,
})This package does not generate video. It does not do speech, transcription, or rerank. Use a dedicated adapter for those activities.
The catalog is a closed list from GET /v1/models. Daily CI updates that list.