Operating system set to macOS
Documentation jurisdiction set to United States
Sign inConnect Claude

Managing keys

Every key operation in the portal is also reachable from the SDK β€” they hit the same /api/v1/connect-keys/* backend, share the same validation, the same audit log, the same cache invalidation. Pick whichever fits your workflow.

What a key carries

A connect key is a sk_* bearer plus the metadata below. The first two come from WorkOS (the secret store); the rest live in our mirror and can be edited without rotating:

FieldEditable?Notes
namevia mirror (label-only β€” WorkOS-side stays original)Human-readable.
permissionsno β€” mint a replacement keyWorkOS doesn't expose PATCH on api_keys today; rotation inherits the parent's list verbatim.
workspace_idyesPin the key to one workspace in a multi-workspace org. null = earliest workspace (default).
rate_limit_per_minuteyesWins over org override and default. 0 blocks all requests; null clears the override.
descriptionyesFree-text label visible on the portal.
environmentyessandbox or live. Portal listing filter.

Minting a key

Portal

  1. Open Dashboard β†’ Keys.
  2. Click New key.
  3. Name + permissions are required. Workspace pin / rate-limit / description / environment hide behind the Advanced disclosure β€” the basic flow stays fast.
  4. Click Create key. The full sk_… value appears once with copy + multi-language code samples. WorkOS will not show it again.

SDK

from heyarchie import Archie

client = Archie()  # picks up ARCHIE_API_KEY env var

key = client.keys.create(
    name="ci-deploys",
    permissions=["ask:execute", "search:read"],
    workspace_id="b0000000-0000-0000-0000-000000000001",  # optional
    rate_limit_per_minute=30,                              # optional
    description="staging deploy bot",                      # optional
    environment="sandbox",                                 # default
)

# Surface key.value to the operator immediately and discard.
# WorkOS will not return it again.
print(key.value)

On a successful create the backend INSERTs the mirror row + the seed populates the per-key rate-limit cache, so any cap you set is enforced from the very first request through the new key.

Permission slugs must come from the current 11-scope catalog β€” the same taxonomy the scopes table documents, derived live from the skill registry. A retired or unknown slug (e.g. the old messages:create) fails the create with 400 unknown_permissions, and the response lists the valid slugs. Keys minted under the old vocabulary still authenticate, but a retired slug no longer matches any skill's required permission β€” mint a replacement key with current scopes and revoke the old one.

Editing per-key metadata

Portal

Click the chevron on the key row to expand its detail panel. The three editable fields (workspace pin, rate limit, description) auto-save 600ms after you stop typing; a small saved pill confirms.

SDK

# Pin to a workspace
client.keys.update(key.id, workspace_id="b0000000-…")

# Set per-key rate limit (None to clear, 0 to block)
client.keys.update(key.id, rate_limit_per_minute=30)

# Update multiple fields in one call
client.keys.update(
    key.id,
    description="staging deploy bot β€” updated",
    rate_limit_per_minute=60,
)

Trying to PATCH permissions returns 501 β€” WorkOS doesn't expose a PATCH on api_keys today β€” and rotate copies the parent's permission list verbatim. To change a key's permissions, create a replacement key with the scopes you want and revoke the old one.

Rotating

Rotation mints a replacement key inheriting name / permissions / workspace_id / rate_limit_per_minute / description / environment from the parent, and stamps expires_at = now() + grace_period_hours on the parent. A backend reaper job revokes the parent after expiry.

Portal

Hover the key row, click Rotate. The new value appears once.

SDK

# Default: parent dies after 24h
new_key = client.keys.rotate(key.id)

# Or shorter grace window
new_key = client.keys.rotate(key.id, grace_period_hours=1)

print(new_key.value)            # one-time replacement sk_…
print(new_key.rotated_from_key_id)  # = old key.id

Revoking

Revoke is immediate. The backend deletes the WorkOS key, stamps revoked_at on the mirror row, and flushes both caches (the WorkOS validator + the per-key rate-limit cache) so any in-flight cached token gets 401 on the very next request β€” no 5-minute staleness window.

Portal

Hover the row, click Revoke, confirm.

SDK

client.keys.revoke(key.id)
# Subsequent calls through this key 401 with auth_required.

Listing + inspecting

page = client.keys.list()
for k in page.data:
    print(k.id, k.obfuscated_value, k.workspace_id, k.rate_limit_per_minute)

# Single key, merged shape:
key = client.keys.get("api_key_01KRVGB…")
print(key.description, key.environment, key.revoked_at)

What never leaves the backend

  • Your WORKOS_API_KEY β€” only ours, server-side.
  • The full sk_* value after the one-time reveal β€” WorkOS never returns it again.
  • The org-level WorkOS admin token used by the Management API.

Browser sees only the merged response shape with obfuscated_value (the sk_…Ozgi preview). Open dev-tools on /dashboard/keys and confirm β€” zero calls to api.workos.com.

Next