openrouter-team-setup
Enables multi-user OpenRouter management with automated key provisioning, per-user budget caps, and usage tracking.
Install
mkdir -p .claude/skills/openrouter-team-setup && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/8643" && unzip -o skill.zip -d .claude/skills/openrouter-team-setup && rm skill.zipInstalls to .claude/skills/openrouter-team-setup
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Configure OpenRouter for multi-user teams with per-user keys, budgetKey capabilities
- →Provision individual API keys for team members
- →Set credit limits per API key
- →Attribute usage using X-Title headers
- →Enforce per-user budgets via database tracking
- →Revoke access for team members
How it works
It utilizes a management API key to programmatically create and manage keys, while using headers and a local database to track and limit usage per user.
Inputs & outputs
When to use openrouter-team-setup
- →Provision unique API keys for new team members
- →Set monthly spend limits per department
- →Track API usage for internal billing
- →Revoke access for offboarded employees
About this skill
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 asOPENROUTER_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_KEYfor the shared-key attribution pattern — see theopenrouter-install-authskill for setup - Python 3.8+ with the OpenAI SDK and
requests;sqlite3(stdlib) backs the per-user budget database curlandjqfor 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-timekeyvalue and keep thekey_hashfor 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_budgetssqlite tables, callcheck_user_budgetbefore each request andrecord_user_usageafter. - Gate expensive models per tier with the Model Governance allowlists (
enforce_model_policydowngrades 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}).
Key Provisioning via Management API
import os, requests
MGMT_KEY = os.environ["OPENROUTER_MGMT_KEY"] # Management key (cannot call completions)
def create_team_key(name: str, credit_limit: float = 25.0) -> dict:
"""Create a new API key for a team member."""
resp = requests.post(
"https://openrouter.ai/api/v1/keys",
headers={"Authorization": f"Bearer {MGMT_KEY}"},
json={"name": name, "limit": credit_limit},
)
resp.raise_for_status()
data = resp.json()["data"]
return {
"key": data["key"], # sk-or-v1-... (shown once)
"hash": data["key_hash"], # For later identification
"name": name,
"limit": credit_limit,
}
def list_team_keys() -> list[dict]:
"""List all keys with usage and limits."""
resp = requests.get(
"https://openrouter.ai/api/v1/keys",
headers={"Authorization": f"Bearer {MGMT_KEY}"},
)
return [
{
"name": k.get("name"),
"hash": k.get("key_hash"),
"usage": k.get("usage", 0),
"limit": k.get("limit"),
"is_free_tier": k.get("is_free_tier", False),
}
for k in resp.json().get("data", [])
]
def delete_team_key(key_hash: str):
"""Revoke a team member's key."""
resp = requests.delete(
f"https://openrouter.ai/api/v1/keys/{key_hash}",
headers={"Authorization": f"Bearer {MGMT_KEY}"},
)
resp.raise_for_status()
# Provision keys for the team
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]}...")
Shared Key with User Attribution
from openai import OpenAI
# Alternative: single shared key with user identification via headers
def get_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
def init_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
def check_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()
if not row:
return True # No usage yet today
return row[0] < row[1]
def record_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
def team_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
]
Team Key Dashboard Script
#!/bin/bash
# Show all team keys with usage
echo "=== OpenRouter Team Keys ==="
curl -s https://openrouter.ai/api/v1/keys \
-H "Authorization: Bearer $OPENROUTER_MGMT_KEY" | \
jq -r '.data[] | "\(.name)\t$\(.usage // 0 | tostring)\t/\t$\(.limit // "unlimited" | tostring)"' | \
column -t -s $'\t'
echo ""
echo "=== Total Usage ==="
curl -s https://openrouter.ai/api/v1/keys \
-H "Authorization: Bearer $OPENROUTER_MGMT_KEY" | \
jq '.data | map(.usage // 0) | add | "Total spend: $\(.)"'
Model Governance
# 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
}
def enforce_model_policy(user_tier: str, requested_model: str) -> str:
"""Enforce model allowlist based on user tier."""
allowlist = MODEL_ALLOWLISTS.get(user_tier)
if allowlist is None:
return requested_model # Enterprise: unrestricted
if requested_model in allowlist:
return requested_model
# Downgrade to best allowed model
return allowlist[-1]
Output
- Per-member API keys (
sk-or-v1-..., shown once at creation) pluskey_hashrecords carrying name, usage, and credit limit - A
team_usage.dbsqlite database with per-user dailytotal_costandrequest_countrows 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 |
| User exceeds budget | No per-user limits set | Create individual keys with credit limits |
| Attribution missing | No X-Title header | Enforce header in shared client wrapper |
| Key sprawl | Too many keys to track | Implement key lifecycle management; revoke unused keys |
Enterprise Considerations
- Use management keys for programmatic key provisioning -- they can create/list/delete API keys but cannot make completions
- Set per-key credit limits to prevent any single user from exhausting shared budget
- Use
X-Titleheader with user identifiers for dashboard-level attribution - Implem
Content truncated.
When not to use it
- →When managing a single-user environment
- →When the management key is not available
Prerequisites
Limitations
- →Management keys cannot be used to call completions
- →Requires secure storage of the one-time API key value
How it compares
This approach provides granular governance and cost attribution, unlike using a single shared API key for an entire organization.
Compared to similar skills
openrouter-team-setup side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| openrouter-team-setup (this skill) | 0 | 27d | Caution | Advanced |
| setup | 12 | 1mo | No flags | Beginner |
| resources | 1 | 2mo | Review | Intermediate |
| agent-manager-skill | 1 | 6mo | Review | Beginner |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by jeremylongshore
View all by jeremylongshore →You might also like
setup
barefootford
Sets up a Mac for ButterCut. Installs all required dependencies (Homebrew, Ruby, Python, FFmpeg, WhisperX). Use when user says "install buttercut", "set up my mac", "get started", "first time setup", "install dependencies" or "check my installation".
resources
windmill-labs
MUST use when managing resources.
agent-manager-skill
davila7
Manage multiple local CLI agents via tmux sessions (start/stop/monitor/assign) with cron-friendly scheduling.
machine-learning-ops-ml-pipeline
sickn33
Design and implement a complete ML pipeline for: $ARGUMENTS
uv
mitsuhiko
Use `uv` instead of pip/python/venv. Run scripts with `uv run script.py`, add deps with `uv add`, use inline script metadata for standalone scripts.
vastai-core-workflow-b
jeremylongshore
Execute Vast.ai secondary workflow: Core Workflow B. Use when implementing secondary use case, or complementing primary workflow. Trigger with phrases like "vastai secondary workflow", "secondary task with vastai".