Platform API
The Platform API lets you build products on top of 1Claw. Register your app, create bootstrap templates, provision end-users, and manage their secrets infrastructure — all with custody guarantees that prevent your platform from accessing end-user secrets.
The Platform API requires a Pro or higher subscription. Upgrade your plan →
Quickstart (~10 min)
1. Register a Platform App
curl -X POST "https://api.1claw.xyz/v1/platform/apps" \
-H "Authorization: Bearer YOUR_USER_JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "My DeFi Platform",
"slug": "my-defi",
"description": "DeFi automation for end users",
"billing_model": "platform_pays",
"auth_mode": "silent"
}'
Save the returned api_key (prefixed plt_) — it won't be shown again. This key authenticates all subsequent Platform API calls.
Set api_key_expires_at (ISO 8601) when creating the app to auto-expire the key. Rotate at any time with POST /v1/platform/apps/{id}/rotate-key, optionally setting a new expiry. Expired keys return 401.
2. Create a Bootstrap Template
Templates define what gets created for each user: a vault, agents, and access policies.
curl -X POST "https://api.1claw.xyz/v1/platform/apps/APP_ID/templates" \
-H "Authorization: Bearer plt_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "default-template",
"spec": {
"vault": {
"name": "user-vault",
"description": "Auto-provisioned vault"
},
"agents": [{
"name": "defi-bot",
"description": "Automated DeFi agent",
"intents": { "enabled": true },
"shroud_enabled": true,
"shroud_config": {
"pii_policy": "redact",
"enable_secret_redaction": true
}
}],
"policies": [{
"principal_ref": "agents.primary",
"vault_ref": "vault",
"paths": ["api-keys/*", "keys/*"],
"permissions": ["read", "write"],
"conditions": {}
}]
}
}'
3. Provision a User
curl -X POST "https://api.1claw.xyz/v1/platform/users/upsert" \
-H "Authorization: Bearer plt_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"external_subject": "telegram:123456789"
}'
4. Bootstrap the User
curl -X POST "https://api.1claw.xyz/v1/platform/connections/CONNECTION_ID/bootstrap" \
-H "Authorization: Bearer plt_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "TEMPLATE_UUID"
}'
The response includes claim_url, claim_token, and summary (with vault_id, agent_id, policy_ids, agent_api_key — one-time, and signing_keys[] when signing keys are defined in the template). See Step 7 for how to use the agent API key and signing keys.
5. Share the Claim URL
Send the claim_url to your end user (e.g. via your app's UI, email, or bot message). When they visit it, they'll see what was provisioned and can claim the resources with one click.
The claim URL format is https://1claw.xyz/connect/{slug}/claim/{token}. It expires after 10 minutes.
Reissue an expired claim URL:
If the token expires before your user claims, mint a fresh one without re-provisioning:
curl -X POST "https://api.1claw.xyz/v1/platform/connections/CONNECTION_ID/reissue-claim" \
-H "Authorization: Bearer plt_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{}'
# → { "claim_url": "...", "claim_token": "ct_...", "expires_in": 600, "connection_id": "..." }
Programmatic claim (for headless flows):
# Preview what was provisioned
curl "https://api.1claw.xyz/v1/platform/claim/ct_TOKEN"
# Redeem the claim
curl -X POST "https://api.1claw.xyz/v1/platform/claim/ct_TOKEN"
6. Agent Access is Automatic
After bootstrap, the agent already has access to the vault paths defined in your template's policies array. No additional delegation step is needed — the bootstrap template creates both the agent and its access policies in one atomic operation.
If the user needs to grant the agent access to additional paths later, they can:
- Visit the vault's Policies tab in the dashboard
- Create a new access policy for the agent
- Or use the API:
POST /v1/vaults/{vault_id}/policies
7. Operate the Bootstrapped Agent
The bootstrap response includes summary.agent_api_key (one-time, like regular agent creation) and summary.signing_keys (chain, address, public key). Store the API key securely — it won't be shown again.
Get an agent JWT:
curl -X POST "https://api.1claw.xyz/v1/auth/agent-token" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "AGENT_UUID",
"api_key": "ocv_AGENT_API_KEY"
}'
# → { "access_token": "eyJ...", "vault_ids": ["..."] }
Get the agent's wallet address:
The wallet addresses are returned in the bootstrap response under summary.signing_keys. You can also retrieve them later:
curl "https://api.1claw.xyz/v1/agents/AGENT_UUID/signing-keys" \
-H "Authorization: Bearer YOUR_USER_OR_PLATFORM_JWT"
# → { "keys": [{ "chain": "ethereum", "address": "0x...", "public_key": "...", "is_active": true }] }
Submit a transaction (Intents API):
AGENT_JWT="eyJ..." # from token exchange above
curl -X POST "https://api.1claw.xyz/v1/agents/AGENT_UUID/transactions" \
-H "Authorization: Bearer $AGENT_JWT" \
-H "Content-Type: application/json" \
-d '{
"chain": "ethereum",
"chain_id": 1,
"to": "0xRecipientAddress",
"value": "0.01",
"data": "0x"
}'
# → { "tx_hash": "0x...", "signed_tx": "0x...", "status": "broadcast" }
Sign without broadcasting (sign-only mode):
curl -X POST "https://api.1claw.xyz/v1/agents/AGENT_UUID/transactions/sign" \
-H "Authorization: Bearer $AGENT_JWT" \
-H "Content-Type: application/json" \
-d '{
"chain": "ethereum",
"chain_id": 1,
"to": "0xRecipientAddress",
"value": "0.01",
"data": "0x"
}'
# → { "signed_tx": "0x...", "tx_hash": "0x...", "from": "0x...", "status": "sign_only" }
- Bootstrap → save
agent_api_keyandsigning_keys[].addressfrom the response - Token exchange →
POST /v1/auth/agent-tokenwith the agent'socv_key → get a JWT - Operate → use the JWT to submit transactions, sign messages, or read secrets
- The platform never needs a "delegation token" — the agent authenticates directly with its own key
Template Spec Reference
The spec field is a JSON object with three top-level keys: vault, agents, and policies. All are optional — include only what you need.
vault
Creates a single vault for the user.
| Field | Type | Default | Description |
|---|---|---|---|
name | string | "main" | Vault name |
description | string | "" | Vault description |
{
"vault": {
"name": "prod-secrets",
"description": "Production API keys and credentials"
}
}
agents
Array of agent definitions. Each entry creates one agent with an auto-generated ocv_ API key.
| Field | Type | Default | Description |
|---|---|---|---|
name | string | "primary" | Agent name |
description | string | "" | Agent description |
intents.enabled | boolean | false | Enable the Intents API (transaction signing) |
shroud_enabled | boolean | false | Route LLM traffic through Shroud TEE |
shroud_config | object | null | Per-agent Shroud policy (PII, injection thresholds, etc.) |
{
"agents": [
{
"name": "trading-bot",
"description": "Executes DeFi trades",
"intents": { "enabled": true },
"shroud_enabled": true,
"shroud_config": {
"pii_policy": "redact",
"injection_threshold": 0.7,
"allowed_providers": ["openai", "anthropic"],
"enable_secret_redaction": true
}
}
]
}
In the template spec, use "intents": { "enabled": true } (nested object). This is different from the direct agent creation API which uses "intents_api_enabled": true (flat boolean). The bootstrap engine translates between the two formats.
policies
Array of access policies linking agents to vault paths.
| Field | Type | Default | Description |
|---|---|---|---|
principal_ref | string | first agent | Reference to the agent. Use "agents.primary" for the first agent. |
vault_ref | string | created vault | Reference to the vault. Use "vault" for the template-created vault. |
paths | string[] | ["**"] | Glob patterns for secret paths the agent can access |
permissions | string[] | ["read", "write"] | Permission set: read, write, rotate |
conditions | object | {} | Optional conditions (IP allowlist, time windows) |
{
"policies": [
{
"principal_ref": "agents.primary",
"vault_ref": "vault",
"paths": ["api-keys/*", "keys/*"],
"permissions": ["read", "write"]
},
{
"principal_ref": "agents.primary",
"vault_ref": "vault",
"paths": ["config/**"],
"permissions": ["read"],
"conditions": {
"ip_allowlist": ["10.0.0.0/8"]
}
}
]
}
Full Template Example
A complete template for a DeFi trading platform with Shroud inspection, Intents API, and multi-chain signing keys:
{
"name": "defi-trading-template",
"spec": {
"vault": {
"name": "trading-vault",
"description": "Keys and credentials for automated trading"
},
"agents": [
{
"name": "trade-executor",
"description": "Executes on-chain trades via Intents API",
"intents": { "enabled": true },
"shroud_enabled": true,
"shroud_config": {
"pii_policy": "redact",
"injection_threshold": 0.7,
"enable_secret_redaction": true,
"allowed_providers": ["openai", "anthropic"],
"max_requests_per_minute": 60,
"daily_budget_usd": 50
}
}
],
"signing_keys": [
{ "chain": "ethereum" },
{ "chain": "solana" }
],
"policies": [
{
"principal_ref": "agents.primary",
"vault_ref": "vault",
"paths": ["keys/*", "api-keys/*"],
"permissions": ["read"]
},
{
"principal_ref": "agents.primary",
"vault_ref": "vault",
"paths": ["config/**"],
"permissions": ["read", "write"]
}
]
}
}
Redirect URIs & "Sign in with 1Claw"
If your platform app uses the OAuth consent flow ("Sign in with 1Claw"), you need to register allowed redirect URIs. These are the URLs that 1Claw will redirect users back to after login/consent.
Adding Redirect URIs
Dashboard: Go to Platform → your app → Settings tab → Redirect URIs section. Add each callback URL (e.g. https://myapp.com/callback).
API:
curl -X PATCH "https://api.1claw.xyz/v1/platform/apps/APP_ID" \
-H "Authorization: Bearer YOUR_USER_JWT" \
-H "Content-Type: application/json" \
-d '{
"redirect_uris": [
"https://myapp.com/callback",
"http://localhost:3000/callback"
]
}'
SDK:
await client.platform.updateApp(appId, {
redirect_uris: [
"https://myapp.com/callback",
"http://localhost:3000/callback",
],
});
Per RFC 8252 §7.3, http://localhost (any port) is allowed for development. No HTTPS required for loopback addresses.
OAuth Flow
- Your app redirects users to:
https://1claw.xyz/oauth/authorize?client_id=YOUR_SLUG&redirect_uri=https://myapp.com/callback&response_type=code&scope=link&state=RANDOM - The user sees the 1Claw consent page and approves.
- 1Claw redirects back to your
redirect_uriwith an authorizationcode. - Your backend exchanges the code for tokens via
POST /v1/oauth/token.
The client_id parameter must be your platform app's slug (e.g. cubeverse), not the app UUID. You set the slug when creating the app. If you pass the UUID, you'll get "Unknown client_id". Find your slug in the dashboard at Platform → your app → Details.
Cross-Org User Linking
When you call POST /v1/platform/users/upsert and the user already exists in a different organization, the API returns 409 Conflict with a link_required response:
{
"link_required": {
"consent_url": "https://1claw.xyz/oauth/authorize?client_id=your-slug&scope=link&login_hint=user@example.com",
"user_email": "user@example.com",
"message": "User exists in a different organization. Redirect them to consent_url to link."
}
}
Redirect the user to consent_url. After they consent, a cross-org connection is created and subsequent upsert calls will succeed.
Auth Modes
Set auth_mode when creating your platform app:
| Mode | Description |
|---|---|
silent | Users are provisioned without sign-in. Best for bot-first platforms (Telegram, Discord). The claim_url is still returned — share it so users can manage their vault in the dashboard. |
user_signin | Users must sign in to 1Claw before claiming. Best for web apps where users already have accounts. |
configurable | Let the operator choose per-user at bootstrap time. |
Billing Models
| Model | Description |
|---|---|
platform_pays | All API usage is billed to the platform's subscription. |
user_pays | Each connected user is billed individually. |
hybrid | Platform covers base usage; overages billed to users. |
signing_keys
Array of blockchain signing keys to auto-provision for the first agent at bootstrap time. Each entry generates a keypair, stores the private key in the __agent-keys vault, and records the public key on the agent. Requires at least one agent with intents.enabled: true.
| Field | Type | Description |
|---|---|---|
chain | string | Blockchain name: ethereum, bitcoin, solana, xrp, cardano, tron |
{
"signing_keys": [
{ "chain": "ethereum" },
{ "chain": "solana" }
]
}
Signing keys are provisioned server-side during bootstrap — the platform operator never sees the private keys, and no user interaction is required. The plt_ key cannot read signing keys across the org boundary, maintaining custody separation.
Resource Grants (User-Side)
After a user claims their bootstrapped resources, they can grant your platform app access to additional vaults and agents beyond what the template provisioned. This is useful when your users have pre-existing 1Claw resources they want to connect.
How It Works
- Your app redirects the user to the 1Claw grant page:
https://1claw.xyz/connect/{your-slug}/grant?connection={connection_id} - The user selects which vaults and agents to share.
- Your backend can query the grants to discover what access it has.
API
Grant resources (user-authenticated, 1ck_ key):
curl -X POST "https://api.1claw.xyz/v1/platform/connections/CONNECTION_ID/grant" \
-H "Authorization: Bearer 1ck_USER_KEY" \
-H "Content-Type: application/json" \
-d '{
"vault_ids": ["vault-uuid-1", "vault-uuid-2"],
"agent_ids": ["agent-uuid-1"]
}'
List active grants:
curl "https://api.1claw.xyz/v1/platform/connections/CONNECTION_ID/grants" \
-H "Authorization: Bearer 1ck_USER_KEY"
Revoke a grant:
curl -X DELETE "https://api.1claw.xyz/v1/platform/connections/CONNECTION_ID/grants/GRANT_ID" \
-H "Authorization: Bearer 1ck_USER_KEY"
SDK
// User-authenticated client (1ck_ key)
const userClient = new OneclawClient({ apiKey: "1ck_user_key" });
// Grant access
const { data } = await userClient.platform.grantAccess(connectionId, {
vault_ids: ["vault-uuid"],
agent_ids: ["agent-uuid"],
});
// List grants
const { data: grants } = await userClient.platform.listGrants(connectionId);
// Revoke
await userClient.platform.revokeGrant(connectionId, grantId);
Dashboard
Users can manage grants from Settings → Connected Apps — each app shows shared resource counts with expandable grant panels and per-grant revoke buttons.
Resource grants are always user-initiated. Platform operators cannot grant themselves access — only the connected user can share their resources. Grants are instantly revocable.
Platform Audit
Track all platform-related events for your app:
curl "https://api.1claw.xyz/v1/platform/apps/APP_ID/audit" \
-H "Authorization: Bearer plt_YOUR_KEY"
Returns platform.* audit events (app creation, user provisioning, bootstrap, template changes).
Current Limitations
- Delegated token exchange (RFC 8693
DelegatedTokenRequest) is defined but not yet wired. Platform operators cannot issue delegated JWTs on behalf of connected users. plt_keys can see user metadata but cannot directly access user signing keys (GET /v1/agents/{id}/signing-keys). The org boundary prevents cross-org reads. Use the user's agent token or wait for delegated tokens.
Security
- OIDC audience enforcement: Platform apps can set
oidc_audienceto restrict which JWT audiences are accepted during OIDC user provisioning. When set, JWTs with a mismatchedaudclaim are rejected. - JWKS SSRF prevention: The
oidc_jwks_urlfield is validated against private CIDRs, cloud metadata endpoints, and localhost to prevent SSRF attacks. - Cross-org binding protection:
upsert_userenforces that the user belongs to the same org as the platform app.
SDK Usage
import { OneclawClient } from "@1claw/sdk";
const client = new OneclawClient({
baseUrl: "https://api.1claw.xyz",
apiKey: "plt_YOUR_KEY",
});
// Create a template
const template = await client.platform.createTemplate(appId, {
name: "default-template",
spec: {
vault: { name: "user-vault" },
agents: [{ name: "bot", intents: { enabled: true } }],
policies: [{ principal_ref: "agents.primary", vault_ref: "vault", paths: ["**"] }],
},
});
// Provision + bootstrap a user
const user = await client.platform.upsertUser({
email: "user@example.com",
external_subject: "tg:12345",
});
const result = await client.platform.bootstrapUser(user.data.connection_id, {
template_id: template.data.id,
});
console.log("Claim URL:", result.data.claim_url);
console.log("Agent ID:", result.data.summary.agent_id);
console.log("Agent API Key:", result.data.summary.agent_api_key); // one-time — store securely