# AgentSend > The email API your agent can sign up for. Transactional and marketing email over REST and MCP. An agent creates its own sandbox account with one API call; a human claims it before real mail goes out. - API root: https://agentsend.co/api/v1 (`Authorization: Bearer `) - MCP (streamable HTTP): https://agentsend.co/api/mcp - OpenAPI 3.1: https://agentsend.co/api/v1/openapi.json - Full reference: https://agentsend.co/llms-full.txt - Docs: https://agentsend.co/docs - Every refusal is `{"error": {"code", "reason", "fix"}}`. Apply `fix` and retry. ## 1. Sign up (no human, no CAPTCHA) 1. `GET /api/v1/accounts/pow` returns `{"challenge", "difficulty", "expires_at", "algorithm": "sha256"}`. A challenge expires in 5 minutes and creates one account. 2. Find `nonce`: try 0, 1, 2, ... as decimal strings until sha256(UTF-8 of challenge + nonce) has at least `difficulty` leading zero bits (counted from the first byte's top bit). At the default 20 bits this takes about a second. 3. `POST /api/v1/accounts` with `{"name"?: string, "owner_email"?: string, "pow": {"challenge", "nonce"}}` returns 201 `{"id", "api_key", "api_key_id", "status": "sandbox", "claimed": false, "claim_url", "expires_at", "limits", "next_steps"}`. `api_key` is a full-access key shown only once: store it. With `owner_email`, the claim link is also emailed there. Errors: `pow_required` and `pow_invalid` (a fresh challenge is in `error.pow`), `signup_rate_limited` (3 per hour and 10 per day per IP), `signup_disabled`. ```sh POW=$(curl -s https://agentsend.co/api/v1/accounts/pow) NONCE=$(node -e 'const{createHash}=require("crypto");const{challenge,difficulty}=JSON.parse(process.argv[1]);for(let n=0;;n++){const h=createHash("sha256").update(challenge+n).digest();let z=0;for(const b of h){if(b){z+=Math.clz32(b)-24;break}z+=8}if(z>=difficulty){console.log(n);break}}' "$POW") CHALLENGE=$(printf %s "$POW" | sed 's/.*"challenge":"\([^"]*\)".*/\1/') curl -s -X POST https://agentsend.co/api/v1/accounts -H "Content-Type: application/json" \ -d "{\"name\": \"release-bot\", \"pow\": {\"challenge\": \"$CHALLENGE\", \"nonce\": \"$NONCE\"}}" ``` Python instead of Node for the nonce: ```sh NONCE=$(python3 -c 'import hashlib,itertools,json,sys;p=json.loads(sys.argv[1]);print(next(n for n in itertools.count() if int.from_bytes(hashlib.sha256((p["challenge"]+str(n)).encode()).digest(),"big")>>(256-p["difficulty"])==0))' "$POW") ``` TypeScript SDK (`npm i agentsend`, publishing soon): `const { data, error } = await AgentSend.signup({ name: "release-bot", ownerEmail: "ops@acme.com" })`, then `new AgentSend(data.apiKey)`. MCP: connect without a key and call `create_account`. The first call (no `pow`) returns a `pow_required` error carrying a challenge; solve it in code and call again with `pow: {challenge, nonce}`. Then reconnect with `Authorization: Bearer `; every other tool needs it. ## 2. Send right away (sandbox) Until a human claims the account it sends only to `delivered@`, `bounced@`, and `complained@simulator.agentsend.co` (anything else: `account_unclaimed`), from any address @agentsend.co, adds at most 1 domain, and is deleted at `expires_at` (7 days). ```sh curl -s -X POST https://agentsend.co/api/v1/emails -H "Authorization: Bearer $AGENTSEND_API_KEY" \ -H "Content-Type: application/json" \ -d '{"from": "Agent ", "to": ["delivered@simulator.agentsend.co"], "subject": "Hello", "text": "It works."}' curl -s "https://agentsend.co/api/v1/events?since=0" -H "Authorization: Bearer $AGENTSEND_API_KEY" ``` ## 3. Hand the account to your human Give them `claim_url`. They sign in or sign up, accept the Terms of Service and Acceptable Use Policy, and the account moves into their account: your API key keeps working, `GET /api/v1/account` shows `claimed: true` (its `id` becomes their account id), and an `account.claimed` event fires. Lost the link? `POST /api/v1/account/claim-link` (optional `{"owner_email"}` emails it) returns a new one; earlier links stop working. Real recipients after the claim: verify a domain (below) and have your human request live sending in the dashboard; an operator approves it. ## 4. Verify a sending domain 1. `POST /api/v1/domains {"name": "mail.acme.com"}` returns the DNS records to publish. 2. Publish them, then `POST /api/v1/domains/{id}/verify`. It returns `status` and `missing_records` with the exact name, type, and value still needed. Repeat until `status` is `verified`. ## 5. Check status `GET /api/v1/account` returns `{"status": "sandbox" | "live" | "paused", "claimed", "expires_at"?, "plan", "limits", "usage"}`.