AppAIGatewayDocs
Integrate your app

Calling the provider proxy

The URL shape, the credential headers, what is forwarded to the provider, and how streaming works.

The proxy route forwards a provider-native request to one of your providers:

{METHOD} https://api.appaigateway.com/v1/apps/{app}/proxy/{provider}/{provider_path}
  • {app} is the app ID.
  • {provider} is a provider slug: openai, anthropic, gemini, xai, or a custom slug such as openai-dev. It names one of your provider keys, not a provider type.
  • {provider_path} is the provider's own API path, verbatim and without a leading slash. New apps allow inference endpoints, including each provider's image, embedding and speech endpoints. Other operations need an explicit allowed path, and a fixed model for pricing when the request does not name one. The provider route must support the operation too.

The body is the provider's body and the response is the provider's response. The provider's documentation is the reference for both.

Credentials

AppHeader
iOS appAuthorization: Bearer <gateway token> and X-App-Version: <your app's version>, plus X-AppAttest-Proof: <proof> when the app verifies App Attest on every request
Server app with signed-in usersAuthorization: Bearer <gateway token> and X-App-Version: <your app's version>
Server app without signed-in usersAuthorization: Bearer agw_…

X-App-Version is required whenever the credential is a gateway token. It is recorded on every request and is how the console's By app version breakdown tells one release from another. The Swift package sets it from the bundle's version.

When an iOS app's Verify App Attest on is Every request, the default for new apps, every request with its gateway token must carry X-AppAttest-Proof, an App Attest assertion signed over that request. It covers the method, the path and query, the body and the token, so build the request completely, body included, before it is signed, and send it unchanged. A missing proof answers 401 assertion_required; one that does not match the request answers 403 attest_failed. With Token exchange the gateway token alone is enough, and a proof sent anyway is ignored. The header is consumed by the gateway and never reaches the provider, and a server app's requests do not need it. The Swift package computes it: see Per-request proof.

The credential may also be sent in the provider's own key header, such as x-api-key for Anthropic or x-goog-api-key for Gemini. That is what lets a provider SDK work with the gateway token in place of the provider key. The Bearer prefix is optional.

Paths by provider

ProviderTypical path
OpenAIv1/responses, v1/chat/completions, v1/audio/transcriptions
Anthropicv1/messages
Geminiv1beta/models/{model}:generateContent or v1beta/openai/chat/completions
xAIv1/responses, v1/chat/completions, v1/stt
Perplexity, DeepSeek, ByteDancechat/completions
Groqopenai/v1/chat/completions
Fireworks AIinference/v1/chat/completions
Mistral, Together AI, Cerebras, Moonshot AI, Hugging Face, Baseten, OpenRouterv1/chat/completions

A provider routed through Vercel AI Gateway accepts only v1/responses, v1/chat/completions and v1/messages. The full table with each provider's caveats is on Supported providers.

Transcription response formats

A transcription is billed by the tokens or the audio duration its answer reports, and a plain-text answer reports neither. So the gateway refuses a response_format of text, srt or vtt with 400 invalid_request on OpenAI, Groq and Together AI. On OpenAI, use the default json, or verbose_json or diarized_json where the model supports them. Groq and Together AI report the duration only in verbose_json, so the gateway sends their transcriptions in that format when the request names json or no format at all. The answer still carries the same text field, plus timing details.

On these providers a transcription must be a multipart/form-data upload that names response_format at most once. On every provider, an upload that names model more than once is refused. Both are refused with 400 invalid_request.

For an SDK, the base URL is the proxy URL up to the provider's version prefix, for example …/proxy/openai/v1; the SDK appends responses or chat/completions itself.

Model names

Model names are the provider's own on every route: gemini-2.5-flash, never google/gemini-2.5-flash. If the app has a model rewrite, send the name the rewrite expects. If it has an allowlist, only listed names are accepted.

Streaming

Set the provider's own streaming option, "stream": true for most providers or the :streamGenerateContent path for Gemini, and read the stream as the provider documents it. The gateway forwards it unbuffered and reads usage out of it as it passes. If your client disconnects mid-stream, the provider call is cancelled.

What reaches the provider

The gateway adds your provider key and forwards a fixed set of request headers:

  • content-type, accept, accept-language, user-agent
  • anthropic-version, anthropic-beta, anthropic-dangerous-direct-browser-access, openai-beta, x-goog-api-client, idempotency-key
  • anything starting with x-stainless-, which the OpenAI and Anthropic SDKs send

Everything else is dropped, so a provider never sees your user's address, cookies or a header you did not intend to send. In particular openai-organization, openai-project and x-goog-user-project are dropped because they would redirect spend to another account, and any provider key your client sends is replaced.

Through a Cloudflare AI Gateway, the documented cf-aig-cache-ttl, cf-aig-skip-cache, cf-aig-max-attempts, cf-aig-backoff and cf-aig-retry-delay controls are forwarded too.

On the way back, set-cookie, openai-organization and openai-project are removed. x-request-id is kept, because provider support asks for it. The gateway adds a Server-Timing header with the time spent on authentication, limits and the provider's time to first byte.

Limits on a request

  • Request bodies are limited to 20 MB. Larger ones answer 413 payload_too_large.
  • If the provider sends no response headers within 120 seconds, the request answers 504 provider_error. After headers arrive there is no gateway body deadline. Self-hosted gateways can configure this timeout.
  • If the app sets Max output tokens for the provider, a request asking for more is refused with 403 max_output_tokens_exceeded, and one that asks for nothing gets the cap injected.

Provider errors

A provider's own error response, such as a 400 for a malformed body or a 429 from the provider's rate limit, is passed through with the provider's status and body, and recorded as a provider_error event. A request the gateway could not deliver at all answers 502 provider_error in the gateway's own format.

Reading your own limits

An app that identifies users can call GET /v1/apps/{app}/me with the same credentials as a proxy request:

{
  "user_id": "firebase-uid",
  "install_id": "c2lnbmVkLWtleS1pZA==",
  "limits": {
    "requests_today": 12,
    "requests_remaining": 9988,
    "requests_per_minute": 100,
    "requests_per_day": 10000,
    "monthly_cost_usd": 0.41,
    "monthly_budget_usd": null,
    "blocked": false
  }
}

install_id is the App Attest key id of the calling install on an iOS app, whose request counts and, by default, spend are per install; it is null on a server app. requests_today is null when the app sets no per-user limit, because requests are then not counted. An app with no user identity answers 404 auth_method_not_supported.

On this page