OP

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.zip

Installs 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, budget
68 charsno explicit “when” trigger
Advanced

Key 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

You give it
Team member names and credit limits
You get back
Provisioned API keys and usage tracking records

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 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

  1. Create a management key at openrouter.ai/keys and export it as OPENROUTER_MGMT_KEY.
  2. 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.
  3. 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).
  4. 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.
  5. Gate expensive models per tier with the Model Governance allowlists (enforce_model_policy downgrades disallowed requests).
  6. 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.
  7. 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) 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

ErrorCauseFix
Management key 403Using API key instead of management keyManagement keys are separate -- create one at openrouter.ai/keys
User exceeds budgetNo per-user limits setCreate individual keys with credit limits
Attribution missingNo X-Title headerEnforce header in shared client wrapper
Key sprawlToo many keys to trackImplement 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-Title header 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

Management key exported as OPENROUTER_MGMT_KEYPython 3.8+ with openai, requests, and sqlite3

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.

SkillInstallsUpdatedSafetyDifficulty
openrouter-team-setup (this skill)027dCautionAdvanced
setup121moNo flagsBeginner
resources12moReviewIntermediate
agent-manager-skill16moReviewBeginner

Try saying

Example prompts that trigger this skill in your AI assistant.

More by jeremylongshore

View all by jeremylongshore

analyzing-logs

jeremylongshore

Analyze application logs to detect performance issues, identify error patterns, and improve stability by extracting key insights.

14123

ollama-setup

jeremylongshore

Configure auto-configure Ollama when user needs local LLM deployment, free AI alternatives, or wants to eliminate hosted API costs. Trigger phrases: "install ollama", "local AI", "free LLM", "self-hosted AI", "replace OpenAI", "no API costs". Use when appropriate context detected. Trigger with relevant phrases based on skill purpose.

1167

backtesting-trading-strategies

jeremylongshore

Backtest crypto and traditional trading strategies against historical data. Calculates performance metrics (Sharpe, Sortino, max drawdown), generates equity curves, and optimizes strategy parameters. Use when user wants to test a trading strategy, validate signals, or compare approaches. Trigger with phrases like "backtest strategy", "test trading strategy", "historical performance", "simulate trades", "optimize parameters", or "validate signals".

1071

generating-database-seed-data

jeremylongshore

Process this skill enables AI assistant to generate realistic test data and database seed scripts for development and testing environments. it uses faker libraries to create realistic data, maintains relational integrity, and allows configurable data volumes. u... Use when working with databases or data models. Trigger with phrases like 'database', 'query', or 'schema'.

1033

cursor-codebase-indexing

jeremylongshore

Execute set up and optimize Cursor codebase indexing. Triggers on "cursor index setup", "codebase indexing", "index codebase", "cursor semantic search". Use when working with cursor codebase indexing functionality. Trigger with phrases like "cursor codebase indexing", "cursor indexing", "cursor".

885

testing-mobile-apps

jeremylongshore

Execute mobile app testing on iOS and Android devices/simulators. Use when performing specialized testing. Trigger with phrases like "test mobile app", "run iOS tests", or "validate Android functionality".

810

Search skills

Search the agent skills registry