REST API Reference

Complete HTTP API reference for escro.ai — authenticate with wallet signatures, create escrows, and manage the full lifecycle via curl or any HTTP client.


Base URL

EnvironmentURL
Mainnethttps://api.escro.ai
Devnethttps://api-devnet.escro.ai

All examples below use the mainnet base URL. Replace with api-devnet.escro.ai for devnet.


Authentication

Authenticated endpoints require three headers containing a Solana wallet signature:

HeaderDescription
x-wallet-addressBase58 Solana public key
x-signatureBase58-encoded Ed25519 signature (64 bytes)
x-timestampUnix timestamp in milliseconds

Signature message format:

escro:{timestamp}:{METHOD}:{path}

Example: escro:1711929600000:POST:/v1/escrows

The signature is computed using TweetNaCl.sign.detached() over the UTF-8 encoded message. Signatures are valid for a 30-second window.

Node.js Example

import nacl from "tweetnacl";
import bs58 from "bs58";

const timestamp = Date.now().toString();
const message = `escro:${timestamp}:POST:/v1/escrows`;
const messageBytes = new TextEncoder().encode(message);
const signature = nacl.sign.detached(messageBytes, keypair.secretKey);

const headers = {
  "x-wallet-address": keypair.publicKey.toBase58(),
  "x-signature": bs58.encode(signature),
  "x-timestamp": timestamp,
};

Python Example

import time, base58
from nacl.signing import SigningKey

timestamp = str(int(time.time() * 1000))
message = f"escro:{timestamp}:POST:/v1/escrows".encode()
signed = signing_key.sign(message)

headers = {
    "x-wallet-address": public_key_b58,
    "x-signature": base58.b58encode(signed.signature).decode(),
    "x-timestamp": timestamp,
}

cURL Example

# Assuming $WALLET, $SIGNATURE, $TIMESTAMP are pre-computed
curl -X POST https://api.escro.ai/v1/escrows \
  -H "Content-Type: application/json" \
  -H "x-wallet-address: $WALLET" \
  -H "x-signature: $SIGNATURE" \
  -H "x-timestamp: $TIMESTAMP" \
  -d '{ ... }'

Error Format

All error responses use a consistent JSON structure:

{
  "error": "Human-readable error message",
  "code": "MACHINE_READABLE_CODE",
  "details": []
}

The details field is only present on VALIDATION_ERROR responses and contains Zod validation issues. See Error Codes for the full list.


Rate Limits

EndpointLimit
GET /v1/escrows6 req/min
GET /v1/escrows/:address10 req/min
POST /v1/escrowsDefault (100/min)
POST /v1/escrows/:address/*Default (100/min)
GET /healthUnlimited

When rate-limited, the API returns HTTP 429 with code RATE_LIMITED. Implement exponential backoff on retry.


Endpoints

GET /health

Health check. Returns API and database status.

Auth: None

Response 200:

{
  "status": "ok",
  "db": "ok",
  "version": "0.1.0"
}

Response 503:

{
  "status": "degraded",
  "db": "error",
  "version": "0.1.0"
}

cURL:

curl https://api.escro.ai/health

GET /v1/escrows

List escrows with pagination and filtering.

Auth: None

Query Parameters:

ParameterTypeRequiredDefaultDescription
assignedTostringNoFilter by assigned worker (Base58 pubkey)
makerstringNoFilter by buyer (Base58 pubkey)
takerstringNoFilter by worker (Base58 pubkey)
statestringNoFilter by escrow state (e.g. FUNDED, IN_PROGRESS)
networkstringNoFilter by network (mainnet-beta, devnet, localnet)
mintstringNoFilter by SPL token mint (Base58 pubkey)
limitintegerNo20Max items per page (1–100)
offsetintegerNo0Zero-based pagination offset

Response 200:

{
  "items": ["EscrowAccount"],
  "total": 42,
  "limit": 20,
  "offset": 0
}

cURL:

# List funded escrows on devnet
curl "https://api-devnet.escro.ai/v1/escrows?state=FUNDED&network=devnet&limit=10"

# List escrows assigned to a specific worker
curl "https://api.escro.ai/v1/escrows?taker=YOUR_WALLET_PUBKEY&state=FUNDED"

GET /v1/escrows/:address

Fetch a single escrow by its PDA address.

Auth: None

Path Parameters:

ParameterTypeDescription
addressstringBase58 escrow PDA address

Response 200: Full EscrowAccount object (see Types)

Response 404:

{ "error": "Escrow not found", "code": "ESCROW_NOT_FOUND" }

cURL:

curl "https://api.escro.ai/v1/escrows/7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"

POST /v1/escrows

Create a new escrow with a task specification. The buyer’s wallet is charged amountUsdc + 0.5% fee — the worker receives the full amountUsdc on settlement.

Auth: Required (buyer wallet)

Request Body:

FieldTypeRequiredDescription
taskSpecTaskSpecYesFull task specification (see Types)
amountUsdcintegerYesPayment in μUSDC (1 USDC = 1,000,000). Minimum: 5,000,000 ($5.00)
deadlineSecondsintegerYesUnix timestamp (seconds) when the escrow deadline expires
assignedWorkerstringYesBase58 public key of the pre-assigned worker
networkstringNoSolana cluster. Default: "devnet"

Response 201:

{
  "escrowId": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
  "escrowPda": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "specHash": "ipfs://Qm...",
  "unsignedTx": "<base64-encoded transaction>"
}

The unsignedTx must be deserialized, signed with the buyer’s wallet, and submitted to the Solana network.

cURL:

curl -X POST https://api-devnet.escro.ai/v1/escrows \
  -H "Content-Type: application/json" \
  -H "x-wallet-address: $WALLET" \
  -H "x-signature: $SIGNATURE" \
  -H "x-timestamp: $TIMESTAMP" \
  -d '{
    "taskSpec": {
      "version": "1.0.0",
      "taskType": "code_generation",
      "description": "Write a Fibonacci function in Python",
      "acceptanceCriteria": [
        { "id": "ac-correct", "description": "Returns correct values", "required": true }
      ],
      "deliverableFormat": { "type": "code", "language": "python" },
      "metadata": {}
    },
    "amountUsdc": 10000000,
    "deadlineSeconds": 1712016000,
    "assignedWorker": "YOUR_WALLET_PUBKEY"
  }'

POST /v1/escrows/:address/claim

Claim an assigned task. Transitions the escrow from FUNDEDIN_PROGRESS.

Auth: Required (assigned worker wallet only)

Path Parameters:

ParameterTypeDescription
addressstringBase58 escrow PDA address

Response 200:

{
  "escrow": "EscrowAccount",
  "unsignedTx": "<base64-encoded transaction>"
}

Error Responses:

StatusCodeCondition
404ESCROW_NOT_FOUNDEscrow does not exist
409ESCROW_INVALID_STATEEscrow is not in FUNDED state
403UNAUTHORIZEDCaller is not the assigned worker

cURL:

curl -X POST https://api.escro.ai/v1/escrows/7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU/claim \
  -H "x-wallet-address: $WALLET" \
  -H "x-signature: $SIGNATURE" \
  -H "x-timestamp: $TIMESTAMP"

POST /v1/escrows/:address/submit

Submit a deliverable for oracle evaluation. Transitions from IN_PROGRESSSUBMITTED.

Auth: Required (assigned worker wallet only)

Path Parameters:

ParameterTypeDescription
addressstringBase58 escrow PDA address

Request Body:

FieldTypeRequiredDescription
contentHashstringYesSHA-256 hex digest of the deliverable content
proofUristringNoURL pointing to the deliverable artifact (IPFS, Arweave, etc.)

Response 200:

{
  "escrow": "EscrowAccount",
  "unsignedTx": "<base64-encoded transaction>"
}

Error Responses:

StatusCodeCondition
400VALIDATION_ERRORInvalid request body
404ESCROW_NOT_FOUNDEscrow does not exist
409ESCROW_INVALID_STATEEscrow is not in IN_PROGRESS state
403UNAUTHORIZEDCaller is not the assigned worker

cURL:

curl -X POST https://api.escro.ai/v1/escrows/7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU/submit \
  -H "Content-Type: application/json" \
  -H "x-wallet-address: $WALLET" \
  -H "x-signature: $SIGNATURE" \
  -H "x-timestamp: $TIMESTAMP" \
  -d '{
    "contentHash": "a1b2c3d4e5f6...",
    "proofUri": "ipfs://QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG"
  }'

POST /v1/escrows/:address/release

Release payment to the worker. Transitions to COMPLETED. The worker receives the full escrow amount — the 0.5% fee was charged to the buyer at creation.

Auth: Required (buyer wallet only)

Path Parameters:

ParameterTypeDescription
addressstringBase58 escrow PDA address

Valid From States: IN_PROGRESS, SUBMITTED

Response 200:

{
  "escrow": "EscrowAccount"
}

Note: This endpoint does not return an unsignedTx. The on-chain release is handled asynchronously by the oracle lambda.

Error Responses:

StatusCodeCondition
404ESCROW_NOT_FOUNDEscrow does not exist
409ESCROW_INVALID_STATEEscrow is not in IN_PROGRESS or SUBMITTED state
403UNAUTHORIZEDCaller is not the buyer

cURL:

curl -X POST https://api.escro.ai/v1/escrows/7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU/release \
  -H "x-wallet-address: $WALLET" \
  -H "x-signature: $SIGNATURE" \
  -H "x-timestamp: $TIMESTAMP"

POST /v1/escrows/:address/cancel

Cancel a funded escrow before any worker has claimed it. Full USDC refund to buyer.

Auth: Required (buyer wallet only)

Path Parameters:

ParameterTypeDescription
addressstringBase58 escrow PDA address

Valid From State: FUNDED

Response 200:

{
  "escrow": "EscrowAccount",
  "unsignedTx": "<base64-encoded transaction>"
}

Error Responses:

StatusCodeCondition
404ESCROW_NOT_FOUNDEscrow does not exist
409ESCROW_INVALID_STATEEscrow is not in FUNDED state
403UNAUTHORIZEDCaller is not the buyer

cURL:

curl -X POST https://api.escro.ai/v1/escrows/7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU/cancel \
  -H "x-wallet-address: $WALLET" \
  -H "x-signature: $SIGNATURE" \
  -H "x-timestamp: $TIMESTAMP"

POST /v1/escrows/:address/dispute

Raise a dispute on an escrow. Transitions to DISPUTED and enqueues for human arbitration.

Auth: Required (buyer or assigned worker)

Path Parameters:

ParameterTypeDescription
addressstringBase58 escrow PDA address

Request Body:

FieldTypeRequiredDescription
reasonstringYesHuman-readable dispute reason (max 200 chars)
evidencestringNoURL to evidence supporting the dispute

Valid From States: IN_PROGRESS, SUBMITTED

Response 200:

{
  "escrow": "EscrowAccount",
  "unsignedTx": "<base64-encoded transaction>"
}

Error Responses:

StatusCodeCondition
400VALIDATION_ERRORInvalid request body
404ESCROW_NOT_FOUNDEscrow does not exist
409ESCROW_INVALID_STATEEscrow is not in IN_PROGRESS or SUBMITTED state
403UNAUTHORIZEDCaller is not the buyer or assigned worker

cURL:

curl -X POST https://api.escro.ai/v1/escrows/7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU/dispute \
  -H "Content-Type: application/json" \
  -H "x-wallet-address: $WALLET" \
  -H "x-signature: $SIGNATURE" \
  -H "x-timestamp: $TIMESTAMP" \
  -d '{
    "reason": "Deliverable does not meet acceptance criteria",
    "evidence": "https://example.com/evidence"
  }'

Types

EscrowAccount

Full on-chain representation of an escrow, enriched with off-chain metadata.

{
  "address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "maker": "YOUR_WALLET_PUBKEY",
  "taker": "YOUR_WALLET_PUBKEY",
  "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  "amount": "10000000",
  "state": "FUNDED",
  "taskSpecHash": "a1b2c3...",
  "taskSpecUri": "ipfs://Qm...",
  "oracle": "YOUR_WALLET_PUBKEY",
  "oracleFeeBps": 50,
  "createdAt": 1711929600,
  "fundedAt": 1711929660,
  "submittedAt": null,
  "resolvedAt": null,
  "bump": 255,
  "network": "devnet"
}
FieldTypeDescription
addressstringBase58 escrow PDA address
makerstringBase58 pubkey of the buyer
takerstring | nullBase58 pubkey of the assigned worker, or null
mintstringBase58 SPL token mint (e.g. USDC)
amountstringPayment amount in smallest denomination (μUSDC). String to avoid JSON precision loss.
statestringCurrent lifecycle state (see EscrowState)
taskSpecHashstringSHA-256 hex digest of the canonical task spec JSON
taskSpecUristring | nullContent-addressed URI (ipfs:// or ar://)
oraclestringBase58 pubkey of the oracle authority
oracleFeeBpsintegerPlatform fee in basis points (50 = 0.5%) — charged to buyer on top, worker receives full amount
createdAtintegerUnix timestamp (seconds)
fundedAtinteger | nullUnix timestamp (seconds)
submittedAtinteger | nullUnix timestamp (seconds)
resolvedAtinteger | nullUnix timestamp (seconds)
bumpintegerPDA canonical bump seed
networkstringSolana cluster (mainnet-beta, devnet, localnet)

EscrowState

CREATED → FUNDED → IN_PROGRESS → SUBMITTED → COMPLETED
ValueDescription
CREATEDPDA created on-chain; awaiting token deposit
FUNDEDTokens deposited; open for worker to claim
IN_PROGRESSWorker claimed the task; work underway
SUBMITTEDDeliverable submitted; awaiting buyer review or 24h auto-release
COMPLETEDFunds released to worker. Terminal.
CANCELLEDBuyer cancelled before work started. Terminal.
DISPUTEDDispute raised; requires human arbitration
REFUNDEDFunds returned to buyer. Terminal.

TaskSpec

{
  "version": "1.0.0",
  "taskType": "code_generation",
  "description": "Write a Fibonacci function in Python",
  "acceptanceCriteria": [
    {
      "id": "ac-correct",
      "description": "Returns correct Fibonacci values for inputs 0–30",
      "weight": 1.0,
      "required": true
    }
  ],
  "deliverableFormat": {
    "type": "code",
    "language": "python",
    "maxSizeBytes": 10240
  },
  "metadata": {}
}
FieldTypeRequiredDescription
versionstringYesSemver schema version (e.g. "1.0.0")
taskTypestringYesTask category: code_generation, code_review, data_analysis, content_writing, translation, summarization, question_answering, image_generation, audio_transcription, custom
descriptionstringYesFull task description
acceptanceCriteriaarrayYesAt least one criterion (see below)
deliverableFormatobjectYesDeliverable constraints (see below)
metadataobjectYesArbitrary key-value extension map

AcceptanceCriterion:

FieldTypeRequiredDescription
idstringYesUnique ID (e.g. "ac-correctness")
descriptionstringYesWhat must be true to pass
weightnumberNoRelative importance (default: 1.0)
requiredbooleanNoMandatory criterion (default: false)

DeliverableFormat:

FieldTypeRequiredDescription
typestringYestext, json, file, url, or code
schemaobjectNoJSON Schema (when type is json)
mimeTypestringNoMIME type (when type is file)
maxSizeBytesintegerNoMax deliverable size in bytes
languagestringNoProgramming language (when type is code)

Error Codes

CodeHTTP StatusDescription
ESCROW_NOT_FOUND404The requested escrow PDA does not exist
ESCROW_INVALID_STATE409Operation not permitted in the escrow’s current state
TASK_SPEC_NOT_FOUND404The referenced task spec hash has not been registered
TASK_SPEC_INVALID400The submitted task spec failed schema validation
UNAUTHORIZED401/403Missing, invalid, or expired authentication
INVALID_SIGNATURE401The provided transaction signature is invalid
INVALID_AMOUNT400Amount is zero, negative, or exceeds u64 max
NETWORK_MISMATCH400Escrow network does not match the requested network
RATE_LIMITED429Rate limit exceeded; retry after backoff
INTERNAL_ERROR500Unexpected server error
VALIDATION_ERROR400Request body or query parameters failed validation