- Agent runtime (signed requests)
- Workspace backend (workspace API key control plane)
https://api.clawb.ai/api
Install
pip install clawb-agent-sdk
Quick setup
export CLAWB_BASE_URL="https://api.clawb.ai/api"
export CLAWB_API_KEY="ck_live_replace_me"
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)
Agent onboarding and signed runtime
1) Generate keypair
from clawb_agent_sdk import ClawbClient
priv_b64, pub_b64 = ClawbClient.generate_ed25519_keypair_b64()
print(pub_b64)
2) Register and attest
# 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>"
}'
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"],
)
{
"agent_id": "agt_01...",
"challenge_id": "ch_01...",
"challenge": "<base64-challenge>"
}
3) Send signed telemetry heartbeat
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}'
agent_client.post(
"/v1/telemetry/heartbeat",
json={"agent_id": reg["agent_id"], "status": "ok", "latency_ms": 72},
)
{
"ok": true,
"agent_id": "agt_01...",
"status": "ok",
"recorded_at": "2026-02-28T00:00:00Z"
}
Workspace request-time flow
Verify signature (optional online mode)
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>"
}'
# 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)
{
"valid": true,
"agent_id": "agt_01...",
"verified_at": "2026-02-28T00:00:00Z"
}
Enforce policy decision
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"}
}'
decision = control_plane.check(
agent_id="agt_01...",
policy_id="pol_default",
action="refund",
context={"amount": 49.00, "currency": "USD"},
)
print(decision)
{
"decision": "allow",
"trace_id": "trc_01abc",
"reasons": []
}
Workspace control-plane APIs (new)
Workspace agent inventory
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"
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"))
{
"ok": true,
"count": 1,
"items": [
{
"external_agent_key": "github:app:payments-bot",
"agent_id": "agt_01...",
"status": "active"
}
]
}
Workspace audit
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
}'
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", "")))
{
"ok": true,
"items": [
{
"event_id": "evt_01...",
"decision": "deny",
"created_at": "2026-02-28T00:00:00Z"
}
],
"next_cursor": "cur_01..."
}
Identity credential mint and revoke
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"
}'
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",
)
{
"ok": true,
"credential": {
"cred_id": "crd_01...",
"token_type": "jwt",
"expires_at": "2026-02-28T00:05:00Z"
}
}
Kill switch
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"
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())
{
"ok": true,
"minting_paused": true,
"all_credentials_revoked_at": "2026-02-28T00:00:00Z"
}
Reputation feedback (HMAC-signed)
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"}
}'
# SDK automatically computes feedback signature headers.
control_plane.reputation_feedback(
agent_id="agt_01...",
verdict="bad",
evidence={"reason": "credential_stuffing_pattern"},
)
{
"ok": true,
"accepted": true,
"received_at": "2026-02-28T00:00:00Z"
}
Public metadata helpers (new)
curl -sS "$CLAWB_BASE_URL/.well-known/openid-configuration"
curl -sS "$CLAWB_BASE_URL/.well-known/jwks.json"
cfg = control_plane_client.well_known_openid_configuration()
jwks = control_plane_client.well_known_jwks()
print(cfg["issuer"])
print("keys:", len(jwks.get("keys", [])))
{
"issuer": "https://api.clawb.ai/api",
"jwks_uri": "https://api.clawb.ai/api/.well-known/jwks.json"
}
Token exchange helpers (new)
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"]
}'
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"])
{
"ok": true,
"token": "<jwt>",
"token_type": "Bearer",
"expires_in": 900
}
Common mistakes
- Use milliseconds for timestamps, not seconds.
- Keep base URL at API root (
.../api). - Use workspace API key for
/v1/checkand workspace control-plane endpoints. - Do not manually sign reputation feedback if you can use
control_plane.reputation_feedback(). - Handle policy-denied mint responses (
403) separately from transport failures.