---
name: clawkey
version: 1.0.0
description: Verifiable human ownership for OpenClaw agents. Register your agent under your human owner via VeryAI palm verification.
homepage: https://clawkey.ai
metadata: { "category": "identity", "api_base": "https://api.clawkey.ai/v1" }
---

# ClawKey — Verifiable Human Ownership

Register your agent under your human owner powered by VeryAI palm verification. The agent starts registration with a signed challenge showing it controls its key; the human opens a URL and completes VeryAI's palm verification. Once the human finishes, the agent is registered under their ownership and can be verified by third parties.

> **CRITICAL: Never open browsers**
>
> Do **not** use `open`, `xdg-open`, `start`, or any command that opens a browser. Always output the **registration URL** as a text or markdown link for the user to click.
>
> - WRONG: `open "https://api.very.org/oauth2/..."`
> - CORRECT: "Open this link to complete verification: https://api.very.org/oauth2/..."

## Key files

| File         | Purpose                                                                                   |
| ------------ | ----------------------------------------------------------------------------------------- |
| SKILL.md     | https://clawkey.ai/skill.md — registration flow, challenge generation, verification, API  |
| HEARTBEAT.md | https://clawkey.ai/heartbeat.md — periodic checklist: registration status, etc            |
| Identity     | `~/.openclaw/identity/device.json` — OpenClaw deviceId and keys (do not send private key) |

## Security

- **Private key**: Used to sign the challenge. Never send the private key to ClawKey or any server; only send `publicKey`, `message`, and `signature`.
- **Registration URL**: Single-use and short-lived. Give it only to the human owner who will complete VeryAI palm verification.
- **deviceId**: Use a stable identifier (e.g. from your identity store or hash of public key). It ties the agent to the registration and is used for lookup and verification.

## Generating the AgentChallenge

The **AgentChallenge** is a signed payload that shows you control an Ed25519 key. It has the shape used by standard OpenClaw identity flows: `deviceId`, `publicKey`, `message`, `signature`, `timestamp`. Generate it once and send it to `/agent/register/init` or `/agent/verify/signature`.

### Where identity comes from (OpenClaw)

If you run on OpenClaw, device identity is stored at:

- **Path:** `~/.openclaw/identity/device.json`

That file contains (never send `privateKeyPem` to any server):

| Field           | Use                                                                      |
| --------------- | ------------------------------------------------------------------------ |
| `deviceId`      | Use as `deviceId` in the challenge. Stable id for this agent/device.     |
| `publicKeyPem`  | Use to derive `publicKey` (see below).                                   |
| `privateKeyPem` | Use only locally to sign the message. **Never include in API requests.** |

If you don’t use OpenClaw, use your own identity store; ensure you have a stable `deviceId`, an Ed25519 key pair, and that you sign the **exact** string you send as `message`.

### Build the challenge (step-by-step)

1. **Choose the message to sign**  
   For registration, use a one-time challenge to avoid replay, e.g.:
   - `clawkey-register-<unix_timestamp_ms>`  
     Example: `clawkey-register-1738500000000`  
     For verify/signature, the message is whatever you are proving (e.g. a nonce from a third party).

2. **Sign the message** with your Ed25519 **private** key. The signature must be over the **exact** UTF-8 bytes of `message` (no extra prefix/suffix).

3. **Encode for the API:**
   - **publicKey**: Ed25519 public key in **SPKI DER** form, then **base64** (no PEM wrapper).
   - **signature**: Raw Ed25519 signature bytes, **base64**.
   - **timestamp**: Unix time in **milliseconds** when the challenge was created (e.g. `Date.now()`).

4. **JSON body (AgentChallenge):**
   - `deviceId` — from your identity (e.g. `device.json`)
   - `publicKey` — base64 DER SPKI
   - `message` — exact string that was signed
   - `signature` — base64 signature
   - `timestamp` — number (ms)

### Example: Node.js

```javascript
const crypto = require("crypto");
const fs = require("fs");

const identityPath = `${process.env.HOME}/.openclaw/identity/device.json`;
const identity = JSON.parse(fs.readFileSync(identityPath, "utf8"));

const message = `clawkey-register-${Date.now()}`;
const privateKey = crypto.createPrivateKey(identity.privateKeyPem);
const signature = crypto.sign(null, Buffer.from(message, "utf8"), privateKey);

const publicKeyDer = crypto
  .createPublicKey(identity.publicKeyPem)
  .export({ type: "spki", format: "der" });

const challenge = {
  deviceId: identity.deviceId,
  publicKey: publicKeyDer.toString("base64"),
  message,
  signature: signature.toString("base64"),
  timestamp: Date.now(),
};
// POST challenge to https://api.clawkey.ai/v1/agent/register/init
```

### Using a script

If you have a script that already produces an AgentChallenge (e.g. signs a message and outputs JSON with `deviceId`, `publicKey`, `message`, `signature`, `timestamp`), you can reuse it for ClawKey:

1. Generate a challenge string, e.g. `clawkey-register-$(date +%s)000` (seconds + "000" for ms) or use your script’s convention.
2. Run the script to sign that message and get the challenge JSON.
3. POST that JSON to `https://api.clawkey.ai/v1/agent/register/init`.

Same challenge format works for `POST /agent/verify/signature` when verifying a signature remotely.

## Quick start

### 1. Start registration (agent-initiated)

Build an **AgentChallenge** as above, then send it to ClawKey to create a session and get a registration URL.

```bash
curl -X POST https://api.clawkey.ai/v1/agent/register/init \
  -H "Content-Type: application/json" \
  -d '{
    "deviceId": "my-agent-device-id",
    "publicKey": "<base64-DER-SPKI-Ed25519>",
    "message": "clawkey-register-1738500000000",
    "signature": "<base64-Ed25519-signature>",
    "timestamp": 1738500000000
  }'
```

**Response (201):**

- `sessionId` — use to poll status
- `registrationUrl` — **output this as a link for the human; do not open it in a browser**
- `expiresAt` — session expiry (ISO 8601)

If the agent is already registered (`deviceId` exists), the API returns **409 Conflict**.

### 2. Human completes verification

Tell the human owner to open the `registrationUrl` in their browser. They will go through VeryAI's palm verification via OAuth. When they finish, the agent is registered under their ownership.

### 3. Poll registration status

Poll until the human has completed or the session has expired:

```bash
curl "https://api.clawkey.ai/v1/agent/register/SESSION_ID/status"
```

**Response:** `status` is one of `pending` | `completed` | `expired` | `failed`. When `status` is `completed`, the response includes `deviceId` and `registration` (e.g. `publicKey`, `registeredAt`).

### 4. Verify signatures or look up an agent

- **Verify a signature** — check that a message was signed by the given key and whether that agent is registered under a verified human:

```bash
curl -X POST https://api.clawkey.ai/v1/agent/verify/signature \
  -H "Content-Type: application/json" \
  -d '{
    "deviceId": "...",
    "publicKey": "...",
    "message": "...",
    "signature": "...",
    "timestamp": 1738500000000
  }'
```

Response: `verified` (signature valid), `registered` (agent under verified human).

- **Look up an agent by device id** — get registration and verification status:

```bash
curl "https://api.clawkey.ai/v1/agent/verify/device/DEVICE_ID"
```

Response: `registered`, `verified`, and optionally `registeredAt`.

## API reference

**Base URL:** `https://api.clawkey.ai/v1`  
**Local:** `http://localhost:3000/v1`

### Endpoints

| Method | Endpoint                             | Auth | Description                                                                      |
| ------ | ------------------------------------ | ---- | -------------------------------------------------------------------------------- |
| POST   | `/agent/register/init`               | None | Start registration session; returns `sessionId`, `registrationUrl`, `expiresAt`. |
| GET    | `/agent/register/{sessionId}/status` | None | Poll registration status: `pending` / `completed` / `expired` / `failed`.        |
| POST   | `/agent/verify/signature`            | None | Verify a signature and whether the agent is registered under a verified human.   |
| GET    | `/agent/verify/device/{deviceId}`    | None | Get agent registration and verification status by device id.                     |

### Request/response schemas

**AgentChallenge** (used in register/init and verify/signature):

| Field     | Type   | Required | Description                                              |
| --------- | ------ | -------- | -------------------------------------------------------- |
| deviceId  | string | yes      | Key/device id (e.g. public key hash or app id).          |
| publicKey | string | yes      | Ed25519 public key, base64 DER SPKI.                     |
| message   | string | yes      | Exact message that was signed (e.g. challenge or nonce). |
| signature | string | yes      | Ed25519 signature over message, base64.                  |
| timestamp | int64  | yes      | Unix timestamp (ms) when the challenge was created.      |

**Register init response (201):**

```json
{
  "sessionId": "uuid",
  "registrationUrl": "https://clawkey.ai/register/...",
  "expiresAt": "2026-02-02T12:00:00Z"
}
```

**Register status response (200):**

```json
{
  "status": "completed",
  "deviceId": "my-agent-device-id",
  "registration": {
    "publicKey": "...",
    "registeredAt": "2026-02-02T12:00:00Z"
  }
}
```

**Verify signature response (200):**

```json
{
  "verified": true,
  "registered": true
}
```

**Device status response (200):**

```json
{
  "registered": true,
  "verified": true,
  "registeredAt": "2026-02-02T12:00:00Z"
}
```

**Error (4xx/5xx):**

```json
{
  "error": "Human-readable message",
  "code": "optional_code",
  "details": {}
}
```

### Error codes

| Code | Meaning                                              |
| ---- | ---------------------------------------------------- |
| 400  | Bad request (invalid or missing fields).             |
| 404  | Session or device not found.                         |
| 409  | Agent already registered (device_id already exists). |
| 500  | Server error.                                        |

## What this proves

After registration and VeryAI verification:

- **Human ownership** — The agent is bound to a human who passed palm verification.
- **Key binding** — Ed25519 signatures prove the agent controls the key; ClawKey ties that key to the verified human.
- **Public verification** — Third parties can call `/agent/verify/signature` or `/agent/verify/device/{deviceId}` to confirm an agent is registered and verified.

## When to use this skill

- Registering an OpenClaw (or other) agent under a human owner.
- Proving to a third party that an agent is owned by a verified human (e.g. before granting access or privileges).
- Checking whether a given key or device is registered and verified.

## Need help?

- API base: https://api.clawkey.ai/v1
- Homepage: https://clawkey.ai
