v1.2.0 AI Drafted

Claude API & Console Hardening Guide

AI/ML Platform Last updated: 2026-11-05

Harden Claude API credentials, workspaces, residency, retention, customer-managed keys, Managed Agents and usage governance.

View:

Overview

The Claude API (api.anthropic.com) and its admin Console are the developer platform surface of Anthropic — API keys, workspaces, data-residency and retention settings, and spend controls all live here. A compromised or over-scoped API key is the platform’s highest-frequency risk; workspace segmentation and workload identity federation are its strongest structural mitigations.

This is a product guide within the Anthropic platform. Organization-wide controls (SSO, roles, admin API keys, integration governance) live in the Anthropic Common Controls hub; Claude Code controls live in the Claude Code guide.

Research and validation boundary

This refresh used the source-first candidate ledger and public vendor references, including the published Cowork/unified baseline. It did not access a live tenant, change settings or create credentials. Source verification and mocked/native-interface tests do not earn live-validation status. The exact applicable Security Center checklist must be reconciled with an Owner’s current report; the effective-settings schema is a separate evidence surface.

Intended Audience

  • Platform engineers integrating the Claude API
  • Security engineers governing AI API usage
  • FinOps/engineering leaders managing AI spend
  • GRC professionals assessing AI platform compliance

How to Use This Guide

  • L1 (Crawl): Essential controls for all organizations
  • L2 (Walk): Enhanced controls for security-sensitive environments
  • L3 (Run): Strictest controls for regulated industries

Scope

This guide covers Claude API and Console hardening: API key workspace scoping, rotation, and elimination via workload identity federation; workspace segmentation and membership; data residency and retention; usage monitoring and spend limits. Organization identity and Claude Code controls are covered by the sibling Anthropic guides.


Table of Contents

  1. API Key Management
  2. Workspace Security
  3. Data Security & Privacy
  4. Monitoring & Usage Controls

1. API Key Management

1.1 Scope API Keys to Workspaces

Profile Level: L1 (Crawl)

Framework Control
NIST 800-53 AC-3, AC-6
SOC 2 CC6.1, CC6.3

Description

Prefer workspace-scoped API keys. Current authentication also supports personal/service-account keys with organization scope; an active key with null legacy workspace_id is not sufficient to identify its authority. Inspect scope and principal metadata. Leverage this design by creating separate workspaces for different environments (development, staging, production) and teams, ensuring API keys cannot access resources across workspace boundaries.

Rationale

Why This Matters:

  • A compromised development API key cannot access production workspaces
  • Workspace-scoped keys enable granular cost tracking and rate limiting
  • API keys persist when users are removed — they’re scoped to the organization, not individuals
  • Keys can only be created via the Console (not via API) — another security design choice

Attack Prevented: Lateral movement from development to production, blast radius of key compromise

Prerequisites

  • Organization Admin or Workspace Admin access
  • Workspace naming convention established

ClickOps Implementation

Step 1: Create Workspace-Scoped Keys

  1. Navigate to: platform.claude.com → Select target workspace
  2. Go to: Settings → API Keys
  3. Click Create Key
  4. Name the key descriptively: {team}-{environment}-{purpose} (e.g., “ml-team-prod-inference”)

Step 2: Audit Existing Keys

  1. Navigate to: Settings → API Keys (org-wide view)
  2. Review each key’s workspace assignment
  3. Identify keys in the Default Workspace — migrate to dedicated workspaces

Time to Complete: ~10 minutes per key

Code Implementation

Code Pack: API Script
hth-anthropic-claude-2.01-scope-api-keys-to-workspaces.sh View source on GitHub ↗
# List all API keys across the organization, grouped by workspace
info "Auditing API keys across all workspaces..."
API_KEYS=$(anthropic_list_all "/v1/organizations/api_keys?status=active") || {
  fail "2.1 Failed to list API keys"
  summary; exit 0
}
KEY_COUNT=$(echo "${API_KEYS}" | jq 'length')
info "Found ${KEY_COUNT} active API keys"
# Group by workspace
echo "${API_KEYS}" | jq -r 'group_by(.scope.type, .scope.workspace_id) | .[] |
  "Scope: \(.[0].scope.type // "unknown") Workspace: \(.[0].scope.workspace_id // "unbound-or-default")\n" +
  ([ .[] | "  Key: \(.name // "unnamed") | Status: \(.status) | Created: \(.created_at)" ] | join("\n"))
'
# Flag unnamed keys
UNNAMED=$(echo "${API_KEYS}" | jq '[.[] | select(.name == null or .name == "")] | length')
if [[ "${UNNAMED}" -gt 0 ]]; then
  warn "2.1 ${UNNAMED} API keys have no name — add descriptive names for auditability"
else
  pass "2.1 All API keys have descriptive names"
fi
# Flag keys in default workspace
DEFAULT_WS_KEYS=$(echo "${API_KEYS}" | jq '[.[] | select(.scope.type != "workspace" or .workspace_id == null)] | length')
if [[ "${DEFAULT_WS_KEYS}" -gt 0 ]]; then
  warn "2.1 ${DEFAULT_WS_KEYS} keys are organization-scoped, Default Workspace-bound or have unknown scope — review each before migration"
fi
# Disable an API key by setting status to inactive
# Usage: Set KEY_ID before running
if [[ -n "${KEY_ID:-}" ]]; then
  [[ "${KEY_ID}" =~ ^apikey_[A-Za-z0-9]+$ ]] || exit 2
  info "Disabling API key ${KEY_ID}..."
  anthropic_post "/v1/organizations/api_keys/${KEY_ID}" \
    '{"status": "inactive"}' || {
    fail "2.1 Failed to disable API key"
    summary; exit 0
  }
  pass "2.1 API key ${KEY_ID} disabled"
fi

Validation & Testing

  1. List active keys and inspect scope.type, workspace binding, principal and expiration; identify organization-scoped keys separately from Default Workspace keys.
  2. Verify no unnamed keys exist
  3. Test that a key scoped to Workspace A cannot be used with Workspace B resources

Expected result: All API keys have descriptive names and are assigned to appropriate workspaces

Compliance Mappings

Framework Control ID Control Description
SOC 2 CC6.1, CC6.3 Logical access security; role-based access
NIST 800-53 AC-3, AC-6 Access enforcement; least privilege
ISO 27001 A.9.4.1 Information access restriction

1.2 Rotate API Keys Regularly

Profile Level: L2 (Walk)

Framework Control
NIST 800-53 IA-5(1)
SOC 2 CC6.1

Description

Make key lifetime bounded by default: set an expiration at key creation and back it with an organization maximum expiration policy, then run a rotation schedule for whatever non-expiring keys remain. Expiration is chosen when the key is created — presets of 3 hours, 1 day, 7 days, or 30 days, a custom duration, or Never — and cannot be changed after creation. Since API keys can only be created via the Console, rotation still means creating a new key, updating dependents, and disabling the old key via the Admin API.

Native expiration changed this control (verified 2026-08-15). Earlier guidance treated rotation as purely procedural because keys never expired on their own. Keys now carry an expires_at timestamp (null for non-expiring keys), reported by the Admin API’s List and Retrieve API Keys endpoints. With an org maximum expiration policy in place, the Console caps the selectable durations and removes Never entirely — making unbounded credentials impossible to mint rather than merely discouraged. Expired keys return 401 authentication_error and cannot be reactivated; Anthropic emails the key’s creator 7 days before expiry (keys with ≥14-day lifetimes) and 1 day before (≥7-day lifetimes). (Key expiration)

Rationale

Why This Matters:

  • Long-lived API keys increase the window of opportunity for attackers; a leaked expiring key dies on its own, a leaked non-expiring key lives until someone notices
  • Keys may be accidentally exposed in logs, error messages, or code repositories
  • Anthropic API keys persist after user removal — orphaned keys remain active
  • expires_at == null is now an auditable finding, not an unavoidable state

Attack Prevented: Stale credential exploitation, leaked key abuse

Prerequisites

  • API key inventory with creation dates
  • Deployment pipeline that supports key rotation (secrets manager integration)

ClickOps Implementation

Step 1: Adopt Expiring Keys

  1. When creating keys at Console → Settings → API keys, choose the shortest expiration the workload tolerates; reserve Never for keys held in a secrets manager with their own rotation automation
  2. If available to your organization, set the maximum expiration policy so the Console stops offering Never and caps custom durations

Step 2: Identify Keys Due for Rotation

  1. Navigate to: Console → Settings → API keys
  2. Review each key’s expiration column; audit expires_at across the org via the Admin API and flag null
  3. Flag any non-expiring key older than 90 days

Step 3: Rotate

  1. Create a new key in the same workspace with the same naming convention — with an expiration
  2. Update the dependent application/service to use the new key
  3. Verify the application works with the new key
  4. Disable the old key (set status to inactive via Admin API)
  5. Once replacement is verified, follow the approved key-lifecycle procedure. For post-exposure rotation use the dedicated How to Rotate guidance; do not leave a known-compromised key active for a generic grace period.

Time to Complete: ~15 minutes per key (excluding application updates)

Code Implementation

Code Pack: API Script
hth-anthropic-claude-2.02-rotate-api-keys-regularly.sh View source on GitHub ↗
# Two-part key-lifetime audit.
#   Part 1 — NON-EXPIRING keys: the Admin API reports each key's expires_at
#   (null = never expires). With native key expiration available, expires_at
#   == null is a finding in itself, not an unavoidable state.
#   Part 2 — stale keys: anything older than the 90-day rotation window.
info "Auditing key expirations and staleness..."
API_KEYS=$(anthropic_list_all "/v1/organizations/api_keys?status=active") || {
  fail "2.2 Failed to list API keys"
  summary; exit 0
}
NEVER_EXPIRING=$(echo "${API_KEYS}" | jq '[.[] | select(.expires_at == null)]')
NEVER_COUNT=$(echo "${NEVER_EXPIRING}" | jq 'length')
if [[ "${NEVER_COUNT}" -gt 0 ]]; then
  warn "2.2 ${NEVER_COUNT} active keys NEVER expire (expires_at=null) — prefer expiring keys; adopt the org maximum expiration policy"
  echo "${NEVER_EXPIRING}" | jq -r '.[] | "  \(.name // "unnamed") | Created: \(.created_at) | Workspace: \(.workspace_id)"'
else
  pass "2.2 Every active key carries an expiration (expires_at set)"
fi
CUTOFF=$(date -d '90 days ago' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || \
         date -v-90d '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null)
STALE_KEYS=$(echo "${API_KEYS}" | jq --arg cutoff "${CUTOFF}" \
  '[.[] | select(.created_at < $cutoff)]')
STALE_COUNT=$(echo "${STALE_KEYS}" | jq 'length')
if [[ "${STALE_COUNT}" -gt 0 ]]; then
  warn "2.2 ${STALE_COUNT} API keys are older than 90 days:"
  echo "${STALE_KEYS}" | jq -r '.[] | "  \(.name // "unnamed") | Created: \(.created_at) | Expires: \(.expires_at // "never") | Workspace: \(.workspace_id)"'
else
  pass "2.2 All API keys are within 90-day rotation window"
fi
# Archive an old API key after rotation
# Usage: Set OLD_KEY_ID before running
if [[ -n "${OLD_KEY_ID:-}" ]]; then
  info "Archiving old API key ${OLD_KEY_ID}..."
  anthropic_post "/v1/organizations/api_keys/${OLD_KEY_ID}" \
    '{"status": "archived"}' || {
    fail "2.2 Failed to archive API key"
    summary; exit 0
  }
  pass "2.2 API key ${OLD_KEY_ID} archived"
fi

Validation & Testing

  1. Audit active-key lifetime/expiration and owner policy; a 90-day review threshold is an HTH example, not a vendor-enforced lifetime.
  2. Verify disabled keys return 401 when used
  3. Confirm application functionality with rotated keys

Expected result: No API key is older than 90 days; old keys are archived

Compliance Mappings

Framework Control ID Control Description
SOC 2 CC6.1 Logical access security over protected information assets
NIST 800-53 IA-5(1) Authenticator management — password-based authentication
ISO 27001 A.9.3.1 Use of secret authentication information

1.3 Eliminate Static API Keys via Workload Identity Federation

Profile Level: L2 (Walk)

Framework Control
NIST 800-53 IA-5(1), IA-9, AC-3
SOC 2 CC6.1
CIS Controls 5.6, 6.5

Description

Anthropic’s Workload Identity Federation (WIF) lets workloads authenticate to the Claude API using short-lived OpenID Connect (OIDC) tokens issued by an identity provider you already operate — AWS IAM, Google Cloud, Microsoft Azure / Entra ID, GitHub Actions, Kubernetes service accounts, SPIFFE/SPIRE, or Okta — instead of long-lived sk-ant-... API keys. Your workload presents a signed JWT to POST /v1/oauth/token (RFC 7523 jwt-bearer grant); Anthropic validates it against the trust rule you configured in the Console and returns a short-lived sk-ant-oat01-... access token bound to a service account in your organization. There are no static secrets to mint, store in CI, rotate, or leak.

WIF complements (rather than replaces) the workspace scoping in 1.1 and the rotation discipline in 1.2: the federation rule pins the upstream identity, and the minted access token still inherits the target workspace’s rate limits, billing, and OAuth scope. Use WIF anywhere a workload runs in a federable environment; keep static API keys only for environments that cannot present an OIDC JWT.

What changed (verified 2026-08-15). Three updates since this control was written: (1) the Console now documents a Connect workload wizard (Settings → Workload identity → Connect workload) that creates the issuer, service account, and federation rule in one guided flow — with per-provider tiles, a Verify issuer JWKS dry-run before anything is created, and a 15-minute listener that confirms a successful end-to-end token exchange. (2) The OAuth scope set has grown beyond workspace:developer: workspace:inference exists as an API-manageable scope, workspace:manage_tunnels is set by the MCP-tunnels flow, and org:admin federation rules exist — a rule with oauth_scope: org:admin must target a service account whose organization_role is admin, and creating any rule beyond workspace:developer/workspace:inference requires the Console: “granting a workload organization-admin access is a deliberate human action, not something automation can bootstrap for itself.” (3) WIF resources are now manageable programmatically via Admin API endpoints under /v1/organizations/ (service_accounts, federation_issuers, federation_rules) — these endpoints reject Admin API keys and require an org:admin OAuth bearer token. (WIF setup · WIF Admin API)

Rationale

Why This Matters:

  • Removes long-lived sk-ant-... API keys from CI runners, container images, and secrets managers — the highest-value Anthropic credential class
  • Tokens expire in minutes, not never: the minted lifetime is the lesser of the rule’s token_lifetime_seconds (API default 3600s) and twice the remaining lifetime of the presented IdP JWT, never below 60s — short-lived upstream JWTs automatically shorten Anthropic tokens; SDKs refresh on a two-tier schedule (advisory at expiry−120s, mandatory at expiry−30s)
  • Federation rule’s subject_prefix, audience, claims, and CEL condition matchers bind the credential to a specific workload identity (e.g., a single GitHub repo + branch, a specific Kubernetes service account, or an EKS IRSA role)
  • Audit trail attributes API calls to the federated workload identity, not just to “the API key”
  • Eliminates the “key was leaked, rotation forgotten” incident class for federated workloads

Attack Prevented: Static API key exfiltration from CI logs, container images, secrets managers, or developer machines; long-lived credential abuse after personnel changes; lateral movement using a stolen long-lived key

Important caveat: WIF inherits the trust of your upstream IdP. A compromised IdP, an over-broad federation rule, or a misconfigured audience value can grant broader access than intended. The documented concrete vector: subject_prefix is an exact match unless it ends in *, and for GitHub Actions a trailing wildcard like repo:my-org/my-repo:* also matches pull_request runs — including runs triggered from forks — so anyone who can open a PR against the repository could mint a token under that rule (catastrophic for an org:admin rule). Pin the subject to a protected branch, e.g. repo:my-org/my-repo:ref:refs/heads/main. Pair WIF with your IdP’s existing controls (workload identity binding, conditional access, audit logging) for defense in depth.

Prerequisites

  • Organization Admin access to the Claude Console (Settings → Workload identity)
  • An OIDC-capable identity provider with a reachable JWKS endpoint (or an inline JWKS document for air-gapped clusters)
  • A workload that can obtain an identity token from that provider (Kubernetes projected service-account token, GitHub Actions OIDC, AWS STS web identity, GCP metadata server, Azure IMDS, etc.)
  • Workspace IDs (wrkspc_...) for any workspaces the federated workload should act in
  • ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN removed from anywhere the workload runs (they sit above federation in the SDK credential precedence chain and silently shadow it)

ClickOps Implementation

Step 0 (preferred path): Use the Connect Workload Wizard

  1. Navigate to: Console → Settings → Workload identity → Connect workload
  2. Pick the provider tile (GitHub Actions, AWS, Google Cloud, Microsoft Entra ID, Kubernetes, or Custom OIDC); the wizard creates the issuer, service account, and federation rule in one flow
  3. Run Verify issuer — a JWKS-reachability dry-run — before anything is created
  4. Note the wizard prefills oauth_scope=workspace:developer and token_lifetime_seconds=600 (the API default when the field is omitted is 3600) — keep the tighter 600s unless the workload needs longer
  5. Let the wizard’s 15-minute listener confirm a successful token exchange end-to-end
  6. Steps 1–3 below remain the manual equivalents for providers or flows the wizard doesn’t cover

Step 1: Register a Federation Issuer

  1. Navigate to: Console → Settings → Workload identity → Issuers tab
  2. Click Create issuer and select the appropriate preset (AWS, Google Cloud, or generic OIDC for GitHub Actions / Kubernetes / Entra ID / Okta)
  3. Set Issuer URL to the exact iss claim your IdP puts in its JWTs. Decode a sample token to verify: jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson | .iss' token
  4. Set JWKS source to discovery for any provider that serves /.well-known/openid-configuration. Use explicit_url for providers without discovery, or inline for air-gapped clusters
  5. URLs must be https, port 443, public DNS (no IP literals) — except for inline and explicit_url modes where the issuer_url is only string-compared

Step 2: Create a Service Account

  1. Go to: Settings → Service accounts → Create service account
  2. Name it after the workload it represents (inference-worker, ci-deploy, eks-prod-namespace-foo)
  3. Note the service account ID (svac_...)
  4. Add the service account to each target workspace via that workspace’s Members page — the federated token inherits the workspace’s rate limits and usage attribution
  5. Every service account is implicitly a member of the organization’s default workspace — a matched rule can mint tokens acting there with no explicit grant. Keep production keys and data out of the default workspace, or account for this when scoping rules; prefer explicit workspace_id on rules over applies_to_all_workspaces: true

Step 3: Create a Federation Rule

  1. Back on Workload identity → Federation rules tab → Create rule
  2. Select the issuer from Step 1 and the service account from Step 2
  3. Configure the Match block as narrowly as possible:
    • Static matchers: subject_prefix (with optional trailing * for prefix match), exact audience, and a map of exact claims values
    • CEL matcher: a CEL condition expression for complex logic (nested claims, list membership, boolean logic)
    • At least one of subject_prefix, claims, or condition is required — a rule that only matches audience is rejected
  4. Set Authorization scope (workspace:developer for standard workloads; org:admin rules are Console-only, must target an organization_role: admin service account, and deserve the strictest possible match block) and Token lifetime (60–86400 seconds; API default 3600, wizard default 600)
  5. Note the rule ID (fdrl_...); the workload passes it on every token-exchange request

Step 4: Migrate the Workload Off the Static Key

  1. Configure WIF in parallel with the existing ANTHROPIC_API_KEY
  2. Inspect ant auth status locally to identify the selected source; it is not a health check and can include credential fragments, so keep its output private. Unset key/auth-token/profile overrides before an approved exchange to test actual WIF.
  3. Unset ANTHROPIC_API_KEY everywhere it is injected (CI secrets, container env, shell profiles). Re-confirm ant auth status reports the federation source as winning
  4. Revoke the old API key in Settings → API keys

Time to Complete: ~30–60 minutes for first issuer + rule; ~10 minutes per additional rule

Code Implementation

Code Pack: Config
hth-anthropic-claude-2.03-wif-profile.jsonc View source on GitHub ↗
{
  "version": "1.0",
  "authentication": {
    "type": "oidc_federation",
    "federation_rule_id": "fdrl_REPLACE_ME",
    "service_account_id": "svac_REPLACE_ME",
    "identity_token": {
      "source": "file",
      "path": "/var/run/secrets/anthropic.com/token"
    }
  },
  "organization_id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "wrkspc_REPLACE_ME",
  "base_url": "https://api.anthropic.com"
}
Code Pack: API Script
hth-anthropic-claude-2.03-wif-token-exchange.sh View source on GitHub ↗
for name in ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN ANTHROPIC_PROFILE; do
  if [[ "${!name+x}" == x ]]; then
    echo "Unset ${name} entirely before WIF (empty values still select a source)." >&2
    exit 2
  fi
done
[[ "${ANTHROPIC_API_BASE:-https://api.anthropic.com}" == https://api.anthropic.com ]] || exit 2
: "${ANTHROPIC_FEDERATION_RULE_ID:?Use a reviewed federation rule}"
: "${ANTHROPIC_ORGANIZATION_ID:?Use its organization UUID}"
: "${ANTHROPIC_IDENTITY_TOKEN_FILE:?Use the projected OIDC token file}"
[[ "${ANTHROPIC_FEDERATION_RULE_ID}" =~ ^fdrl_[A-Za-z0-9]+$ ]] || exit 2
[[ "${ANTHROPIC_ORGANIZATION_ID}" =~ ^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$ ]] || exit 2
[[ -r "${ANTHROPIC_IDENTITY_TOKEN_FILE}" ]] || exit 2
[[ -z "${ANTHROPIC_WORKSPACE_ID:-}" || "${ANTHROPIC_WORKSPACE_ID}" =~ ^wrkspc_[A-Za-z0-9]+$ ]] || exit 2
[[ -z "${ANTHROPIC_SERVICE_ACCOUNT_ID:-}" || "${ANTHROPIC_SERVICE_ACCOUNT_ID}" =~ ^svac_[A-Za-z0-9]+$ ]] || exit 2
if [[ "${HTH_EXCHANGE:-0}" != 1 ]]; then
  echo 'Preflight complete; no token minted. Review the rule and set HTH_EXCHANGE=1 for an explicit exchange.'
  exit 0
fi
# JSON escaping via jq; neither JWT nor access token enters argv.
body=$(jq -cn --rawfile jwt "${ANTHROPIC_IDENTITY_TOKEN_FILE}" \
  --arg rule "${ANTHROPIC_FEDERATION_RULE_ID}" --arg org "${ANTHROPIC_ORGANIZATION_ID}" \
  --arg account "${ANTHROPIC_SERVICE_ACCOUNT_ID:-}" --arg workspace "${ANTHROPIC_WORKSPACE_ID:-}" '
  ($jwt | sub("[\\r\\n]+$";"")) as $token |
  if ($token | test("^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$")) then
    {grant_type:"urn:ietf:params:oauth:grant-type:jwt-bearer",assertion:$token,federation_rule_id:$rule,organization_id:$org}
    + (if $account == "" then {} else {service_account_id:$account} end)
    + (if $workspace == "" then {} else {workspace_id:$workspace} end)
  else error("Expected a JWT") end')
response=$(printf '%s' "${body}" | curl -q --silent --show-error --fail --proto '=https' --connect-timeout 10 --max-time 30 \
  --header 'Content-Type: application/json' --data-binary @- https://api.anthropic.com/v1/oauth/token) || {
    echo 'Exchange failed; response content suppressed.' >&2; exit 1;
  }
printf '%s' "${response}" | jq -cse 'if length == 1 and (.[0].access_token | type) == "string"
  and (.[0].access_token | startswith("sk-ant-oat01-")) and (.[0].expires_in | type) == "number"
  and .[0].expires_in > 0 and (.[0].scope | type) == "string"
  then {exchange_verified:true,scope:.[0].scope,expires_in:.[0].expires_in}
  else error("Unexpected exchange response") end'
unset body response
Code Pack: SDK Script
hth-anthropic-claude-2.03-wif-python.py View source on GitHub ↗
import os
for name in ("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_PROFILE"):
    if name in os.environ:
        raise SystemExit(f"Unset {name} entirely before WIF; an empty value is not an unset value")
from anthropic import Anthropic, WorkloadIdentityCredentials, IdentityTokenFile
# Explicit construction — recommended when you ship one image to multiple
# environments and want the federation parameters in code rather than env.
client = Anthropic(
    credentials=WorkloadIdentityCredentials(
        identity_token_provider=IdentityTokenFile(
            "/var/run/secrets/anthropic.com/token"
        ),
        federation_rule_id="fdrl_REPLACE_ME",
        organization_id="00000000-0000-0000-0000-000000000000",
        service_account_id="svac_REPLACE_ME",
        workspace_id="wrkspc_REPLACE_ME",
    ),
)
# Construction only: no token exchange or billable inference is executed by this sample.
# Zero-argument construction — recommended for production. Ship the same
# container image everywhere; inject these env vars per environment:
#   ANTHROPIC_FEDERATION_RULE_ID
#   ANTHROPIC_ORGANIZATION_ID
#   ANTHROPIC_SERVICE_ACCOUNT_ID
#   ANTHROPIC_WORKSPACE_ID         (required when the rule covers >1 workspace)
#   ANTHROPIC_IDENTITY_TOKEN_FILE  (or ANTHROPIC_IDENTITY_TOKEN literal)
#
# IMPORTANT: ANTHROPIC_API_KEY sits ABOVE federation in the SDK's credential
# precedence chain. A leftover key silently shadows federation. The next block
# is a defensive startup check.
import os, sys
for var in ("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_PROFILE"):
    if var in os.environ:
        sys.exit(
            f"FATAL: {var} is set; it will shadow Workload Identity Federation. "
            "Unset it (empty-string also wins precedence)."
        )
from anthropic import Anthropic
client = Anthropic()  # SDK reads the ANTHROPIC_FEDERATION_* vars from env

Validation & Testing

  1. Run the pack’s default preflight, which mints no token. Only an explicitly approved exchange (HTH_EXCHANGE=1) checks returned scope/lifetime; it prints no token and performs no inference.
  2. From inside the workload, run ant auth status — federation should be the winning credential source (not API key)
  3. Trigger a 403 test: temporarily change the federation rule’s match block to a non-matching value and confirm the exchange returns 400 invalid_grant
  4. Confirm the guardrail runs before token exchange and rejects present ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN and explicit profile variables, including empty strings.
  5. For the GitHub Actions workflow: confirm the job succeeds with no ANTHROPIC_API_KEY repo secret configured
  6. Audit the authentication history page in the Console after a successful exchange — verify the issuer, rule, and matched claims are what you expect

Expected result: All federable workloads run with no sk-ant-... static keys in their environment; every Claude API call is attributable to a federation rule + service account in the audit trail

Monitoring & Maintenance

Ongoing monitoring:

  • Watch the Console’s Workload identity → Authentication history for failed exchanges (400 invalid_grant) — sustained failures indicate IdP key rotation, claim drift, or an attempted misuse
  • Inventory federation rules quarterly: archive any rule whose service account or issuer is no longer in active use
  • Monitor for new sk-ant-... API keys created in workspaces that were supposed to be 100% federated — drift indicates an engineer fell back to a static key

Maintenance schedule:

  • Monthly: Review the active federation rules list against current production workloads
  • Quarterly: Review the match blocks of every rule for over-broad scope (especially CEL condition expressions and bare subject_prefix patterns ending in *)
  • Annually: Tabletop exercise an IdP-compromise scenario — confirm you can disable a federation issuer in the Console and that all dependent workloads fail closed

Operational Impact

Aspect Impact Level Details
User Experience None Workloads run unchanged; SDKs handle the exchange/refresh loop
System Performance Low One extra HTTPS round-trip per token refresh (every ~hour by default)
Maintenance Burden Low Eliminates manual key rotation; only changes when IdP trust changes
Rollback Difficulty Easy Re-issue a static API key and revert the workload’s env vars

Potential Issues:

  • ANTHROPIC_API_KEY shadow: A leftover key in the env wins precedence over WIF and silently keeps the workload on a static credential. Check with ant auth status.
  • Empty-string variables: ANTHROPIC_API_KEY="" is treated as “API key path with an empty key” — unset the variable entirely, do not blank it.
  • JWKS rotation lag: In discovery and explicit_url modes, Anthropic caches the JWKS for up to 60 seconds. If your IdP rotates and signs immediately, exchanges may briefly fail. Publish new keys 15+ minutes before first use; in inline mode you must update the issuer config manually.
  • Multi-workspace rules: When a federation rule covers more than one workspace, workspace_id is required on every exchange (400 invalid_request: workspace_id_required).

Rollback Procedure:

  1. In the Console, create a new standard API key in the target workspace
  2. Inject it as ANTHROPIC_API_KEY in the workload’s environment
  3. Restart the workload — it will pick up the static key (which sits above WIF in precedence) without any code change
  4. Optionally archive the federation rule

Compliance Mappings

Framework Control ID Control Description
SOC 2 CC6.1 Logical access security over protected information assets
NIST 800-53 IA-5(1) Authenticator management — short-lived authenticators replacing static credentials
NIST 800-53 IA-9 Service identification and authentication
NIST 800-53 AC-3 Access enforcement
ISO 27001 A.9.4.3 Password management system (eliminating long-lived shared credentials)
CIS Controls 5.6 Centralized account management
CIS Controls 6.5 Require MFA for administrative access (via the upstream IdP)
NIST AI RMF GOVERN-1.4 Authority and accountability for AI system credentials

2. Workspace Security

2.1 Segment Workspaces by Environment

Profile Level: L1 (Crawl)

Framework Control
NIST 800-53 AC-4, SC-7
SOC 2 CC6.6

Description

Create separate workspaces for development, staging, and production environments. Each workspace provides an isolated boundary for API keys, rate limits, spend limits, and data residency settings. Anthropic allows up to 100 workspaces per organization (archived workspaces do not count).

Rationale

Why This Matters:

  • Workspace segmentation limits blast radius of API key compromise
  • Enables different rate limits and spend caps per environment
  • Data residency can be set per workspace (immutable after creation)
  • Simplifies cost attribution and usage monitoring

Attack Prevented: Cross-environment contamination, production data exposure via development keys

Prerequisites

  • Organization Admin access
  • Environment naming convention (e.g., engineering-prod, engineering-dev, analytics-prod)

ClickOps Implementation

Step 1: Plan Workspace Structure

  1. Define workspaces for each team and environment combination
  2. Determine data residency requirements per workspace (workspace_geo is immutable after creation)

Step 2: Create Workspaces

  1. Navigate to: platform.claude.com → Settings → Workspaces
  2. Click Create Workspace
  3. Enter workspace name following naming convention
  4. Configure data residency settings if required
  5. Repeat for each planned workspace

Step 3: Archive Unused Workspaces

  1. Identify workspaces with no recent activity
  2. Archive via Console (caution: this deactivates ALL API keys in the workspace and is irreversible)

Two special workspaces need their own handling (verified 2026-08-15). The Default Workspace cannot be archived, cannot carry spend or rate limits, and its API keys, usage, and cost rows report null for workspace_id — so the hardening move is keeping zero production keys in it and migrating all traffic to explicit workspaces where limits apply. The auto-created Claude Code workspace (created when any member first signs in to Claude Code with a Console account) mints per-user API keys you cannot create manually, is the only workspace supporting per-user monthly spend limits — and archiving it disables Claude Code sign-in through Console billing for the whole organization, a far larger blast radius than archiving a normal workspace. (Workspaces)

Isolation nuance for multi-cloud consumers: prompt caches are isolated per workspace on the Claude API, Claude Platform on AWS, and Microsoft Foundry — but only per organization on Amazon Bedrock and Google Cloud. If workspace segmentation is your cache-isolation boundary, it does not hold on those two platforms.

Time to Complete: ~5 minutes per workspace

Code Implementation

Code Pack: Terraform
hth-anthropic-claude-3.01-workspace-segmentation.tf View source on GitHub ↗
terraform {
  required_version = ">= 1.7.0"
  required_providers {
    anthropic = {
      source  = "jianyuan/anthropic"
      version = "0.3.0"
    }
  }
}
provider "anthropic" {
  base_url = "https://api.anthropic.com"
}
variable "workspace_names" {
  type    = set(string)
  default = [] # No resources until you choose the environment/team names.
  validation {
    condition     = alltrue([for n in var.workspace_names : can(regex("^[a-z0-9][a-z0-9-]{2,63}$", n))])
    error_message = "Use reviewed team-environment names."
  }
}
resource "anthropic_workspace" "environment" {
  for_each = var.workspace_names
  name     = each.value
  data_residency = {
    workspace_geo         = "us" # Immutable; choose before creation.
    default_inference_geo = "us"
    allowed_inference_geos = {
      values = ["us"]
    }
  }
  lifecycle {
    prevent_destroy = true # Workspace archive is irreversible; no destroy as rollback.
  }
}
data "anthropic_workspaces" "inventory" {}
output "workspace_inventory" {
  sensitive = true
  value     = data.anthropic_workspaces.inventory.workspaces
}
Code Pack: API Script
hth-anthropic-claude-3.01-segment-workspaces.sh View source on GitHub ↗
# List all workspaces and their configuration
info "Listing all workspaces..."
WORKSPACES=$(anthropic_list_all "/v1/organizations/workspaces?include_default=true") || {
  fail "3.1 Failed to list workspaces"
  summary; exit 0
}
WS_COUNT=$(echo "${WORKSPACES}" | jq 'length')
info "Found ${WS_COUNT} workspaces (limit: 100 per organization)"
echo "${WORKSPACES}" | jq -r '.[] |
  "  \(.name) | ID: \(.id) | Geo: \(.data_residency.workspace_geo // "default") | Archived: \(.archived_at // "no")"'
pass "3.1 Workspace inventory complete"
# Create a new workspace for environment segmentation
# Usage: Set WS_NAME and optionally WS_GEO before running
if [[ -n "${WS_NAME:-}" ]]; then
  [[ "${WS_GEO:-us}" == us ]] || { echo "Only the verified us storage geo is supported by this recipe." >&2; exit 2; }
  BODY=$(jq -cn --arg name "${WS_NAME}" '{name:$name}')
  if [[ -n "${WS_GEO:-}" ]]; then
    BODY=$(echo "${BODY}" | jq --arg geo "${WS_GEO}" '. + {data_residency: {workspace_geo: $geo}}')
  fi
  info "Creating workspace '${WS_NAME}'..."
  RESULT=$(anthropic_post "/v1/organizations/workspaces" "${BODY}") || {
    fail "3.1 Failed to create workspace"
    summary; exit 0
  }
  NEW_ID=$(echo "${RESULT}" | jq -r '.id')
  pass "3.1 Workspace created: ${NEW_ID}"
fi
Code Pack: CLI Script
hth-anthropic-claude-3.01-workspace-inventory.sh View source on GitHub ↗
# ant 2.7.0+; Includes Default Workspace explicitly; the API otherwise omits it.
anthropic_cli_inventory organization:workspaces workspace --include-default
Code Pack: SDK Script
hth-anthropic-claude-3.01-workspace-inventory.js View source on GitHub ↗
export async function workspaceInventory(key,transport){
  const client=scopedClient(key,transport),rows=[],ids=new Set(),seen=new Set();let after_id;
  while(true){
    const response=await client.organization.workspaces.list({include_default:true,limit:1000,...(after_id?{after_id}:{})}).asResponse();
    const data=await response.json();
    if(!Array.isArray(data.data)||typeof data.has_more!=='boolean')throw Error('Incomplete native page');
    for(const row of data.data){
      if(row.type!=='workspace'||typeof row.id!=='string'||ids.has(row.id)||!row.data_residency)throw Error('Invalid/repeated workspace');
      ids.add(row.id);rows.push(row);
    }
    if(!data.has_more)break;after_id=data.last_id;if(typeof after_id!=='string'||!after_id||seen.has(after_id)||seen.size>=10000)throw Error('Invalid native cursor');seen.add(after_id);
  }
  return rows; // Includes Default Workspace; does not prove segmentation/residency policy.
}
if(process.argv[1]&&import.meta.url===pathToFileURL(process.argv[1]).href){
  try{privateEvidence('workspaces',await workspaceInventory(process.env.ANTHROPIC_ADMIN_KEY));}
  catch{console.error('Incomplete workspace inventory; no success file.');process.exitCode=2;}
}

Validation & Testing

  1. List all workspaces via Admin API — verify naming convention adherence
  2. Verify production workspaces have data residency configured
  3. Confirm workspace count is within the 100-workspace limit

Expected result: Separate workspaces exist for each team/environment; naming convention followed

Compliance Mappings

Framework Control ID Control Description
SOC 2 CC6.6 System boundaries and security measures
NIST 800-53 AC-4, SC-7 Information flow enforcement; boundary protection
ISO 27001 A.13.1.3 Segregation in networks

2.2 Manage Workspace Membership

Profile Level: L1 (Crawl)

Framework Control
NIST 800-53 AC-2, AC-6
SOC 2 CC6.2, CC6.3

Description

Assign users to only the workspaces they need. Five workspace roles provide granular access control: workspace_user, Workspace Limited Developer (create/manage API keys and use the API, but no session-tracing views or file downloads — prefer it over Workspace Developer wherever tracing and file access aren’t needed), workspace_developer, workspace_admin, and workspace_billing. Organization admins automatically inherit workspace_admin in every workspace, and the Workspace Billing role cannot be manually assigned — it is inherited from the organization billing role, so workspace-level access reviews for org admins and billing members happen at the organization-role level.

Rationale

Why This Matters:

  • Users should only access workspaces relevant to their team and function
  • Workspace-level roles limit what actions a user can take within that workspace
  • Regular membership audits catch stale access from role changes or departures

Attack Prevented: Unauthorized workspace access, privilege creep, insider threat

Prerequisites

  • Workspace Admin or Organization Admin access
  • Team-to-workspace mapping documented

ClickOps Implementation

Step 1: Review Current Membership

  1. Navigate to: platform.claude.com → Select workspace → Members
  2. Review each member’s workspace role
  3. Document any users who don’t belong in this workspace

Step 2: Adjust Membership

  1. Remove users who no longer need access
  2. Downgrade workspace roles where appropriate (e.g., workspace_admin → workspace_developer)
  3. Add users to workspaces they need access to

Time to Complete: ~10 minutes per workspace

Code Implementation

Code Pack: API Script
hth-anthropic-claude-3.02-manage-workspace-membership.sh View source on GitHub ↗
# Audit membership for a specific workspace
# Usage: Set WORKSPACE_ID before running
WORKSPACE_ID="${WORKSPACE_ID:-}"
if [[ -z "${WORKSPACE_ID}" ]]; then
  info "Listing all workspaces to select for audit..."
  WORKSPACES=$(anthropic_list_all "/v1/organizations/workspaces") || {
    fail "3.2 Failed to list workspaces"
    summary; exit 0
  }
  echo "${WORKSPACES}" | jq -r '.[] | "  \(.id)\t\(.display_name)"' | column -t -s $'\t'
  info "Set WORKSPACE_ID=<id> and re-run to audit a specific workspace"
  summary; exit 0
fi
info "Auditing members of workspace ${WORKSPACE_ID}..."
MEMBERS=$(anthropic_list_all "/v1/organizations/workspaces/${WORKSPACE_ID}/members") || {
  fail "3.2 Failed to list workspace members"
  summary; exit 0
}
MEMBER_COUNT=$(echo "${MEMBERS}" | jq 'length')
info "Workspace has ${MEMBER_COUNT} members"
# List members with roles
echo "${MEMBERS}" | jq -r '.[] | "  \(.user_id)\t\(.workspace_role)"' | \
  column -t -s $'\t' -N "USER_ID,ROLE"
# Flag workspace admins
ADMIN_COUNT=$(echo "${MEMBERS}" | jq '[.[] | select(.workspace_role == "workspace_admin")] | length')
if [[ "${ADMIN_COUNT}" -gt 2 ]]; then
  warn "3.2 ${ADMIN_COUNT} workspace admins found — review for least privilege"
else
  pass "3.2 Workspace admin count (${ADMIN_COUNT}) is appropriate"
fi
# Remove a user from a workspace
# Usage: Set WORKSPACE_ID and REMOVE_USER_ID before running
if [[ -n "${REMOVE_USER_ID:-}" ]]; then
  info "Removing user ${REMOVE_USER_ID} from workspace ${WORKSPACE_ID}..."
  anthropic_delete "/v1/organizations/workspaces/${WORKSPACE_ID}/members/${REMOVE_USER_ID}" || {
    fail "3.2 Failed to remove workspace member"
    summary; exit 0
  }
  pass "3.2 User ${REMOVE_USER_ID} removed from workspace"
fi

Validation & Testing

  1. List workspace members via Admin API for each workspace
  2. Verify no workspace has more than 2 workspace_admin members (excluding inherited org admins)
  3. Confirm removed users can no longer sign in to workspace resources — but do not treat removal as key revocation: standard workspace API keys are scoped to the organization/workspace, not the person, and keys created by a removed user keep working. Pair every membership removal with a key rotation pass (see 1.2). The one exception is the Claude Code workspace, whose per-user keys stop working when their owner is removed.

Expected result: Each workspace has only authorized members at appropriate role levels, and offboarding always pairs membership removal with key rotation

Compliance Mappings

Framework Control ID Control Description
SOC 2 CC6.2, CC6.3 Access provisioning; role-based access
NIST 800-53 AC-2, AC-6 Account management; least privilege
ISO 27001 A.9.2.5 Review of user access rights

2.3 Adapt Controls for Claude Platform on AWS

Profile Level: L2 (Walk)

Framework Control
NIST 800-53 AC-3, AC-6, AU-2, SC-12
CIS Controls 4.6, 8.5

Description

If your organization consumes Claude through Claude Platform on AWS (base URL aws-external-anthropic.{region}.api.aws, SigV4 service name aws-external-anthropic, required anthropic-workspace-id header on inference/resource calls (not Admin API requests)), this guide must be mapped to the AWS variant’s IAM and region boundary. The workspace endpoints and newer Usage and Cost Analytics API are available; legacy usage/cost, organization/workspace membership and key-management endpoint parity must not be assumed.

Rationale

Why This Matters:

  • The §1 key-inventory/rotation and §2.2 member-listing packs target the direct Claude API and must not be sent unchanged to the AWS variant. Newer Usage and Cost Analytics has AWS support; configure its regional authentication and routing from the AWS-specific documentation rather than reusing this guide’s fixed-host transport
  • Access control moves to AWS IAM: policies target workspace ARNs (arn:aws:aws-external-anthropic:{region}:{account-id}:workspace/{workspace-id}) with actions in the aws-external-anthropic namespace, and AWS ships five managed policies (AnthropicFullAccess, AnthropicReadOnlyAccess, AnthropicInferenceAccess, AnthropicLimitedAccess, AnthropicSelfHostedEnvironmentAccess)
  • Two hardening levers exist here that the first-party API has no equivalent for: denying the aws-external-anthropic:CallWithBearerToken IAM action forces SigV4-only authentication (no bearer tokens at all), and where bearer tokens are unavoidable, short-term API keys minted from AWS credentials are capped at 12 hours
  • The AWS region binding does not pin inference geography — Anthropic (not AWS) is the inference data processor, and data may route to Anthropic’s primary cloud; pinning requires inference_geo per request or a workspace default_inference_geo
  • Monitoring shifts to CloudTrail, where only workspace, compliance, vault, and webhook operations are Management events; inference, batch, file, skill, and model operations are Data events requiring explicit (paid) data-event logging — default CloudTrail config silently misses the traffic that matters most

Attack Prevented: Silent audit-coverage gaps on the AWS variant, unbounded bearer-token authentication, unlogged inference activity, mistaken data-residency assumptions

Prerequisites

  • Confirmation of which platform variant each business unit actually uses
  • AWS account admin for IAM policy and CloudTrail configuration
  • One-time account prerequisite: aws iam enable-outbound-web-identity-federation (disabled by default; its absence is the most common setup failure)

ClickOps Implementation

Step 1: Map This Guide’s Controls to the AWS Variant

  1. For each Admin-API-driven control in this guide, record its AWS substitute: membership → IAM policies on workspace ARNs; key inventory → short-term keys + CallWithBearerToken governance; usage/cost → newer Analytics API per region plus Console/CloudTrail; legacy endpoints are not the substitute

Step 2: Force SigV4 Where Possible

  1. Deny aws-external-anthropic:CallWithBearerToken for all principals that do not strictly need bearer tokens
  2. Where bearer tokens are unavoidable (gateways, serverless), mint short-term keys via AWS’s token-generator libraries — lifetime defaults to 12 hours, capped at the lesser of the requested duration, the AWS credentials’ expiry, and 12 hours

Step 3: Enable Data-Event Logging

  1. In CloudTrail, add data-event logging for the aws-external-anthropic service so inference, batch, file, skill, and model operations are captured
  2. Index on x-amzn-requestid for CloudTrail correlation

Step 4: Pin Inference Geography Deliberately

  1. Set default_inference_geo on residency-sensitive workspaces (supported geos: us — with a 1.1x pricing multiplier — and global), or pass inference_geo per request
  2. Note ZDR is opt-in and Anthropic’s HIPAA-ready program is not available on this variant

Time to Complete: ~2–4 hours initial mapping and IAM work

Validation & Testing

  1. Attempt a bearer-token call from a principal denied CallWithBearerToken — must fail; SigV4 from the same principal must succeed
  2. Run an inference call and confirm it appears in CloudTrail as a Data event
  3. Confirm every workspace serving residency-sensitive workloads carries default_inference_geo

Expected result: Every control in this guide has an explicit AWS-variant disposition; no bearer tokens outside approved principals; inference activity visible in CloudTrail. (Claude Platform on AWS)

Compliance Mappings

Framework Control ID Control Description
SOC 2 CC6.1, CC7.2 Access security; monitoring
NIST 800-53 AC-6 Least privilege
NIST 800-53 AU-2 Event logging
ISO 27001 A.8.15 Logging

3. Data Security & Privacy

3.1 Enforce Data Residency Restrictions

Profile Level: L2 (Walk)

Framework Control
NIST 800-53 SC-7, SA-9(5)
SOC 2 CC6.6, P6.1

Description

Configure data residency at the workspace level to control where Claude processes inference requests. The workspace_geo setting (immutable after creation) controls data storage location. default_inference_geo and allowed_inference_geos control where requests are processed. Available regions include us and global.

Rationale

Why This Matters:

  • Regulatory requirements (GDPR, data sovereignty laws) may mandate processing within specific regions
  • workspace_geo cannot be changed after workspace creation — plan carefully
  • The inference_geo parameter can also be set per-request by API callers, but allowed_inference_geos restricts what values are permitted
  • Model floor (verified 2026-08-15): the per-request inference_geo parameter is supported on Claude 4.6 and later models — requests carrying it on Claude Opus 4.5, Sonnet 4.5, or Haiku 4.5 return a 400 error, and models released before February 2026 report not_available in usage reports for this dimension. Residency-restricted workspaces must therefore also pin allowed model versions

Attack Prevented: Data sovereignty violations, regulatory non-compliance

Prerequisites

  • Organization Admin access
  • Data residency requirements documented per team/workspace
  • Legal/compliance approval for geo settings

ClickOps Implementation

Step 1: Audit Current Settings

  1. Navigate to: platform.claude.com → Settings → Workspaces
  2. Review each workspace’s data residency configuration
  3. Note any workspaces without explicit geo settings

Step 2: Configure New Workspaces with Correct Geo

  1. When creating new workspaces, select the appropriate workspace_geo
  2. This setting is immutable — double-check before confirming

Step 3: Restrict Inference Geos

  1. For regulated workspaces, set allowed_inference_geos to ["us"] only
  2. Set default_inference_geo to "us" to ensure all requests default to US processing

Time to Complete: ~5 minutes per workspace

Code Implementation

Code Pack: API Script
hth-anthropic-claude-4.01-enforce-data-residency.sh View source on GitHub ↗
evidence=$(anthropic_evidence_dir)
trap 'rm -f "${evidence}/workspaces.pending"' EXIT
anthropic_list_all "/v1/organizations/workspaces?include_default=true" > "${evidence}/workspaces.pending"
jq -e 'all(.[]; .type=="workspace" and (.id|type)=="string" and (.data_residency|type)=="object")' "${evidence}/workspaces.pending" >/dev/null
mv "${evidence}/workspaces.pending" "${evidence}/workspaces.json"
printf 'Private workspace residency inventory: %s. Storage geo alone does not prove constrained inference.\n' "${evidence}/workspaces.json"
# Example optional US-only update. workspace_geo is immutable and is NOT changed.
# Review the target/region and set BOTH WORKSPACE_ID and HTH_APPLY=1 deliberately.
if [[ -n "${WORKSPACE_ID:-}" ]]; then
  [[ "${WORKSPACE_ID}" =~ ^wrkspc_[A-Za-z0-9]+$ ]] || exit 2
  [[ "${HTH_APPLY:-0}" == 1 ]] || { echo 'Update refused without HTH_APPLY=1.' >&2; exit 2; }
  jq -e --arg id "${WORKSPACE_ID}" 'any(.[]; .id==$id)' "${evidence}/workspaces.json" >/dev/null || exit 2
  anthropic_post "/v1/organizations/workspaces/${WORKSPACE_ID}" '{"data_residency":{"default_inference_geo":"us","allowed_inference_geos":["us"]}}' > "${evidence}/update-response.json"
  anthropic_get "/v1/organizations/workspaces/${WORKSPACE_ID}" > "${evidence}/updated-workspace.json"
  jq -e --arg id "${WORKSPACE_ID}" '.id==$id and .data_residency.default_inference_geo=="us" and .data_residency.allowed_inference_geos==["us"]' "${evidence}/updated-workspace.json" >/dev/null || { echo 'Update readback differs; review private evidence.' >&2; exit 1; }
  echo 'Selected workspace readback restricts inference to us; storage/model/provider eligibility still requires review.'
fi

Validation & Testing

  1. List all workspaces via Admin API — verify workspace_geo and allowed_inference_geos
  2. Attempt a request with inference_geo: "global" against a US-restricted workspace — should fail
  3. Verify new workspaces are created with correct geo from the start

Expected result: Regulated workspaces have explicit data residency configuration; inference geo restrictions enforced

Compliance Mappings

Framework Control ID Control Description
SOC 2 CC6.6, P6.1 System boundaries; privacy — consent and choice
NIST 800-53 SC-7, SA-9(5) Boundary protection; processing, storage, and service location
ISO 27001 A.18.1.4 Privacy and protection of personally identifiable information

3.2 Configure Data Retention Policies

Profile Level: L2 (Walk)

Description

Choose API data handling by feature, model, processor and organization/workspace arrangement. The current API documentation no longer supports a blanket “all inputs and outputs retained for 30 days” or “ZDR deletes all feature state” assertion.

Rationale

Why This Matters:

  • Stateful Files, batches, caching, Managed Agents, Console and third-party tools have different storage needs.
  • Covered Models can require 30-day retention even when other conversation traffic is not retained by default.

Attack Prevented: Sensitive data retained or routed under an arrangement that does not cover the selected feature.

ClickOps Implementation

  1. At Console → Settings, review the organization’s retention/HIPAA arrangement and workspace configuration with the account team. Use the current API feature eligibility table and model-specific requirements before admitting protected data.
  2. Changed guidance: current API docs say conversation content is not retained by default except Covered Models; retain the explicit model/feature exceptions, abuse/legal retention and product-specific TTLs. Do not equate a Messages API route with universal ZDR coverage.
  3. For ZDR, obtain organization-specific enablement and verify supported features; it does not cover Console playground, Managed Agents, consumer products, most Team/Enterprise interfaces or third-party integrations. Commercial Code/API ZDR and Enterprise Code ZDR are separate offerings.
  4. For PHI, use only the signed, enabled HIPAA arrangement’s eligible features and processor; the ordinary API BAA does not blanket-cover Code, Console use, beta features, Bedrock/Google-operated services or Claude Platform on AWS. Self-service agreement acceptance is a separate authorized legal step.

Automation: Unverified — no write handle verified for this setting in the inspected public Admin/Compliance references. Configure it on the documented surface; the effective-settings read pack in Enterprise 2.4 covers only rows actually returned, not device policy or manual attestations.

Validation & Testing

  1. Review api_zero_data_retention_enabled where returned and actual workspace/model/feature eligibility; an org flag does not prove workspace or downstream-tool retention.
  2. Verify the selected processor for Google/Foundry/AWS variants and document exceptions and lifecycle handling for persisted resources.

Expected result: The documented boundary is exercised and any unsupported surface or exception is recorded.

Operational Impact

Some features/models are unavailable under an arrangement. Removing a feature or deleting data is not used as a documentation test; provider and legal holds remain distinct.

Compliance Mappings

Framework Control ID Control Description
CIS Controls 4.1, 6.8 Secure configuration and role-based access
NIST 800-53 AC-6, CM-6, AU-6 Least privilege, configuration and evidence
SOC 2 CC6.1, CC7.2 Access and monitoring

Sources: platform.claude.com (checked 2026-11-05).


3.3 Audit Workspace Encryption Key Boundaries

Profile Level: L3 (Run)

Description

For eligible API organizations, configure and audit workspace CMEK bindings only after reviewing irreversible attachment, retention and availability requirements. Enterprise chat CMEK and API workspace encryption are different scopes.

Rationale

Why This Matters:

  • The organization-level effective setting does not report an individual workspace’s key binding.
  • A key applies prospectively and cannot be detached/replaced; a KMS outage can make protected data unavailable.

Attack Prevented: Misattributed encryption coverage and unauthorized access to persisted workspace data.

ClickOps Implementation

  1. An organization Admin follows the current CMEK → Claude Platform setup and External Keys API reference after account-team enablement. Choose the key service and scope access to the documented encryption compartment.
  2. Review each workspace’s external_key_id and compartment_id, including which features remain under Anthropic’s keys. The key configuration must be approved before binding; no attachment or deletion is executed by this guide.
  3. Account for the requirement that keyed Platform workspaces retain data and reconcile it with ZDR; rotating key material in KMS does not replace the bound configuration. Preserve availability/recovery and audit key use.

Code Implementation

Workspace inventories include native key metadata; the new Terraform recipe deliberately creates no key or binding:

Code Pack: Terraform
hth-anthropic-claude-3.01-workspace-segmentation.tf View source on GitHub ↗
terraform {
  required_version = ">= 1.7.0"
  required_providers {
    anthropic = {
      source  = "jianyuan/anthropic"
      version = "0.3.0"
    }
  }
}
provider "anthropic" {
  base_url = "https://api.anthropic.com"
}
variable "workspace_names" {
  type    = set(string)
  default = [] # No resources until you choose the environment/team names.
  validation {
    condition     = alltrue([for n in var.workspace_names : can(regex("^[a-z0-9][a-z0-9-]{2,63}$", n))])
    error_message = "Use reviewed team-environment names."
  }
}
resource "anthropic_workspace" "environment" {
  for_each = var.workspace_names
  name     = each.value
  data_residency = {
    workspace_geo         = "us" # Immutable; choose before creation.
    default_inference_geo = "us"
    allowed_inference_geos = {
      values = ["us"]
    }
  }
  lifecycle {
    prevent_destroy = true # Workspace archive is irreversible; no destroy as rollback.
  }
}
data "anthropic_workspaces" "inventory" {}
output "workspace_inventory" {
  sensitive = true
  value     = data.anthropic_workspaces.inventory.workspaces
}
Code Pack: API Script
hth-anthropic-claude-3.01-segment-workspaces.sh View source on GitHub ↗
# List all workspaces and their configuration
info "Listing all workspaces..."
WORKSPACES=$(anthropic_list_all "/v1/organizations/workspaces?include_default=true") || {
  fail "3.1 Failed to list workspaces"
  summary; exit 0
}
WS_COUNT=$(echo "${WORKSPACES}" | jq 'length')
info "Found ${WS_COUNT} workspaces (limit: 100 per organization)"
echo "${WORKSPACES}" | jq -r '.[] |
  "  \(.name) | ID: \(.id) | Geo: \(.data_residency.workspace_geo // "default") | Archived: \(.archived_at // "no")"'
pass "3.1 Workspace inventory complete"
# Create a new workspace for environment segmentation
# Usage: Set WS_NAME and optionally WS_GEO before running
if [[ -n "${WS_NAME:-}" ]]; then
  [[ "${WS_GEO:-us}" == us ]] || { echo "Only the verified us storage geo is supported by this recipe." >&2; exit 2; }
  BODY=$(jq -cn --arg name "${WS_NAME}" '{name:$name}')
  if [[ -n "${WS_GEO:-}" ]]; then
    BODY=$(echo "${BODY}" | jq --arg geo "${WS_GEO}" '. + {data_residency: {workspace_geo: $geo}}')
  fi
  info "Creating workspace '${WS_NAME}'..."
  RESULT=$(anthropic_post "/v1/organizations/workspaces" "${BODY}") || {
    fail "3.1 Failed to create workspace"
    summary; exit 0
  }
  NEW_ID=$(echo "${RESULT}" | jq -r '.id')
  pass "3.1 Workspace created: ${NEW_ID}"
fi
Code Pack: CLI Script
hth-anthropic-claude-3.01-workspace-inventory.sh View source on GitHub ↗
# ant 2.7.0+; Includes Default Workspace explicitly; the API otherwise omits it.
anthropic_cli_inventory organization:workspaces workspace --include-default
Code Pack: SDK Script
hth-anthropic-claude-3.01-workspace-inventory.js View source on GitHub ↗
export async function workspaceInventory(key,transport){
  const client=scopedClient(key,transport),rows=[],ids=new Set(),seen=new Set();let after_id;
  while(true){
    const response=await client.organization.workspaces.list({include_default:true,limit:1000,...(after_id?{after_id}:{})}).asResponse();
    const data=await response.json();
    if(!Array.isArray(data.data)||typeof data.has_more!=='boolean')throw Error('Incomplete native page');
    for(const row of data.data){
      if(row.type!=='workspace'||typeof row.id!=='string'||ids.has(row.id)||!row.data_residency)throw Error('Invalid/repeated workspace');
      ids.add(row.id);rows.push(row);
    }
    if(!data.has_more)break;after_id=data.last_id;if(typeof after_id!=='string'||!after_id||seen.has(after_id)||seen.size>=10000)throw Error('Invalid native cursor');seen.add(after_id);
  }
  return rows; // Includes Default Workspace; does not prove segmentation/residency policy.
}
if(process.argv[1]&&import.meta.url===pathToFileURL(process.argv[1]).href){
  try{privateEvidence('workspaces',await workspaceInventory(process.env.ANTHROPIC_ADMIN_KEY));}
  catch{console.error('Incomplete workspace inventory; no success file.');process.exitCode=2;}
}

Validation & Testing

  1. Use the workspace API/CLI/Terraform inventory and inspect actual key bindings; do not substitute org cmek_enabled for workspace evidence.
  2. Verify pre-/post-binding and feature coverage against the current reference, including data stored by integrations and local clients.

Expected result: The documented boundary is exercised and any unsupported surface or exception is recorded.

Operational Impact

Deleting a key can render data unrecoverable. Source/schema validation of the inventory is not a successful key binding or a tested recovery procedure.

Compliance Mappings

Framework Control ID Control Description
CIS Controls 4.1, 6.8 Secure configuration and role-based access
NIST 800-53 AC-6, CM-6, AU-6 Least privilege, configuration and evidence
SOC 2 CC6.1, CC7.2 Access and monitoring

Sources: platform.claude.com · platform.claude.com (checked 2026-11-05).


3.4 Limit Managed Agents Tools, Credentials and Runtime Egress

Profile Level: L3 (Run)

Description

Treat Claude Managed Agents as a separate beta API runtime. Limit tools and approval policies, vault credentials, environment networking, mounted repositories and session lifecycle; Enterprise chat posture does not configure these agents.

Rationale

Why This Matters:

  • Built-in toolsets can enable powerful tools by default; auto is a server policy decision, not a human checkpoint.
  • A shared multiagent session shares its sandbox/files/credentials even if individual agents have different tool lists.

Attack Prevented: Prompt-injection-driven writes, credential misuse and exfiltration through autonomous agents.

ClickOps Implementation

  1. In the Managed Agents Console/API resource configuration, disable toolset defaults and enable only required tools. Set always_ask on irreversible external actions, powerful tools receiving relayed end-user text, and MCP writes; auto alone is insufficient where a person must review.
  2. Mount only trusted repositories: repository skills can be loaded as instructions without review. Prefer a narrow custom tool whose application holds and authorizes the downstream credential over a general shell with that credential.
  3. Limit the cloud environment network and web tool domain lists separately; search-result domains do not restrict the sensitive text in the query. For self-hosted sandboxes, enforce equivalent firewall policy and assess worker/custom/MCP code with the same process privileges.
  4. Bound budgets, idle/running sessions, deployment schedules and stored transcripts/memory. IP allowlisting governs the API request, not runtime egress; Managed Agents is not ZDR-eligible and its state persists until deleted.

Code Implementation

Code Pack: Config
hth-anthropic-claude-4.02-managed-agents.jsonc View source on GitHub ↗
// Agent request: no shell, web search, writes, credentials or MCP toolsets.
// Replace model/host with the reviewed supported choices before creation.
{
  "name": "HTH reviewed research agent",
  "model": "claude-opus-5-5",
  "tools": [
    {
      "type": "agent_toolset_20260401",
      "default_config": {
        "enabled": false,
        "permission_policy": {"type": "always_ask"}
      },
      "configs": [
        {"name": "read", "enabled": true, "permission_policy": {"type": "always_allow"}},
        {"name": "web_fetch", "enabled": true, "allowed_domains": ["help.example.com"], "permission_policy": {"type": "always_ask"}}
      ]
    }
  ]
}
// Separate Environment request. Do not POST both objects as a single document.
// allowed_hosts opens every operation to each host; web tool limits are separate.
{
  "name": "HTH limited research environment",
  "config": {
    "type": "cloud",
    "networking": {
      "type": "limited",
      "allowed_hosts": ["help.example.com"],
      "allow_package_managers": false,
      "allow_mcp_servers": false
    }
  }
}

Validation & Testing

  1. Validate serialized agent/environment configuration, enabled-tool set and paused approval handling with harmless fixtures before any deployment.
  2. Test runtime and web/MCP boundaries independently; a client that automatically approves every paused call removes the human gate. Separate sessions/environments/vaults are required for stronger isolation than subagent tool lists.

Expected result: The documented boundary is exercised and any unsupported surface or exception is recorded.

Operational Impact

No agent is created, no vault credential is minted and no model is invoked here. The native request example must be combined with a reviewed environment/network/budget and application authorization layer.

Compliance Mappings

Framework Control ID Control Description
CIS Controls 4.1, 6.8 Secure configuration and role-based access
NIST 800-53 AC-6, CM-6, AU-6 Least privilege, configuration and evidence
SOC 2 CC6.1, CC7.2 Access and monitoring

Sources: platform.claude.com · platform.claude.com · platform.claude.com · platform.claude.com (checked 2026-11-05).


4. Monitoring & Usage Controls

4.1 Monitor API Usage and Costs

Profile Level: L1 (Crawl)

Description

Use the newer Usage and Cost Analytics API for new Console and Enterprise integrations, while recognizing that the legacy Console reports remain available. Collect complete pages with an explicit time range and correct money units.

Rationale

Why This Matters:

  • Wrong parameter names, flat parsing of bucketed responses and ignored pagination silently lose evidence.
  • Costs are decimal strings in fractional cents, not dollars; workspace and actor dimensions have distinct scopes.

Attack Prevented: Unnoticed credential abuse, cost spikes and incomplete activity attribution.

ClickOps Implementation

  1. Review Console → Usage and Cost; Enterprise uses its own organization analytics pages and scoped read:analytics credential, not a Console admin key.
  2. For new collectors use GET /v1/organizations/analytics/usage and /cost, beta analytics-usage-cost-2026-08-24 with API version 2026-10-21. Set inclusive starting_at and exclusive ending_at (at most 31 days) and follow has_more/next_page.
  3. Group by the actual dimension, e.g. workspace_id for Console and product/actor where appropriate. Do not silently exclude deleted/unattributed users or double-count overlapping RBAC groups. On AWS call each region with its native authentication and IAM, not the first-party-key pack.
  4. The legacy report endpoints use starting_at/ending_at, bracketed group-by and nested bucket results; do not use the older pack’s start_time, flat cost_usd or a first-page-only aggregate.

Code Implementation

Code Pack: API Script
hth-anthropic-claude-5.01-monitor-usage-and-costs.sh View source on GitHub ↗
: "${HTH_STARTING_AT:?Set RFC3339 UTC inclusive start}"
: "${HTH_ENDING_AT:?Set RFC3339 UTC exclusive end, at most 31 days later}"
for value in "${HTH_STARTING_AT}" "${HTH_ENDING_AT}"; do
  [[ "${value}" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]] || exit 2
done
ANTHROPIC_VERSION=2026-10-21
HTH_ANTHROPIC_BETA=analytics-usage-cost-2026-08-24
evidence=$(anthropic_evidence_dir)
trap 'rm -f "${evidence}/usage.pending" "${evidence}/cost.pending"' EXIT
query="starting_at=${HTH_STARTING_AT}&ending_at=${HTH_ENDING_AT}&group_by%5B%5D=workspace_id&time_bucket_width=1d&limit=1000"
anthropic_report_all "/v1/organizations/analytics/usage?${query}" > "${evidence}/usage.pending"
anthropic_report_all "/v1/organizations/analytics/cost?${query}" > "${evidence}/cost.pending"
jq -e 'all(.[]; (.group|type)=="object" and (.usage.total_tokens|type)=="number" and .usage.total_tokens>=0)' "${evidence}/usage.pending" >/dev/null
jq -e 'all(.[]; (.group|type)=="object" and .cost.currency=="USD" and (.cost.amount|type)=="string" and (.cost.amount|test("^[0-9]+(\\.[0-9]+)?$")))' "${evidence}/cost.pending" >/dev/null
mv "${evidence}/usage.pending" "${evidence}/usage.json"
mv "${evidence}/cost.pending" "${evidence}/cost.json"
printf 'Private, complete analytics: %s. Cost amount is fractional CENTS, not dollars. Empty results are no activity evidence, not a hardening pass.\n' "${evidence}"

Validation & Testing

  1. Run the read-only pack against an approved reporting window and reconcile every page with Console totals; verify decimal fractional-cent conversion before dollar thresholds.
  2. Treat empty reports as no captured activity, not proof of a secure organization. Review unexpected workspace/actor/model/product usage and provider logging gaps.

Expected result: The documented boundary is exercised and any unsupported surface or exception is recorded.

Operational Impact

No cap is set. Authentication, date/range or access errors must return nonzero; reports contain identity/usage data and are saved privately.

Compliance Mappings

Framework Control ID Control Description
CIS Controls 4.1, 6.8 Secure configuration and role-based access
NIST 800-53 AC-6, CM-6, AU-6 Least privilege, configuration and evidence
SOC 2 CC6.1, CC7.2 Access and monitoring

Sources: platform.claude.com · platform.claude.com (checked 2026-11-05).


4.2 Configure Spend Limits per Workspace

Profile Level: L1 (Crawl)

Framework Control
NIST 800-53 SA-9, SI-4
SOC 2 CC7.2, CC6.8

Description

Set per-workspace spend limits and rate limits to prevent cost overruns and abuse. Workspace limits cannot exceed organization-level limits. Configure both monthly spend caps and per-model rate limits (requests per minute, input/output tokens per minute).

Rationale

Why This Matters:

  • A compromised API key without spend limits can generate unlimited costs
  • Rate limits prevent individual workspaces from consuming the organization’s entire quota
  • Workspace-level limits enable differentiated resource allocation (e.g., production gets higher limits)

Attack Prevented: Denial-of-wallet attacks, runaway cost from compromised keys or bugs

Prerequisites

  • Organization Admin or Workspace Admin access
  • Budget allocation per workspace/team

ClickOps Implementation

Step 1: Set Organization-Level Limits

  1. Navigate to: platform.claude.com → Settings → Limits
  2. Review and configure organization-level spend limits
  3. For custom limits beyond Tier 4, contact Anthropic sales

Step 2: Set Workspace-Level Limits

  1. Navigate to the target workspace: limits are split across two tabs — the Rate limits tab (per model tier: requests/min, input tokens, output tokens) and the Spend limits tab (monthly cap plus threshold alerts)
  2. Set the monthly spend cap below the org limit (e.g., $500 for dev, $5000 for prod) and rate limits as needed
  3. Repeat for each workspace — except the Default Workspace, which cannot carry limits at all (the mitigation is keeping production keys out of it, per 2.1); unset workspace limits inherit the organization’s limits, and org-wide limits always apply even when per-workspace limits sum higher

Time to Complete: ~5 minutes per workspace

Code Implementation

Code Pack: API Script
hth-anthropic-claude-5.02-configure-spend-limits.sh View source on GitHub ↗
: "${WORKSPACE_ID:?Use an explicit named workspace ID}"
[[ "${WORKSPACE_ID}" =~ ^wrkspc_[A-Za-z0-9]+$ ]] || exit 2
ANTHROPIC_VERSION=2026-10-21
evidence=$(anthropic_evidence_dir)
trap 'rm -f "${evidence}/rate-limits.pending"' EXIT
anthropic_page_token_all "/v1/organizations/workspaces/${WORKSPACE_ID}/rate_limits?include_inherited=true&limit=1000" > "${evidence}/rate-limits.pending"
mv "${evidence}/rate-limits.pending" "${evidence}/rate-limits.json"
printf 'Private rate-limit evidence: %s. Check the workspace Spend limits tab separately; this GET sets no cap.\n' "${evidence}/rate-limits.json"

Validation & Testing

  1. Verify spend limits are set for every workspace via Console
  2. Run cost anomaly detection script to validate monitoring
  3. Test that requests return 429 when rate limits are exceeded (check retry-after header)
  4. Audit configured limits programmatically with the read-only Rate Limits API (verified 2026-08-15): GET /v1/organizations/rate_limits returns org-level limits grouped by group_type (model_group, batch, token_count, files, skills, web_search) with limiter {type, value} pairs; GET /v1/organizations/workspaces/{workspace_id}/rate_limits returns only workspace overrides with org-limit comparison values — a workspace absent from the response has no overrides. The API cannot write limits (Console only). (Rate Limits API)

Expected result: Every non-default workspace has explicit spend and rate limits configured, and the configuration is auditable in code rather than by Console screenshot

Compliance Mappings

Framework Control ID Control Description
SOC 2 CC7.2, CC6.8 System monitoring; change management
NIST 800-53 SA-9, SI-4 External system services; system monitoring
ISO 27001 A.12.1.3 Capacity management

Compliance Quick Reference

Per-control compliance mappings appear inside each control above. For organization-level SOC 2 / NIST / ISO mappings spanning the Anthropic platform, see the Anthropic Common Controls hub.


Appendix A: References

See the Anthropic platform hub references for the shared reference list; key API/Console sources:


Changelog

Date Version Maturity Changes Author
2026-11-05 1.2.0 ai-drafted Corrected principal/key scope, WIF preflight and secret handling, API retention and AWS analytics; added workspace CMEK and Managed Agents isolation; fixed complete usage/cost and rate-limit evidence. Codex
2026-08-15 1.1.0 ai-drafted Admin API currency pass. §1.2 rewritten around native key expiration: creation-time presets/custom/Never, the organization maximum-expiration policy that removes Never entirely, expires_at auditing (null = finding), 401 at expiry, creator warning emails. §1.3 WIF updated: the Connect-workload wizard as the documented setup path (per-provider tiles, Verify-issuer JWKS dry-run, 15-minute exchange listener, 600s prefill vs the 3600s API default), the grown OAuth scope set (workspace:inference, workspace:manage_tunnels, Console-only org:admin rules targeting admin-role service accounts), the fork-PR wildcard-subject attack with the protected-branch pin, the 2×-remaining-IdP-JWT token-lifetime cap, implicit default-workspace service-account membership, and the OAuth-only WIF Admin API endpoints. §2.1 gains the special-workspace handling (Default Workspace cannot carry limits and reports null workspace_id; archiving the auto-created Claude Code workspace disables org-wide Claude Code sign-in) and the prompt-cache isolation platform split. §2.2 corrected: five workspace roles including Workspace Limited Developer, billing-role inheritance, and the offboarding fix — standard workspace keys survive member removal and demand a rotation pass. NEW §2.3: Claude Platform on AWS variant (workspace-endpoints-only Admin API, IAM policies on workspace ARNs, CallWithBearerToken denial forcing SigV4, 12-hour short-term keys, CloudTrail data-event logging gap, AWS region does not pin inference geography). §3.1: inference_geo model floor (Claude 4.6+; 400 on 4.5-generation). §4.1: expanded dimensions (context window, speed beta, inference_geo with the pre-Feb-2026 not_available caveat), bucket caps and pagination, cost-endpoint mechanics (daily-only, cents strings, Priority Tier excluded, code-execution only here), and the anthropic-workspace-id response header. §4.2: split Rate-limits/Spend-limits tabs and read-only Rate Limits API validation. All ClickOps hosts canonicalized to platform.claude.com. Claude Code (Opus 5)
2026-08-03 1.0.0 ai-drafted Split out of the monolithic Anthropic Claude guide as part of the multi-product platform restructure; carries the API key, workspace, data, and usage controls (formerly sections 2-5) renumbered into four sections. Claude Code (Sonnet 5)

Contributing

Found an issue or want to improve this guide? Open an issue or PR on GitHub. Keep all code in Code Packs (no inline code blocks).