---
title: "AI Gateway"
description: "Set up AI Gateway, Authenticated Gateway and third-party models, and use session affinity and cached tokens."
---

> Documentation Index
> Fetch the complete documentation index at: https://laravel-cf-workersai-docs.mdick85.workers.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# AI Gateway

Cloudflare AI Gateway is a proxy for AI requests. It adds request logs, cost analytics, caching and rate limits in the Cloudflare dashboard. You create and configure a gateway in the dashboard. The package only sends requests to it.

To compare the gateway route with the direct route, see [Direct and gateway routes](/docs/concepts/routes/).

## Route requests through a gateway

1. **Create a gateway**

   In the Cloudflare dashboard, go to AI, then AI Gateway, then Create Gateway. Copy the gateway name. This name is the slug.
2. **Add the slug to your .env file**

   Add this line to your application's `.env` file:

   ```ini
   CLOUDFLARE_AI_GATEWAY=my-gateway
   ```
3. **Read it in config/ai.php**

   Add the `gateway` line to the provider entry. It reads the value from `.env`, so do not write the slug here.

   ```php
   'workers-ai' => [
       'driver'     => 'workers-ai',
       'key'        => env('CLOUDFLARE_AI_API_TOKEN'),
       'account_id' => env('CLOUDFLARE_ACCOUNT_ID'),
       'gateway'    => env('CLOUDFLARE_AI_GATEWAY'),
   ],
   ```
4. **Send a request and open the logs**

   Your calling code does not change. Open the gateway in the dashboard and go to Logs. Text and embedding calls show under `compat`.

Text and embedding calls go to `https://gateway.ai.cloudflare.com/v1/<account_id>/<gateway>/compat`. Clef and Clairic calls go to `api.cloudflare.com` with a `cf-aig-gateway-id` header, and Cloudflare applies the gateway's features to them.

Use only the slug. If you give a URL or a value with a slash, the package throws and tells you to use the `url` key.

## Authenticated Gateway

With Authenticated Gateway on, `gateway.ai.cloudflare.com` rejects a request that has no gateway token. To set it up:

1. Open the gateway settings in the dashboard.
2. Create an authentication token. This is the gateway token, and it has Run permission. Copy it.
3. Turn on Authenticated Gateway.
4. Add the token to your application's `.env` file:

```ini
CLOUDFLARE_AI_GATEWAY_TOKEN=your-gateway-token
```

5. Read it in the provider entry in `config/ai.php`:

```php
'workers-ai' => [
    // ...
    'gateway_token' => env('CLOUDFLARE_AI_GATEWAY_TOKEN'),
],
```

The package sends `cf-aig-authorization: Bearer <gateway_token>`. Your `key` still goes in `Authorization`. If `gateway_token` is empty, the package does not send the header.

Calls to `api.cloudflare.com`, such as Clef and Clairic, do not need the gateway token. That API reads your API token from `Authorization`. Tested on 2026-11-05. FLUX.2 image models do need it. See [Non-JSON models](/docs/concepts/routes/#non-json-models).

Without the header, or with a wrong token, an authenticated gateway returns HTTP 401 with `{"code":2009,"message":"Unauthorized"}`. Laravel throws `Illuminate\Http\Client\RequestException`. The message does not name the gateway or the header, so check `gateway_token` first.

## Direct route with a gateway header

Cloudflare's REST API on `api.cloudflare.com` also routes through a gateway. Send the `cf-aig-gateway-id` header on the direct route. It needs no `gateway` key, and no second token even on an authenticated gateway. FLUX.2 is the exception, and `nova-3` cannot use a gateway. See [Non-JSON models](/docs/concepts/routes/#non-json-models).

Keep the slug in your application's `.env` file, as `CLOUDFLARE_AI_GATEWAY`, and read it in `config/ai.php`:

```php
'workers-ai' => [
    'driver'     => 'workers-ai',
    'key'        => env('CLOUDFLARE_AI_API_TOKEN'),
    'account_id' => env('CLOUDFLARE_ACCOUNT_ID'),
    'headers'    => ['cf-aig-gateway-id' => env('CLOUDFLARE_AI_GATEWAY')],
],
```

Text, Clef, Clairic and embeddings all returned HTTP 200 this way on 2026-11-05. Cloudflare's gateway docs list chat completions on this API, but not embeddings. See the [AI Gateway REST API](https://developers.cloudflare.com/ai-gateway/usage/rest-api/).

## Third-party models

Cloudflare's REST API serves other providers' models with your Cloudflare token. Use the direct route, and name the model in `author/model` form:

```php
use function Laravel\Ai\agent;

agent()->prompt('Hello', provider: 'workers-ai', model: 'openai/gpt-5.5');
```

- Cloudflare bills the call from AI Gateway credits, or uses a key stored under the `default` alias. See [Unified Billing](https://developers.cloudflare.com/ai-gateway/features/unified-billing/) and [BYOK](https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/).
- The call goes through your account's default gateway. To pick another, add the `cf-aig-gateway-id` header as in the previous section.
- The token needs Account, Workers AI, Read, the same as other calls. Tested on 2026-11-05 with `openai/gpt-4.1-mini`.

> **Do not use the gateway key for third-party models**
>
> With `gateway` set, the call goes to `/compat` and the package sends `key` as `Authorization`. Cloudflare forwards that header to the provider as a provider key, and it does not use a stored key. A Cloudflare token is not a valid provider key, so the call returns HTTP 401 with OpenAI's `Incorrect API key provided`. Tested on 2026-11-05.

## Use another provider's driver through a gateway

You can point another Laravel AI driver at a gateway to get logs and caching. Use your own provider key, because Laravel AI's drivers always send `key` as `Authorization`. Add the key to your application's `.env` file as `OPENAI_API_KEY`. `BaseUrl::providerUrl()` builds the URL in `config/ai.php`:

```php
use Meirdick\WorkersAi\Cloudflare\BaseUrl;

'openai' => [
    'driver'  => 'openai',
    'key'     => env('OPENAI_API_KEY'),
    'url'     => BaseUrl::providerUrl(env('CLOUDFLARE_ACCOUNT_ID'), env('CLOUDFLARE_AI_GATEWAY'), 'openai'),
    // Remove this line unless the gateway is authenticated.
    'headers' => ['cf-aig-authorization' => 'Bearer '.env('CLOUDFLARE_AI_GATEWAY_TOKEN')],
],
```

Replace `'openai'` with `'anthropic'` or another provider slug. Do not call `config()` here, because `config/ai.php` is still loading. To use a stored key instead, use [third-party models](#third-party-models).

## Use the provider path

To use `<gateway>/workers-ai/v1` instead of `/compat`, set `gateway_path`:

```php
'gateway_path' => 'workers-ai/v1',
```

`/compat` stays the default. See [The gateway provider path](/docs/concepts/routes/#the-gateway-provider-path).

## Use a custom URL

Set `url` for a full URL, such as a proxy. Add it to your application's `.env` file as `CLOUDFLARE_AI_URL`, and read it in `config/ai.php`. Do not set `account_id` with it, because the package throws if both are set.

```php
'workers-ai' => [
    'driver' => 'workers-ai',
    'key'    => env('CLOUDFLARE_AI_API_TOKEN'),
    'url'    => env('CLOUDFLARE_AI_URL'),
],
```

If the URL ends in `/compat`, the package adds the `workers-ai/` model prefix, and Clef and Clairic throw. For any other URL, it sends the bare model name. See [Where each call goes](/docs/concepts/routes/#where-each-call-goes).

## Session affinity

Set `session_affinity` to send the `x-session-affinity` header on every request:

```php
'workers-ai' => [
    // ...
    'session_affinity' => 'my-app-session',
],
```

Related requests then reach the same cache shard, which helps the model prefix cache. See the next section.

## Cached tokens

Some models cache the start of a prompt, and a cache hit costs fewer neurons. Cloudflare reports the cached count in `usage.prompt_tokens_details.cached_tokens`.

The two `laravel/ai` lines count differently:

- **`laravel/ai` 0.9 to 0.11.** `Usage::$promptTokens` is the uncached count. `Usage::$cacheReadInputTokens` is the cached count. The two add up to the `prompt_tokens` that Cloudflare reports.
- **`laravel/ai` 1.x.** The package returns `TextUsage`. `inputTokens` is the `prompt_tokens` that Cloudflare reports, with cached tokens included. `cacheReadInputTokens` is the cached part of it. It is `null` when the response has no cache field. `outputTokens` is the completion count.

`WorkersAiUsageReported::$promptTokens` is the uncached count on both lines.

```php
// laravel/ai 0.9 to 0.11
$uncached = $response->usage->promptTokens;
$cached   = $response->usage->cacheReadInputTokens;

// laravel/ai 1.x
$total    = $response->usage->inputTokens;
$cached   = $response->usage->cacheReadInputTokens ?? 0;
$uncached = $total - $cached;
```

Two identical calls on `@cf/zai-org/glm-5.3-flash`, under one `x-session-affinity` value, measured on 2026-09-16:

| Call | Wire `prompt_tokens` | Wire `cached_tokens` | 0.x `promptTokens` | 1.x `inputTokens` | `cacheReadInputTokens` | Neurons |
| --- | --- | --- | --- | --- | --- | --- |
| 1 | 1221 | 0 | 1221 | 1221 | 0 | 17.8 |
| 2 | 1221 | 1216 | 5 | 1221 | 1216 | 8.3 |

The cache depends on the model. In tests, `@cf/zai-org/glm-5.3-flash` and `@cf/moonshotai/kimi-k2.6` hit the cache. `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, `@cf/meta/llama-4-scout-17b-16e-instruct` and `@cf/openai/gpt-oss-120b` did not.

> **Note**
>
> This model prefix cache is separate from AI Gateway response caching. The package has no setting for gateway response caching. Set that in the dashboard.

`#[CacheInstructions]` from Laravel AI has no effect on this provider.

## Custom headers

The package sends the `headers` array from the provider config. A configured header replaces a package header with the same name. Names match without regard to case. The package sends these headers on chat, streaming and embeddings.

```php
'workers-ai' => [
    // ...
    'headers' => ['X-Trace-Id' => env('TRACE_ID')],
],
```

On `laravel/ai` 1.x, `Provider::withHeaders()` writes to the same array, so per-call headers reach Workers AI. Before package version 1.0.0, the package dropped the `headers` key and per-call headers.

## Logging

AI Gateway logs are in the Cloudflare dashboard. The package does not write request logs. It writes one warning to the Laravel log when it raises the token cap for a `thinking` request.

## Gateway retries

An AI Gateway with `retry_max_attempts` set retries inside your single HTTP request, and the package retries too. A late error can make the request several times longer, and your own timeout can cut it. Use one retry layer. If you use the gateway retry, set `retry` to `false` in the package config. See [Errors and retries](/docs/guides/errors-and-retries/).

Source: https://laravel-cf-workersai-docs.mdick85.workers.dev/docs/guides/ai-gateway/index.mdx
