> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clawb.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

This page is a step-by-step guide for both sides of a Clawb integration:

* Agent runtime (signed requests)
* Workspace backend (workspace API key control plane)

It maps the core control-plane workflow: identity, verification/decisioning, bounded credentials, and audit-ready operations.

Base URL used below:

<CodeGroup>
  ```text Text theme={null}
  https://api.clawb.ai/api
  ```
</CodeGroup>

## Install

<CodeGroup>
  ```bash bash theme={null}
  pip install clawb-agent-sdk
  ```
</CodeGroup>

## Quick setup

<CodeGroup>
  ```bash curl theme={null}
  export CLAWB_BASE_URL="https://api.clawb.ai/api"
  export CLAWB_API_KEY="ck_live_replace_me"
  ```

  ```python Python SDK theme={null}
  from clawb_agent_sdk import ClawbClient, WorkspaceControlPlane

  BASE_URL = "https://api.clawb.ai/api"
  WORKSPACE_API_KEY = "ck_live_replace_me"

  # Unsigned low-level client for workspace server-to-server calls.
  control_plane_client = ClawbClient(base_url=BASE_URL)

  # High-level workspace helper.
  control_plane = WorkspaceControlPlane(client=control_plane_client, api_key=WORKSPACE_API_KEY)
  ```
</CodeGroup>

## Agent onboarding and signed runtime

### 1) Generate keypair

<CodeGroup>
  ```python Python SDK theme={null}
  from clawb_agent_sdk import ClawbClient

  priv_b64, pub_b64 = ClawbClient.generate_ed25519_keypair_b64()
  print(pub_b64)
  ```
</CodeGroup>

### 2) Register and attest

<CodeGroup>
  ```bash curl theme={null}
  # 1) Register agent (replace public key).
  REGISTER_OUT=$(curl -sS -X POST "$CLAWB_BASE_URL/v1/agents/register" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "my-agent",
      "public_key": "<base64-ed25519-public-key>"
    }')

  # 2) Sign challenge bytes locally, then attest with the signature.
  curl -sS -X POST "$CLAWB_BASE_URL/v1/agents/attest" \
    -H "Content-Type: application/json" \
    -d '{
      "agent_id": "agt_01...",
      "challenge_id": "ch_01...",
      "signature": "<base64-ed25519-signature>"
    }'
  ```

  ```python Python SDK theme={null}
  client = ClawbClient(base_url=BASE_URL)
  reg = client.register(name="my-agent", public_key_b64=pub_b64)

  agent_client = ClawbClient(
      base_url=BASE_URL,
      agent_id=reg["agent_id"],
      private_key_b64=priv_b64,
  )

  # Attest signs the challenge bytes with Ed25519.
  agent_client.attest(
      challenge_id=reg["challenge_id"],
      challenge_b64=reg["challenge"],
  )
  ```
</CodeGroup>

Example response:

<CodeGroup>
  ```json JSON theme={null}
  {
    "agent_id": "agt_01...",
    "challenge_id": "ch_01...",
    "challenge": "<base64-challenge>"
  }
  ```
</CodeGroup>

### 3) Send signed telemetry heartbeat

<CodeGroup>
  ```bash curl theme={null}
  curl -sS -X POST "$CLAWB_BASE_URL/v1/telemetry/heartbeat" \
    -H "Content-Type: application/json" \
    -H "X-Clawb-Agent-Id: agt_01..." \
    -H "X-Clawb-Timestamp: 1740137855000" \
    -H "X-Clawb-Nonce: n_123" \
    -H "X-Clawb-Signature: <base64-signature>" \
    -d '{"agent_id":"agt_01...","status":"ok","latency_ms":72}'
  ```

  ```python Python SDK theme={null}
  agent_client.post(
      "/v1/telemetry/heartbeat",
      json={"agent_id": reg["agent_id"], "status": "ok", "latency_ms": 72},
  )
  ```
</CodeGroup>

Example response:

<CodeGroup>
  ```json JSON theme={null}
  {
    "ok": true,
    "agent_id": "agt_01...",
    "status": "ok",
    "recorded_at": "2026-02-28T00:00:00Z"
  }
  ```
</CodeGroup>

## Workspace request-time flow

### Verify signature (optional online mode)

<CodeGroup>
  ```bash curl theme={null}
  curl -sS -X POST "https://api.clawb.ai/api/v1/verify" \
    -H "Content-Type: application/json" \
    -H "X-Clawb-Api-Key: ck_live_replace_me" \
    -d '{
      "agent_id": "agt_01...",
      "method": "POST",
      "path": "/v1/refunds",
      "timestamp_ms": 1740137855000,
      "nonce": "2f8d8b19-5e0a-4f8b-b7d4-6dc15b1fe201",
      "body_sha256": "3adfd3eb02f15d4f4b5a9f5b2d18f8d1b6d8a7eac03f4b7a56ec8f8c2f2ff321",
      "signature_b64": "<base64-signature>"
    }'
  ```

  ```python Python SDK theme={null}
  # Build verify payload from the original inbound request values.
  verify = control_plane.verify(
      agent_id="agt_01...",
      method="POST",
      path="/v1/refunds",
      timestamp_ms=1740137855000,
      nonce="2f8d8b19-5e0a-4f8b-b7d4-6dc15b1fe201",
      body_sha256=control_plane.sha256_hex(b'{"amount":49.00}'),
      signature_b64="<x-clawb-signature-header>",
  )
  print(verify)
  ```
</CodeGroup>

Example response:

<CodeGroup>
  ```json JSON theme={null}
  {
    "valid": true,
    "agent_id": "agt_01...",
    "verified_at": "2026-02-28T00:00:00Z"
  }
  ```
</CodeGroup>

### Enforce policy decision

<CodeGroup>
  ```bash curl theme={null}
  curl -sS -X POST "$CLAWB_BASE_URL/v1/check" \
    -H "Content-Type: application/json" \
    -H "X-Clawb-Api-Key: $CLAWB_API_KEY" \
    -d '{
      "agent_id": "agt_01...",
      "policy_id": "pol_default",
      "action": "refund",
      "context": {"amount": 49.00, "currency": "USD"}
    }'
  ```

  ```python Python SDK theme={null}
  decision = control_plane.check(
      agent_id="agt_01...",
      policy_id="pol_default",
      action="refund",
      context={"amount": 49.00, "currency": "USD"},
  )
  print(decision)
  ```
</CodeGroup>

Example response:

<CodeGroup>
  ```json JSON theme={null}
  {
    "decision": "allow",
    "trace_id": "trc_01abc",
    "reasons": []
  }
  ```
</CodeGroup>

## Workspace control-plane APIs (new)

### Workspace agent inventory

<CodeGroup>
  ```bash curl theme={null}
  curl -sS -X POST "$CLAWB_BASE_URL/v1/workspace/agents/upsert" \
    -H "Content-Type: application/json" \
    -H "X-Clawb-Api-Key: $CLAWB_API_KEY" \
    -d '{
      "external_agent_key": "github:app:payments-bot",
      "agent_id": "agt_01...",
      "display_name": "Payments bot",
      "labels": ["prod", "payments"],
      "environment": "prod",
      "source": "provider_api",
      "status": "active"
    }'

  curl -sS "$CLAWB_BASE_URL/v1/workspace/agents?environment=prod&label=payments" \
    -H "X-Clawb-Api-Key: $CLAWB_API_KEY"
  ```

  ```python Python SDK theme={null}
  control_plane.workspace_agents_upsert(
      external_agent_key="github:app:payments-bot",
      agent_id="agt_01...",
      display_name="Payments bot",
      labels=["prod", "payments"],
      environment="prod",
      source="provider_api",
      status="active",
  )

  inventory = control_plane.workspace_agents_list(environment="prod", label="payments")
  print(inventory.get("count"))
  ```
</CodeGroup>

Example response:

<CodeGroup>
  ```json JSON theme={null}
  {
    "ok": true,
    "count": 1,
    "items": [
      {
        "external_agent_key": "github:app:payments-bot",
        "agent_id": "agt_01...",
        "status": "active"
      }
    ]
  }
  ```
</CodeGroup>

### Workspace audit

<CodeGroup>
  ```bash curl theme={null}
  curl -sS "$CLAWB_BASE_URL/v1/workspace/audit/events?start_ms=1740137000000&end_ms=1740139999000&decision=deny&limit=100" \
    -H "X-Clawb-Api-Key: $CLAWB_API_KEY"

  curl -sS -X POST "$CLAWB_BASE_URL/v1/workspace/audit/export" \
    -H "Content-Type: application/json" \
    -H "X-Clawb-Api-Key: $CLAWB_API_KEY" \
    -d '{
      "format": "csv",
      "filters": {"decision": "deny"},
      "limit": 500
    }'
  ```

  ```python Python SDK theme={null}
  events = control_plane.workspace_audit_events(
      start_ms=1740137000000,
      end_ms=1740139999000,
      decision="deny",
      limit=100,
  )
  print(events.get("next_cursor"))

  csv_export = control_plane.workspace_audit_export(
      format="csv",
      filters={"decision": "deny"},
      limit=500,
  )
  print(len(csv_export.get("csv", "")))
  ```
</CodeGroup>

Example response:

<CodeGroup>
  ```json JSON theme={null}
  {
    "ok": true,
    "items": [
      {
        "event_id": "evt_01...",
        "decision": "deny",
        "created_at": "2026-02-28T00:00:00Z"
      }
    ],
    "next_cursor": "cur_01..."
  }
  ```
</CodeGroup>

### Identity credential mint and revoke

<CodeGroup>
  ```bash curl theme={null}
  MINT_OUT=$(curl -sS -X POST "$CLAWB_BASE_URL/v1/identity/credentials/mint" \
    -H "Content-Type: application/json" \
    -H "X-Clawb-Api-Key: $CLAWB_API_KEY" \
    -d '{
      "agent_id":"agt_01...",
      "provider":"sendgrid",
      "audience":"clawb.provider",
      "ttl_seconds":300,
      "one_time":true,
      "scopes":["email:send"],
      "token_type":"jwt"
    }')

  curl -sS -X POST "$CLAWB_BASE_URL/v1/identity/credentials/revoke" \
    -H "Content-Type: application/json" \
    -H "X-Clawb-Api-Key: $CLAWB_API_KEY" \
    -d '{
      "token":"<credential-token>",
      "reason":"cleanup"
    }'

  curl -sS -X POST "$CLAWB_BASE_URL/v1/identity/credentials/revoke-by-agent" \
    -H "Content-Type: application/json" \
    -H "X-Clawb-Api-Key: $CLAWB_API_KEY" \
    -d '{
      "agent_id":"agt_01...",
      "reason":"agent reset"
    }'
  ```

  ```python Python SDK theme={null}
  mint = control_plane.identity_credentials_mint(
      agent_id="agt_01...",
      provider="sendgrid",
      audience="clawb.provider",
      ttl_seconds=300,
      one_time=True,
      scopes=["email:send"],
      token_type="jwt",
  )

  # Revoke one credential by token.
  control_plane.identity_credentials_revoke(
      token=mint["credential"]["token"],
      reason="cleanup",
  )

  # Revoke all active credentials for one agent.
  control_plane.identity_credentials_revoke_by_agent(
      agent_id="agt_01...",
      reason="agent reset",
  )
  ```
</CodeGroup>

Example response:

<CodeGroup>
  ```json JSON theme={null}
  {
    "ok": true,
    "credential": {
      "cred_id": "crd_01...",
      "token_type": "jwt",
      "expires_at": "2026-02-28T00:05:00Z"
    }
  }
  ```
</CodeGroup>

### Kill switch

<CodeGroup>
  ```bash curl theme={null}
  curl -sS -X POST "$CLAWB_BASE_URL/v1/identity/kill-switch/minting" \
    -H "Content-Type: application/json" \
    -H "X-Clawb-Api-Key: $CLAWB_API_KEY" \
    -d '{"paused":true,"reason":"incident INC-7"}'

  curl -sS -X POST "$CLAWB_BASE_URL/v1/identity/kill-switch/revoke-all" \
    -H "Content-Type: application/json" \
    -H "X-Clawb-Api-Key: $CLAWB_API_KEY" \
    -d '{"reason":"credential compromise"}'

  curl -sS "$CLAWB_BASE_URL/v1/identity/kill-switch/status" \
    -H "X-Clawb-Api-Key: $CLAWB_API_KEY"
  ```

  ```python Python SDK theme={null}
  control_plane.identity_kill_switch_minting(paused=True, reason="incident INC-7")
  control_plane.identity_kill_switch_revoke_all(reason="credential compromise")
  print(control_plane.identity_kill_switch_status())
  ```
</CodeGroup>

Example response:

<CodeGroup>
  ```json JSON theme={null}
  {
    "ok": true,
    "minting_paused": true,
    "all_credentials_revoked_at": "2026-02-28T00:00:00Z"
  }
  ```
</CodeGroup>

### Reputation feedback (HMAC-signed)

<CodeGroup>
  ```bash curl theme={null}
  curl -sS -X POST "$CLAWB_BASE_URL/v1/reputation/feedback" \
    -H "Content-Type: application/json" \
    -H "X-Clawb-Api-Key: $CLAWB_API_KEY" \
    -H "X-Clawb-Feedback-Timestamp: 1740137855000" \
    -H "X-Clawb-Feedback-Nonce: n_123" \
    -H "X-Clawb-Feedback-Signature: <base64-hmac>" \
    -d '{
      "agent_id":"agt_01...",
      "verdict":"bad",
      "evidence":{"reason":"credential_stuffing_pattern"}
    }'
  ```

  ```python Python SDK theme={null}
  # SDK automatically computes feedback signature headers.
  control_plane.reputation_feedback(
      agent_id="agt_01...",
      verdict="bad",
      evidence={"reason": "credential_stuffing_pattern"},
  )
  ```
</CodeGroup>

Example response:

<CodeGroup>
  ```json JSON theme={null}
  {
    "ok": true,
    "accepted": true,
    "received_at": "2026-02-28T00:00:00Z"
  }
  ```
</CodeGroup>

## Public metadata helpers (new)

<CodeGroup>
  ```bash curl theme={null}
  curl -sS "$CLAWB_BASE_URL/.well-known/openid-configuration"
  curl -sS "$CLAWB_BASE_URL/.well-known/jwks.json"
  ```

  ```python Python SDK theme={null}
  cfg = control_plane_client.well_known_openid_configuration()
  jwks = control_plane_client.well_known_jwks()

  print(cfg["issuer"])
  print("keys:", len(jwks.get("keys", [])))
  ```
</CodeGroup>

Example response:

<CodeGroup>
  ```json JSON theme={null}
  {
    "issuer": "https://api.clawb.ai/api",
    "jwks_uri": "https://api.clawb.ai/api/.well-known/jwks.json"
  }
  ```
</CodeGroup>

## Token exchange helpers (new)

<CodeGroup>
  ```bash curl theme={null}
  curl -sS -X POST "https://api.clawb.ai/api/v1/token/exchange" \
    -H "Content-Type: application/json" \
    -H "X-Clawb-Agent-Id: agt_01..." \
    -H "X-Clawb-Timestamp: 1740137855000" \
    -H "X-Clawb-Nonce: n_123" \
    -H "X-Clawb-Signature: <base64-signature>" \
    -d '{
      "audience": "aws",
      "policy_id": "pol_default",
      "scopes": ["s3:GetObject"]
    }'
  ```

  ```python Python SDK theme={null}
  from clawb_agent_sdk import ClawbClient, ClawbIdentity, get_aws_credentials

  agent_client = ClawbClient(
      base_url=BASE_URL,
      agent_id="agt_01...",
      private_key_b64=priv_b64,
  )

  # Sign request material with local private key.
  sig = ClawbIdentity(private_key_b64=priv_b64).sign_request(
      method="POST",
      path="/v1/token/exchange",
      timestamp_ms=1740137855000,
      nonce="n_123",
      body_sha256="3adfd3eb02f15d4f4b5a9f5b2d18f8d1b6d8a7eac03f4b7a56ec8f8c2f2ff321",
  )

  # Exchange for a short-lived OIDC JWT (with local cache).
  token = agent_client.get_token(
      audience="aws",
      policy_id="pol_default",
      scopes=["s3:GetObject"],
  )

  # Optional cloud helper for AWS STS.
  creds = get_aws_credentials(
      token=token["token"],
      role_arn="arn:aws:iam::123456789012:role/ClawbRole",
      role_session_name="clawb-agent-session",
  )
  print(creds["AccessKeyId"])
  ```
</CodeGroup>

Example response:

<CodeGroup>
  ```json JSON theme={null}
  {
    "ok": true,
    "token": "<jwt>",
    "token_type": "Bearer",
    "expires_in": 900
  }
  ```
</CodeGroup>

## Common mistakes

1. Use milliseconds for timestamps, not seconds.
2. Keep base URL at API root (`.../api`).
3. Use workspace API key for `/v1/check` and workspace control-plane endpoints.
4. Do not manually sign reputation feedback if you can use `control_plane.reputation_feedback()`.
5. Handle policy-denied mint responses (`403`) separately from transport failures.

## Related pages

* [Workspace control plane tutorial](/integration/workspace-control-plane-tutorial)
* [Identity credential APIs](/api-reference/identity-credentials)
* [Token exchange API](/api-reference/token-exchange)
* [Workspace audit APIs](/api-reference/workspace-audit)
* [Public metadata endpoints](/api-reference/public-metadata)
