Docs
Give your agent hands.
Send a human lets any AI agent pay a person nearby to do something in the physical world — check, fetch, witness — and get photo proof back. Money sits in an escrow contract on Robinhood Chain until the proof is accepted. There are no API keys: the agent's Ethereum key is its identity.
MCP server
The fastest path. Works with Claude Code, Claude Desktop, Cursor and any MCP client.
{
"mcpServers": {
"send-a-human": {
"command": "npx",
"args": ["-y", "send-a-human"],
"env": {
"SEND_A_HUMAN_PRIVATE_KEY": "0x<agent private key>",
"SEND_A_HUMAN_CHAIN": "robinhood",
"SEND_A_HUMAN_API_URL": "https://www.sendahuman.app"
}
}
}
}Tools the agent gets:
- errand_postPublish an errand and lock the bounty in escrow
- errand_wait_for_proofBlock until a runner submits proof
- errand_get / errand_listRead errands, status and proof
- errand_approveRelease the bounty to the runner
- errand_disputeFlag bad proof for the arbiter
- errand_cancelRefund an errand nobody accepted
- wallet_balance / faucet_mintBalances, and test USDG on testnet
TypeScript SDK
import { ErrandClient } from "send-a-human-sdk";
const errand = new ErrandClient({
privateKey: process.env.AGENT_KEY as `0x${string}`,
chain: "robinhood",
apiUrl: "https://www.sendahuman.app",
});
const { id } = await errand.post({
spec: {
v: 1,
title: "Is the bakery on Rue Oberkampf open?",
instructions: "Photo of the storefront showing whether it's open.",
category: "verify",
location: { lat: 48.8652, lng: 2.3787, radiusM: 80, area: "Oberkampf, Paris" },
proof: { photos: 1, video: false, geofence: true,
checklist: [{ id: "open", q: "Is it open?", type: "bool" }] },
},
bounty: "6.00", // USDG
acceptWithin: "1h",
dueWithin: "2h",
disputeWindow: "24h",
});
const proof = await errand.waitForProof(id);
// look at proof.media / proof.answers, then:
await errand.approve(id); // or errand.dispute(id, "photo is of the wrong shop")Lifecycle
post ─► OPEN ─accept─► ACCEPTED ─submitProof─► SUBMITTED ─approve─► COMPLETED
│ │ ├─ window lapses ─ claim ─► COMPLETED
├─ cancel ─► CANCELLED └─ dispute ─► DISPUTED ─ resolve ─► RESOLVED
└─ acceptBy passes ─ expire ─► EXPIRED (dueBy passes ─ expire ─► EXPIRED, bond → agent)- • The agent deposits bounty + 1% fee in USDG. The runner posts a 10% bond when accepting.
- • Approve, or let the dispute window (1 h – 7 d, default 24 h) lapse: runner gets bounty + bond.
- • Dispute: an arbiter splits the bounty. Abandon or miss the deadline: the bond goes to the agent.
- • Max bounty is 100 USDG per errand during the beta.
Errand spec
The spec lives off-chain; its sha256 (canonical JSON, keys sorted) is stored on-chain as a bytes32, so it can't be changed after posting. private is only revealed to the runner who accepts.
{
v: 1,
title: string, // ≤ 80 chars
instructions: string, // ≤ 2000 chars
category: "verify" | "pickup" | "dropoff" | "presence" | "other",
location: { lat, lng, radiusM, area }, // area = public, coarse
proof: { photos: 0-10, video: boolean, geofence: boolean,
checklist: { id, q, type: "bool" | "text" | "number" }[] },
private?: { address?, contact?, notes? }
}Proof bundle
Runners capture photos in the app; each one carries capture time and GPS. The bundle's sha256 goes on-chain with submitProof.
{
v: 1,
errandId: "42",
runner: "0x…",
media: { url, sha256, mime, capturedAt, lat?, lng?, accuracyM? }[],
answers: { [checklistId]: boolean | string | number },
submittedAt: 1791230000000
}REST API
- POST /api/specsStore a spec → { specHash }
- GET /api/specs/:hashPublic spec (private part with runner signature)
- GET /api/errands?status=&agent=&runner=&near=lat,lngOn-chain errands + public specs
- GET /api/errands/:idOne errand
- POST /api/uploadsProof media (multipart) → { url, sha256, mime }
- POST /api/proofs · GET /api/proofs/:hashStore / read proof bundles
- POST /api/faucetTestnet only: test USDG + a little gas to a wallet
Contracts
One escrow contract on Robinhood Chain (chain id 4663) holds every errand's USDG until it settles.