Chapter 64

Admin API: Complete Enterprise Management Guide for Organizations / Members / Workspaces / API Keys

Chapter 64: Admin API: Organization Management, User Permissions, and API Key Lifecycle

64.1 The Admin API's Role and Use Cases

Anthropic's Admin API is a set of REST endpoints for enterprise administrators, enabling programmatic management of Claude usage resources: organization settings, user accounts, Workspace allocation, API key lifecycle, and usage queries.

Compared to manual management through the Anthropic Console, the Admin API is indispensable in the following scenarios:

The Admin API uses a dedicated Admin Key (sk-ant-admin-...), which must be stored and managed separately from regular API keys.

64.2 Authentication and Basic Configuration

64.2.1 Obtaining an Admin Key

Admin Keys can only be created by users with the Owner role in the Anthropic Console:

  1. Log in to https://console.anthropic.com
  2. Go to Settings โ†’ Admin Keys
  3. Click Create Admin Key, set a name and expiry
  4. Save the generated sk-ant-admin-... key (shown only once)

64.2.2 Basic Request Structure

All Admin API requests use the same authentication headers:

import httpx
import os
from typing import Optional

ADMIN_BASE_URL = "https://api.anthropic.com/v1"

class AnthropicAdminClient:
    """Anthropic Admin API client"""
    
    def __init__(self, admin_key: Optional[str] = None):
        self.admin_key = admin_key or os.environ["ANTHROPIC_ADMIN_KEY"]
        self.base_url = ADMIN_BASE_URL
        self.headers = {
            "x-api-key": self.admin_key,
            "anthropic-version": "2023-06-01",
            "content-type": "application/json"
        }
        self.client = httpx.Client(timeout=30)
    
    def get(self, path: str, params: dict = None) -> dict:
        response = self.client.get(
            f"{self.base_url}{path}",
            headers=self.headers,
            params=params
        )
        response.raise_for_status()
        return response.json()
    
    def post(self, path: str, body: dict) -> dict:
        response = self.client.post(
            f"{self.base_url}{path}",
            headers=self.headers,
            json=body
        )
        response.raise_for_status()
        return response.json()
    
    def delete(self, path: str) -> dict:
        response = self.client.delete(
            f"{self.base_url}{path}",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

64.3 Organization Management (/v1/organizations)

64.3.1 Get Organization Information

admin = AnthropicAdminClient()

# GET /v1/organizations
org_info = admin.get("/organizations")

Request:

GET https://api.anthropic.com/v1/organizations
x-api-key: sk-ant-admin-...
anthropic-version: 2023-06-01

Response example:

{
  "id": "org_01XXXXXXXXXXXXXXXXXX",
  "name": "Acme Corporation",
  "created_at": "2024-01-15T08:00:00Z",
  "billing": {
    "plan": "enterprise",
    "usage_limit_usd": 10000.00,
    "current_month_usage_usd": 2345.67
  },
  "settings": {
    "default_model": "claude-opus-4-5",
    "require_mfa": true,
    "allowed_ip_ranges": ["203.0.113.0/24", "198.51.100.0/24"]
  }
}

64.3.2 Update Organization Settings

# PATCH /v1/organizations
updated = admin.post("/organizations", {
    "settings": {
        "require_mfa": True,
        "allowed_ip_ranges": ["203.0.113.0/24"]
    }
})

64.4 User Management (/v1/users)

64.4.1 List Organization Members

# GET /v1/users - paginated list of all users
def list_all_users(admin: AnthropicAdminClient) -> list[dict]:
    """Get all users in the organization (handles pagination)"""
    all_users = []
    after_cursor = None
    
    while True:
        params = {"limit": 100}
        if after_cursor:
            params["after_id"] = after_cursor
        
        response = admin.get("/users", params=params)
        users = response.get("data", [])
        all_users.extend(users)
        
        if not response.get("has_more", False):
            break
        
        after_cursor = users[-1]["id"] if users else None
    
    return all_users

GET /v1/users response example:

{
  "data": [
    {
      "id": "user_01XXXXXXXXXXXXXXXXXX",
      "email": "[email protected]",
      "name": "Alice Johnson",
      "role": "developer",
      "created_at": "2024-03-01T10:00:00Z",
      "last_active_at": "2025-04-27T15:30:00Z",
      "status": "active"
    },
    {
      "id": "user_02XXXXXXXXXXXXXXXXXX",
      "email": "[email protected]",
      "name": "Bob Smith",
      "role": "admin",
      "created_at": "2024-01-20T09:00:00Z",
      "last_active_at": "2025-04-28T08:00:00Z",
      "status": "active"
    }
  ],
  "has_more": true,
  "first_id": "user_01XXXXXXXXXXXXXXXXXX",
  "last_id": "user_02XXXXXXXXXXXXXXXXXX"
}

64.4.2 Invite a New User

# POST /v1/users/invite
def invite_user(admin: AnthropicAdminClient, email: str, role: str = "developer") -> dict:
    """
    Invite a user to join the organization.
    role: owner | admin | developer | billing
    """
    return admin.post("/users/invite", {"email": email, "role": role})

Request body:

{
  "email": "[email protected]",
  "role": "developer"
}

Response example:

{
  "id": "invite_01XXXXXXXXXXXXXXXXXX",
  "email": "[email protected]",
  "role": "developer",
  "status": "pending",
  "invited_at": "2025-04-28T10:00:00Z",
  "expires_at": "2025-05-05T10:00:00Z"
}

64.4.3 Update User Role

# PATCH /v1/users/{user_id}
def update_user_role(admin: AnthropicAdminClient, user_id: str, new_role: str) -> dict:
    response = admin.client.patch(
        f"{admin.base_url}/users/{user_id}",
        headers=admin.headers,
        json={"role": new_role}
    )
    response.raise_for_status()
    return response.json()

PATCH /v1/users/{user_id} response:

{
  "id": "user_01XXXXXXXXXXXXXXXXXX",
  "email": "[email protected]",
  "name": "Alice Johnson",
  "role": "admin",
  "updated_at": "2025-04-28T10:05:00Z"
}

64.4.4 Remove a User

# DELETE /v1/users/{user_id}
def remove_user(admin: AnthropicAdminClient, user_id: str) -> dict:
    """Remove a user from the organization (also revokes all their API keys)"""
    return admin.delete(f"/users/{user_id}")

DELETE response:

{
  "id": "user_01XXXXXXXXXXXXXXXXXX",
  "deleted": true
}

64.5 API Key Lifecycle Management (/v1/api_keys)

API keys are the core credentials for Claude usage. Their lifecycle management is one of the most important Admin API functions.

64.5.1 List API Keys

# GET /v1/api_keys
def list_api_keys(admin: AnthropicAdminClient, workspace_id: Optional[str] = None) -> list[dict]:
    """List all API keys (optionally filtered by workspace)"""
    params = {}
    if workspace_id:
        params["workspace_id"] = workspace_id
    return admin.get("/api_keys", params=params).get("data", [])

GET /v1/api_keys response example:

{
  "data": [
    {
      "id": "apikey_01XXXXXXXXXXXXXXXXXX",
      "name": "Production Backend",
      "created_at": "2024-06-01T00:00:00Z",
      "last_used_at": "2025-04-28T09:45:00Z",
      "status": "active",
      "workspace_id": "workspace_01XXXXXXXXXXXXXXXX",
      "created_by": {
        "id": "user_01XXXXXXXXXXXXXXXXXX",
        "email": "[email protected]"
      },
      "partial_key": "sk-ant-api03-...ABCD"
    }
  ],
  "has_more": false
}

64.5.2 Create a New API Key

# POST /v1/api_keys
def create_api_key(
    admin: AnthropicAdminClient,
    name: str,
    workspace_id: str,
    expires_at: Optional[str] = None
) -> dict:
    """
    Create a new API key.
    IMPORTANT: The full key is returned only once โ€” save it immediately.
    """
    body = {"name": name, "workspace_id": workspace_id}
    if expires_at:
        body["expires_at"] = expires_at  # ISO 8601 format
    return admin.post("/api_keys", body)

POST /v1/api_keys request body:

{
  "name": "Customer Portal Service",
  "workspace_id": "workspace_01XXXXXXXXXXXXXXXX",
  "expires_at": "2026-04-28T00:00:00Z"
}

Response (full key shown only this once):

{
  "id": "apikey_03XXXXXXXXXXXXXXXXXX",
  "name": "Customer Portal Service",
  "key": "sk-ant-api03-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "workspace_id": "workspace_01XXXXXXXXXXXXXXXX",
  "created_at": "2025-04-28T10:10:00Z",
  "expires_at": "2026-04-28T00:00:00Z",
  "status": "active"
}

64.5.3 Revoke an API Key

# DELETE /v1/api_keys/{api_key_id}
def revoke_api_key(admin: AnthropicAdminClient, api_key_id: str) -> dict:
    """Immediately revoke an API key (all requests using this key will fail instantly)"""
    return admin.delete(f"/api_keys/{api_key_id}")

DELETE response:

{
  "id": "apikey_01XXXXXXXXXXXXXXXXXX",
  "deleted": true
}

64.6 Workspace Management (/v1/workspaces)

Workspaces are resource isolation units within an organization, allowing different teams to use independent API key pools and usage quotas.

64.6.1 List Workspaces

def list_workspaces(admin: AnthropicAdminClient) -> list[dict]:
    return admin.get("/workspaces").get("data", [])

Response example:

{
  "data": [
    {
      "id": "workspace_01XXXXXXXXXXXXXXXX",
      "name": "Production",
      "created_at": "2024-01-15T08:00:00Z",
      "usage_limits": {
        "monthly_spend_usd": 5000.00,
        "requests_per_minute": 1000
      }
    },
    {
      "id": "workspace_02XXXXXXXXXXXXXXXX",
      "name": "Development",
      "created_at": "2024-01-15T08:00:00Z",
      "usage_limits": {
        "monthly_spend_usd": 500.00,
        "requests_per_minute": 100
      }
    }
  ]
}

64.6.2 Create a Workspace

# POST /v1/workspaces
def create_workspace(
    admin: AnthropicAdminClient,
    name: str,
    monthly_spend_limit: float,
    rpm_limit: int = 100
) -> dict:
    """Create a new workspace with usage limits"""
    return admin.post("/workspaces", {
        "name": name,
        "usage_limits": {
            "monthly_spend_usd": monthly_spend_limit,
            "requests_per_minute": rpm_limit
        }
    })

64.7 Usage Queries and Cost Monitoring

64.7.1 Get Usage Data

# GET /v1/usage
def get_usage(
    admin: AnthropicAdminClient,
    start_date: str,   # YYYY-MM-DD
    end_date: str,
    workspace_id: Optional[str] = None,
    granularity: str = "day"  # hour | day | month
) -> dict:
    params = {"start_date": start_date, "end_date": end_date, "granularity": granularity}
    if workspace_id:
        params["workspace_id"] = workspace_id
    return admin.get("/usage", params=params)

GET /v1/usage response example:

{
  "data": [
    {
      "date": "2025-04-27",
      "input_tokens": 12345678,
      "output_tokens": 3456789,
      "cache_read_tokens": 987654,
      "total_cost_usd": 456.78,
      "requests": 23456,
      "model_breakdown": {
        "claude-opus-4-5": {
          "input_tokens": 5000000,
          "output_tokens": 2000000,
          "cost_usd": 380.00
        },
        "claude-haiku-4-5": {
          "input_tokens": 7345678,
          "output_tokens": 1456789,
          "cost_usd": 76.78
        }
      }
    }
  ],
  "total": {
    "input_tokens": 12345678,
    "output_tokens": 3456789,
    "total_cost_usd": 456.78
  }
}

64.7.2 Automated Cost Alerting

from datetime import datetime

class CostAlertSystem:
    """Automated cost alerting system based on the Admin API"""
    
    def __init__(self, admin: AnthropicAdminClient, alert_threshold_usd: float):
        self.admin = admin
        self.threshold = alert_threshold_usd
    
    def check_and_alert(self) -> bool:
        """Check current month usage; alert if threshold exceeded"""
        today = datetime.now()
        month_start = today.replace(day=1).strftime("%Y-%m-%d")
        today_str = today.strftime("%Y-%m-%d")
        
        usage = self.admin.get("/usage", params={
            "start_date": month_start,
            "end_date": today_str,
            "granularity": "month"
        })
        
        total_cost = usage.get("total", {}).get("total_cost_usd", 0)
        
        if total_cost > self.threshold:
            self._send_alert(total_cost)
            return True
        return False
    
    def _send_alert(self, current_cost: float):
        alert_message = f"""
[COST ALERT] Anthropic API Usage Exceeds Budget

Current month usage: ${current_cost:.2f} USD
Alert threshold: ${self.threshold:.2f} USD
Overage: {((current_cost / self.threshold - 1) * 100):.1f}%

Log in to Anthropic Console for details. Consider:
1. Check for anomalous API calls
2. Adjust Workspace usage limits
3. Optimize model selection (use Haiku instead of Opus for high-frequency calls)
"""
        print(alert_message)
        # In production: send via email, Slack, PagerDuty, etc.

64.8 Automated Onboarding and Offboarding

Combining multiple Admin API endpoints into a complete user lifecycle automation:

class UserLifecycleManager:
    """Automated user onboarding/offboarding management"""
    
    def __init__(self, admin: AnthropicAdminClient):
        self.admin = admin
    
    def onboard_developer(
        self, email: str, name: str, team: str, workspace_id: str
    ) -> dict:
        """
        Developer onboarding:
        1. Invite user to organization
        2. Create a dedicated API key for the user
        3. Return credentials for secure delivery
        """
        invite = self.admin.post("/users/invite", {
            "email": email, "role": "developer"
        })
        
        from datetime import datetime, timedelta
        expires = (datetime.now() + timedelta(days=365)).isoformat() + "Z"
        
        api_key = create_api_key(
            self.admin,
            name=f"{name} - {team}",
            workspace_id=workspace_id,
            expires_at=expires
        )
        
        return {
            "invite_id": invite["id"],
            "invite_email": email,
            "api_key_id": api_key["id"],
            "api_key": api_key["key"],  # One-time display โ€” save and deliver securely
            "workspace_id": workspace_id,
            "expires_at": expires
        }
    
    def offboard_developer(self, user_id: str) -> dict:
        """
        Developer offboarding:
        1. Revoke all API keys belonging to this user
        2. Remove user from organization
        """
        revoked_keys = []
        
        all_keys = list_api_keys(self.admin)
        user_keys = [k for k in all_keys if k.get("created_by", {}).get("id") == user_id]
        
        for key in user_keys:
            revoke_api_key(self.admin, key["id"])
            revoked_keys.append(key["id"])
        
        removal = self.admin.delete(f"/users/{user_id}")
        
        return {
            "user_id": user_id,
            "deleted": removal.get("deleted", False),
            "revoked_keys": revoked_keys,
            "revoked_count": len(revoked_keys)
        }

64.9 Security Best Practices

64.9.1 Protecting Admin Keys

# Retrieve Admin Key from AWS Secrets Manager
import boto3
import json

def get_admin_key_from_secrets_manager(secret_name: str, region: str = "us-east-1") -> str:
    client = boto3.client("secretsmanager", region_name=region)
    response = client.get_secret_value(SecretId=secret_name)
    secret = json.loads(response["SecretString"])
    return secret["anthropic_admin_key"]

# Environment variable injection (recommended for CI/CD)
# export ANTHROPIC_ADMIN_KEY=$(aws secretsmanager get-secret-value ...)

64.9.2 Operation Audit Logging

import logging
import functools
import time

logger = logging.getLogger("anthropic_admin_audit")

def audit_log(action: str):
    """Decorator for auditing Admin API operations"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            error = None
            try:
                result = func(*args, **kwargs)
                return result
            except Exception as e:
                error = str(e)
                raise
            finally:
                logger.info({
                    "action": action,
                    "timestamp": time.time(),
                    "elapsed_ms": int((time.time() - start) * 1000),
                    "args": str(args[1:])[:200],
                    "success": error is None,
                    "error": error
                })
        return wrapper
    return decorator

@audit_log("invite_user")
def invite_user_audited(admin: AnthropicAdminClient, email: str, role: str) -> dict:
    return admin.post("/users/invite", {"email": email, "role": role})

@audit_log("revoke_api_key")
def revoke_api_key_audited(admin: AnthropicAdminClient, api_key_id: str) -> dict:
    return admin.delete(f"/api_keys/{api_key_id}")

Summary

The Admin API gives enterprises complete programmatic control over Anthropic Claude usage resources. Core endpoints include: /v1/organizations (organization configuration), /v1/users (invite/remove/role changes), /v1/api_keys (key creation/listing/revocation), /v1/workspaces (resource isolation), and /v1/usage (usage and cost monitoring). Best practices are: store Admin Keys in Secrets Manager rather than code, record all operations in audit logs, set independent quotas for different teams via Workspaces, and build automated cost alert systems on top of /v1/usage. User lifecycle managementโ€”creating keys on onboarding, batch-revoking on offboardingโ€”is the most typical automation scenario, significantly reducing manual management overhead and eliminating the security risk of lingering keys after departure.

Rate this chapter
4.5  / 5  (3 ratings)

๐Ÿ’ฌ Comments