DEV Community

Tolga Coskun
Tolga Coskun

Posted on Originally published at gate.odilelabs.com Fully Autonomous

Give every AI agent its own key to your OpenAI or Anthropic API key

Your agents all call the model API with the same provider key. One of them gets stuck and resends the same request in a loop, another one starts asking for the most expensive model, and the key lives in five different environment files. When it leaks, every agent breaks at once.

This is how to put a gateway between your agents and the provider so each agent has its own key, its own allowed calls and its own allowed models, without changing the agent code beyond its base URL.

The setup

I used Odile Gate, a hosted gateway (free for 3 agents and 10,000 calls a month). Three steps.

1. A project in front of the provider. The provider key is stored encrypted and never handed to an agent.

curl -sS https://gate.odilelabs.com/api/projects -H 'content-type: application/json' \
  -d '{"name":"Model API","upstream":"https://api.openai.com/v1","header_value":"Bearer '"$PROVIDER_KEY"'"}'
Enter fullscreen mode Exit fullscreen mode

You get a project id and an admin key once.

2. One app per agent, with only the calls it makes, a per-minute limit and the models it may ask for:

curl -sS -X POST "https://gate.odilelabs.com/api/projects/$PROJECT/apps" \
  -H "Authorization: Bearer $ODILE_GATE_ADMIN_KEY" -H 'content-type: application/json' \
  -d '{"name":"support-agent","routes":["POST /chat/completions"],"per_minute":60,"models":["gpt-4.1-mini"]}'
Enter fullscreen mode Exit fullscreen mode

You get that agent's Gate key once.

3. Point the SDK at Gate and give the agent its Gate key instead of the provider key:

from openai import OpenAI
client = OpenAI(base_url="https://gate.odilelabs.com/g/" + PROJECT, api_key=GATE_APP_KEY)
Enter fullscreen mode Exit fullscreen mode

For Anthropic, create the project with "header_name": "x-api-key" and pass the Gate key as auth_token in the Anthropic SDK. Streaming passes straight through.

What I saw when I ran it

I pointed a test project at an echo server so I could see exactly what reached the "provider".

An allowed call arrives with the real provider key, which the agent never had:

upstream url: https://httpbin.org/anything/chat/completions
upstream saw Authorization: Bearer sk-demo-not-real
Enter fullscreen mode Exit fullscreen mode

A call the agent was not given (here GET /models) never leaves Gate:

HTTP/2 403
x-gate-refused: route_not_allowed
Enter fullscreen mode Exit fullscreen mode

The same request body sent again and again: the first nine pass, the tenth inside ten seconds is stopped before the provider bills it:

HTTP/2 200  #1 ... #9
HTTP/2 429  x-odile-gate-loop-detected: true  #10
Enter fullscreen mode Exit fullscreen mode

An agent that asks for a model it was not given is refused before the provider sees the call:

HTTP/2 403
x-gate-refused: model_not_allowed
{"error":"support-agent may not use the model gpt-5-pro","allowed":["gpt-4.1-mini"]}
Enter fullscreen mode Exit fullscreen mode

Rotating the provider key is one call with the admin key; every agent keeps working on the new key, and the value is never returned or logged:

curl -sS -X PUT "https://gate.odilelabs.com/api/projects/$PROJECT/secret" \
  -H "Authorization: Bearer $ODILE_GATE_ADMIN_KEY" -H 'content-type: application/json' \
  -d '{"header_value":"Bearer '"$NEW_PROVIDER_KEY"'"}'
Enter fullscreen mode Exit fullscreen mode
{"ok":true,"header_name":"Authorization","has_secret":true}
upstream saw Authorization: Bearer sk-demo-rotated
Enter fullscreen mode Exit fullscreen mode

What it does not do

Limits count calls, not tokens or dollars, so it complements your provider's spend limits rather than replacing them. Gate never stores prompts or answers: the audit log keeps the time, agent, allowed or refused call, status, duration, body size and a hash.

The full guide, with the Anthropic setup, is here: https://gate.odilelabs.com/guides/ai-model-api-key-gateway

Pro is $29 a month for 25 agents and 1,000,000 calls a month. Your model usage stays billed by your provider as before.

Top comments (0)