ClearPact API

ERC-8183 compliant job escrow for AI agents. Immutable infrastructure — no admin can freeze, decide, or upgrade.

API v2.0 · SDK v0.3.0 Updated May 16, 2026

Quick Start

Get from zero to your first escrow in 3 steps. No signup form, no OAuth. Just an email and an API call.

Self-hosting prerequisite: Run npm run migrate before starting the server. Without it, /api/job returns HTTP 500 (missing jobs / job_events tables).

1

Get an API Key

Create a key with your email. You'll get a key that starts with cpk_live_. Save it — it's only shown once.

curl
curl -X POST https://clearpact.polsia.app/api/keys \
  -H "Content-Type: application/json" \
  -d '{"email": "dev@example.com", "name": "My First Key"}'
2

Create an Escrow

Lock USDC between two parties with conditions for release.

curl
curl -X POST https://clearpact.polsia.app/api/escrow \
  -H "Content-Type: application/json" \
  -H "X-API-Key: cpk_live_YOUR_KEY_HERE" \
  -d '{
    "payer": "0xAliceWallet",
    "payee": "0xBobWallet",
    "amount": "250.00",
    "token": "USDC",
    "conditions": [
      {"type": "task_completion", "description": "Complete data analysis"}
    ]
  }'
3

Settle When Conditions Are Met

Verify conditions and release funds to the payee.

curl
curl -X POST https://clearpact.polsia.app/api/escrow/ESCROW_ID/settle \
  -H "Content-Type: application/json" \
  -H "X-API-Key: cpk_live_YOUR_KEY_HERE" \
  -d '{
    "verifications": {"0": {"completed": true}},
    "actor": "0xAliceWallet"
  }'

The Quick Start uses testnet (Base Sepolia) by default — free and instant. Ready for production? See Network Selection to switch to Base mainnet with a single parameter change.

Try It: Generate an API Key

Enter your email to get a live API key right now.

Save this key now. It won't be shown again.

Key ID:

⚡ Try it Live

Create a real testnet escrow right here — no terminal required. Paste your API key from the generator above, then click Create.

📊 Try it Live — Key Usage Logs

Inspect your own API call history. Paste your key and key_id below.

🔔 Try it Live — Webhook Deliveries

Inspect delivery attempts for a webhook. Paste your key and webhook UUID.

Network Selection

ClearPact supports both Base Sepolia testnet and Base mainnet. Use testnet for prototyping and CI. Use mainnet for production workloads with real USDC.

ParameterTestnet (default)Mainnet
network value"testnet""mainnet"
Chain ID84532 (Base Sepolia)8453 (Base)
USDC contract0x036CbD53842c5426634e7929541eC2318f3dCF7e0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Get test fundsAlchemy FaucetReal USDC required

How to specify the network

Pass "network" in any create/fund/settle escrow request body. Omit it for testnet (default).

# Testnet (default — omit network or pass "testnet")
curl -X POST https://clearpact.polsia.app/api/escrow \
  -H "X-API-Key: cpk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"payer": "0xAlice", "payee": "0xBob", "amount": "100.00"}'

# Mainnet — add "network": "mainnet"
curl -X POST https://clearpact.polsia.app/api/escrow \
  -H "X-API-Key: cpk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"payer": "0xAlice", "payee": "0xBob", "amount": "100.00", "network": "mainnet"}'
// Testnet (default)
const escrow = await fetch("https://clearpact.polsia.app/api/escrow", {
  method: "POST",
  headers: { "X-API-Key": "cpk_live_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ payer: "0xAlice", payee: "0xBob", amount: "100.00" })
}).then(r => r.json());

// Mainnet — same call, add network field
const prodEscrow = await fetch("https://clearpact.polsia.app/api/escrow", {
  method: "POST",
  headers: { "X-API-Key": "cpk_live_YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ payer: "0xAlice", payee: "0xBob", amount: "100.00", network: "mainnet" })
}).then(r => r.json());
import requests

HEADERS = {"X-API-Key": "cpk_live_YOUR_KEY"}

# Testnet (default)
escrow = requests.post(
    "https://clearpact.polsia.app/api/escrow",
    headers=HEADERS,
    json={"payer": "0xAlice", "payee": "0xBob", "amount": "100.00"}
).json()

# Mainnet — add network="mainnet"
prod_escrow = requests.post(
    "https://clearpact.polsia.app/api/escrow",
    headers=HEADERS,
    json={"payer": "0xAlice", "payee": "0xBob", "amount": "100.00", "network": "mainnet"}
).json()

Default is testnet. If you omit network, all requests use Base Sepolia. Explicitly pass "network": "mainnet" for production transactions.

Authentication

All escrow endpoints require an API key passed in the X-API-Key header. Key management endpoints (/api/keys) are public.

API Key Format: Keys start with cpk_live_ followed by 48 hex characters. Example: cpk_live_a1b2c3d4e5f6...

# Every request to /api/escrow/* must include:
X-API-Key: cpk_live_your_key_here

Protected endpoints (require X-API-Key):

MethodEndpointDescription
GET/api/escrowList escrows
POST/api/escrowCreate escrow
GET/api/escrow/:idGet escrow details
POST/api/escrow/:id/settleSettle escrow
POST/api/x402/verifyVerify x402 payment authorization
POST/api/x402/settleSettle x402 payment on-chain

Public endpoints (no auth needed):

MethodEndpointDescription
POST/api/keysCreate API key
GET/api/keysList your keys
DELETE/api/keys/:key_idRevoke a key
POST/api/keys/:key_id/rotateRotate a key
GET/api/x402/healthx402 facilitator status

Rate Limits

Each API key has independent rate limits. Default limits:

LimitDefaultHeader
Per minute60 requestsX-RateLimit-Limit
Per day10,000 requests-
Remaining-X-RateLimit-Remaining

When rate limited, responses include a Retry-After header (in seconds). Response code: 429.

Create API Key

POST /api/keys

Generate a new API key. Maximum 5 active keys per email.

REQUEST BODY

FieldTypeRequiredDescription
emailstringYesYour email address
namestringNoHuman-readable label (default: "Default")
curl -X POST https://clearpact.polsia.app/api/keys \
  -H "Content-Type: application/json" \
  -d '{"email": "dev@example.com", "name": "Production"}'
const res = await fetch("https://clearpact.polsia.app/api/keys", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    email: "dev@example.com",
    name: "Production"
  })
});
const { key } = await res.json();
console.log("Save this key:", key);
import requests

res = requests.post("https://clearpact.polsia.app/api/keys", json={
    "email": "dev@example.com",
    "name": "Production"
})
data = res.json()
print(f"Save this key: {data['key']}")

RESPONSE (201)

{
  "success": true,
  "message": "API key created. Save it now — it won't be shown again.",
  "key": "cpk_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6",
  "key_id": "cp_1a2b3c4d5e6f7g8h",
  "key_prefix": "cpk_live_a1b2c3",
  "name": "Production",
  "permissions": ["escrow:read", "escrow:write", "escrow:settle"],
  "rate_limits": { "per_minute": 60, "per_day": 10000 },
  "created_at": "2026-04-09T10:00:00.000Z"
}

List API Keys

GET /api/keys?email=you@example.com

List all API keys associated with your email. Keys are returned without the secret value.

curl "https://clearpact.polsia.app/api/keys?email=dev@example.com"
const res = await fetch(
  "https://clearpact.polsia.app/api/keys?email=dev@example.com"
);
const { keys } = await res.json();
res = requests.get("https://clearpact.polsia.app/api/keys", params={
    "email": "dev@example.com"
})
keys = res.json()["keys"]

RESPONSE (200)

{
  "success": true,
  "keys": [
    {
      "key_id": "cp_1a2b3c4d5e6f7g8h",
      "key_prefix": "cpk_live_a1b2c3",
      "name": "Production",
      "is_active": true,
      "last_used_at": "2026-04-09T10:30:00.000Z",
      "requests_today": 42,
      "created_at": "2026-04-09T10:00:00.000Z"
    }
  ],
  "count": 1
}

Revoke API Key

DELETE /api/keys/:key_id

Immediately revoke an API key. All future requests with this key will be rejected.

curl -X DELETE https://clearpact.polsia.app/api/keys/cp_1a2b3c4d5e6f7g8h \
  -H "Content-Type: application/json" \
  -d '{"email": "dev@example.com"}'
await fetch("https://clearpact.polsia.app/api/keys/cp_1a2b3c4d5e6f7g8h", {
  method: "DELETE",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email: "dev@example.com" })
});

Rotate API Key

POST /api/keys/:key_id/rotate

Revoke the old key and generate a new one with the same settings. Zero-downtime key rotation.

curl -X POST https://clearpact.polsia.app/api/keys/cp_1a2b3c4d5e6f7g8h/rotate \
  -H "Content-Type: application/json" \
  -d '{"email": "dev@example.com"}'
const res = await fetch(
  "https://clearpact.polsia.app/api/keys/cp_1a2b3c4d5e6f7g8h/rotate",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email: "dev@example.com" })
  }
);
const { key } = await res.json();
// Update your config with the new key

GET /api/keys/:key_id/usage

GET /api/keys/:key_id/usage

List API requests made by a key — timestamps, paths, status codes, latency. Auth: the calling X-API-Key must match :key_id. Rate-limited at 60 req/min.

QUERY PARAMETERS

ParamTypeDefaultDescription
fromISO 86017 days agoStart of date range
toISO 8601nowEnd of date range
statusstringFilter by status code or class: 200, 2xx, 4xx, 5xx
endpointstringPrefix filter: /escrow matches all escrow paths
limitint50Max results (max 500)
cursorstringOpaque cursor from next_cursor
curl "https://clearpact.polsia.app/api/keys/cp_YOUR_KEY_ID/usage?status=4xx&limit=50" \
  -H "X-API-Key: cpk_live_YOUR_KEY"
const res = await fetch(
  "https://clearpact.polsia.app/api/keys/cp_YOUR_KEY_ID/usage?status=4xx&limit=50",
  { headers: { "X-API-Key": "cpk_live_YOUR_KEY" } }
);
const { data, next_cursor, summary } = await res.json();

// SDK equivalent:
const { data, next_cursor, summary } = await client.keys.usage("cp_YOUR_KEY_ID", {
  status: "4xx", limit: 50
});
import requests

res = requests.get(
    "https://clearpact.polsia.app/api/keys/cp_YOUR_KEY_ID/usage",
    headers={"X-API-Key": "cpk_live_YOUR_KEY"},
    params={"status": "4xx", "limit": 50}
)
data = res.json()

RESPONSE (200)

{
  "data": [
    {
      "request_id": "req_1234",
      "method": "POST",
      "path": "/api/escrow",
      "status_code": 200,
      "latency_ms": 87,
      "created_at": "2026-05-05T09:11:23Z"
    }
  ],
  "next_cursor": null,
  "summary": {
    "count": 150,
    "by_status_class": { "2xx": 143, "4xx": 7 },
    "by_endpoint": { "/api/escrow": 90, "/api/escrow/:id": 60 }
  }
}

List Escrows

GET /api/escrow

List escrows for the authenticated API key with optional filtering and pagination.

Query Parameters

ParameterTypeDescription
statusstringFilter by escrow status: pending_funding, funded, active, awaiting_verification, settling, settled, cancelled, refunded, expired, expiry_failed. Supports comma-separated multiple values. See Escrow Status Values for descriptions.
networkstringFilter by network: testnet or mainnet. Supports comma-separated multiple values.
created_afterstringISO 8601 date — only escrows created on or after this date.
created_beforestringISO 8601 date — only escrows created on or before this date.
limitintegerNumber of results per page. Default: 20, max: 100.
offsetintegerNumber of results to skip. Default: 0. Use with limit for pagination.
sortstringSort field: created_at (default), updated_at, amount, status.
orderstringSort direction: desc (default) or asc.

Response

{
  "success": true,
  "escrows": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "network": "testnet",
      "payer": "0xPayer...",
      "payee": "0xPayee...",
      "amount": 500,
      "token": "USDC",
      "status": "settled",
      "conditions": [...],
      "created_at": "2025-04-01T12:00:00Z",
      "settled_at": "2025-04-02T15:30:00Z",
      "tx_hashes": { "create": "0x...", "settle": "0x..." }
    }
  ],
  "pagination": {
    "total": 42,
    "limit": 20,
    "offset": 0,
    "has_more": true
  }
}

Examples

List all escrows (newest first)
curl https://clearpact.polsia.app/api/escrow \
  -H "X-API-Key: cpk_live_YOUR_KEY_HERE"
Filter by status and network
curl "https://clearpact.polsia.app/api/escrow?status=settled&network=testnet" \
  -H "X-API-Key: cpk_live_YOUR_KEY_HERE"
Paginate — page 2, 10 per page
curl "https://clearpact.polsia.app/api/escrow?limit=10&offset=10" \
  -H "X-API-Key: cpk_live_YOUR_KEY_HERE"
Date range — last 7 days
curl "https://clearpact.polsia.app/api/escrow?created_after=2025-04-10&sort=created_at&order=asc" \
  -H "X-API-Key: cpk_live_YOUR_KEY_HERE"
Active escrows only
curl "https://clearpact.polsia.app/api/escrow?status=active,funded,pending_funding" \
  -H "X-API-Key: cpk_live_YOUR_KEY_HERE"
Node.js
const res = await fetch(
  "https://clearpact.polsia.app/api/escrow?status=settled&limit=20",
  { headers: { 'X-API-Key': key } }
);
const { escrows, pagination } = await res.json();
// pagination.total tells you the total count
// pagination.has_more tells you if more pages exist
Python
resp = requests.get(
    "https://clearpact.polsia.app/api/escrow",
    params={"status": "settled", "limit": "20"},
    headers={"X-API-Key": key}
)
data = resp.json()
escrows = data["escrows"]

Create Escrow

POST /api/escrow

Create a new escrow contract between two parties. Requires X-API-Key header.

REQUEST BODY

FieldTypeRequiredDescription
payerstringYesPayer wallet address or identifier
payeestringYesPayee wallet address or identifier
amountstring/numberYesAmount in token units (e.g. "250.00")
tokenstringNoToken symbol (default: "USDC")
networkstringNo"testnet" (default) or "mainnet" — see Network Selection
conditionsarrayNoSettlement conditions (see Condition Types)
metadataobjectNoArbitrary metadata (project name, etc.)
expires_atstringNoISO 8601 expiration timestamp
curl -X POST https://clearpact.polsia.app/api/escrow \
  -H "Content-Type: application/json" \
  -H "X-API-Key: cpk_live_YOUR_KEY" \
  -d '{
    "payer": "0xAlice",
    "payee": "0xBob",
    "amount": "500.00",
    "token": "USDC",
    "conditions": [
      {"type": "task_completion", "description": "Ship v2.0 release"},
      {"type": "approval", "approver": "0xAlice"}
    ],
    "metadata": {"project": "agent-audit"},
    "expires_at": "2026-12-31T00:00:00Z"
  }'
const res = await fetch("https://clearpact.polsia.app/api/escrow", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "cpk_live_YOUR_KEY"
  },
  body: JSON.stringify({
    payer: "0xAlice",
    payee: "0xBob",
    amount: "500.00",
    conditions: [
      { type: "task_completion", description: "Ship v2.0" },
      { type: "approval", approver: "0xAlice" }
    ]
  })
});

const { escrow } = await res.json();
console.log("Escrow ID:", escrow.id);
import requests

res = requests.post(
    "https://clearpact.polsia.app/api/escrow",
    headers={"X-API-Key": "cpk_live_YOUR_KEY"},
    json={
        "payer": "0xAlice",
        "payee": "0xBob",
        "amount": "500.00",
        "conditions": [
            {"type": "task_completion", "description": "Ship v2.0"}
        ]
    }
)
escrow = res.json()["escrow"]
print(f"Escrow ID: {escrow['id']}")

RESPONSE (201)

{
  "success": true,
  "network": "testnet",
  "escrow": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "network": "testnet",
    "payer": "0xAlice",
    "payee": "0xBob",
    "amount": 500,
    "token": "USDC",
    "status": "pending_funding",
    "conditions": [
      {"type": "task_completion", "description": "Ship v2.0 release"},
      {"type": "approval", "approver": "0xAlice"}
    ],
    "metadata": {"project": "agent-audit"},
    "created_at": "2026-04-09T10:00:00.000Z",
    "expires_at": "2026-12-31T00:00:00.000Z"
  },
  "blockchain": {
    "network": "testnet",
    "chain_id": 84532,
    "contract_address": "0x7CDB80e9B154c99354d66604103fAEb148c6f5A8",
    "onchain_escrow_id": 43,
    "onchain_status": "pending_funding",  // contract enum 0 — just created
    "create_tx_hash": "0xabc1...",
    "asset_address": "0x036C...",
    "explorer": "https://sepolia.basescan.org/tx/0xabc1...",
    "explorer_contract": "https://sepolia.basescan.org/address/0xe1E4..."
  },
  "erc8004": null
}

New escrows always start with status: "pending_funding" and blockchain.onchain_status: "pending_funding". See Escrow Status Values and On-Chain Mapping for the full lifecycle.

Get Escrow

GET /api/escrow/:id

Retrieve escrow details and full event audit trail.

curl https://clearpact.polsia.app/api/escrow/550e8400-e29b-41d4-a716-446655440000 \
  -H "X-API-Key: cpk_live_YOUR_KEY"
const res = await fetch(
  "https://clearpact.polsia.app/api/escrow/550e8400-...",
  { headers: { "X-API-Key": "cpk_live_YOUR_KEY" } }
);
const { escrow, events } = await res.json();
res = requests.get(
    "https://clearpact.polsia.app/api/escrow/550e8400-...",
    headers={"X-API-Key": "cpk_live_YOUR_KEY"}
)
data = res.json()

RESPONSE (200)

{
  "success": true,
  "network": "testnet",
  "escrow": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "network": "testnet",
    "payer": "0xAlice",
    "payee": "0xBob",
    "amount": 500,
    "token": "USDC",
    "status": "funded",            // all possible values
    "conditions": [{"type": "task_completion"}],
    "metadata": {},
    "created_at": "2026-04-18T10:00:00.000Z",
    "updated_at": "2026-04-18T10:05:00.000Z",
    "funded_at": "2026-04-18T10:05:00.000Z",
    "settled_at": null,
    "expires_at": "2026-04-25T10:00:00.000Z",
    "tx_hashes": {
      "create": "0x1a2b...",
      "fund": "0x3c4d...",
      "settle": null,
      "refund": null
    }
  },
  "blockchain": {
    "network": "testnet",
    "chain_id": 84532,
    "contract_address": "0xe1E4...0DB0",
    "onchain_escrow_id": 42,
    "onchain_status": "funded",
    "tx_hashes": { "create": "0x1a2b...", "fund": "0x3c4d...", "settle": null, "refund": null },
    "explorer": "https://sepolia.basescan.org/tx/0x3c4d..."
  },
  "events": [
    {
      "event_type": "created",
      "actor": "0xAlice",
      "details": {"amount": 500, "token": "USDC"},
      "created_at": "2026-04-18T10:00:00.000Z"
    },
    {
      "event_type": "funded",
      "actor": "0xAlice",
      "details": {"tx_hash": "0x3c4d...", "amount": 500},
      "created_at": "2026-04-18T10:05:00.000Z"
    }
  ]
}

Settle Escrow

POST /api/escrow/:id/settle

Verify all conditions and release funds. All conditions must be met for settlement.

REQUEST BODY

FieldTypeRequiredDescription
verificationsobjectYesMap of condition index to verification data
actorstringNoWho is triggering the settlement
curl -X POST https://clearpact.polsia.app/api/escrow/ESCROW_ID/settle \
  -H "Content-Type: application/json" \
  -H "X-API-Key: cpk_live_YOUR_KEY" \
  -d '{
    "verifications": {
      "0": {"completed": true},
      "1": {"approved": true, "approver": "0xAlice"}
    },
    "actor": "0xOracle"
  }'
const res = await fetch(
  `https://clearpact.polsia.app/api/escrow/${escrowId}/settle`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": "cpk_live_YOUR_KEY"
    },
    body: JSON.stringify({
      verifications: {
        "0": { completed: true },
        "1": { approved: true, approver: "0xAlice" }
      },
      actor: "0xOracle"
    })
  }
);

const { settlement } = await res.json();
console.log("Settled:", settlement.amount, settlement.token);
res = requests.post(
    f"https://clearpact.polsia.app/api/escrow/{escrow_id}/settle",
    headers={"X-API-Key": "cpk_live_YOUR_KEY"},
    json={
        "verifications": {
            "0": {"completed": True},
            "1": {"approved": True, "approver": "0xAlice"}
        },
        "actor": "0xOracle"
    }
)
settlement = res.json()["settlement"]

RESPONSE — SUCCESS (200)

{
  "success": true,
  "message": "Escrow settled. 500 USDC released to 0xBob",
  "escrow": { "status": "settled", /* ... */ },
  "settlement": {
    "amount": 500,
    "token": "USDC",
    "from": "0xAlice",
    "to": "0xBob",
    "conditions": [
      {"index": 0, "type": "task_completion", "met": true},
      {"index": 1, "type": "approval", "met": true}
    ],
    "settled_at": "2026-04-09T12:00:00.000Z"
  }
}

RESPONSE — CONDITIONS NOT MET (422)

{
  "success": false,
  "message": "Not all conditions are met",
  "conditions": [
    {"index": 0, "type": "task_completion", "met": true},
    {"index": 1, "type": "approval", "met": false}
  ]
}

ERC-8004 Automatic Settlement

Phase 2 adds a validation adapter that automatically settles escrows when an ERC-8004-compatible proof is received. No human needs to call /settle.

How it works: Create an escrow with external_job_id. When the job is verified, submit a validation signal via POST /api/validation/submit. The adapter finds the matching escrow and calls settleEscrow() on-chain within seconds.

Flow

1

Create escrow with external_job_id

POST /api/escrow
{
  "payer": "0xAlice",
  "payee": "0xBob",
  "amount": 100,
  "external_job_id": "job_abc123"
}
// Response includes erc8004: { external_job_id, status: "linked" }
2

Fund the escrow

POST /api/escrow/ESCROW_ID/fund
// status: pending_funding → funded
3

Submit validation signal

POST /api/validation/submit
{
  "external_job_id": "job_abc123",
  "proof_hash": "0x...",
  "result": "success"
}
4

Settlement happens automatically

The adapter processes the signal (within 5 seconds), calls settleEscrow() on-chain, and the payee receives USDC. No manual action required.

GET /api/escrow/ESCROW_ID
// escrow.status: "settled"
// escrow.tx_hashes.settle: "0x..."

POST /api/validation/submit

Submit an ERC-8004-compatible validation signal. Triggers automatic settlement if result = "success" and the linked escrow is funded.

FieldTypeRequiredDescription
external_job_idstringYesMatches the escrow's external_job_id
proof_hashstringNo0x-prefixed keccak256 of proof payload
resultstringNo"success" (default) or "failure"
metadataobjectNoArbitrary proof metadata
curl
curl -X POST https://clearpact.polsia.app/api/validation/submit \
  -H "Content-Type: application/json" \
  -H "X-API-Key: cpk_live_YOUR_KEY" \
  -d '{
    "external_job_id": "job_abc123",
    "proof_hash": "0xabc...",
    "result": "success",
    "metadata": {"validator": "oracle-v1"}
  }'

RESPONSE (202 Accepted)

{
  "success": true,
  "message": "Validation received. Settlement will process automatically.",
  "record": {
    "id": "uuid...",
    "external_job_id": "job_abc123",
    "proof_hash": "0xabc...",
    "result": "success",
    "status": "pending",
    "escrow_id": "uuid..."
  }
}

GET /api/validation/:id

Check the processing status of a validation record.

curl
curl https://clearpact.polsia.app/api/validation/RECORD_ID \
  -H "X-API-Key: cpk_live_YOUR_KEY"

RECORD STATUS VALUES

StatusMeaning
pendingReceived, queued for processing
processingAdapter is running settlement now
settledEscrow settled — settle_tx_hash populated
rejectedDuplicate signal or already-settled escrow
failedError during settlement — check error_message

Replay Protection

The adapter prevents double-settlement via two mechanisms:

  1. Database constraint: UNIQUE(external_job_id, proof_hash) — submitting the same signal twice returns 409 Conflict.
  2. Status check: Before calling settleEscrow(), the adapter checks the escrow is not already settled, cancelled, or expired. Already-settled escrows return status: "rejected" on the validation record.

Deprecation notice: POST /api/escrow/:id/settle is deprecated. It still works during the 7-day drainage period (sunset: 2026-05-23) but emits X-Deprecated: true headers. Migrate to POST /api/job/:id/complete (v2) or POST /api/validation/submit (ERC-8004 auto-settlement).

x402 Protocol

ClearPact is a native x402 facilitator — compatible with Coinbase's open agentic payment protocol (35M+ transactions). x402 lets AI agents authorize USDC payments using EIP-712 typed-data signatures, with no gas wallet required on the payer side.

How ClearPact bridges x402 into escrow: A payer agent signs a TransferWithAuthorization payload (ERC-3009). ClearPact verifies the signature off-chain (/verify), then submits USDC.transferWithAuthorization() on Base Sepolia (/settle). If the payer has an active escrow, settlement automatically funds it. Two-layer replay protection prevents double-spend.

Testnet (default): Base Sepolia (chain 84532) · USDC: 0x036CbD53842c5426634e7929541eC2318f3dCF7e
Mainnet: Base (chain 8453) · USDC: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Pass "network": "mainnet" in x402 verify/settle calls to operate on Base mainnet.

GET /api/x402/health

Check facilitator status. Returns the current chain config and feature flags. No API key required.

curl
curl https://clearpact.polsia.app/api/x402/health

RESPONSE (200)

{
  "status": "ok",
  "phase": "1B — verify + on-chain settlement",
  "networks": {
    "testnet": {
      "chain_id": 84532,
      "network": "base-sepolia",
      "usdc_contract": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
      "settle_enabled": true
    },
    "mainnet": {
      "chain_id": 8453,
      "network": "base",
      "usdc_contract": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "settle_enabled": true
    }
  },
  "features": {
    "verify": true,
    "settle": true
  }
}

POST /api/x402/verify

POST /api/x402/verify

Verify an x402 payment authorization. Accepts an EIP-712 TransferWithAuthorization payload. Checks the signature, validates time window and amount, runs two-layer replay protection (in-memory nonce + DB constraint), and logs the attempt. Returns the verification result and a transaction_id for use with /settle.

REQUEST BODY

FieldTypeRequiredDescription
fromstringYesPayer wallet address (EIP-55 checksum)
tostringYesRecipient wallet address
valuestringYesTransfer amount in USDC base units (6 decimals)
validAfterstringYesUnix timestamp — signature not valid before this
validBeforestringYesUnix timestamp — signature expires after this
noncestringYes32-byte hex nonce (must be unique per payer address)
signaturestringYesEIP-712 signature (0x-prefixed, 65 bytes)
networkstringNo"testnet" (default) or "mainnet"
curl -X POST https://clearpact.polsia.app/api/x402/verify \
  -H "Content-Type: application/json" \
  -H "X-API-Key: cpk_live_YOUR_KEY" \
  -d '{
    "from": "0xAgentWalletAddress",
    "to": "0xRecipientAddress",
    "value": "1000000",
    "validAfter": "0",
    "validBefore": "9999999999",
    "nonce": "0xabc123...32bytes",
    "signature": "0xsig..."
  }'
const res = await fetch("https://clearpact.polsia.app/api/x402/verify", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "cpk_live_YOUR_KEY"
  },
  body: JSON.stringify({
    from: "0xAgentWalletAddress",
    to: "0xRecipientAddress",
    value: "1000000",          // 1 USDC = 1_000_000 (6 decimals)
    validAfter: "0",
    validBefore: "9999999999",
    nonce: "0xabc123...32bytes",
    signature: "0xsig..."
  })
});

const { verified, transaction_id, escrow_id } = await res.json();
import requests

res = requests.post(
    "https://clearpact.polsia.app/api/x402/verify",
    headers={"X-API-Key": "cpk_live_YOUR_KEY"},
    json={
        "from": "0xAgentWalletAddress",
        "to": "0xRecipientAddress",
        "value": "1000000",  # 1 USDC
        "validAfter": "0",
        "validBefore": "9999999999",
        "nonce": "0xabc123...32bytes",
        "signature": "0xsig..."
    }
)
data = res.json()
transaction_id = data["transaction_id"]

RESPONSE — VERIFIED (200)

{
  "success": true,
  "verified": true,
  "transaction_id": "uuid-of-x402-record",
  "recovered_address": "0xAgentWalletAddress",
  "escrow_id": "uuid-of-linked-escrow",   // null if no active escrow
  "amount_usdc": "1.000000"
}

RESPONSE — INVALID SIGNATURE (422)

{
  "success": false,
  "verified": false,
  "error": "invalid_signature",
  "message": "Recovered address does not match 'from'"
}

POST /api/x402/settle

POST /api/x402/settle

Execute an on-chain USDC settlement via transferWithAuthorization() (ERC-3009). Supports two call modes: Mode A — pass a transaction_id from a prior /verify call; Mode B — pass the full x402 payload for a single-call verify + settle. On success, returns the on-chain tx hash and block number. If the payer has an active escrow, it is automatically marked funded.

REQUEST BODY

FieldTypeModeDescription
transaction_idstringAUUID from a prior /verify response
fromstringBPayer wallet address
tostringBRecipient wallet address
valuestringBAmount in USDC base units (6 decimals)
validAfterstringBUnix timestamp — valid after
validBeforestringBUnix timestamp — valid before
noncestringB32-byte hex nonce
signaturestringBEIP-712 signature
# Mode A — settle a pre-verified transaction
curl -X POST https://clearpact.polsia.app/api/x402/settle \
  -H "Content-Type: application/json" \
  -H "X-API-Key: cpk_live_YOUR_KEY" \
  -d '{"transaction_id": "uuid-from-verify-response"}'

# Mode B — verify + settle in one call
curl -X POST https://clearpact.polsia.app/api/x402/settle \
  -H "Content-Type: application/json" \
  -H "X-API-Key: cpk_live_YOUR_KEY" \
  -d '{
    "from": "0xAgentWalletAddress",
    "to": "0xRecipientAddress",
    "value": "1000000",
    "validAfter": "0",
    "validBefore": "9999999999",
    "nonce": "0xabc123...32bytes",
    "signature": "0xsig..."
  }'
// Mode A — settle pre-verified tx
const res = await fetch("https://clearpact.polsia.app/api/x402/settle", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "cpk_live_YOUR_KEY"
  },
  body: JSON.stringify({ transaction_id: "uuid-from-verify" })
});

const { settlement_tx_hash, block_number, escrow_id } = await res.json();
# Mode A — settle pre-verified tx
res = requests.post(
    "https://clearpact.polsia.app/api/x402/settle",
    headers={"X-API-Key": "cpk_live_YOUR_KEY"},
    json={"transaction_id": "uuid-from-verify"}
)
data = res.json()
tx_hash = data["settlement_tx_hash"]
block   = data["block_number"]

RESPONSE — SETTLED (200)

{
  "success": true,
  "settlement_tx_hash": "0xabc123...on-chain tx",
  "block_number": 19842301,
  "amount_usdc": "1.000000",
  "escrow_id": "uuid-of-funded-escrow",   // null if no linked escrow
  "escrow_status": "funded"
}

x402 SETTLE ERROR CODES

Error CodeHTTPDescription
gas_estimation_failed502Could not estimate gas — network congestion or invalid payload
nonce_already_used409This nonce has already been used for this payer address
insufficient_usdc_balance422Payer does not hold enough USDC to cover the transfer
tx_reverted502On-chain transaction was submitted but reverted
authorization_expired422Current time is outside the validAfter/validBefore window
invalid_signature422EIP-712 signature recovery failed or address mismatch
transaction_not_found404No x402 record found for the given transaction_id

Contract Addresses

ClearPact deploys escrow contracts independently per network. Use the correct contract address for explorer links and direct on-chain interaction.

Base Sepolia — Testnet (Phase 4 v2)

ContractAddressExplorer / Verification
ClearPactJob proxy 0x7CDB80e9B154c99354d66604103fAEb148c6f5A8 BaseScan ↗
ClearPactEvaluator proxy 0x1DDefFED6a9e28C37e1E10c292F6774D837a7Ab6 BaseScan ↗
ClearPactJob impl 0xf901FAE0851a78156b6952753D266E1151798a94 Sourcify FULL MATCH ↗
ClearPactEvaluator impl 0x12c34A6EAeaE5016B9420801CBf13B4b5b7b3c95 Sourcify FULL MATCH ↗
USDC 0x036CbD53842c5426634e7929541eC2318f3dCF7e BaseScan ↗
Chain ID84532
Faucetalchemy.com/faucets/base-sepolia

Base — Mainnet

ContractAddressExplorer
ClearPactEscrow Deployment in progress — wallet funding required
USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 BaseScan ↗
Chain ID8453

API responses include contract_address, chain_id, and BaseScan explorer links in the escrow object so you never need to hardcode these values.

Condition Types

Conditions define what must be true before funds are released. Each escrow can have multiple conditions — all must be met for settlement.

task_completion

Requires explicit confirmation that work is done.

Condition: {"type": "task_completion"}

Verification: {"completed": true}

approval

Requires sign-off from a specific approver address.

Condition: {"type": "approval", "approver": "0x..."}

Verification: {"approved": true, "approver": "0x..."}

deadline

Automatically met if current time is before the deadline.

Condition: {"type": "deadline", "deadline": "2026-12-31T..."}

Verification: None needed (auto-checked)

threshold

A numeric value must meet or exceed a minimum.

Condition: {"type": "threshold", "min_value": 100}

Verification: {"value": 150}

oracle

An external oracle must confirm the outcome.

Condition: {"type": "oracle"}

Verification: {"oracle_confirmed": true}

custom

Any custom condition type. Requires explicit met flag.

Condition: {"type": "my_custom_check"}

Verification: {"met": true}

Error Codes

All errors return JSON with success: false and a descriptive error code.

HTTPError CodeDescription
400invalid_emailEmail is missing or malformed
400invalid_escrow_idEscrow ID is not a valid UUID
401missing_api_keyX-API-Key header not provided
401invalid_api_keyAPI key not recognized
401invalid_api_key_formatKey doesn't match cpk_ format
401api_key_revokedKey has been revoked
401api_key_expiredKey has expired
404escrow_not_foundNo escrow with that ID
404key_not_foundAPI key not found or already revoked
409already_settledEscrow is already settled/expired/cancelled
422conditions_not_metNot all conditions verified — includes details per condition
429rate_limit_exceededToo many requests per minute
429daily_limit_exceededDaily request limit reached

ERROR RESPONSE FORMAT

{
  "success": false,
  "error": "missing_api_key",
  "message": "API key required. Pass your key in the X-API-Key header.",
  "docs": "https://clearpact.polsia.app/docs#authentication"
}

Escrow Status Values

Every escrow has a status field that reflects its position in the lifecycle. Build your integration to handle all statuses — webhook payloads will deliver any of these values.

LIFECYCLE (NON-TERMINAL)

StatusDescriptionTriggered byValid transitionsWebhook event
pending_funding Created on-chain, awaiting USDC deposit POST /api/escrow funded, cancelled, expired escrow.created
funded USDC deposited on-chain, awaiting settlement or verification POST /api/escrow/:id/fund or x402 settle settled, refunded, expired, expiry_failed escrow.funded
awaiting_verification Funds held on-chain, waiting for ERC-8004 verification signal On-chain state (smart contract enum 2) settled, refunded, expired
active Created without conditions — funds locked, ready to settle POST /api/escrow (empty conditions) settled, refunded, expired escrow.created
settling Settlement transaction in progress (transient, typically <5 s) x402 settle flow settled

TERMINAL STATES

StatusDescriptionTriggered byWebhook event
settled Conditions met, funds released to payee on-chain ERC-8004 auto-settlement or POST /api/escrow/:id/settle escrow.settled
cancelled Cancelled before funding — no funds moved POST /api/escrow/:id/cancel (unfunded escrow) escrow.cancelled
refunded Funded escrow cancelled — USDC returned to payer on-chain POST /api/escrow/:id/cancel (funded escrow) escrow.cancelled
expired Past expires_at deadline, auto-refunded by expiry worker Expiry worker (runs automatically) escrow.expired
expiry_failed Expiry attempted but on-chain refund failed — requires manual review Expiry worker (on-chain refund error) escrow.expired

Legacy status: Escrows created before on-chain support may have status pending. This is equivalent to pending_funding and is accepted by settle/cancel endpoints. New escrows always start as pending_funding.

TYPICAL LIFECYCLE

  Happy path:
  pending_funding → funded → settled

  With ERC-8004 verification:
  pending_funding → funded → awaiting_verification → settled

  Cancellation (unfunded):
  pending_funding → cancelled

  Cancellation (funded — triggers on-chain refund):
  funded → refunded

  Expiry (auto-refund):
  funded → expired
  pending_funding → expired

  Expiry failure (on-chain refund failed):
  funded → expiry_failed

Job Status Mapping (v2 ERC-8183)

ClearPact v2 uses the ERC-8183 JobStatus enum — exactly 6 values. The v1 escrow API (drainage period) is mapped below for consumers migrating from legacy routes.

v2 Contract (immutable): 0x7CDB80e9B154c99354d66604103fAEb148c6f5A8 (Base Sepolia) — enum JobStatus { Open, Funded, Submitted, Completed, Rejected, Expired }

v2 JobStatus (ERC-8183) — 6 values

v2 status Enum value Description Valid transitions from
Open0 Job created; awaiting budget and funding Initial state after createJob()
Funded1 Budget deposited on-chain Open → Funded via fund()
Submitted2 Provider submitted deliverable; awaiting evaluation Funded → Submitted via submit()
Completed3 Evaluator or client approved; payment released to provider Submitted → Completed via complete()
Rejected4 Result rejected; symmetric refund from Funded/Submitted (E3) Open/Funded/Submitted → Rejected via reject()
Expired5 Past expiredAt; refund claimed by client Funded/Submitted → Expired via claimRefund()

v1 → v2 Status Mapping (migration reference)

v1 status (legacy) v2 status (ERC-8183) Notes
PendingFunding (0) / pending Open Job created but unfunded
Funded (1) / active Funded Budget locked on-chain
AwaitingVerification (2) Submitted Work delivered, pending evaluation
Settled (3) / completed Completed Payment released to provider
Cancelled (5) Rejected Rejected with symmetric refund
Refunded (4) / expired Expired Refund claimed after expiry
settling, expiry_failed (no direct mapping) Transient backend-only states, no v2 equivalent

ERC-8183 State Machine

STATE MACHINE — ERC-8183 JobStatus
createJob() ──► Open
├─ setBudget() + fund() ──► Funded
│ └─ submit() ──────────► Submitted
│ ├─ complete() ───────► Completed ✓ payment released
│ └─ reject() ─────────► Rejected ↩ refund
│ └─ post-expiredAt: claimRefund() ─► Expired ↩ refund
└─ reject() ────────────────────► Rejected (from Open, no refund needed)
Decision 6: complete() + reject() callable by evaluator OR client at Submitted.
E3: reject() from Funded/Submitted triggers symmetric automatic refund.
claimRefund guarantee: unconditional, non-hookable, non-pausable (PAUSER renounced).

Immutability Commitment

ClearPact is pure immutable escrow infrastructure: no admin can freeze, no admin can decide, no admin can upgrade.

At Phase 4 deploy (May 2026), ClearPact executed a 5-renounce sequence across both proxy contracts — permanently removing all administrative control. This is verifiable on-chain.

5-Renounce Sequence (Phase 4)

Role renounced Contract Effect
PAUSER_ROLEClearPactJob No one can pause the contract. claimRefund() is always available.
OPERATOR_ROLEClearPactJob No operator can intervene in job lifecycle.
EVALUATOR_ROLEClearPactEvaluator ClearPact cannot appoint evaluators. Self-service only (Decision 9).
DEFAULT_ADMIN_ROLEClearPactJob No admin functions callable. Contract parameters immutable.
DEFAULT_ADMIN_ROLEClearPactEvaluator Evaluator router permanently immutable.

Self-Service Evaluator Pattern (Decision 9)

Each job creator specifies their own evaluator EOA at createJob(). ClearPact does not appoint or arbitrate evaluators. The evaluator EOA calls complete() or reject() directly via the Job API — the Evaluator contract is not required.

// Self-service: job creator picks their evaluator at creation
const { job } = await client.jobs.create(
  '0xProviderWallet',
  '0xEvaluatorEOA',   // YOUR evaluator — could be a DAO, a multisig, or yourself
  '2026-06-01T00:00:00Z',
  'Build and deploy landing page'
);

// Evaluator calls complete() directly (no Evaluator contract needed)
await client.jobs.complete(job.id, 'Work accepted — link verified');

Competitor Comparison

Platform Admin freeze surface Admin evaluation surface Admin upgrade surface
ClearPact None (PAUSER renounced) None (EVALUATOR renounced) None (DEFAULT_ADMIN renounced)
Virtuals Admin pause present Platform arbitrates Upgradeable, admin held
MeshLedger Timelocked pause DAO override possible Timelocked upgrade
PayCrow Admin pause present Platform decides disputes Proxy admin held

Migration v1 → v2

v1 deprecation: v1 /api/escrow routes entered 7-day drainage on 2026-05-16. Sunset: 2026-05-23. Migrate all integrations to /api/job before sunset. After sunset, v1 routes will return HTTP 410 Gone.

The 7-day drainage period (v1 routes remain active) gives existing consumers time to migrate. After drainage, v1 routes are sunset.

Route Changes

v1 routev2 routeNotes
POST /api/escrowPOST /api/job5 positional args: provider, evaluator, expiredAt, description, hook
GET /api/escrow/:idGET /api/job/:idReturns Job struct (9 fields)
POST /api/escrow/:id/fundPOST /api/job/:id/fundIdentical semantics
POST /api/escrow/:id/settlePOST /api/job/:id/completecomplete() = approveResult (D7)
POST /api/escrow/:id/cancelPOST /api/job/:id/rejectE3: symmetric refund from Funded/Submitted. Response: {"success":true, "job":{...}, "refund": null | {"status":"automatic", "note":"Symmetric refund..."}}. refund:null when previousStatus=Open; automatic when Funded/Submitted. Emits job.refunded with {jobId, reason:"reject_symmetric_refund", from_status}.
(new)POST /api/job/:id/budgetsetBudget with optional token + conditionRef
(new)POST /api/job/:id/submitProvider submits deliverable
(new)POST /api/job/:id/claim-refundUnconditional post-expiredAt refund

Webhook Event Changes

v1 eventv2 eventDuring drainage
escrow.createdjob.createdBoth emitted
escrow.fundedjob.fundedBoth emitted
escrow.releasedjob.completed + job.paymentReleasedBoth emitted
escrow.disputedjob.rejectedBoth emitted
escrow.expiredjob.expiredBoth emitted
(new)job.submittedv2 only
(new)job.refundedv2 only
(new)job.metadatav2 only (ClearPactJobMetadata)
(new)job.tokenSetv2 only (ClearPactJobTokenSet)

SDK Changes

SDK v0.3.0 (breaking in types, non-breaking in runtime during drainage):

  • client.jobs — new namespace with 8 lifecycle methods + 1 view
  • client.escrow — deprecated alias (warns on construction), removed in v0.4.0
  • JobStatus — exactly 6 values: Open|Funded|Submitted|Completed|Rejected|Expired
  • Job type — 9 ERC-8183 fields (replaces EscrowObject)
  • ClearPact.contracts — static property with deployed addresses

How ClearPact Extends ERC-8183

Hooks (IACPHook)

Jobs can optionally specify a hook contract at createJob(). The hook receives beforeAction() / afterAction() callbacks on lifecycle transitions. Hooks must implement IERC165 and be whitelisted. Gas cap: 100k per hook call. claimRefund() is non-hookable — the unconditional refund guarantee cannot be blocked by a hook.

Per-Job Payment Tokens

By default, jobs use the global USDC address set at contract initialization (0x036CbD53842c5426634e7929541eC2318f3dCF7e on Base Sepolia). Callers can override the token per-job at setBudget() time by passing token. Emits job.tokenSet webhook event.

Conditional Reference Hash (conditionRef)

Pass conditionRef at setBudget() to attach a bytes32 conditional reference hash (e.g. IPFS CID of the conditions document). Emits ClearPactJobMetadata event (mapped to job.metadata webhook). This event is also emitted at createJob() with conditionRef=0 (Phase 2bis Fix #1).

Symmetric Refund on Reject (E3)

reject() implements a 3-path ACL: callable from Open, Funded, or Submitted state. When called from Funded or Submitted, it automatically returns funds to the client — no separate claimRefund() required. This is the symmetric refund guarantee (Phase 2bis Fix #3).

Webhooks

Receive real-time notifications when escrow events occur. Register a URL, choose which events to subscribe to, and ClearPact will POST signed JSON payloads to your endpoint.

Delivery guarantees: Every webhook is retried up to 3 times with exponential backoff (5 s → 30 s → 5 min). Each delivery attempt is logged in your webhook delivery history. Payloads are signed with HMAC-SHA256 so you can verify authenticity.

Register Webhook

POST /api/webhooks

Register a new webhook endpoint. Returns a one-time signing_secret for HMAC verification. Max 10 webhooks per API key.

REQUEST BODY

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint that will receive webhook payloads
eventsstring[]YesEvent types to subscribe to. 19 available — see Webhook Events for the full list (12 v2 job.* + 7 v1 escrow.* during drainage).
curl -X POST https://clearpact.polsia.app/api/webhooks \
  -H "Content-Type: application/json" \
  -H "X-API-Key: cpk_live_YOUR_KEY" \
  -d '{
    "url": "https://your-app.com/webhooks/clearpact",
    "events": [
      "escrow.created",
      "escrow.funded",
      "escrow.settled",
      "escrow.cancelled",
      "escrow.expired"
    ]
  }'
const res = await fetch("https://clearpact.polsia.app/api/webhooks", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "cpk_live_YOUR_KEY"
  },
  body: JSON.stringify({
    url: "https://your-app.com/webhooks/clearpact",
    events: [
      "escrow.created",
      "escrow.funded",
      "escrow.settled",
      "escrow.cancelled",
      "escrow.expired"
    ]
  })
});
const { signing_secret } = await res.json();
// Store signing_secret securely — shown only once
res = requests.post(
    "https://clearpact.polsia.app/api/webhooks",
    headers={"X-API-Key": "cpk_live_YOUR_KEY"},
    json={
        "url": "https://your-app.com/webhooks/clearpact",
        "events": [
            "escrow.created",
            "escrow.funded",
            "escrow.settled",
            "escrow.cancelled",
            "escrow.expired"
        ]
    }
)
data = res.json()
signing_secret = data["signing_secret"]

RESPONSE (201)

{
  "success": true,
  "webhook": {
    "id": "wh_550e8400-e29b-41d4-a716-446655440000",
    "url": "https://your-app.com/webhooks/clearpact",
    "events": ["escrow.created", "escrow.funded", "escrow.settled", "escrow.cancelled", "escrow.expired"],
    "created_at": "2026-04-18T10:00:00.000Z"
  },
  "signing_secret": "whsec_abc123...shown_only_once"
}

List Webhooks

GET /api/webhooks

List all webhooks registered under your API key.

curl
curl https://clearpact.polsia.app/api/webhooks \
  -H "X-API-Key: cpk_live_YOUR_KEY"

RESPONSE (200)

{
  "success": true,
  "webhooks": [
    {
      "id": "wh_550e8400-e29b-41d4-a716-446655440000",
      "url": "https://your-app.com/webhooks/clearpact",
      "events": ["escrow.created", "escrow.funded", "escrow.settled", "escrow.cancelled", "escrow.expired"],
      "created_at": "2026-04-18T10:00:00.000Z"
    }
  ]
}

Delete Webhook

DELETE /api/webhooks/:id

Remove a registered webhook. Stops all future deliveries to that URL.

curl
curl -X DELETE https://clearpact.polsia.app/api/webhooks/WEBHOOK_ID \
  -H "X-API-Key: cpk_live_YOUR_KEY"

RESPONSE (200)

{
  "success": true,
  "message": "Webhook deleted"
}

GET /api/webhooks/:id/deliveries

GET /api/webhooks/:id/deliveries

List delivery attempts for a webhook. Returns attempt outcome, response status, and body preview for each delivery. Scoped to the authenticated key — you can only inspect your own webhooks.

QUERY PARAMETERS

ParamTypeDefaultDescription
fromISO 86017 days agoStart of date range
toISO 8601nowEnd of date range
statusstringsuccess | failed | pending
event_typestringFilter by event: escrow.funded, etc.
limitint50Max results (max 500)
cursorstringOpaque cursor from next_cursor
curl "https://clearpact.polsia.app/api/webhooks/WEBHOOK_UUID/deliveries?status=failed&limit=20" \
  -H "X-API-Key: cpk_live_YOUR_KEY"
const res = await fetch(
  "https://clearpact.polsia.app/api/webhooks/WEBHOOK_UUID/deliveries?status=failed",
  { headers: { "X-API-Key": "cpk_live_YOUR_KEY" } }
);
const { data, next_cursor } = await res.json();

// SDK equivalent:
const { data, next_cursor } = await client.webhooks.deliveries("WEBHOOK_UUID", {
  status: "failed", event_type: "escrow.funded"
});
import requests

res = requests.get(
    "https://clearpact.polsia.app/api/webhooks/WEBHOOK_UUID/deliveries",
    headers={"X-API-Key": "cpk_live_YOUR_KEY"},
    params={"status": "failed", "limit": 20}
)
data = res.json()

RESPONSE (200)

{
  "data": [
    {
      "delivery_id": "whd_550e8400-...",
      "event_id": "evt_a1b2c3d4-...",
      "event_type": "escrow.funded",
      "attempt_number": 2,
      "response_status": 200,
      "response_body_preview": "{\"ok\":true}",
      "duration_ms": null,
      "attempted_at": "2026-05-05T09:11:25Z",
      "next_attempt_at": null,
      "final_status": "delivered"
    }
  ],
  "next_cursor": null
}

FINAL STATUS VALUES

StatusMeaning
deliveredEndpoint returned 2xx
pendingIn-flight or scheduled for retry
failedAll retry attempts exhausted (3 total)

Webhook Events

19 event types are available (12 v2 job events + 7 v1 escrow events during drainage). Subscribe to any combination when registering a webhook.

Event nameTriggered byPayload shape
job.createdPOST /api/jobfull Job
job.metadatacreateJob (conditionRef=0) + setBudget (conditionRef≠0){jobId, conditionRef, description}
job.tokenSetsetBudget non-default token{jobId, token}
job.providerSetsetProviderfull Job
job.budgetSetsetBudgetfull Job
job.fundedfundfull Job
job.submittedsubmitfull Job
job.completedcompletefull Job
job.paymentReleasedcomplete post-transition{jobId, amount, token}
job.rejectedrejectfull Job
job.refundedreject from Funded/Submitted OR claimRefund{jobId, reason, from_status}
job.expiredauto-expire OR claimRefundfull Job
escrow.createdv1 dual-emit during drainagefull Job
escrow.fundedv1 dual-emit during drainagefull Job
escrow.releasedv2 dual-emit at complete (renamed from escrow.settled)full Job
escrow.disputedv2 dual-emit at rejectfull Job
escrow.expiredv1 dual-emit during drainagefull Job
escrow.settledlegacy v1 — NOT emitted by v2 (registrable for backward-compat)
escrow.cancelledlegacy v1 — NOT emitted by v2 (registrable for backward-compat)

Payload Format

Every webhook delivery is an HTTP POST with a JSON body. The envelope is consistent across all event types:

{
  "id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "escrow.created",
  "created_at": "2026-04-18T10:00:00.000Z",
  "data": { /* escrow object — same shape as GET /api/escrow/:id */ }
}

EXAMPLE: escrow.created

{
  "id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "escrow.created",
  "created_at": "2026-04-18T10:00:00.000Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "pending",
    "payer": "0xAlice",
    "payee": "0xBob",
    "amount": 500,
    "token": "USDC",
    "network": "testnet",
    "conditions": [{"type": "task_completion"}],
    "created_at": "2026-04-18T10:00:00.000Z"
  }
}

EXAMPLE: escrow.funded

{
  "id": "evt_b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "type": "escrow.funded",
  "created_at": "2026-04-18T10:05:00.000Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "funded",
    "payer": "0xAlice",
    "payee": "0xBob",
    "amount": 500,
    "token": "USDC",
    "network": "testnet",
    "funded_at": "2026-04-18T10:05:00.000Z",
    "tx_hashes": {"fund": "0xabc123..."},
    "created_at": "2026-04-18T10:00:00.000Z"
  }
}

EXAMPLE: escrow.settled

{
  "id": "evt_c3d4e5f6-a7b8-9012-cdef-123456789012",
  "type": "escrow.settled",
  "created_at": "2026-04-18T10:10:00.000Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "settled",
    "payer": "0xAlice",
    "payee": "0xBob",
    "amount": 500,
    "token": "USDC",
    "network": "testnet",
    "settled_at": "2026-04-18T10:10:00.000Z",
    "tx_hashes": {"fund": "0xabc123...", "settle": "0xdef456..."},
    "created_at": "2026-04-18T10:00:00.000Z"
  }
}

EXAMPLE: escrow.cancelled

{
  "id": "evt_d4e5f6a7-b8c9-0123-defa-234567890123",
  "type": "escrow.cancelled",
  "created_at": "2026-04-18T10:15:00.000Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "cancelled",
    "payer": "0xAlice",
    "payee": "0xBob",
    "amount": 500,
    "token": "USDC",
    "network": "testnet",
    "cancelled_at": "2026-04-18T10:15:00.000Z",
    "created_at": "2026-04-18T10:00:00.000Z"
  }
}

EXAMPLE: escrow.expired

{
  "id": "evt_e5f6a7b8-c9d0-1234-efab-345678901234",
  "type": "escrow.expired",
  "created_at": "2026-04-18T12:00:00.000Z",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "expired",
    "payer": "0xAlice",
    "payee": "0xBob",
    "amount": 500,
    "token": "USDC",
    "network": "testnet",
    "expires_at": "2026-04-18T12:00:00.000Z",
    "tx_hashes": {"refund": "0x789abc..."},
    "created_at": "2026-04-18T10:00:00.000Z"
  }
}

Signature Verification

Every webhook payload includes an X-ClearPact-Signature header containing an HMAC-SHA256 hex digest of the raw request body. Always verify this before processing.

// Node.js verification example
const crypto = require('crypto');

function verifyWebhook(body, signature, secret) {
  const expected = 'sha256=' +
    crypto.createHmac('sha256', secret)
      .update(body, 'utf8')
      .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your Express handler:
app.post('/webhooks/clearpact', (req, res) => {
  const sig = req.headers['x-clearpact-signature'];
  if (!verifyWebhook(JSON.stringify(req.body), sig, SIGNING_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  // Process the event
  console.log(req.body.type, req.body.data.id);
  res.status(200).json({ received: true });
});

Important: Your endpoint must return a 2xx status within 10 seconds. Non-2xx responses or timeouts trigger a retry. After 3 failed attempts, the delivery is marked as failed in the delivery log.

JavaScript / TypeScript SDK

The official ClearPact SDK wraps the REST API in a clean, fully-typed client. Zero dependencies — uses native fetch.

Install

npm install clearpact
<script src="https://clearpact.polsia.app/sdk"></script>
<!-- Exposes window.ClearPact and window.ClearPactError globally -->

Quick Start

const { ClearPact } = require('clearpact');

const client = new ClearPact({
  apiKey:  'cpk_live_...',
  network: 'testnet',       // 'testnet' | 'mainnet'
});

// Create an escrow
const { escrow } = await client.escrow.create({
  payer:  '0xPayerWallet',
  payee:  '0xPayeeWallet',
  amount: 100,              // USDC
  conditions: [{ type: 'task_completion' }],
});

console.log(escrow.id, escrow.status);
// "3f2ac1d0-..." "pending_funding"

// Fund it
await client.escrow.fund(escrow.id, { tx_hash: '0x...' });

// Settle
await client.escrow.settle(escrow.id, {
  verifications: { task_completion: { completed: true } },
});
import { ClearPact } from 'clearpact';

const client = new ClearPact({ apiKey: 'cpk_live_...' });

const { escrow } = await client.escrow.create({
  payer:  '0xPayerWallet',
  payee:  '0xPayeeWallet',
  amount: 50,
});
import { ClearPact, ClearPactError } from 'clearpact';
import type { EscrowObject } from 'clearpact/types';

const client = new ClearPact({ apiKey: 'cpk_live_...' });

try {
  const { escrow }: { escrow: EscrowObject } =
    await client.escrow.create({
      payer:  '0xPayerWallet',
      payee:  '0xPayeeWallet',
      amount: 100,
    });
  console.log(escrow.id);
} catch (err) {
  if (err instanceof ClearPactError) {
    console.error(err.status, err.message);
  }
}

Escrow Methods

MethodDescription
client.escrow.create(params)Create a new escrow
client.escrow.get(id)Get status + audit trail
client.escrow.fund(id, params?)Record a funding event
client.escrow.settle(id, params?)Manually settle conditions
client.escrow.cancel(id, params?)Cancel or refund
// ERC-8004 auto-settlement — link escrow to an external job
const { escrow } = await client.escrow.create({
  payer:           '0xPayer',
  payee:           '0xPayee',
  amount:          500,
  external_job_id: 'job_abc123',   // POST /api/validation/submit triggers auto-settle
  conditions: [{ type: 'task_completion' }],
});

x402 Methods

MethodDescription
client.x402.verify(payload, opts?)Verify an x402 payment authorization
client.x402.settle(params?)Settle a verified payment on-chain
client.x402.health()Facilitator health + chain config
client.x402.listTransactions(filters?)List x402 transaction records
client.x402.getTransaction(id)Get a single transaction

Webhooks

MethodDescription
client.webhooks.create(params)Register a webhook URL + events
client.webhooks.list()List webhooks for this key
client.webhooks.delete(id)Remove a webhook
client.webhooks.deliveries(id, opts?)List delivery history for a webhook
// Register a webhook
const { signing_secret } = await client.webhooks.create({
  url:         'https://your-app.com/webhooks/clearpact',
  event_types: ['escrow.created', 'escrow.funded', 'escrow.settled', 'escrow.cancelled', 'escrow.expired'],
});
// Store signing_secret — shown only once. Verify X-ClearPact-Signature on incoming events.

// List webhooks
const { webhooks } = await client.webhooks.list();

// Inspect delivery history (debug failed deliveries)
const { data } = await client.webhooks.deliveries(webhooks[0].id, {
  status:     'failed',
  event_type: 'escrow.funded',
  limit:      20,
});
console.log(data[0].response_body_preview);

// Remove a webhook
await client.webhooks.delete(webhookId);

Keys / Observability

MethodDescription
client.keys.usage(keyId, opts?)List API usage logs for a key
// Inspect your own API usage (last 7 days)
const { data, summary } = await client.keys.usage('cp_YOUR_KEY_ID', {
  from:   '2026-05-01T00:00:00Z',
  status: '4xx',
  limit:  100,
});
console.log(`${summary.count} total requests`);
console.log('By status:', summary.by_status_class);
// { '2xx': 143, '4xx': 7 }

// Paginate with cursor
let cursor = null;
do {
  const page = await client.keys.usage('cp_YOUR_KEY_ID', { cursor, limit: 100 });
  for (const req of page.data) {
    console.log(req.path, req.status_code, req.latency_ms + 'ms');
  }
  cursor = page.next_cursor;
} while (cursor);

Error Handling

All API errors throw a ClearPactError with a typed shape:

import { ClearPact, ClearPactError } from 'clearpact';

try {
  await client.escrow.get('not-a-real-id');
} catch (err) {
  if (err instanceof ClearPactError) {
    err.status;   // 400
    err.message;  // "Invalid escrow ID format"
    err.error;    // machine-readable code (if present)
    err.errors;   // array of validation messages (if present)
    err.raw;      // full response body
  }
}

Changelog

Major milestones shipped, newest first.

MAY 2026
v2 Launch — ERC-8183 compliance + Immutability commitment

Phase 5 v2: Full ERC-8183 job lifecycle API (POST /api/job and 7 lifecycle sub-routes). 4 Phase 2bis fixes (E1: ClearPactJobMetadata re-emit; E2: global paymentToken; E3: 3-path reject + symmetric refund; P8: EvaluatorRejected). 5-renounce immutability sequence executed on both contracts. SDK v0.3.0 with new JobStatus enum (6 values), client.jobs namespace, and deprecated client.escrow alias. Dual-emit webhook during 7-day v1 drainage period. Migration guide at #migration-v1-to-v2.

MAY 2026
Self-service observability — key usage + webhook delivery history

Two new read-only endpoints: GET /api/keys/:id/usage returns per-request logs with status, latency, and path breakdown. GET /api/webhooks/:id/deliveries returns full delivery history with attempt outcomes and response previews. Both support cursor pagination, date ranges, and status filters. SDK v0.2.0 adds client.keys.usage() and client.webhooks.deliveries().

APRIL 2026
Escrow status lifecycle — 10 canonical statuses

Complete lifecycle reference with 10 documented states (5 non-terminal, 5 terminal), triggers, transitions, and corresponding webhook events. Full state machine diagram at #status-values.

APRIL 2026
Webhooks — all 5 event types with payload examples

Expanded webhook docs covering escrow.created, escrow.funded, escrow.settled, escrow.cancelled, and escrow.expired, each with full payload examples and HMAC signature verification guide.

MARCH 2026
API key prefix standardized — cpk_live_

All production API keys now use the cpk_live_ prefix. SDK, docs, and playground updated throughout.

MARCH 2026
TypeScript SDK published

Full-featured SDK with typed methods for escrow lifecycle, x402 protocol, and webhook management. Zero-dependency. Install via npm install clearpact.

MARCH 2026
Escrow expiry worker + auto-refund

Funded escrows past their deadline are automatically detected, refunded on-chain, and fire an escrow.expired webhook. No manual intervention required.

FEBRUARY 2026
Escrow list endpoint with filters and pagination

GET /api/escrow supports filtering by status, network, and date range with cursor-based pagination and sort. See List Escrows.

FEBRUARY 2026
x402 Protocol Phase 1B — settle endpoint

POST /api/x402/settle closes the x402 payment loop on Base Sepolia. Integrators can now use ClearPact as the settlement layer in x402 agent-to-agent payment flows.

JANUARY 2026
Webhook system launch

Real-time event delivery for all 5 escrow lifecycle events. HMAC-SHA256 signing, 3 retries with exponential backoff (5s → 30s → 5min), non-blocking dispatch.

JANUARY 2026
API key management endpoints

Create, list, revoke, and rotate API keys. Up to 5 keys per email. Zero-downtime key rotation supported. Rate limited to 60 req/min per key.

DECEMBER 2025
Auth layer + core escrow API launch

Initial public release. X-API-Key authentication, create/fund/settle/cancel escrow on Base Sepolia, 6 condition types, ERC-8004 auto-settlement.