all docs
/ integrations

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: createOpenAICompatible sends gateway fields from providerOptions.<name>; with createOpenAI or createAnthropic, add them with a fetch wrapper.
  • 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_HEADERS and gateway fields through CLAUDE_CODE_EXTRA_BODY.
  • Not supported: @ai-sdk/gateway with baseURL pointed 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
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, …); a byok credential is never logged
  • top-level models and provider body fields
  • ai-reporting-tags and ai-reporting-user headers
  • Vercel's generation id (gen_…), in the first streamed chunk and as the response id

Not supported

  • @ai-sdk/gateway (createGateway) with baseURL pointed 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). Use createOpenAICompatible, createOpenAI or createAnthropic.
  • 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.' });