Provider Integrations (Zero-Trust Headers)
Per-provider pass-through credential headers for OpenAI, Anthropic, Azure, Gemini, Vertex and Bedrock — which headers each channel needs, and what a stored vendor key replaces.
In zero-trust mode the upstream credential rides on the request and is used for that one call — never persisted, never logged. Each provider channel needs a different header set, and they are not interchangeable: the shape below is per channel.
Sending a pass-through header always wins over a stored vendor key, so a tenant with a vault entry can still override it per request without changing any configuration.
OpenAI
/v1/chat/completions · accepted x-ace-provider values: openai
The same headers also serve /v1/responses.
| Header | Required | Description |
|---|---|---|
| x-ace-openai-key | Yes | Upstream OpenAI key — or the key of whichever OpenAI-compatible host x-ace-openai-base-url points at. Used for this call only, never persisted or logged. |
| x-ace-openai-base-url | Situational | Any OpenAI-compatible host (OpenRouter, Together, Groq, a self-hosted vLLM) is this channel pointed somewhere else: set the host here, its key above, and send its own model slug — anthropic/claude-sonnet-4.5 reaches OpenRouter spelled exactly like that. x-ace-served-by still names the channel (openai), not the host. |
A stored openai vendor key replaces x-ace-openai-key; a stored endpoint on it replaces x-ace-openai-base-url.
curl -X POST https://engine.acefleet.dev/v1/chat/completions \
-H "Authorization: Bearer ace_dev_<your-dev-key>" \
-H "x-ace-openai-key: sk-proj-..." \
-H "Content-Type: application/json" \
-d '{ "model": "gpt-4o", "messages": [{ "role": "user", "content": "ping" }] }'
# The same channel, pointed at OpenRouter (or any OpenAI-compatible host):
curl -X POST https://engine.acefleet.dev/v1/chat/completions \
-H "Authorization: Bearer ace_dev_<your-dev-key>" \
-H "x-ace-provider: openai" \
-H "x-ace-openai-base-url: https://openrouter.ai/api/v1" \
-H "x-ace-openai-key: sk-or-v1-..." \
-H "Content-Type: application/json" \
-d '{ "model": "anthropic/claude-sonnet-4.5", "messages": [{ "role": "user", "content": "ping" }] }'
Anthropic
/anthropic/v1/messages · accepted x-ace-provider values: anthropic
The same headers also serve /anthropic/v1/messages/count_tokens.
| Header | Required | Description |
|---|---|---|
| x-ace-anthropic-key | Yes | Upstream Anthropic key. Used for this call only, held in memory. |
A stored anthropic vendor key replaces x-ace-anthropic-key. The same surface also serves Claude on Vertex: pin x-ace-provider: google_vertex and send the Vertex credential set instead — see Google Vertex AI below, which shows the call.
curl -X POST https://engine.acefleet.dev/anthropic/v1/messages \
-H "Authorization: Bearer ace_dev_<your-dev-key>" \
-H "x-ace-anthropic-key: sk-ant-..." \
-H "Content-Type: application/json" \
-d '{ "model": "claude-sonnet-4-5", "max_tokens": 1024,
"messages": [{ "role": "user", "content": "ping" }] }'
Azure OpenAI
/azure/openai/deployments/<deployment>/chat/completions · accepted x-ace-provider values: azure
The same headers also serve /azure/openai/v1/chat/completions, /azure/openai/v1/responses, /azure/openai/responses?api-version=….
| Header | Required | Description |
|---|---|---|
| x-ace-azure-key | Yes | Upstream Azure OpenAI deployment key. |
| x-ace-azure-endpoint | Yes | Your Azure OpenAI resource endpoint. Azure has no global host, so the key alone does not say where to send the call. |
| x-ace-provider | Situational | Pin the channel to azure when the model name would not imply it. |
A stored azure vendor key replaces x-ace-azure-key; the endpoint is stored with it. The deployment ACE addresses is the request's model (the <deployment> path segment on this door), unless the stored Azure config maps that model to a differently-named deployment — set per model in the settings page or with store_provider_key's deployments. x-ace-azure-deployment is deprecated: it does not override model, and one that contradicts it is a 400.
curl -X POST "https://engine.acefleet.dev/azure/openai/deployments/gpt-4o-prod/chat/completions?api-version=2024-02-15-preview" \
-H "Authorization: Bearer ace_dev_<your-dev-key>" \
-H "x-ace-provider: azure" \
-H "x-ace-azure-key: <your-azure-key>" \
-H "x-ace-azure-endpoint: https://<resource>.openai.azure.com" \
-H "Content-Type: application/json" \
-d '{ "messages": [{ "role": "user", "content": "ping" }] }'
Google Gemini (Developer API)
/gemini/v1beta/models/<model>:generateContent · accepted x-ace-provider values: google, google_gemini, gemini
| Header | Required | Description |
|---|---|---|
| x-ace-google-key | Yes | Upstream Google AI key, for the Gemini-shaped surface. |
| x-ace-provider | Situational | Pin the channel to google_gemini rather than Vertex. |
A stored google vendor key replaces x-ace-google-key.
curl -X POST "https://engine.acefleet.dev/gemini/v1beta/models/gemini-2.5-flash:generateContent" \
-H "x-goog-api-key: ace_dev_<your-dev-key>" \
-H "x-ace-provider: google_gemini" \
-H "x-ace-google-key: <your-gemini-key>" \
-H "Content-Type: application/json" \
-d '{ "contents": [{ "role": "user", "parts": [{ "text": "ping" }] }] }'
Google Vertex AI
/gemini/v1beta/models/<model>:generateContent · accepted x-ace-provider values: google_vertex, google-vertex, vertex
The same headers also serve /gemini/v1beta/models/<model>:streamGenerateContent, /anthropic/v1/messages, /v1/chat/completions.
| Header | Required | Description |
|---|---|---|
| x-ace-provider | Yes | google_vertex. Unpinned, the Gemini-shaped path is served by the Gemini Developer API and a stored Vertex key is never consulted. |
| x-ace-vertex-key | Situational | GCP service-account JSON (raw or Base64). Use this OR x-ace-vertex-token, not both — the value is parsed as JSON, so a ya29 token here fails as malformed. |
| x-ace-vertex-token | Situational | Direct OAuth bearer (ya29…), e.g. from gcloud auth print-access-token. The alternative to x-ace-vertex-key; one of the two is required. |
| x-ace-google-project | Yes | GCP project id. Auto-extracted when passing service-account JSON. |
| x-ace-google-region | Situational | Regional publisher endpoint. Defaults to us-central1; global is addressed at the unprefixed aiplatform.googleapis.com host. |
A stored google_vertex vendor key replaces the credential header; project and region are stored with it — x-ace-provider: google_vertex is still required. Gemini models on Vertex take Google's own GenerateContentRequest on /gemini/v1beta/models/{model}:generateContent (:streamGenerateContent when streaming): the body goes to publishers/google/models/{model} as written and the reply, thought parts and thoughtSignature included, comes back as sent. There is no /v1/projects/{project}/locations/{location}/… route on the gateway; project and region are headers, and a Google OAuth token belongs on x-ace-vertex-token, never on Authorization (that header carries the ACE key). An OpenAI-shaped body on /v1/chat/completions pinned to this channel is translated to the same upstream and answered in OpenAI's shape. Claude models on Vertex are served from /anthropic/v1/messages with the same credential and x-ace-provider: google_vertex: your Messages body goes to publishers/anthropic/models/{model}:rawPredict (:streamRawPredict when streaming) spelled as Anthropic's own Vertex SDK spells it — model in the path (use Vertex's id, e.g. claude-sonnet-4-5@20250929), anthropic_version: vertex-2023-10-16 in the body, your anthropic-beta header as the anthropic_beta array — and the reply, thinking and signatures included, is the vendor's verbatim. A Claude model pinned to this channel on /v1/chat/completions is refused: that surface has no Anthropic body to send. From an AI SDK client this is createAnthropic with these headers and no Vertex package — see the Vercel AI SDK quickstart.
curl -X POST "https://engine.acefleet.dev/gemini/v1beta/models/gemini-2.5-flash:generateContent" \
-H "x-goog-api-key: ace_dev_<your-dev-key>" \
-H "x-ace-provider: google_vertex" \
-H "x-ace-vertex-token: ya29..." \
-H "x-ace-google-project: <your-gcp-project>" \
-H "x-ace-google-region: us-central1" \
-H "Content-Type: application/json" \
-d '{ "contents": [{ "role": "user", "parts": [{ "text": "ping" }] }] }'
# Claude on Vertex, from the Anthropic surface (EU residency: region europe-west1):
curl -X POST "https://engine.acefleet.dev/anthropic/v1/messages" \
-H "x-api-key: ace_dev_<your-dev-key>" \
-H "x-ace-provider: google_vertex" \
-H "x-ace-vertex-key: <base64-service-account-json>" \
-H "x-ace-google-project: <your-gcp-project>" \
-H "x-ace-google-region: europe-west1" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{ "model": "claude-sonnet-4-5@20250929", "max_tokens": 1024, "messages": [{ "role": "user", "content": "ping" }] }'
AWS Bedrock
/bedrock/converse · accepted x-ace-provider values: bedrock, aws
The same headers also serve /bedrock/converse-stream, /bedrock/model/{modelId}/converse, /bedrock/model/{modelId}/converse-stream, /v1/chat/completions, /anthropic/v1/messages.
| Header | Required | Description |
|---|---|---|
| x-ace-bedrock-api-key | Situational | An Amazon Bedrock API key (long-term ABSK… or short-term). Use this OR the access-key pair below, never both — a key beside a pair, or half a pair, is a 400 naming both headers. ACE sends it upstream as Authorization: Bearer and signs nothing, so no AWS secret or STS token leaves your side. Not read off Authorization, which carries your ACE key. |
| x-ace-bedrock-key | Situational | AWS access key id, half of the SigV4 pair — the alternative to x-ace-bedrock-api-key; one of the two is required. ACE computes the SigV4 signature per request — there is no local AWS CLI, profile or assumed role involved. |
| x-ace-aws-secret-access-key | Situational | AWS secret access key, the other half of the pair. Bedrock is signed rather than forwarded on this path, so unlike every other provider here the credential is a pair, not a single token — unless you send the API key instead. |
| x-ace-aws-region | Yes | Region the model is served from. With the pair it is part of the SigV4 scope (defaults to us-east-1 when absent); with an API key it is required — a short-term key is bound to the region it was minted in, and a missing region is a 400, never a defaulted host. |
| x-ace-aws-session-token | Situational | Only for temporary STS credentials with the pair. Omit it for a long-lived key pair or an API key. |
| x-ace-provider | Situational | Pin the channel to bedrock on /v1/chat/completions (an OpenAI body translated to Converse) or /anthropic/v1/messages (a Messages body sent to InvokeModel as Anthropic's Bedrock SDK sends it); the /bedrock/… paths imply it. |
A stored bedrock vendor key replaces the credential headers; region is stored with it. POST /api/v1/vendor_key/create with provider: bedrock takes the packed access-key pair as api_key by default, or an Amazon Bedrock API key when the body says credential_kind: "api_key"; a pair stored before that field existed is unchanged. A pass-through pair beats a stored API key, as a pass-through key beats a stored one everywhere else. An AWS SDK or the AI SDK's Bedrock provider pointed at /bedrock builds /model/{modelId}/converse[-stream] with the id percent-encoded in the path (ARNs included); the gateway serves that path and lifts modelId into the body, so the SDK needs only a base URL and these headers — see the Vercel AI SDK quickstart.
curl -X POST https://engine.acefleet.dev/bedrock/converse \
-H "Authorization: Bearer ace_dev_<your-dev-key>" \
-H "x-ace-bedrock-api-key: ABSK..." \
-H "x-ace-aws-region: us-east-1" \
-H "Content-Type: application/json" \
-d '{ "modelId": "anthropic.claude-3-5-sonnet-20241022-v2:0",
"messages": [{ "role": "user", "content": [{ "text": "ping" }] }] }'
# The SigV4 pair instead (never both), at the path an AWS SDK builds:
curl -X POST https://engine.acefleet.dev/bedrock/model/anthropic.claude-3-5-sonnet-20241022-v2%3A0/converse \
-H "Authorization: Bearer ace_dev_<your-dev-key>" \
-H "x-ace-bedrock-key: AKIAIOSFODNN7EXAMPLE" \
-H "x-ace-aws-secret-access-key: wJalrXUtnFEMI/K7MDENG/..." \
-H "x-ace-aws-region: us-east-1" \
-H "Content-Type: application/json" \
-d '{ "messages": [{ "role": "user", "content": [{ "text": "ping" }] }] }'