Skip to content

Direct and gateway routes

The ways the package reaches Cloudflare, what each setup needs, and which features work on each.

Updated

The package reaches Cloudflare in one of four ways. Your config selects the route. Your calling code is the same on all of them. You can change route at any time by changing config.

Choose a route

You want Use
The shortest setup, and no gateway features Direct route
AI Gateway logs, caching and rate limits Gateway route
Gateway features with one token, on api.cloudflare.com Direct route with a gateway header
OpenAI, Anthropic or another provider’s models, billed by Cloudflare Direct route. See third-party models.
A proxy, or a URL that the package does not build Custom url

What each route needs

All routes need a Cloudflare API token with the Account, Workers AI, Read permission.

Route Provider config Second token
Direct key, account_id No
Gateway key, account_id, gateway gateway_token, only if Authenticated Gateway is on
Direct with a gateway header key, account_id, headers gateway_token, only on an authenticated gateway, and only for FLUX.2
Custom key, url Depends on the URL

Values go in your application’s .env file. config/ai.php reads them with env().

Add to .env:

CLOUDFLARE_ACCOUNT_ID=your-account-id
CLOUDFLARE_AI_API_TOKEN=your-api-token

Add to the providers array in config/ai.php:

'workers-ai' => [
    'driver'     => 'workers-ai',
    'key'        => env('CLOUDFLARE_AI_API_TOKEN'),
    'account_id' => env('CLOUDFLARE_ACCOUNT_ID'),
],

Add to .env. Leave the gateway token empty unless Authenticated Gateway is on:

CLOUDFLARE_ACCOUNT_ID=your-account-id
CLOUDFLARE_AI_API_TOKEN=your-api-token
CLOUDFLARE_AI_GATEWAY=your-gateway-slug
CLOUDFLARE_AI_GATEWAY_TOKEN=

Add to the providers array in config/ai.php:

'workers-ai' => [
    'driver'        => 'workers-ai',
    'key'           => env('CLOUDFLARE_AI_API_TOKEN'),
    'account_id'    => env('CLOUDFLARE_ACCOUNT_ID'),
    'gateway'       => env('CLOUDFLARE_AI_GATEWAY'),
    'gateway_token' => env('CLOUDFLARE_AI_GATEWAY_TOKEN'),
],

Add to .env:

CLOUDFLARE_ACCOUNT_ID=your-account-id
CLOUDFLARE_AI_API_TOKEN=your-api-token
CLOUDFLARE_AI_GATEWAY=your-gateway-slug

Add to the providers array in config/ai.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')],
],

Add to .env:

CLOUDFLARE_AI_API_TOKEN=your-api-token
CLOUDFLARE_AI_URL=https://your-proxy.example.com/ai/v1

Add to the providers array in config/ai.php:

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

To change route, change .env and the provider entry. Your calling code stays the same.

If you set both url and account_id, the package throws. If you pass a URL as account_id or gateway, the package also throws, before it sends a request.

An empty gateway or gateway_token is ignored. So the Gateway entry above also works in an environment with no gateway, such as local development. Leave those two variables out of that environment’s .env.

Where each call goes

Route Text and embeddings Run API calls
Direct api.cloudflare.com/client/v4/accounts/{account}/ai/v1 api.cloudflare.com/client/v4/accounts/{account}/ai/run/{model}
Gateway gateway.ai.cloudflare.com/v1/{account}/{gateway}/compat The direct run URL, plus the cf-aig-gateway-id header
Direct with a gateway header The direct URL, plus the header The direct run URL, plus the header
url ending in /ai/v1 That URL The /ai/v1 suffix becomes /ai/run/{model}
url ending in /compat That URL Throws. Set account_id instead.
Any other url That URL {url}/run/{model}

Run API calls are Clef, Clairic, reranking, images, speech and transcription. They use a different URL because /compat and /ai/v1 do not serve those models. You do not need a second provider entry. One workers-ai entry handles all calls, and they go through your gateway when you set gateway.

Three calls do not follow this table:

  • Non-JSON models with a gateway use gateway.ai.cloudflare.com/v1/{account}/{gateway}/workers-ai/{model}.
  • Markdown::of() calls /ai/tomarkdown on api.cloudflare.com, or on your url if it ends in /ai/v1. It does not use gateway.
  • CloudflareWebSearch calls /ai/websearch/ on the same host, and names its own gateway.

What works on each route

Feature Direct Gateway Direct with a gateway header
Text, tools, streaming, structured output Yes Yes Yes
Embeddings Yes Yes Yes. Tested on 2026-11-05. Cloudflare’s gateway docs do not list this endpoint.
Clef and Clairic Yes Yes Yes
Reranking, images, speech, transcription Yes Yes, except nova-3 Yes, except nova-3
Neuron event Yes Yes Yes
Gateway logs, analytics, caching, rate limits No Yes Yes
Authenticated Gateway Not applicable Needs gateway_token key is enough, except for FLUX.2
Third-party model with model: Yes No. See below. Yes

Set the features that apply to a gateway, such as caching and rate limits, in the Cloudflare dashboard. The package does not set them.

Tokens on the gateway route

An authenticated gateway on gateway.ai.cloudflare.com needs two credentials on text and embeddings calls:

  • key goes in Authorization and pays for the Workers AI call.
  • gateway_token goes in cf-aig-authorization and admits the request to the gateway. It is an AI Gateway token with Run permission.

Without gateway_token, or with a wrong one, these calls return HTTP 401.

Run API calls with a JSON body go to api.cloudflare.com, which reads the API token from Authorization. They do not need gateway_token. Clef, Clairic, reranking, speech, Whisper models and JSON image models returned 200 this way on an authenticated gateway, tested on 2026-11-05. If you set the token, the package still sends the header, and the calls still work.

Non-JSON models

Cloudflare’s REST API rejects a non-JSON body through a gateway, so three kinds of model behave differently with a gateway. Tested on 2026-11-05.

Model Body With a gateway
@cf/black-forest-labs/flux-2-klein-4b, flux-2-klein-9b, flux-2-dev Multipart Uses gateway.ai.cloudflare.com/v1/{account}/{gateway}/workers-ai/{model}. On an authenticated gateway, needs gateway_token on both routes.
@cf/openai/whisper, @cf/openai/whisper-tiny-en Raw audio Sends a JSON byte array instead. key is enough. JSON is about 4 times the size of the audio.
@cf/deepgram/nova-3 Raw audio Throws InvalidArgumentException. Cloudflare has no form of this request that works through a gateway.

To use nova-3 in an app with a gateway, add a second provider entry with no gateway, and pass its name for that call. The call then skips the gateway, so its logs and limits do not apply.

'workers-ai-direct' => [
    'driver'     => 'workers-ai',
    'key'        => env('CLOUDFLARE_AI_API_TOKEN'),
    'account_id' => env('CLOUDFLARE_ACCOUNT_ID'),
],

Third-party models and the gateway route

The gateway route 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 OpenAI key, so the call returns HTTP 401 with OpenAI’s Incorrect API key provided. A gateway_token does not change this. Tested on 2026-11-05. Use the direct route for third-party models. See third-party models.

Model names

Write bare Workers AI IDs on every route, such as @cf/openai/gpt-oss-120b. The /compat path routes on a workers-ai/ prefix, and the package adds it. On any other URL, a workers-ai/ prefix throws on text calls. For Clef and Clairic, the package removes the prefix.

The gateway provider path

AI Gateway also has a workers-ai/v1 path. Set gateway_path to use it. It is not the default. On an authenticated gateway, chat and embeddings returned 200 on this path with gateway_token, tested on 2026-11-05. Tests on 2026-09-09 had 401 on embeddings on a gateway with authentication on, so /compat stays the default.

Type to search…

↑↓ navigate↵ selectEsc close