Configure OpenRouter for multi-user teams with per-user keys, budget controls, and usage attribution. Triggers: 'openrouter team', 'openrouter multi-user', 'openrouter organization', 'team api keys openrouter'.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Configure OpenRouter for multi-user teams with per-user keys, budget controls, and usage attribution. Triggers: 'openrouter team', 'openrouter multi-user', 'openrouter organization', 'team api keys openrouter'.
Designed for Claude Code, also compatible with Codex and OpenClaw
OpenRouter Team Setup
Overview
OpenRouter supports team usage through per-user API keys with individual credit limits, management keys for programmatic key provisioning, and usage attribution via headers. This skill covers key provisioning, per-user budgets, usage tracking, and governance policies for multi-user deployments.
Prerequisites
A management key (sk-or-v1-...) with provisioning rights exported as OPENROUTER_MGMT_KEY — created separately at openrouter.ai/keys; it can create/list/delete API keys but cannot call completions
A regular OpenRouter API key exported as OPENROUTER_API_KEY for the shared-key attribution pattern — see the openrouter-install-auth skill for setup
Python 3.8+ with the OpenAI SDK and requests; sqlite3 (stdlib) backs the per-user budget database
curl and jq for the Team Key Dashboard Script
Instructions
Create a management key at openrouter.ai/keys and export it as OPENROUTER_MGMT_KEY.
Provision one key per team member via Key Provisioning via Management API — create_team_key(name, credit_limit) posts to /api/v1/keys; record the one-time key value and keep the key_hash for later listing/revocation.
Alternatively, keep a single shared key and attribute usage per user with the Shared Key with User Attribution pattern (X-Title: my-app:{user_id} header shows each user in the dashboard).
Enforce spend locally with Per-User Budget Enforcement — initialize the user_usage / user_budgets sqlite tables, call check_user_budget before each request and record_user_usage after.
Gate expensive models per tier with the Model Governance allowlists (enforce_model_policy downgrades disallowed requests).
Monitor continuously: run the Team Key Dashboard Script (curl + jq against /api/v1/keys) and generate the weekly Team Usage Report from the sqlite DB.
Revoke keys for departed members with delete_team_key(key_hash) (DELETE /api/v1/keys/{hash}).
f"Created key for {member}: {key_info['key'][:20]}..."
Shared Key with User Attribution
from openai import OpenAI
# Alternative: single shared key with user identification via headersdefget_client_for_user(user_id: str) -> OpenAI:
"""Create a client that attributes usage to a specific user."""return OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
default_headers={
"HTTP-Referer": "https://my-app.com",
"X-Title": f"my-app:{user_id}", # User shows in dashboard
},
)
# Each user's requests appear under their X-Title in the dashboard
alice_client = get_client_for_user("alice")
response = alice_client.chat.completions.create(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "Hello"}],
max_tokens=100,
)
Per-User Budget Enforcement
import sqlite3, time
definit_team_db(db_path: str = "team_usage.db"):
conn = sqlite3.connect(db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS user_usage (
user_id TEXT NOT NULL,
date TEXT NOT NULL,
total_cost REAL DEFAULT 0,
request_count INTEGER DEFAULT 0,
PRIMARY KEY (user_id, date)
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS user_budgets (
user_id TEXT PRIMARY KEY,
daily_limit REAL NOT NULL,
model_allowlist TEXT -- JSON array of allowed model IDs
)
""")
conn.commit()
return conn
defcheck_user_budget(conn, user_id: str) -> bool:
"""Check if user is within their daily budget."""
today = time.strftime("%Y-%m-%d")
row = conn.execute(
"SELECT u.total_cost, b.daily_limit FROM user_usage u ""JOIN user_budgets b ON u.user_id = b.user_id ""WHERE u.user_id = ? AND u.date = ?",
(user_id, today),
).fetchone()
ifnot row:
returnTrue# No usage yet todayreturn row[0] < row[1]
defrecord_user_usage(conn, user_id: str, cost: float):
"""Record a request's cost for a user."""
today = time.strftime("%Y-%m-%d")
conn.execute(
"""INSERT INTO user_usage (user_id, date, total_cost, request_count)
VALUES (?, ?, ?, 1)
ON CONFLICT(user_id, date) DO UPDATE SET
total_cost = total_cost + ?, request_count = request_count + 1""",
(user_id, today, cost, cost),
)
conn.commit()
Team Usage Report
defteam_usage_report(conn) -> list[dict]:
"""Generate a team usage report for the current week."""
rows = conn.execute("""
SELECT u.user_id, SUM(u.total_cost) as weekly_cost,
SUM(u.request_count) as requests,
b.daily_limit
FROM user_usage u
JOIN user_budgets b ON u.user_id = b.user_id
WHERE u.date >= date('now', '-7 days')
GROUP BY u.user_id
ORDER BY weekly_cost DESC
""").fetchall()
return [
{
"user": row[0],
"weekly_cost": round(row[1], 4),
"requests": row[2],
"daily_limit": row[3],
}
for row in rows
]
# Define which models each tier can use
MODEL_ALLOWLISTS = {
"free": ["google/gemma-2-9b-it:free"],
"basic": ["openai/gpt-4o-mini", "meta-llama/llama-3.1-8b-instruct"],
"pro": ["openai/gpt-4o-mini", "openai/gpt-4o", "anthropic/claude-3.5-sonnet"],
"enterprise": None, # None = all models allowed
}
defenforce_model_policy(user_tier: str, requested_model: str) -> str:
"""Enforce model allowlist based on user tier."""
allowlist = MODEL_ALLOWLISTS.get(user_tier)
if allowlist isNone:
return requested_model # Enterprise: unrestrictedif requested_model in allowlist:
return requested_model
# Downgrade to best allowed modelreturn allowlist[-1]
Output
Per-member API keys (sk-or-v1-..., shown once at creation) plus key_hash records carrying name, usage, and credit limit
A team_usage.db sqlite database with per-user daily total_cost and request_count rows plus per-user budgets and model allowlists
A columnar key dashboard from the curl + jq script: key name, spend, and limit per row, plus a total-spend line
A weekly team usage report list sorted by weekly_cost, one dict per user with requests and daily limit
Examples
Provision keys for three team members with a $50 credit limit each:
for member in ["alice-backend", "bob-frontend", "carol-ml"]:
key_info = create_team_key(member, credit_limit=50.0)
print(f"Created key for {member}: {key_info['key'][:20]}...")
# Created key for alice-backend: sk-or-v1-a1b2c3d4e5...# Created key for bob-frontend: sk-or-v1-f6a7b8c9d0...# Created key for carol-ml: sk-or-v1-e1f2a3b4c5...
Each key value is only returned once — store it securely and keep the hash for revocation. More worked examples: references/examples.md.
Error Handling
Error
Cause
Fix
Management key 403
Using API key instead of management key
Management keys are separate -- create one at openrouter.ai/keys