---
title: "Provider Recipes"
url: "https://docs.caracal.run/v1.0/guides/provider-recipes/"
markdown_url: "https://docs.caracal.run/markdown/v1.0/guides/provider-recipes.md"
description: "Concrete copy-paste provider setups for OpenAI, Anthropic, Google, GitHub, Slack, LiteLLM, Ollama, and internal APIs, with the enforcement boundary and client wiring each one uses."
page_type: "page"
concepts: []
requires: []
---

# Provider Recipes

Canonical URL: https://docs.caracal.run/v1.0/guides/provider-recipes/
Markdown URL: https://docs.caracal.run/markdown/v1.0/guides/provider-recipes.md
Description: Concrete copy-paste provider setups for OpenAI, Anthropic, Google, GitHub, Slack, LiteLLM, Ollama, and internal APIs, with the enforcement boundary and client wiring each one uses.
Page type: page
Concepts: none
Requires: none

---

import { Tabs, TabItem } from '@astrojs/starlight/components'

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](/v1.0/guides/resources-providers/) and the [Admin package reference](/v1.0/sdks/admin/).

## Prerequisites

* 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.

## Choose the enforcement boundary first

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.

| Boundary                 | Enforcement point                          | Credential holder                | Action-result audit                     | Use when                                                                                                                  |
| ------------------------ | ------------------------------------------ | -------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Gateway-mediated         | Caracal Gateway                            | Gateway (sealed provider secret) | Gateway                                 | The upstream is HTTP-routable and you want Caracal to broker the credential and enforce every call.                       |
| Adapter-verified         | Your service process                       | Your service                     | Your service after adapter verification | You own the service and must enforce mandates in process.                                                                 |
| Application-managed call | Your application code                      | Your application                 | Your application                        | You call the provider directly and only attach Caracal context for attribution. Not Caracal-enforced.                     |
| Runtime token injection  | Your runtime, eligibility gated by Caracal | Your runtime process             | Your application                        | An 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.

## OpenAI API key (Gateway-mediated)

Broker an OpenAI key so the agent never holds it.

| Field                 | Value                                        |
| --------------------- | -------------------------------------------- |
| Provider kind         | `api_key`                                    |
| `header_name`         | `Authorization`                              |
| `auth_scheme`         | `Bearer`                                     |
| `api_key`             | Your OpenAI secret key (sealed at creation). |
| Resource identifier   | `resource://openai`                          |
| Resource upstream URL | `https://api.openai.com`                     |
| Resource scopes       | `openai: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)

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

| Field                             | Value                                                                                                           |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Provider kind                     | `oauth2_authorization_code`                                                                                     |
| `authorization_endpoint`          | `https://accounts.google.com/o/oauth2/v2/auth`                                                                  |
| `token_endpoint`                  | `https://oauth2.googleapis.com/token`                                                                           |
| `redirect_uri`                    | Your exact Caracal callback, e.g. `https://api.pipernet.example/v1/zones/<zone>/provider-connections/oauth/callback` |
| `client_id` / `client_secret`     | From the Google Cloud OAuth client.                                                                             |
| `scopes`                          | e.g. `https://www.googleapis.com/auth/drive.readonly`                                                           |
| `allowed_token_hosts`             | `oauth2.googleapis.com`                                                                                         |
| `authorization_params` (Advanced) | `access_type=offline`, `prompt=consent` for refresh tokens.                                                     |
| Resource upstream URL             | `https://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.

:::note[FAQ]
[When should I use per-user OAuth instead of a shared provider credential?](/v1.0/reference/faq/#faq-014)
:::

## Google Cloud service account (jwt\_bearer)

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.

| Field                          | Value                                                                   |
| ------------------------------ | ----------------------------------------------------------------------- |
| Provider kind                  | `oauth2_client_credentials`                                             |
| `grant_type`                   | `jwt_bearer`                                                            |
| `token_endpoint`               | `https://oauth2.googleapis.com/token`                                   |
| `client_id`                    | The service account email, e.g. `agent@project.iam.gserviceaccount.com` |
| `private_key`                  | The service account's PEM private key (sealed).                         |
| `scopes`                       | e.g. `https://www.googleapis.com/auth/cloud-platform`                   |
| `assertion_subject` (Advanced) | A user email, only for domain-wide delegation.                          |
| Resource upstream URL          | e.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`.

## Atlassian Jira (HTTP Basic)

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

| Field                 | Value                                   |
| --------------------- | --------------------------------------- |
| Provider kind         | `http_basic`                            |
| `username`            | The Atlassian account email.            |
| `password`            | The Atlassian API token (sealed).       |
| Resource identifier   | `resource://jira`                       |
| Resource upstream URL | `https://your-site.atlassian.net`       |
| Resource scopes       | `jira: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.

## GitHub (bearer token, with OAuth note)

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

| Field                 | Value                                                 |
| --------------------- | ----------------------------------------------------- |
| Provider kind         | `bearer_token`                                        |
| `bearer_token`        | A GitHub fine-grained personal access token (sealed). |
| `allowed_token_hosts` | `api.github.com`                                      |
| Resource identifier   | `resource://github`                                   |
| Resource upstream URL | `https://api.github.com`                              |
| Resource scopes       | `github: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)

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

| Field                 | Value                                     |
| --------------------- | ----------------------------------------- |
| Provider kind         | `bearer_token`                            |
| `bearer_token`        | The Slack bot/user OAuth token (sealed).  |
| `allowed_token_hosts` | `slack.com`                               |
| Resource identifier   | `resource://slack`                        |
| Resource upstream URL | `https://slack.com/api`                   |
| Resource scopes       | `slack:chat-write`, `slack:channels-read` |

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

## Internal API (Caracal mandate or none)

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

| Setup                  | Provider kind     | Use when                                                                                                                                                                        |
| ---------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Mandate-aware          | `caracal_mandate` | The internal service verifies Caracal tokens directly (issuer, audience, scopes, expiry, revocation). The Gateway forwards the resource mandate as `Authorization: Bearer ...`. |
| No upstream credential | `none`            | The Gateway is the enforcement point and the upstream expects no credential.                                                                                                    |

| Field                 | Value                              |
| --------------------- | ---------------------------------- |
| Resource identifier   | `resource://pipernet`                 |
| Resource upstream URL | `https://api.pipernet.example`        |
| Resource scopes       | `pipernet:read`, `pipernet:submit`    |

## Runtime token injection

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.

## Wire the transport into provider clients

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.

```sh
export CARACAL_RESOURCES="resource://openai=https://api.openai.com"
```

Bindings can also live in the runtime profile as `[[credentials]]` entries; see [Configure Workloads](/v1.0/runtime-console/config-file/). 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.

:::caution[Failure point: snippet scopes]
The wiring snippets below all request one illustrative scope, `inference:invoke`. The recipe tables above declare provider-specific scopes such as `openai:chat`. Pick one convention and use it in **both** the resource's declared scopes and the transport call - a scope the resource does not declare, or policy does not grant, is denied.
:::

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](/v1.0/sdks/typescript/#call-protected-resources)).

### OpenAI

<Tabs syncKey="lang">
  <TabItem label="TypeScript">
    ```ts
    import OpenAI from 'openai'

    const openai = new OpenAI({
      apiKey: 'caracal-gateway',
      fetch: caracal.transport({ scopes: ['inference:invoke'], propagation: 'gateway-only' }),
    })
    ```
  </TabItem>

  <TabItem label="Python">
    ```python
    from openai import AsyncOpenAI

    openai = AsyncOpenAI(
        api_key="caracal-gateway",
        http_client=caracal.transport(scopes=["inference:invoke"], propagation="gateway-only"),
    )
    ```
  </TabItem>

  <TabItem label="Go">
    ```go
    import (
        "github.com/openai/openai-go"
        "github.com/openai/openai-go/option"
    )

    openaiClient := openai.NewClient(
        option.WithAPIKey("caracal-gateway"),
        option.WithHTTPClient(client.Transport(nil, caracal.CallOptions{
            Scopes: []string{"inference:invoke"},
            Propagation: caracal.PropagationGatewayOnly,
        })),
    )
    ```
  </TabItem>
</Tabs>

### Anthropic

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

<Tabs syncKey="lang">
  <TabItem label="TypeScript">
    ```ts
    import Anthropic from '@anthropic-ai/sdk'

    const anthropic = new Anthropic({
      apiKey: 'caracal-gateway',
      fetch: caracal.transport({ scopes: ['inference:invoke'], propagation: 'gateway-only' }),
    })
    ```
  </TabItem>

  <TabItem label="Python">
    ```python
    from anthropic import AsyncAnthropic

    anthropic = AsyncAnthropic(
        api_key="caracal-gateway",
        http_client=caracal.transport(scopes=["inference:invoke"], propagation="gateway-only"),
    )
    ```
  </TabItem>

  <TabItem label="Go">
    ```go
    import (
        "github.com/anthropics/anthropic-sdk-go"
        "github.com/anthropics/anthropic-sdk-go/option"
    )

    anthropicClient := anthropic.NewClient(
        option.WithAPIKey("caracal-gateway"),
        option.WithHTTPClient(client.Transport(nil, caracal.CallOptions{
            Scopes: []string{"inference:invoke"},
            Propagation: caracal.PropagationGatewayOnly,
        })),
    )
    ```
  </TabItem>
</Tabs>

### Google Gemini

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

<Tabs syncKey="lang">
  <TabItem label="Python">
    ```python
    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"
            )
        ),
    )
    ```
  </TabItem>

  <TabItem label="Go">
    ```go
    import "google.golang.org/genai"

    gemini, err := genai.NewClient(ctx, &genai.ClientConfig{
        APIKey:     "caracal-gateway",
        HTTPClient: client.Transport(nil, caracal.CallOptions{
            Scopes: []string{"inference:invoke"},
            Propagation: caracal.PropagationGatewayOnly,
        }),
    })
    ```
  </TabItem>
</Tabs>

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.

### LiteLLM

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

```python
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.

### Ollama

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

<Tabs syncKey="lang">
  <TabItem label="TypeScript">
    ```ts
    import { Ollama } from 'ollama'

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

  <TabItem label="Go">
    ```go
    import "github.com/ollama/ollama/api"

    base, _ := url.Parse("http://ollama.internal.example:11434")
    ollamaClient := api.NewClient(base, client.Transport(nil, caracal.CallOptions{
        Scopes: []string{"inference:invoke"},
        Propagation: caracal.PropagationGatewayOnly,
    }))
    ```
  </TabItem>
</Tabs>

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.

## Validate before the first call

After you create the provider, bind it to the resource, and activate a policy, run [Check Provider Readiness](/v1.0/examples/provider-preflight/). 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.

:::caution[Failure point: transport propagation]
Pass `propagation: 'gateway-only'` (or the language equivalent) for third-party provider clients. Do not send Caracal context to unrelated hosts.
:::

:::caution[Failure point: retries]
Gateway does not make an unsafe upstream operation idempotent. Supply a destination-supported idempotency key for mutations and follow [Safe Retries and Idempotency](/v1.0/guides/idempotency/).
:::

## Related

* [Define Resources and Providers](/v1.0/guides/resources-providers/)
* [Govern Agent Frameworks](/v1.0/guides/frameworks/)
* [Author Policy Data](/v1.0/guides/author-policy/)
* [Debug Authorization Decisions](/v1.0/guides/authorize-access/)
* [Protect a Gateway-Routed HTTP API](/v1.0/guides/protect-gateway-http/)

## Next Step

Integrate the caller with the [TypeScript](/v1.0/guides/sdk-typescript/), [Python](/v1.0/guides/sdk-python/), or [Go](/v1.0/guides/sdk-go/) workflow.
