# AI Gateway Policy Chains

Every request to an AI Gateway app runs down through the app's **policy chain**:
an ordered list of policies that control model access, budgets, caching,
guardrails, and tracing for that app. Different apps on the same gateway can run
completely different chains.

The chain works in two layers:

- **The gateway declares the menu.** `config/policies.json` in the gateway's
  [repository](./source-control.mdx) declares every policy that apps may use
  —built-in AI Gateway policies and any [custom policies](./custom-policies.mdx)
  you write. Changing the menu is a repository change that takes effect on the
  next production deploy.
- **Each app selects from the menu.** An app's chain—which policies run, in what
  order, with what settings—is edited on the app's **Policies** tab in the
  portal. Chain changes apply within about a minute, with no deploy.

No policy is required. An app whose chain is empty still serves requests: each
request names its own model, and no model restrictions, budgets, guardrails, or
caching apply. Every control is something you add.

A request passes through whatever the app selected before the AI Gateway handler
calls the provider:

```text
Request
  → Model Filtering   ─┐
  → Budgets and Costs  ├─ the app's policy chain
  → Custom policy     ─┘
  → AI Gateway handler
  → LLM provider
```

## Editing a chain

The app's **Policies** tab shows the chain in execution order. Drag entries to
reorder them, and click **Add Policy** to add one from the gateway's menu.

Each declared policy has a single **Add** button. For a policy with settings,
Add opens its settings screen, where you confirm or change the values before the
policy joins the chain. For a policy with no settings screen, Add puts it
straight into the chain with the options declared in `policies.json`.

A policy that's already in the chain shows **Already added** instead of a
button—edit it in the chain instead.

Entries seeded by a [team policy template](./policy-templates.mdx) may be
locked: a lock icon means the template controls whether this app may edit or
remove that entry.

## How the chain executes

- Entries run in the listed order, after the gateway's route-level policies and
  before the AI Gateway handler calls the provider.
- A policy can answer the request itself—for example, a semantic cache hit or a
  guardrail block. Later entries don't run in that case.
- An entry can be disabled to keep its configuration without running it.
  Disabled entries are still validated, so a disabled entry can't reference a
  policy that's no longer declared.
- An app with an empty chain runs no app-selected policies; requests go straight
  to the AI Gateway handler.
- The chain runs on the request path. Policies that need the response—Semantic
  Cache and the Akamai AI Firewall—register a response hook from their position
  in the chain, so one entry covers both directions.
- There's no allow-list of policy types. Any policy declared in `policies.json`
  is a valid chain entry, including a
  [custom code policy](./custom-policies.mdx). The only exclusions are the
  [Configuration Executor](#configuration-executor) and its companion
  Configuration Loader.
- Chain validation is all-or-nothing: if any entry is invalid—for example, it
  references a policy that isn't declared in the gateway's `policies.json`—the
  request fails with an error identifying the entry to fix, and no entries run.
  The gateway never guesses.

### Options and secrets

An entry either **inherits** the options declared in `policies.json` or
**replaces them completely** with its own—options are never merged
field-by-field. What matters is whether the entry has an `options` key at all:
an entry with empty options replaces the declared options with an empty object,
which breaks a policy that has required settings. Omit options entirely to
inherit.

Keep credentials in the declaration's options and let entries inherit them, so
the gateway's repository—not app configuration—holds them. See
[Configure credentials](./custom-policies.mdx#configure-credentials).

## Recommended order

```text
Model Filtering → Fallback Model → Budgets and Costs → Semantic Cache
```

- **Model Filtering** first: it accepts or rejects the request and creates the
  model selection.
- **Fallback Model** second: it only enriches an existing model selection and
  never creates one. A chain with Fallback Model but no Model Filtering does
  nothing at all—the policy logs a warning and passes the request through
  unchanged. That's also why a fallback can never bypass the filter.
- **Budgets and Costs** third: it needs the resolved selection so an exceeded
  budget can activate the quota fallback.
- **Semantic Cache** after Budgets and Costs: cache hits still count toward
  request limits.

Tracing policies work well at the end of the chain, so traces reflect the
request the earlier policies produced.

## Built-in policies

See the [policies overview](./policies/overview.mdx) for every policy an app can
run, what each one does, and the identifier it needs in `config/policies.json`.
Any [custom policy](./custom-policies.mdx) declared in the repository appears in
the Add Policy dialog alongside the built-in ones.

## Configuration Executor

`ai-gateway-configuration-executor-v2-inbound` is the policy that makes the
two-layer model work. It loads the app's configuration and then runs the app's
stored inbound chain, instantiating only policies already declared in
`config/policies.json`. Every scaffolded gateway declares it and puts it on the
AI Gateway route—that route entry is what gives an app's chain somewhere to run.

The scaffolded route also carries a **Configuration Loader**
(`ai-gateway-configuration-loader-v2-inbound`) ahead of the executor. The
executor loads the app's configuration by default, so the loader changes nothing
on its own—it's there for when you want to manipulate the loaded configuration
programmatically before the chain runs: place your own route policy between the
loader and the executor.

Both take a single option, `cacheTtlSeconds`, which sets how long the gateway
caches a loaded app configuration. The default is `10` seconds, `10` is also the
minimum, and a lower value throws a configuration error—as does any other option
key.

## Authentication

The `ai-gateway-auth-v2-inbound` policy—**API Key Authentication** in the
portal—requires callers to present the app's API key, and it applies per app:
add it to an app's chain to require a key for that app alone. A new top-level
team's [policy template](./policy-templates.mdx) includes it as a locked
entry—and sub-teams inherit that template by default—so apps created in the team
require keys from the start.

Where the policy applies, clients send the app's API key as a bearer token and
the gateway resolves the calling app from the key; for a missing or invalid key
the gateway returns a `401 Unauthorized`. Its `authHeader` and `authScheme`
options let the gateway read the key from somewhere other than
`Authorization: Bearer`—`x-api-key`, for example. The gateway caches key checks
for `cacheTtlSeconds`, which defaults to 10 seconds, so a revoked key can keep
working until the cache expires. Each app's key appears on its app page—see
[Apps](./apps.mdx).

:::caution{title="A gateway without the authentication policy is open"}

When an app's chain doesn't include the policy, the gateway resolves the app
from the `{app_id}` segment of the request URL and serves the request with no
key of any kind. Anyone who knows the URL can spend against the app's providers
and budget. Only run an open gateway when something else, such as network
isolation, controls who can reach it.

:::

## Next steps

- [Custom Policies](./custom-policies.mdx): write your own policy and add it to
  a chain
- [Policy Templates](./policy-templates.mdx): seed consistent chains across a
  team's apps
- [Usage Limits](./usage-limits.mdx): budgets at the gateway, team, and app
  levels
