# A Portkey alternative you run yourself: agentgateway

*Disclosure: I work at Solo.io, which builds agentgateway.*

> agentgateway is an open source Portkey alternative you run yourself: virtual keys, per-key USD or token budgets, guardrails and CEL policy for MCP tools.

- Canonical URL: https://webofmike.com/portkey-alternative/
- Author: Mike Moore (https://webofmike.com/about/)
- Published: 2026-10-26
- Last modified: 2026-10-26
- Tags: AI Gateways, Kubernetes, MCP, Platform Engineering, Generative AI
- Cite as: Mike Moore, "Portkey Alternative: agentgateway, Self-Hosted", Web of Mike (webofmike.com), 2026-10-26. https://webofmike.com/portkey-alternative/


agentgateway is an open source gateway you run yourself, and it covers the jobs most teams use Portkey for: virtual keys that hide the provider key, per-key budgets in dollars or tokens, guardrails on prompts and responses, and CEL policy over which MCP tools an agent can see and call. It is Apache 2.0 and lives under the Linux Foundation. Run it as one binary with a YAML file, or on Kubernetes with Gateway API. I ran the virtual key, budget, MCP and SDK examples on this page on v1.6.0 on 2026-10-26, and the outputs below are from those runs. The guardrail section is from the docs.

If you want LiteLLM and Portkey side by side, that is on my [comparison page](/agentgateway-vs-litellm-vs-portkey/).

## What agentgateway is

[agentgateway](https://agentgateway.dev/docs/standalone/latest/documentation/about/introduction/) is one data plane for HTTP, gRPC, LLM, MCP and A2A traffic. It is written in Rust, licensed Apache 2.0, was donated to the Linux Foundation in 2025, and became an Agentic AI Foundation (AAIF) project in 2026.

It has two modes, and you choose based on where you run things:

| | Standalone | Kubernetes |
| --- | --- | --- |
| Install | A [binary](https://agentgateway.dev/docs/standalone/latest/documentation/setup/install/binary/), a Docker container, or a single-Deployment Helm chart | Two Helm charts: CRDs plus the control plane |
| Config | One YAML file. The UI can edit it | Custom resources plus Gateway API. The UI is read-only |
| Storage | Optional database. Per-key budgets require one (SQLite or PostgreSQL) | Token budgets use an external rate-limit service and Redis |
| Per-key budgets | USD or tokens | Tokens (open source). USD needs Solo Enterprise |

Every test on this page ran on the open source build. No Solo Enterprise chart, image or license was used.

## Concept map

Here is what each Portkey control looks like in agentgateway config.

| Portkey concept | agentgateway standalone | agentgateway on Kubernetes |
| --- | --- | --- |
| Virtual key | `llm.policies.apiKey.keys[]`, provider key in `params.apiKey` | ConfigMap of hashed keys plus `apiKeyAuthentication`, provider key in a Secret |
| Budget limit | `budgets[]` on a key, `unit: USD` or `unit: Tokens` | `rateLimit.global` with `unit: Tokens` |
| Model access | `allowedModels` on a key | A CEL `authorization` rule on the requested model |
| Guardrails | `promptGuard` request and response guards | `promptGuard` on an `AgentgatewayPolicy` |
| MCP authorization | `mcpAuthorization` CEL rules down to the tool | [CEL rules on an `AgentgatewayPolicy`](https://agentgateway.dev/docs/kubernetes/latest/documentation/mcp/tool-access/), down to the tool |
| Gateway endpoint | `http://localhost:4000/v1` | Your Gateway's address |

## Virtual keys and per-key budgets in standalone mode

This is the whole config I ran. Each client gets its own key. The real provider key lives only in the gateway.

```yaml
config:
  database:
    url: sqlite://budgets.db
llm:
  port: 4000
  policies:
    apiKey:
      mode: strict
      keys:
      - key: sk-team-a-abc123def456
        metadata:
          name: team-a
        allowedModels:
        - "gpt-4o*"
        budgets:
        - name: daily-spend
          limit:
            unit: USD
            amount: 0.10
          window:
            rolling: 24h
          onBudgetExceeded: Block
      - key: sk-team-b-xyz789uvw012
        metadata:
          name: team-b
        budgets:
        - name: daily-tokens
          limit:
            unit: Tokens
            amount: 250000
          window:
            rolling: 24h
          onBudgetExceeded: Block
  models:
  - name: "*"
    provider: openAI
    params:
      apiKey: "sk-provider-real-key-held-by-gateway"
      # my local mock; drop baseUrl to reach OpenAI
      baseUrl: "http://127.0.0.1:18080/v1"
```

There is no price table in that file. Since [v1.6.0](https://agentgateway.dev/docs/standalone/latest/release-notes/release-notes/), a [built-in catalog](https://agentgateway.dev/docs/standalone/latest/documentation/llm/cost-controls/costs/) prices common public models. My mock reported 100,000 input and 50,000 output tokens per call on gpt-4o-mini, and the access log priced each call on its own:

```
gen_ai.request.model=gpt-4o-mini gen_ai.usage.input_tokens=100000 gen_ai.usage.output_tokens=50000 agw.ai.usage.cost.total=0.0450
```

Calls one to three returned 200. The fourth was blocked before it left the gateway:

```
HTTP 429
{"error":{"message":"Budget exceeded","type":"rate_limit_error","code":"budget_exceeded"}}
```

The admin API showed `"used":"0.135"` against a limit of `0.1`. The budget is charged after each response, so the call that crosses the line still completes. Plan for one request of overshoot. The [per-key budget docs](https://agentgateway.dev/docs/standalone/latest/documentation/llm/cost-controls/budget-limits/per-key/) describe this, plus an `Audit` action that logs the overage without blocking.

The same key asking for a model outside `gpt-4o*` got a 403 before any provider call:

```
HTTP 403
{"error":{"message":"Model is not allowed for this API key","type":"invalid_request_error","code":"model_not_allowed"}}
```

The provider only ever saw `Authorization: Bearer sk-provider-real-key-held-by-gateway`. Clients never hold that key. I wrote up the v1.5.0 version of this demo in [per-key LLM budgets that return 429](/agentgateway-per-key-llm-budgets/).

## Virtual keys and token budgets on Kubernetes

On Kubernetes, I installed the open source agentgateway-crds and agentgateway Helm charts, v1.6.0, with Gateway API v1.6.0, on a local k3s cluster. [Virtual keys](https://agentgateway.dev/docs/kubernetes/latest/documentation/llm/cost-controls/virtual-keys/) are stored as hashes in a labeled ConfigMap, and the provider key sits in a Secret:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: llm-api-keys
  namespace: agentgateway-system
  labels: {agentgateway.dev/apikey: "true"}
data:
  alice: '{"keyHash": "sha256:84d589f2439e4d9ff4438aa840e63ffcbf231a87e7fef929f975d2bde03dc78c", "metadata": {"user_id": "alice"}}'
  bob: '{"keyHash": "sha256:8373319fb10e2e023343d98ade630529f49c53f567cbc6626d09aaf650b2e58c", "metadata": {"user_id": "bob"}}'
---
apiVersion: agentgateway.dev/v1alpha1
kind: AgentgatewayPolicy
metadata: {name: api-key-auth, namespace: agentgateway-system}
spec:
  targetRefs: [{group: gateway.networking.k8s.io, kind: Gateway, name: agentgateway-proxy}]
  traffic:
    apiKeyAuthentication:
      mode: Strict
      configMapSelector: {matchLabels: {agentgateway.dev/apikey: "true"}}
    rateLimit:
      global:
        domain: agentgateway
        backendRef: {kind: Service, name: ratelimit, namespace: ratelimit, port: 8081}
        descriptors:
        - entries: [{name: user_id, expression: 'apiKey.user_id'}]
          unit: Tokens
```

The [budget](https://agentgateway.dev/docs/kubernetes/latest/documentation/llm/cost-controls/budget-limits/) itself is a [global rate limit](https://agentgateway.dev/docs/kubernetes/latest/documentation/security/rate-limit-global/) counted in tokens, served by an external rate-limit service backed by Redis. I gave each user 100 tokens a day, and the mock reported 19 tokens per call. An unknown key got 401. Alice's first six calls returned 200, and her seventh and eighth were refused:

```
HTTP/1.1 429 Too Many Requests
x-ratelimit-limit: 100
x-ratelimit-remaining: 0
retry-after: 26228
content-length: 0
```

Bob, on his own budget, still got 200. Note the difference from standalone: this 429 has an empty body, and the signal is in the headers. Open source Kubernetes budgets count tokens. Dollar budgets on Kubernetes need Solo Enterprise for agentgateway, which is what my [team spend caps with virtual keys](/llm-cost-controls-ai-gateway/) demo used.

## Guardrails

Guards run on the request and the response. The [standalone guardrail docs](https://agentgateway.dev/docs/standalone/latest/documentation/llm/prompt-guards/) cover regex guards with built-in detectors and custom patterns, OpenAI Moderation, AWS Bedrock Guardrails, Google Model Armor, and webhooks to your own service. Regex guards can mask, reject or audit. Guards run in sequence, so a cheap regex check can run before a remote one. The same guards exist on [Kubernetes](https://agentgateway.dev/docs/kubernetes/latest/documentation/llm/guardrails/).

One change in v1.6.0 matters if you stream: a guard provider that fails while checking a streamed response now rejects the content. Set `failureMode: failOpen` on the guard if you want the old behavior. My walkthrough is in [agentgateway guardrails](/agentgateway-guardrails/).

## CEL policy for MCP tools

agentgateway authorization is CEL. For MCP, a [rule list](https://agentgateway.dev/docs/standalone/latest/documentation/mcp/mcp-authz/) decides which tools a client can call, and tools that are not allowed are filtered out of `tools/list`, so the client never sees them. This is the config I ran against the MCP "everything" test server:

```yaml
mcp:
  port: 3000
  policies:
    mcpAuthorization:
      rules:
      - 'mcp.tool.name == "echo"'
  targets:
  - name: everything
    stdio:
      cmd: npx
      args: ["-y", "@modelcontextprotocol/server-everything"]
```

Without the rule, `tools/list` returned 13 tools. With it, the list was `['echo']`. Calling `echo` worked. Calling `get-sum` returned:

```
{"jsonrpc":"2.0","id":11,"error":{"code":-32602,"message":"Unknown tool: get-sum"}}
```

Rules can also match JWT claims, so one tool can be open to everyone and another limited to one user. CEL has sharp edges worth reading about first: [CEL authorization gotchas](/agentgateway-cel-authorization-gotchas/) (tested on Solo Enterprise).

For the identity side of who is calling, see [SPIFFE identity for AI agents](/spiffe-identity-for-ai-agents/).

## Where Portkey's gateway stands today (from its docs)

Per Portkey's docs, checked 2026-10-26:

- The [docs index](https://docs.portkey.ai/docs/llms.txt) is titled "Portkey (Prisma AIRS AI Gateway) Docs".
- The [hybrid architecture](https://portkey.ai/docs/aigw/self-hosting/hybrid-deployments/architecture) splits a Data Plane you run from a Management Plane that holds the dashboard, backend, MySQL, ClickHouse, Redis and blob store. The docs say "No prompt or response data is forwarded to the Management Plane directly." A self-hosted gateway is [registered with Prisma AIRS AI Gateway](https://docs.portkey.ai/docs/aigw/self-hosting/hybrid-deployments/gateway-registration).
- [Budget limits](https://docs.portkey.ai/docs/aigw/product/policies/budget-limits) are set per workspace on an integration, with a minimum of $1 or 100 tokens, and reset weekly, monthly or never. Once set, a budget limit "cannot be edited". Budgets and rate limits can also be [set on API keys](https://docs.portkey.ai/docs/aigw/product/administration/enforce-budget-and-rate-limit).
- The [error codes](https://portkey.ai/docs/aigw/api-reference/inference-api/error-codes) list HTTP 412 for an exhausted budget and 446 for failed guardrail checks.
- [MCP authorization](https://docs.portkey.ai/docs/aigw/product/mcp-gateway/authorization) is available at the workspace and MCP server levels. Tool-level authorization is listed as "Coming soon".
- The [Portkey-AI/gateway](https://github.com/Portkey-AI/gateway) repo is MIT licensed and starts with `npx @portkey-ai/gateway`. Its [older docs](https://portkey.ai/docs/virtual_key_old/product/open-source) still link the project at github.com/portkey-ai/rubeus, and that URL now redirects to Portkey-AI/gateway.

## Migration checklist

1. List every Portkey virtual key, its owner, its budget and its allowed models.
2. Write one `apiKey.keys` entry per client in standalone, or one hashed ConfigMap entry per user on Kubernetes.
3. Move provider keys into the gateway config or a Kubernetes Secret. Clients get only virtual keys.
4. Recreate budgets: `unit: USD` or `unit: Tokens` in standalone, `unit: Tokens` on Kubernetes. Plan for one request of overshoot.
5. Recreate guardrails and MCP tool rules, then test a blocked request for each.
6. Point each client at the gateway. For the OpenAI Python SDK, the whole change from a stock client in my test was this:

```python
client = OpenAI(base_url="http://localhost:4000/v1", api_key="<virtual key>")
```

The call returned 200 with the model's reply and usage, and the upstream saw only the gateway's provider key. Test any Portkey-specific headers or SDK features your app uses before cutover.

7. Watch for 429 `budget_exceeded` and 403 `model_not_allowed` in standalone, and for 429 with `x-ratelimit-remaining: 0` on Kubernetes. Portkey's docs list 412 for an exhausted budget, so update any client code that checks for it.

There is no Portkey importer. The agentgateway [config importer](https://agentgateway.dev/docs/standalone/latest/documentation/configuration/import/) reads LiteLLM proxy configs only, so a Portkey setup is moved by hand with the concept map above.

## Related

- [How agentgateway, LiteLLM and Portkey compare](/agentgateway-vs-litellm-vs-portkey/)
- [Per-key LLM budgets that return 429](/agentgateway-per-key-llm-budgets/) (open source, standalone)
- [Team spend caps with virtual keys](/llm-cost-controls-ai-gateway/) (Solo Enterprise)
- [agentgateway guardrails](/agentgateway-guardrails/)
- [How to choose an AI agent gateway](/how-to-choose-an-ai-agent-gateway/)
- [Your AI agent should not hold the LLM API key](/secretless-ai-agents/)
- [agentgateway as an egress proxy](/agentgateway-egress-proxy/)

