Skip to content

Provider Recipes

Use this page while creating a provider for a known upstream. First select the enforcement boundary, then apply one recipe, bind the resource, wire one language-native transport, and validate the whole path. Field schemas stay canonical in Define Resources and Providers and the Admin package reference.

  • The resource, scopes, Gateway application, and active policy already exist.
  • The upstream’s official token or authentication documentation is available.
  • Production secrets are ready to enter through Console or a trusted Admin API process, never application source.

Provider recipes live in the documentation, not the web console. The web console stays focused on the fields a given setup needs; this page is the reference you keep open beside it.

The most common mistake is treating these four boundaries as interchangeable. They are not - they differ in where enforcement happens, who writes the action-result audit, and who holds the upstream credential.

BoundaryEnforcement pointCredential holderAction-result auditUse when
Gateway-mediatedCaracal GatewayGateway (sealed provider secret)GatewayThe upstream is HTTP-routable and you want Caracal to broker the credential and enforce every call.
Adapter-verifiedYour service processYour serviceYour service after adapter verificationYou own the service and must enforce mandates in process.
Application-managed callYour application codeYour applicationYour applicationYou call the provider directly and only attach Caracal context for attribution. Not Caracal-enforced.
Runtime token injectionYour runtime, eligibility gated by CaracalYour runtime processYour applicationAn existing client or CLI must receive a brokered token at runtime, and the provider sets allow_runtime_injection=true.

Prefer Gateway-mediated unless you have a specific reason to enforce elsewhere. The recipes below note which boundary they use.

Broker an OpenAI key so the agent never holds it.

FieldValue
Provider kindapi_key
header_nameAuthorization
auth_schemeBearer
api_keyYour OpenAI secret key (sealed at creation).
Resource identifierresource://openai
Resource upstream URLhttps://api.openai.com
Resource scopesopenai:chat, openai:embeddings

The Gateway forwards Authorization: Bearer <sealed key> upstream and strips caller auth. The agent calls the Caracal resource, not OpenAI directly.

Google Workspace (OAuth authorization code)

Section titled “Google Workspace (OAuth authorization code)”

Delegated user consent for a Google API, refreshed inside STS.

FieldValue
Provider kindoauth2_authorization_code
authorization_endpointhttps://accounts.google.com/o/oauth2/v2/auth
token_endpointhttps://oauth2.googleapis.com/token
redirect_uriYour exact Caracal callback, e.g. https://api.pipernet.example/v1/zones/<zone>/provider-connections/oauth/callback
client_id / client_secretFrom the Google Cloud OAuth client.
scopese.g. https://www.googleapis.com/auth/drive.readonly
allowed_token_hostsoauth2.googleapis.com
authorization_params (Advanced)access_type=offline, prompt=consent for refresh tokens.
Resource upstream URLhttps://www.googleapis.com

Caracal owns client_id, redirect_uri, state, and PKCE. Use the provider connect action to mint the consent URL for the shared upstream account, or bind it to a specific subject for per-customer isolation.

Machine access to Google APIs - Vertex AI, Cloud Storage, and every other googleapis.com surface - signed with a service-account key instead of user consent.

FieldValue
Provider kindoauth2_client_credentials
grant_typejwt_bearer
token_endpointhttps://oauth2.googleapis.com/token
client_idThe service account email, e.g. agent@project.iam.gserviceaccount.com
private_keyThe service account’s PEM private key (sealed).
scopese.g. https://www.googleapis.com/auth/cloud-platform
assertion_subject (Advanced)A user email, only for domain-wide delegation.
Resource upstream URLe.g. https://aiplatform.googleapis.com

STS signs the RFC 7523 assertion with the sealed key, carries the scopes inside the assertion’s scope claim as Google expects, and caches the returned access token. The same shape covers Salesforce’s JWT bearer flow: set client_id to the consumer key, assertion_subject to the Salesforce username, and assertion_audience to https://login.salesforce.com.

Jira Cloud REST authenticates with an email and API token as a Basic pair.

FieldValue
Provider kindhttp_basic
usernameThe Atlassian account email.
passwordThe Atlassian API token (sealed).
Resource identifierresource://jira
Resource upstream URLhttps://your-site.atlassian.net
Resource scopesjira:issues-read, jira:issues-write

Gateway composes Authorization: Basic ... at forward time; callers never see the pair. The same recipe covers Twilio (Account SID as username, auth token as password), Elasticsearch, Zendesk, and other Basic-authenticated REST APIs.

For automation, the simplest path is a pre-issued token.

FieldValue
Provider kindbearer_token
bearer_tokenA GitHub fine-grained personal access token (sealed).
allowed_token_hostsapi.github.com
Resource identifierresource://github
Resource upstream URLhttps://api.github.com
Resource scopesgithub:repo-read, github:issues-write

For a delegated, consented account instead of a shared token, use oauth2_authorization_code with authorization_endpoint=https://github.com/login/oauth/authorize and token_endpoint=https://github.com/login/oauth/access_token.

Slack (OAuth client credentials or bearer)

Section titled “Slack (OAuth client credentials or bearer)”

For a Slack bot token that does not expire, use a bearer provider.

FieldValue
Provider kindbearer_token
bearer_tokenThe Slack bot/user OAuth token (sealed).
allowed_token_hostsslack.com
Resource identifierresource://slack
Resource upstream URLhttps://slack.com/api
Resource scopesslack:chat-write, slack:channels-read

For rotating machine tokens, use oauth2_client_credentials with Slack’s token endpoint and scopes.

For services you own, you usually do not need an upstream credential.

SetupProvider kindUse when
Mandate-awarecaracal_mandateThe internal service verifies Caracal tokens directly (issuer, audience, scopes, expiry, revocation). The Gateway forwards the resource mandate as Authorization: Bearer ....
No upstream credentialnoneThe Gateway is the enforcement point and the upstream expects no credential.
FieldValue
Resource identifierresource://pipernet
Resource upstream URLhttps://api.pipernet.example
Resource scopespipernet:read, pipernet:submit

When an existing CLI or client cannot route through the Gateway but still needs a brokered credential, set allow_runtime_injection=true on the provider. This is a distinct security boundary: the token is injected into your runtime, and enforcement of the actual call is your application’s responsibility, not the Gateway’s. Use it deliberately and confirm eligibility with the preflight below.

Once a provider and resource exist, the client side of a Gateway-mediated setup is two lines: a resource binding and the SDK transport. The binding maps the provider’s natural upstream prefix to the resource, so agent code keeps calling the provider’s real URL - the transport rewrites matching requests through the Gateway, attaches the caller’s mandate, and the Gateway injects the sealed provider credential upstream.

Terminal window
export CARACAL_RESOURCES="resource://openai=https://api.openai.com"

Bindings can also live in the runtime profile as [[credentials]] entries; see Configure Workloads. Provider clients still require an API key argument at construction - pass a placeholder such as caracal-gateway. The transport replaces the Authorization header on Gateway-routed calls, so the placeholder never reaches the provider.

Each recipe below assumes a constructed SDK client (const caracal = new Caracal(), caracal = Caracal(), client, _ := caracal.New()) and runs inside a governed Session context - transport() borrows the ambient Session’s authority, unlike applicationTransport(), which provisions its own (see Call Protected Resources).

import OpenAI from 'openai'
const openai = new OpenAI({
apiKey: 'caracal-gateway',
fetch: caracal.transport({ scopes: ['inference:invoke'], propagation: 'gateway-only' }),
})

Binding: resource://anthropic=https://api.anthropic.com.

import Anthropic from '@anthropic-ai/sdk'
const anthropic = new Anthropic({
apiKey: 'caracal-gateway',
fetch: caracal.transport({ scopes: ['inference:invoke'], propagation: 'gateway-only' }),
})

Binding: resource://gemini=https://generativelanguage.googleapis.com.

from google import genai
from google.genai import types
gemini = genai.Client(
api_key="caracal-gateway",
http_options=types.HttpOptions(
httpx_async_client=caracal.transport(
scopes=["inference:invoke"], propagation="gateway-only"
)
),
)

The @google/genai JavaScript SDK does not accept a custom fetch. In TypeScript, call Gemini through its OpenAI-compatible surface with the OpenAI wiring above and baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/"; the binding stays the same.

The LiteLLM Python library sends traffic through module-level httpx sessions:

import litellm
litellm.aclient_session = caracal.transport(
scopes=["inference:invoke"], propagation="gateway-only"
)
litellm.client_session = caracal.sync_transport(
scopes=["inference:invoke"], propagation="gateway-only"
)

Bind one resource per upstream LiteLLM reaches, or govern a self-hosted LiteLLM proxy as a single resource (for example resource://litellm=https://litellm.internal.example). The proxy speaks the OpenAI protocol, so TypeScript and Go clients use the OpenAI wiring above with baseURL pointed at the proxy.

Binding: resource://ollama=http://ollama.internal.example:11434.

import { Ollama } from 'ollama'
const ollama = new Ollama({
host: 'http://ollama.internal.example:11434',
fetch: caracal.transport({ scopes: ['inference:invoke'], propagation: 'gateway-only' }),
})

The Ollama Python client constructs its own httpx client internally. In Python, call Ollama through its OpenAI-compatible /v1 endpoint with the OpenAI wiring above and base_url="http://ollama.internal.example:11434/v1".

Self-hosted upstreams like LiteLLM and Ollama usually need no upstream credential: use provider kind none and let the Gateway be the enforcement point.

After you create the provider, bind it to the resource, and activate a policy, run Check Provider Readiness. It validates control-plane and Gateway readiness, the resource-to-provider binding, application validity, provider configuration completeness, scope coverage, token endpoint and upstream reachability, runtime-injection eligibility, and that the active policy set actually returns allow for your application, resource, and scopes - with a concrete remediation for every failure.

Then make one allowed call and one scope-denied call. The allowed call must appear as an STS decision followed by a Gateway result under one request ID; the denied call must not reach the upstream.

Integrate the caller with the TypeScript, Python, or Go workflow.