Skip to main content

Platform API (Multi-Tenant)

The Platform API lets you build products on top of 1claw. You register a platform app, define a bootstrap template, and provision 1claw resources (vaults, agents, policies, signing keys) for each of your end users. Your users get secure key management and signing without you building the infrastructure.

Requirements

The Platform API requires a Pro or higher subscription. The dashboard shows an upgrade prompt if you are on the free tier.

How it works​

Your SaaS                          1claw Platform API             End user
| | |
| POST /v1/platform/apps | |
| (register your app) | |
| --------------------------------> | |
| | |
| POST /v1/platform/users/upsert | |
| (provision a user) | |
| --------------------------------> | |
| | |
| POST /v1/platform/ | |
| connections/{id}/bootstrap | |
| (apply template: vault + agent | |
| + policies + signing keys) | |
| --------------------------------> | |
| | |
| <-- claim_url, agent_api_key --- | |
| | |
| Send claim URL to end user ---> | ----------------------> |
| | User claims resources |
  1. Register a platform app and get a plt_ API key.
  2. Create a bootstrap template that defines what each user gets (vault, agent, policies, signing keys).
  3. When a user signs up for your product, provision them via the Platform API.
  4. Bootstrap applies the template: creates a vault, agent, access policies, and optionally signing keys.
  5. The user claims the resources via a one-time URL (or silently, depending on auth mode).

Step 1: Register a platform app​

APP=$(curl -s -X POST https://api.1claw.co/v1/platform/apps \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "My Trading Platform",
"slug": "my-trading-platform",
"description": "Automated trading platform built on 1claw",
"billing_model": "platform_pays",
"auth_mode": "silent"
}')

PLT_KEY=$(echo "$APP" | jq -r '.api_key')
APP_ID=$(echo "$APP" | jq -r '.id')
echo "Platform key: $PLT_KEY" # save this, shown once

Auth modes​

ModeBehavior
silentUser is provisioned without interaction. Your OIDC token is sufficient.
user_signinUser must sign in to 1claw to claim resources.
configurableYou choose per connection.

Billing models​

ModelWho pays
platform_paysYour org's subscription covers all user resources (default).
user_paysEach end user's subscription covers their resources.
hybridYou pay the base; users pay overages.

Step 2: Create a bootstrap template​

Templates define what gets created for each user:

curl -X POST "https://api.1claw.co/v1/platform/apps/$APP_ID/templates" \
-H "Authorization: Bearer $PLT_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "trader-setup",
"description": "Standard trader setup with vault, agent, and Ethereum signing key",
"spec": {
"vault": {
"name": "{{user_name}} Trading Vault",
"description": "Secrets and keys for automated trading"
},
"agents": [
{
"name": "{{user_name}} Trading Bot",
"description": "Automated trading agent",
"intents": { "enabled": true },
"shroud_enabled": true,
"signing_keys": [
{ "chain": "ethereum" },
{ "chain": "solana" }
]
}
],
"policies": [
{
"vault_ref": 0,
"principal_ref": "agent:0",
"paths": ["keys/*", "config/*"],
"permissions": ["read"]
}
]
}
}'

The template spec supports:

FieldDescription
vaultName and description for the user's vault
agents[]Array of agents to create (each with optional Intents API, Shroud, signing keys)
agents[].signing_keys[]Per-chain signing keys provisioned at bootstrap (ethereum, bitcoin, solana, xrp, cardano, tron)
agents[].provision_eoaSet true to generate a standalone EOA for client-side smart account deployment
policies[]Access policies linking vaults, agents, and paths

Per-agent capability flags that a template can set:

FieldDescription
intents_api_enabled / intentsIntents API. Accepts true, "true", or { "enabled": true }
execution_intents_enabled / executionExecution Intents
shroud_enabled, shroud_configShroud LLM proxy
memory_enabled / memoryAgent memory
raw_signing_enabledRaw/blind digest signing (sign_digest, eip712_digest)
message_signing_enabledpersonal_sign message signing
environment, environment_lockedEnvironment pinning
default_llm_model, system_promptLLM defaults

What a template cannot set​

More than one agent in agents[]: a template declaring two or more is refused with 400. See One agent per call below for why.

Spend caps, signing policies and the other guardrails (tx_max_value, tx_daily_limit, raw_signing_policy, pay_*, card_*, usdc_daily_limit_usd, delegation_enabled, …) cannot be set from a template spec. Including one returns 400 naming the field.

That refusal is deliberate. A template spec is stored as-is, so a guardrail it named used to be accepted and then silently absent on the provisioned agent — handing back an agent with no cap to a caller who believed it was capped. Failing loudly at template-write time is the safe direction.

Set them per agent after bootstrap, using the agent_ids in the bootstrap summary:

await platform.agents.update(bootstrap.data.summary.agent_ids[0], {
tx_max_value: "0.1",
raw_signing_policy: "approve",
});

Adding agents after bootstrap​

Bootstrap runs once per connection. A second call returns 409 Connection has already been bootstrapped, because bootstrap also creates the vault, the policies and the plan tier, and none of those should happen twice.

To add an agent to a tenant later, post a template to the connection:

const { data } = await platform.platform.addConnectionAgents(connectionId, {
template_id: templateId,
params: { account: "0xabc..." },
});
// data.agent_ids, data.signing_keys
// data.agent_api_keys[0].api_key — returned once, never retrievable again

One agent per call. A template declaring more than one agent is refused with 400, both when the template is written and when it is used. The reason is the API key: it is returned once and cannot be re-issued to a platform app, because rotate_agent_key only works for a caller in the agent's own org and your plt_ key is not. A response carrying several keys would put several unrecoverable credentials in one payload, so one leaked log line would take out every agent in the batch with no way to rotate any of them.

For N agents, use N single-agent templates and call once per agent. Each call then gets its own params, its own Idempotency-Key, and its own key handed back cleanly.

Only the agent part of the template is applied: its agents and their per-agent signing_keys, eoa and runtime. A vault, policies, automations or entitlements block in the same spec is ignored, and the new agents attach to the vault the connection already has. That is why the configuration comes from a template rather than from fields on the request: the agent portion of a spec has one reader, shared with bootstrap, so a capability cannot work in one and not the other.

The number of agents on a connection is capped by max_agents_per_connection on the platform app. Unset means a built-in default rather than unlimited, because an agent provisioned with signing keys costs a KMS key per chain. The ceiling is checked before anything is created, and the refusal names the current count and the limit.

The call is idempotent on Idempotency-Key: a retry replays the original response rather than provisioning a second agent, and a key reused with different parameters is refused with 409.

Step 3: Provision users​

When a user signs up for your product:

// 1. Upsert the user
const connection = await platformClient.platform.upsertUser({
email: user.email,
});

// 2. Bootstrap their resources from the template
const bootstrap = await platformClient.platform.bootstrapUser(
connection.data.connection_id,
{ template_name: "trader-setup" },
);

console.log("Vault ID:", bootstrap.data.summary.vault_id);
console.log("Agent ID:", bootstrap.data.summary.agent_id);
console.log("Agent API key:", bootstrap.data.summary.agent_api_key); // one-time
console.log("Signing keys:", bootstrap.data.summary.signing_keys);
// signing_keys: [{ chain: "ethereum", address: "0x...", public_key: "..." }, ...]

// 3. Send claim URL to the user (if auth_mode is "user_signin")
if (bootstrap.data.claim_url) {
await sendEmail(user.email, {
subject: "Claim your trading account",
body: `Click here to claim: ${bootstrap.data.claim_url}`,
});
}

OIDC user provisioning​

If your users already authenticate through an OIDC provider, pass their JWT directly:

const connection = await platformClient.platform.upsertUser({
subject_token: userJwt, // validated against your app's oidc_jwks_url
});

Configure oidc_jwks_url and oidc_issuer on your platform app to enable JWT validation.

Step 4: Use the bootstrapped agent​

Once bootstrapped, the agent API key works like any other 1claw agent:

import { createClient } from "@1claw/sdk";

// Client using the bootstrapped agent's API key
const tradingAgent = createClient({
baseUrl: "https://api.1claw.co",
apiKey: bootstrapResult.summary.agent_api_key,
});

// Submit a trade
const tx = await tradingAgent.agents.submitTransaction(agentId, {
chain: "ethereum",
to: "0xUniswapRouter...",
value: "0",
data: swapCalldata,
simulate_first: true,
});

Custody guarantee​

When platform_locked = true (set automatically during bootstrap), the platform operator (you) cannot read secret values. You can manage lifecycle (create, delete, rotate) but cannot access the raw key material. This protects your end users' secrets from your own backend.

Claim flow​

For user_signin mode, the end user visits the claim URL:

  1. The claim page shows your app name, provisioned resources (vaults, agents, policies), and a custody guarantee
  2. The user clicks "Claim Resources"
  3. The connection status changes from pending to claimed

If the claim token expires (10 minutes), reissue it:

const reissue = await platformClient.platform.reissueClaim(connectionId);
console.log("New claim URL:", reissue.data.claim_url);

User grants (resource sharing)​

After claiming, users can grant your platform app access to specific vaults and agents:

// User grants access to their vault
const grant = await userClient.platform.grantAccess(connectionId, {
vault_ids: [vaultId],
agent_ids: [agentId],
allowed_paths: ["config/*"],
permissions: ["read"],
});

Users can revoke grants at any time from the dashboard (Settings > Connected Apps).

Dashboard​

The Platform section in the dashboard provides:

  • App management: create, edit, delete platform apps
  • Template editor: visual template builder (no raw JSON needed)
  • Connected users: list of provisioned users with claim status
  • Audit log: platform-specific events (platform.user_provisioned, platform.claim_redeemed, etc.)

Further reading​