all docs
/ reference

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" }] }] }'