Skip to main content

Error Response Shapes

The API uses three distinct error response shapes depending on the error source.

1. Fastify Validation Error

Returned when the request body, path parameters, or query parameters fail JSON Schema validation. Fastify generates these automatically from the registered route schemas.

2. Policy Error

Returned when the PolicyEngine evaluates a TxIntent and issues a deny decision. Contains structured denial reasons and the policy version that produced the decision.

3. Simple Error

Used for all other error conditions (missing resources, RPC failures, signing errors, approval issues). Contains a machine-readable error code and a human-readable message.

Status Codes


Per-Endpoint Error Reference

POST /v1/tx/build

Build a transaction from a TxIntent. Evaluates policy before building. 400 — Fastify Validation Error TxIntent body does not match the schema. See the TxIntent Schema for the full specification.
403 — policy_denied The PolicyEngine denied the intent. The reasons array contains one or more human-readable denial explanations.
Possible denial reasons include:
  • "Chain X not in allowed chains [...]"
  • "Token X not in allowlist"
  • "Contract X not in allowlist"
  • "Value X exceeds max Y"
  • "Approval amount X exceeds max Y"
  • "Recipient X not in allowlist"
  • "Rate limit exceeded: N transactions in the past hour (limit: M)"

POST /v1/tx/preflight

Simulate a transaction and compute a risk score. Requires an RPC endpoint for the target chain. 400 — Fastify Validation Error TxIntent body does not match the schema. 502 — no_rpc_client No RPC endpoint is configured for the intent’s target chain.

POST /v1/tx/approve-request

Prompt the user for approval and return an approval token. Runs preflight simulation and policy evaluation. 400 — Fastify Validation Error TxIntent body does not match the schema. 403 — policy_denied Policy engine denied the intent after preflight evaluation (includes risk score assessment). Same response shape as /v1/tx/build denial.
403 — user_declined The operator rejected the transaction in the approval prompt (CLI or web UI). This is expected behavior when the human reviewer decides the transaction should not proceed.

POST /v1/tx/sign-and-send

Sign a transaction and broadcast it. Requires an unlocked key in the keystore. If policy requires approval, an approvalTokenId must be provided. 400 — Fastify Validation Error Body does not match the expected schema { intent: TxIntent, approvalTokenId?: uuid }. 403 — policy_denied Policy re-check during signing failed. The policy is evaluated again at sign time to prevent time-of-check/time-of-use attacks.
403 — approval_required The transaction requires an approval token but none was provided. Call /v1/tx/approve-request first.
403 — invalid_approval_token The provided token was not found, has expired (300s TTL), or was already consumed (tokens are single-use).
403 — signing_failed The keystore could not produce a signature. This typically means the key for the wallet address is not unlocked or not present in the keystore.

GET /v1/tx/:hash

Look up a transaction receipt by hash. Requires an RPC endpoint. 400 — Fastify Validation Error The hash path parameter does not match the required pattern ^0x[0-9a-fA-F]{64}$. 404 — not_found The receipt is not yet available (transaction may still be pending) or the hash is invalid.
502 — no_rpc_client No RPC endpoint is configured.

GET /v1/balance/:token/:account

Look up an ERC-20 token or native ETH balance. 400 — Fastify Validation Error The token or account path parameters do not match the required pattern ^0x[0-9a-fA-F]{40}$, or the chainId query parameter is not a numeric string. 502 — no_rpc_client No RPC endpoint is configured, optionally chain-specific when using ?chainId=N.
502 — rpc_error The RPC call succeeded in reaching the provider but the provider returned an error.

POST /v1/skills/register

Register a skill manifest. Validates schema, verifies ECDSA signature, checks file hashes, and runs static analysis. 200 — Success
400 — schema_validation_failed The manifest does not match the SkillManifest schema. See the TxIntent Schema for the full specification.
400 — signature_verification_failed The ECDSA signature on the manifest does not match the declared publisher address.
400 — file_hash_mismatch One or more SHA-256 file hashes in the manifest do not match the actual file contents. The response includes the specific mismatches.
400 — static_scan_failed Security violations were found during static analysis of the skill code.
409 — duplicate_skill A skill with this name is already registered and active.

GET /v1/skills/:name

Get a registered skill by name. 404 — skill_not_found The skill is not registered or has been revoked.

DELETE /v1/skills/:name

Revoke a registered skill. Audit-logged as skill_revoked. 404 — skill_not_found The skill is not registered.

POST /v1/approvals/:requestId/decide

Submit an approve or deny decision for a pending approval request. 400 — Fastify Validation Error Body does not match the expected schema { approved: boolean }. 404 — not_found The approval request has expired (300s TTL) or does not exist.

GET /v1/approvals/pending

List all pending approval requests. No error responses — always returns { pending: [] } even when empty.

GET /v1/approvals/history

Recent audit events for the approval dashboard. No error responses — always returns { events: [] } even when empty. The limit query parameter is clamped to 1-100.

GET /approval-ui

Serves the web approval dashboard as HTML. No error responses — always returns the HTML page.

GET /v1/health

Health check endpoint. No error responses — always returns { status: "ok", version: "0.1.0", uptime: number }.

Error Recovery Guide

Actionable guidance for each error condition.

400 — Validation Errors

Check your TxIntent against the TxIntent Schema. Common issues:
  • Missing required fields (version, id, timestamp, chain, wallet, action, constraints)
  • Wrong address format — must be 0x followed by exactly 40 hex characters
  • Amounts must be numeric strings (e.g., "1000000", not 1000000)
  • action.type must be one of: transfer, transfer_native, approve, swap_exact_in, swap_exact_out
  • chain.chainId must be a supported chain: 1 (Ethereum), 10 (Optimism), 42161 (Arbitrum), 8453 (Base)

403 — policy_denied

Review your PolicyConfig. Check the following:
  • Is the chain in allowedChains?
  • Is the token in tokenAllowlist (if the allowlist is non-empty)?
  • Is the contract in contractAllowlist (if the allowlist is non-empty)?
  • Is the value under maxValueWei?
  • Is the recipient in recipientAllowlist (if the allowlist is non-empty)?
  • Has the wallet exceeded maxTxPerHour?

403 — approval_required

The transaction needs a valid approval token. Call POST /v1/tx/approve-request first to get an approvalTokenId, then pass it in the request body to /v1/tx/sign-and-send:

403 — invalid_approval_token

The token was already consumed (tokens are single-use), expired (300-second TTL), or does not match the intent’s txRequestHash. Get a fresh token by calling POST /v1/tx/approve-request again.

403 — signing_failed

The wallet’s private key is not unlocked in the keystore. Ensure a key has been imported or generated:
In Docker, check that the ISCL_DEMO_PASSPHRASE environment variable is set correctly.

403 — user_declined

The operator denied the transaction in the approval UI (CLI prompt or web dashboard). This is expected behavior — the system is working as designed. The agent should inform the user that the transaction was rejected by the human operator.

404 — not_found

For transaction receipts: the transaction may still be pending. Wait a few seconds and retry the lookup. For skills: verify the skill name is correct and that the skill has not been revoked via DELETE /v1/skills/:name.

409 — duplicate_skill

A skill with this name is already registered and active. Either revoke the existing skill first, then re-register:
Or choose a different skill name.

502 — no_rpc_client

No RPC endpoint is configured for the target chain. Set the appropriate environment variable. See the Configuration Reference for the full configuration reference.

502 — rpc_error

The RPC provider returned an error. Troubleshooting steps:
  1. Verify your RPC URL is correct and the provider is online
  2. Check that you have sufficient API credits or rate limit headroom
  3. Try the RPC URL directly with curl to confirm connectivity
  4. If using a free tier, consider upgrading or switching providers

Client-Side Error Handling

All adapters (OpenClaw, MCP, Eliza, Telegram) use the ISCLClient class which wraps HTTP errors in a typed ISCLError:
Recommended error handling pattern:

See Also