TypeSafe API with AI Gateway
Keep using the TypeSafe SDK and route requests through AI Gateway by changing one setting.
Requests are billed through AI Gateway and appear in your usage and observability alongside every other model you call.
If you are writing new code instead of migrating, use the Decision API. It is the same capability without TypeSafe-specific naming.
The TypeSafe-compatible API is available at the following base URL:
https://ai-gateway.vercel.sh/typesafe
The TypeSafe-compatible API supports the same authentication methods as the main AI Gateway:
- API key: Use your AI Gateway API key with the
Authorization: Bearer <token>header - OIDC token: Use your Vercel OIDC token with the
Authorization: Bearer <token>header
You only need one of these. This is the credential AI Gateway authenticates you with, not the credential used to call the model.
To bill the provider directly instead of through AI Gateway, add a TypeSafe key under BYOK.
Change the base URL and the API key:
import { TypeSafeClient } from '@typesafe-ai/sdk';
const client = new TypeSafeClient({
- apiKey: process.env.TYPESAFE_API_KEY,
+ apiKey: process.env.AI_GATEWAY_API_KEY,
+ baseURL: 'https://ai-gateway.vercel.sh/typesafe',
});Everything else stays the same:
const result = await client.systemOne({
state: 'I was charged twice for my subscription.',
questions: {
refund: { type: 'noul', instructions: 'Is the customer asking for money back?' },
department: {
type: 'choice',
instructions: 'Which team should handle this?',
criteria: { billing: 'Charges and refunds', technical: 'Bugs and outages' },
},
},
});
console.log(result.answers.refund); // { type: 'noul', noul: 0.98 }POST /typesafe/v1/systemoneanswers typed questions about stateGET /typesafe/v1/modelslists the decision models available to you
This API implements the TypeSafe request and response shapes.
curl https://ai-gateway.vercel.sh/typesafe/v1/systemone \
-H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "typesafe-ai/jev",
"state": "I was charged twice for my subscription.",
"questions": {
"refund": {
"type": "noul",
"instructions": "Is the customer asking for money back?"
}
}
}'import { TypeSafeClient } from '@typesafe-ai/sdk';
const client = new TypeSafeClient({
apiKey: process.env.AI_GATEWAY_API_KEY,
baseURL: 'https://ai-gateway.vercel.sh/typesafe',
});
const result = await client.systemOne({
model: 'typesafe-ai/jev',
state: 'I was charged twice for my subscription.',
questions: {
refund: {
type: 'noul',
instructions: 'Is the customer asking for money back?',
},
},
});import os
import requests
response = requests.post(
"https://ai-gateway.vercel.sh/typesafe/v1/systemone",
headers={
"Authorization": f"Bearer {os.environ['AI_GATEWAY_API_KEY']}",
"Content-Type": "application/json",
},
json={
"model": "typesafe-ai/jev",
"state": "I was charged twice for my subscription.",
"questions": {
"refund": {
"type": "noul",
"instructions": "Is the customer asking for money back?",
}
},
},
)
print(response.json()["answers"])The response uses TypeSafe's field names:
{
"model": "typesafe-ai/jev",
"answers": {
"refund": { "type": "noul", "noul": 0.98 }
},
"usage": { "input_tokens": 275, "output_tokens": 20 },
"provider_metadata": {
"gateway": {
"routing": {
"originalModelId": "typesafe-ai/jev",
"resolvedProvider": "typesafe-ai",
"canonicalSlug": "typesafe-ai/jev",
"finalProvider": "typesafe-ai"
},
"cost": "0.00001155",
"marketCost": "0.00001155",
"surchargeCost": "0",
"gatewayCost": "0.00001155",
"generationId": "gen_..."
}
}
}Decision fallbacks are an AI Gateway extension to the TypeSafe request. Add one conditional object to providerOptions.gateway.models to rerun a successful but uncertain decision with another model.
The official TypeSafe SDK forwards additional request properties at runtime, but its request type does not currently include providerOptions. Supply the extension through an intermediate object:
import { TypeSafeClient } from '@typesafe-ai/sdk';
const client = new TypeSafeClient({
apiKey: process.env.AI_GATEWAY_API_KEY,
baseURL: 'https://ai-gateway.vercel.sh/typesafe',
});
const request = {
model: 'typesafe-ai/jev',
state: 'I was charged twice and cannot sign in.',
questions: {
intent: {
type: 'choice' as const,
instructions: 'Which team should handle this?',
criteria: {
billing: 'Charges and refunds',
account: 'Sign-in and account access',
},
},
},
providerOptions: {
gateway: {
models: [
{
model: 'openai/gpt-6-astra',
when: { question: 'intent', confidenceBelow: 0.6 },
},
],
},
},
};
const result = await client.systemOne(request);Noul returns the fallback model's final P(true) normally. The response's top-level model echoes the model ID you requested when the primary answered, and names the fallback model whenever a fallback answered. AI Gateway routing metadata lists each model attempt and marks the one your condition triggered. Triggered requests bill both stages and report their combined usage.
When a condition matches, the response also carries these headers, listed in Access-Control-Expose-Headers so browser clients can read them:
| Header | Value |
|---|---|
x-ai-gateway-decision-fallback-triggered | true |
x-ai-gateway-decision-fallback-final-model | The model that produced the returned answers |
x-ai-gateway-decision-fallback-primary-model | The primary model whose answers matched the condition |
x-ai-gateway-decision-fallback-triggering-questions | A percent-encoded JSON array of the question IDs that matched. Read it with JSON.parse(decodeURIComponent(value)) |
x-ai-gateway-decision-fallback-triggering-questions-truncated | true when the question list was cut short. provider_metadata.gateway.routing has the full list |
AI Gateway omits these headers when the condition doesn't match. Use them to detect a fallback when your client only reads TypeSafe's typed response fields.
AI Gateway also sends the deprecated x-ai-gateway-evaluation-fallback-* headers with identical values. Read the x-ai-gateway-decision-fallback-* headers in new code.
See Decision Fallbacks for Boolean probability conditions, compound policies, virtual models, the complete sentinel response, and two-stage cost and latency.
Errors use TypeSafe's shape, with a machine-readable code alongside the message:
{
"message": "questions.refund.type: expected one of 'noul', 'choice', 'score'",
"error_type": "invalid_request"
}Errors returned by the model provider are passed through unchanged, so a client that already handles TypeSafe errors keeps working.
- Decision for the HTTP API and the AI SDK
- Decision Fallbacks for successful-response escalation
- Learn how to classify, route, and score with Jev and AI SDK and route form submissions
- Learn how to automatically approve tool calls in eve with Jev
- Models and providers for the full catalog
Was this helpful?