AI Gateway

Use Astro AI-provided LLM access without managing your own provider keys

View as Markdown

Set one line in your spec and your agent gets a managed API key for calling supported models. The gateway is OpenAI-API-compatible, so it works with the SDKs and frameworks you already use.

Quick start

Scaffold a new agent wired to the gateway in one step:

$ast create my-agent --model gateway

This generates a gateway model in the spec and agent code that reads the injected env vars — no provider key to configure. To enable the gateway on an existing agent, declare a model with provider: gateway in astropods.yml and list the models you want to choose between:

1models:
2 default:
3 provider: gateway
4 models: [claude-sonnet-4-6, gpt-4o]

The models list is a menu of options. At deploy time — in the web console and CLI — you pick one, and it is injected as MODEL_<NAME> (here MODEL_DEFAULT). Your agent code reads the model id from that env var.

The agent.astro_ai_gateway: true boolean is deprecated in favor of a provider: gateway model. It still works (it enables the gateway with no deploy-time model selection), but declaring a gateway model is preferred and lets you pick the model at deploy.

On ast deploy (and ast dev), your agent container receives:

Env varPurpose
ASTRO_GATEWAY_URLGateway host (stable). Append /v1 for the OpenAI-compatible API (see below).
ASTRO_GATEWAY_API_KEYBearer credential (stable). Treat as a secret.
MODEL_<NAME>The model chosen at deploy for the gateway entry <name> (e.g. MODEL_DEFAULT).

Calling the gateway

The OpenAI-compatible API lives under the /v1 path, so set your client’s base URL to ${ASTRO_GATEWAY_URL}/v1. Everything else is a standard OpenAI-style request — pass one of the supported models as model and authenticate with ASTRO_GATEWAY_API_KEY as a bearer token.

Append /v1 to ASTRO_GATEWAY_URL. The env var is the host only (e.g. https://aig.example); the OpenAI-compatible endpoints are served under /v1 (/v1/chat/completions, /v1/embeddings). Pointing a client at the bare host returns 404.

1from openai import OpenAI
2import os
3
4client = OpenAI(
5 api_key=os.environ["ASTRO_GATEWAY_API_KEY"],
6 base_url=f"{os.environ['ASTRO_GATEWAY_URL']}/v1",
7)
8
9response = client.chat.completions.create(
10 model="claude-sonnet-4-6",
11 messages=[{"role": "user", "content": "Hello"}],
12)
13print(response.choices[0].message.content)

For Mastra: the AI-SDK provider returned by createOpenAI is what Agent.model: accepts, so the rest of your Agent / Workflow / Tool definitions stay the same.

Mastra needs an AI SDK v5+ model. Add @ai-sdk/openai at v2 or later to your own package.json. Version 1.x produces an AI SDK v4 model (specificationVersion: "v1") that Mastra’s stream() rejects with AGENT_STREAM_V1_MODEL_NOT_SUPPORTED. This can be newer than the @ai-sdk/openai version Mastra pins internally, so install it explicitly rather than relying on the transitive one.

Supported models

Pass any of these strings as model on the SDK call:

ModelUse case
claude-opus-4-8Most capable; longer reasoning, agentic workflows.
claude-sonnet-4-6Workhorse; balanced cost / quality. Good default.
claude-haiku-4-5Fast and cheap; tool-use loops, classification.
nova-proAmazon Nova; balanced general use with low latency.
nova-liteAmazon Nova; cheaper and faster for high-volume calls.
nova-microAmazon Nova; lowest-cost text tier.
mistral-large-3Mistral flagship; strong general reasoning and coding.
pixtral-largeMistral vision-language; accepts image + text input.
titan-embed-text-v2Text embeddings (RAG, vector search).

All chat models are called the same way — swap the model string. titan-embed-text-v2 is an embeddings model (use the embeddings endpoint, below).

Streaming

Set stream: true to receive tokens incrementally as they’re generated.

1stream = client.chat.completions.create(
2 model="claude-sonnet-4-6",
3 messages=[{"role": "user", "content": "Write a haiku about the sea."}],
4 stream=True,
5)
6for chunk in stream:
7 delta = chunk.choices[0].delta.content
8 if delta:
9 print(delta, end="", flush=True)

Embeddings

Use titan-embed-text-v2 on the embeddings endpoint for RAG and vector search. It returns a 1024-dimension vector per input.

1resp = client.embeddings.create(
2 model="titan-embed-text-v2",
3 input="Astro AI deploys agents as containers.",
4)
5vector = resp.data[0].embedding # list[float], length 1024

Images (vision)

pixtral-large accepts image input alongside text. Pass the image as a base64 data URI in an image_url content block — the gateway forwards the image bytes to the model, so remote URLs are not fetched server-side.

Python
1import base64
2
3with open("chart.png", "rb") as f:
4 data_uri = "data:image/png;base64," + base64.b64encode(f.read()).decode()
5
6response = client.chat.completions.create(
7 model="pixtral-large",
8 messages=[{
9 "role": "user",
10 "content": [
11 {"type": "text", "text": "What does this chart show?"},
12 {"type": "image_url", "image_url": {"url": data_uri}},
13 ],
14 }],
15)
16print(response.choices[0].message.content)

Structured output

Prompting for a JSON object and parsing it (with a fallback for the occasional non-JSON reply) is a portable way to get structured output that works regardless of which structured-output features a given model exposes through the gateway.

Python
1import json
2
3response = client.chat.completions.create(
4 model="claude-sonnet-4-6",
5 messages=[
6 {"role": "system", "content": "Reply with only a JSON object: {\"sentiment\": \"positive|negative|neutral\"}."},
7 {"role": "user", "content": "I love this product."},
8 ],
9)
10raw = response.choices[0].message.content
11try:
12 result = json.loads(raw)
13except json.JSONDecodeError:
14 result = {"sentiment": "unknown"}

Native Anthropic API (Claude Code, Claude Agent SDK)

Agents built on the native Anthropic Messages API — Claude Code, the Claude Agent SDK, or the @anthropic-ai/sdk client — can’t use the OpenAI-compatible surface above. Use the gateway’s /anthropic passthrough endpoint instead. This is the only way to run a Claude-Code-based agent on the gateway.

The platform injects only ASTRO_GATEWAY_URL and ASTRO_GATEWAY_API_KEY — it does not set any ANTHROPIC_* variables. You map them yourself, and there are three gateway-specific details:

DetailValueWhy
Base URL${ASTRO_GATEWAY_URL}/anthropicThe client appends /v1/messages, giving ${ASTRO_GATEWAY_URL}/anthropic/v1/messages.
Auth headerx-bf-vk: ${ASTRO_GATEWAY_API_KEY}The gateway reads the virtual key from x-bf-vk — not Authorization / x-api-key, which it ignores.
Model idbedrock/claude-opus-4-8 (or -sonnet-4-6, -haiku-4-5)Models are served under bedrock/<name> ids; bare claude-* returns 401.

Authenticate with the x-bf-vk header, not a Bearer token — the gateway ignores Authorization and x-api-key. For Claude Code, also set CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 to disable its pre-release beta flags, which Bedrock rejects.

$export ANTHROPIC_BASE_URL="$ASTRO_GATEWAY_URL/anthropic"
$# The gateway reads the virtual key from x-bf-vk; ANTHROPIC_AUTH_TOKEN is set only so
$# the client has *a* credential (the gateway ignores it).
$export ANTHROPIC_CUSTOM_HEADERS="x-bf-vk: $ASTRO_GATEWAY_API_KEY"
$export ANTHROPIC_AUTH_TOKEN="$ASTRO_GATEWAY_API_KEY"
$# Bedrock-served model ids (bare claude-* returns 401):
$export ANTHROPIC_MODEL="bedrock/claude-opus-4-8"
$export ANTHROPIC_DEFAULT_SONNET_MODEL="bedrock/claude-sonnet-4-6"
$export ANTHROPIC_DEFAULT_HAIKU_MODEL="bedrock/claude-haiku-4-5"
$# Bedrock rejects Claude Code's first-party pre-release beta flags:
>export CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1
># then run `claude` (or the Agent SDK) as usual

Streaming, client-defined tool use, and adaptive thinking all work over this path. Anthropic’s server-side tools (web_search, web_fetch, code execution, computer use) are not available — the models run on Amazon Bedrock, which doesn’t host them. If your agent needs web search, wire up your own tool (call a search API and feed the result back as a tool result).

Local development

ast dev handles the gateway automatically. Run:

$ast login
$ast dev

Your local agent container receives the same ASTRO_GATEWAY_* env vars it would have in production. Code written against those env vars works identically in dev and prod.

Run ast login first if you haven’t — the gateway is account-scoped, so the CLI needs to know who you are.

Mixing with your own keys

astro_ai_gateway: true is independent of any models you declare. You can use the gateway for some calls and your own provider key for others:

1agent:
2 image: my-agent:latest
3 astro_ai_gateway: true # gateway-managed models
4models:
5 custom-fine-tune:
6 provider: openai # your own OpenAI key for a specific use case

Your agent code reads ASTRO_GATEWAY_API_KEY for gateway calls and OPENAI_API_KEY for the bring-your-own-key (BYOK) provider.

Errors and limits

The gateway returns standard OpenAI-style error responses. Common cases:

StatusMeaningWhat to do
401Missing/invalid ASTRO_GATEWAY_API_KEY.Redeploy so the key is re-injected; don’t hardcode it.
404Wrong path.Ensure your base URL ends in /v1.
429Rate or usage limit reached.Back off and retry; the OpenAI SDKs retry 429/5xx automatically.

What it doesn’t cover

  • Provider-native SDKs and prefixes (except Anthropic). Call models through the OpenAI-compatible API by their gateway id (for example claude-sonnet-4-6), not with a provider prefix such as anthropic/. The one exception is the native Anthropic Messages API, served on the /anthropic passthrough for Claude Code / Claude Agent SDK agents — see Native Anthropic API.
  • Anthropic server-side tools. web_search, web_fetch, code execution, and computer use aren’t available — models run on Amazon Bedrock, which doesn’t host them. Client-defined tools work normally.
  • Image generation. No image-generation models are offered. Image input (vision) is supported by pixtral-large.
  • Bring-your-own-model. Adding new models or fine-tunes to the gateway isn’t self-serve; contact support.

Next steps