Vercel AI Gateway
Route your Vercel AI Gateway calls through AI SpendOps for automatic usage tracking and cost attribution.
The gateway is a single endpoint fronting hundreds of models across many providers, addressed as creator/model. AI SpendOps sits in front of it, so you change one base URL and add one header.
Configuration
| Setting | Value |
|---|---|
| Route | /v1/vercel/* |
| Upstream | https://ai-gateway.vercel.sh |
| Auth header | Authorization: Bearer vck_... (also accepts a Vercel OIDC token) |
| Streaming usage | Always included |
| Cost | Reported in-band by the gateway |
SDK base URL
The gateway supports two SDK families, and they need different base URLs.
| Client | Base URL |
|---|---|
OpenAI SDK, AI SDK createOpenAICompatible | https://proxy.aispendops.com/v1/vercel/v1 |
| Anthropic SDK (Messages shape) | https://proxy.aispendops.com/v1/vercel |
The doubled /v1 in the OpenAI case is expected. The Anthropic SDK appends /v1/messages itself, so the shared part of the route stops before /v1.
Example
curl https://proxy.aispendops.com/v1/vercel/v1/chat/completions \
-H "Authorization: Bearer vck_your_gateway_key" \
-H "X-ASO-API-Key: aso_k_yourkey.secret" \
-H "Content-Type: application/json" \
-d '{"model":"anthropic/claude-sonnet-5","messages":[{"role":"user","content":"Hello"}]}'
Python SDK
from openai import OpenAI
client = OpenAI(
api_key="vck_your_gateway_key",
base_url="https://proxy.aispendops.com/v1/vercel/v1",
default_headers={"X-ASO-API-Key": "aso_k_yourkey.secret"},
)
response = client.chat.completions.create(
model="anthropic/claude-sonnet-5",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)
Anthropic SDK
from anthropic import Anthropic
client = Anthropic(
api_key="vck_your_gateway_key",
base_url="https://proxy.aispendops.com/v1/vercel",
default_headers={"X-ASO-API-Key": "aso_k_yourkey.secret"},
)
message = client.messages.create(
model="anthropic/claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
print(message.content[0].text)
Usage fields
| Field | Description |
|---|---|
prompt_tokens | Input tokens |
completion_tokens | Output tokens |
total_tokens | Sum of prompt + completion tokens |
cost | Amount charged by the gateway (provider_cost) |
market_cost | List-price value of the same consumption |
is_byok | Whether your own provider key served the request |
Restricting models
Your API key policy is checked against the full slug, so you can restrict by creator without naming individual models:
allowed_models: ["anthropic/*", "openai/gpt-5*"]
allowed_models: ["vercel:anthropic/*", "vercel:openai/*"]
Because every gateway request arrives under the single vercel provider, provider-level policy cannot separate creators. Use allowed_models for that.
The gateway also accepts fallback models in models[] and in providerOptions.gateway.models[]. AI SpendOps validates every entry against your policy, not just the primary model, so a restricted key cannot reach a disallowed model through a fallback. If any entry fails, the request is denied with model_not_allowed.
Bring your own key (BYOK)
Use gateway credits rather than BYOK if you want AI SpendOps to show what you spent.
Vercel lets you attach your own provider credentials. When a request is served that way, the provider bills you directly and the gateway reports cost: 0, because the gateway itself charged you nothing. That zero is accurate for what Vercel charged, but it is not what the request cost you.
AI SpendOps records these requests with a cost of zero and a source of byok-zero, so they are visibly distinct from a pricing failure. Tokens are still recorded in full, so usage reporting stays accurate. The market_cost field is also captured, so the list-price value of that consumption is retained even when the charged cost is zero.
Budgets and spend alerts are driven by charged cost, so BYOK traffic will not count toward them.
Notes
- Model names use the format
creator/model, for exampleanthropic/claude-sonnet-5oropenai/gpt-5.6-sol. - The gateway reports cost in-band on both streaming and non-streaming responses, so AI SpendOps uses the amount actually charged rather than calculating from token rates.
- Streaming responses include a final usage chunk automatically. You do not need
stream_options.include_usage. - Response ids are Vercel generation ids of the form
gen_..., recorded asgeneration_id. - If a request falls back to a different model, the response reports the model that actually ran, and that is the model AI SpendOps records.
- Requests are attributed to the
vercelprovider. To break spend down by creator, group on the leading segment of the model name. - Vercel may retry a failed request against another provider or model. One AI SpendOps request can therefore correspond to more than one billed generation on your Vercel invoice.
- Latency measurements include Vercel's own routing and any internal retries, so first-token times are higher and noisier than calling a provider directly.