Vercel AI Gateway integration
Run Vercel AI Gateway traffic through the ACE gateway: endpoint, the headers a call sets, what passes through untouched, and a runnable example.
Put ACE in front of Vercel AI Gateway: send x-ace-upstream-profile: vercel and the AI Gateway key on the channel's key header, or store both as the tenant's vendor key. Vercel's failover stays in charge; ACE makes one attempt, so a 429 or 5xx reaches the SDK with its retry-after.
- Models: Vercel's own ids (
anthropic/claude-sonnet-4.5) on every surface; Vercel pays the provider, so no provider key is sent. - SDK:
createOpenAICompatiblesends gateway fields fromproviderOptions.<name>; withcreateOpenAIorcreateAnthropic, add them with afetchwrapper. - Caching: with prompt compaction on,
caching: "auto"keeps the system prompt and tools byte-identical. - Claude Code under a HarnessAgent takes the headers through
ANTHROPIC_CUSTOM_HEADERSand gateway fields throughCLAUDE_CODE_EXTRA_BODY. - Not supported:
@ai-sdk/gatewaywithbaseURLpointed at ACE (a 400,ai_gateway_protocol_unsupported), and ACE behind Vercel AI Gateway.
Endpoint: https://engine.acefleet.dev/v1/chat/completions · also https://engine.acefleet.dev/v1/responses, https://engine.acefleet.dev/anthropic/v1/messages. ACE credential: Authorization: Bearer ace_dev_... (x-api-key on /anthropic/v1/messages).
x-ace-upstream-profile: vercel names the profile.
| Topology | Shape |
|---|---|
ACE in front |
Client → ACE → https://ai-gateway.vercel.sh/v1. |
ACE behind |
Not offered. |
| Header | Required | Description |
|---|---|---|
x-ace-openai-key |
Yes |
The Vercel AI Gateway API key. On /anthropic/v1/messages it rides on x-ace-anthropic-key instead. Vercel credits or dashboard BYOK pay the provider, so no provider key is sent. |
Passed through untouched
providerOptions.gateway(order,only,sort,models,user,tags,byok,zeroDataRetention,caching, …); abyokcredential is never logged- top-level
modelsandproviderbody fields ai-reporting-tagsandai-reporting-userheaders- Vercel's generation id (
gen_…), in the first streamed chunk and as the response id
Not supported
@ai-sdk/gateway(createGateway) withbaseURLpointed at ACE: it speaks Vercel's internal gateway protocol (POST {baseURL}/language-model), not an OpenAI- or Anthropic-shaped API, and ACE answers it with a 400 (ai_gateway_protocol_unsupported). UsecreateOpenAICompatible,createOpenAIorcreateAnthropic.- ACE behind Vercel AI Gateway.
Storing the AI Gateway key and the vercel profile as the tenant's openai (or anthropic) vendor key leaves the client changing only baseURL and apiKey. The tenant has one vendor key per channel, so this replaces any provider key stored there and sends all of the tenant's traffic on that channel through Vercel AI Gateway.
Vercel AI Gateway behind ACE (AI SDK)
import { generateText, streamText } from 'ai';
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';
const ACE_KEY = 'ace_dev_<your-dev-key>';
const AI_GATEWAY_API_KEY = process.env.AI_GATEWAY_API_KEY!; // the Vercel AI Gateway key
// Chat completions, any model Vercel serves. Gateway fields go in providerOptions.vercel
// (the provider's name): openai-compatible copies them into the request body.
const vercel = createOpenAICompatible({
name: 'vercel',
baseURL: 'https://engine.acefleet.dev/v1',
apiKey: ACE_KEY, // Authorization: Bearer: your ACE identity
headers: {
'x-ace-upstream-profile': 'vercel',
'x-ace-openai-key': AI_GATEWAY_API_KEY,
'ai-reporting-tags': 'team-a,feature-b', // optional, reaches Vercel as sent
},
});
const { text, response } = await generateText({
model: vercel('anthropic/claude-sonnet-4.5'), // Vercel's model id, sent verbatim
prompt: 'Summarise the failing checks.',
providerOptions: {
vercel: {
providerOptions: { gateway: { order: ['bedrock', 'anthropic'], caching: 'auto' } },
models: ['openai/gpt-5'], // Vercel's fallbacks
},
},
});
console.log(text, response.id); // response.id is Vercel's generation id
// The Responses API (createOpenAI's default) and Anthropic Messages. These providers only
// serialise their own options, so a fetch wrapper adds Vercel's fields to the body.
const withGateway = (fields: object): typeof fetch => (url, init) =>
fetch(url, typeof init?.body === 'string'
? { ...init, body: JSON.stringify({ ...JSON.parse(init.body), ...fields }) }
: init);
const gatewayFields = { providerOptions: { gateway: { only: ['bedrock'] } } };
const openai = createOpenAI({
baseURL: 'https://engine.acefleet.dev/v1',
apiKey: ACE_KEY,
headers: { 'x-ace-upstream-profile': 'vercel', 'x-ace-openai-key': AI_GATEWAY_API_KEY },
fetch: withGateway(gatewayFields),
});
const anthropic = createAnthropic({
baseURL: 'https://engine.acefleet.dev/anthropic/v1',
apiKey: ACE_KEY, // sent as x-api-key
headers: { 'x-ace-upstream-profile': 'vercel', 'x-ace-anthropic-key': AI_GATEWAY_API_KEY },
fetch: withGateway(gatewayFields),
});
const stream = streamText({
model: anthropic('anthropic/claude-sonnet-4.5'),
prompt: 'List the flaky tests.',
});
for await (const delta of stream.textStream) process.stdout.write(delta);
await generateText({ model: openai('openai/gpt-5'), prompt: 'One line.' });