# Crier - full API reference Crier delivers messages on behalf of AI agents, operated by Vima Brosta LLC. Three channels, one shape: hand over a destination and a message, pay once, and the relay delivers it, retrying for hours after your session is gone. An EMAIL goes to a person from the relay's fixed address; an SMS goes to a phone number from the relay's fixed number; a WEBHOOK goes to a public URL, HMAC signed so the receiver can verify it. The relay never reads content for meaning and drops the destination and the content once a delivery is final. Base URL: https://crier.vimabrosta.com Channels offered on this deployment: email, webhook No accounts. No API keys. Idempotency-Key header (16+ characters; use a UUID) honored on paid POSTs: a retry with the same key and body is answered in front of the paywall with the original response and no second charge. # Payment (x402) 1. Send the request with no payment. You receive HTTP 402. The PAYMENT-REQUIRED response header (base64 JSON, mirrored in the JSON body under `requirements`) carries an accepts array with every enabled payment option. On this deployment: - USDC on Base (default rail) - USDC or native SOL on Solana (same endpoints; pick the Solana option in the 402 accepts list) - Native ETH and BTC are not accepted: the x402 exact scheme settles token authorizations, so native ETH has no path, and Bitcoin has no x402 rail at all (BTC agent payments use L402, a different protocol). Every rejection still costs nothing: requests that cannot run are refused before settlement, and the 402 body carries a preflight naming what the paid retry would be refused for. 2. Produce payment and retry the IDENTICAL request with the X-PAYMENT header. Set an Idempotency-Key header (16+ characters) so network retries can never double-send or double-charge. 3. You receive 202 with a delivery id and a one-time secret. The message is queued and sent within seconds by the relay's own loop; settlement is on-chain. A purchase whose settlement fails after the checks ran is voided: nothing is sent, and the key can be used again. Prices: email $0.01 | sms $0.05 | webhook $0.005. No subscriptions, no hidden fees. # Paid endpoints ## POST /v1/email ($0.01) Body: { "to": "person@example.com", "subject": "Your order 42 shipped", <- one line, 200 chars max "text": "Tracking number 1Z999. It arrives Thursday.", <- plain text, 20000 chars max "html": "
...
", <- optional, 65536 bytes max "reply_to": "ops@your-company.example", <- optional; where a reply goes "from_name": "Order Desk" <- optional, 40 chars; shown as "Order Desk via Crier" } The message is sent from crier@vimabrosta.com (a fixed relay address; you cannot spoof a sender) with a footer naming the relay and an unsubscribe link, and List-Unsubscribe headers. Caps: 4 per hour and 12 per day to one address, refused before payment (429 destination_rate_limited). An address that bounced, complained, or unsubscribed is refused before payment (422 destination_unavailable) for every sender, permanently. 202 returns id, secret (shown once), status "queued", to (masked), links, receipt. ## POST /v1/sms ($0.05) Body: {"to": "+14155550123", "text": "Your table for 2 at 19:30 is confirmed."} to is E.164 (a plus sign, the country code, then the number; spaces and dashes are stripped). text is 300 characters max; the relay appends "(via Crier. Reply STOP to opt out)". Sent from the relay's fixed number. Caps: 2 per hour and 6 per day to one number. A number that replied STOP or is not a mobile number is refused before payment. OPT-IN IS REQUIRED AND YOU CANNOT GRANT IT. A number is reachable only after the person holding it has texted CRIER to the relay's own number from that handset. Until then every request to it is refused, before payment, with 422 destination_not_opted_in. Crier will not text a number to invite it, because that first message would itself be the unsolicited thing opt-in exists to prevent. So this channel reaches people who already expect to hear from whoever runs you; it is not a way to reach someone new. Check a number first with POST /v1/validate, which reports the same state and costs nothing. The workflow, in a form a carrier will accept, is at https://crier.vimabrosta.com/sms-opt-in. ## POST /v1/webhook ($0.005) Body: { "url": "https://your-system.example/hooks/agent", <- public http(s); same URL policy as below "method": "POST", <- or PUT "body": {"event": "task.done", "task_id": "42"}, <- any JSON (sent as application/json) or a string (text/plain), 16384 bytes max "content_type": "application/json", <- optional override "headers": {"X-Task": "42"} <- optional, up to 5; names must start with X- and not X-Crier- } Delivered with these headers: X-Crier-Delivery: {delivery id} X-Crier-Attempt: 1 <- then 2, 3, ... on retries X-Crier-Signature: sha256={hex hmac} Signature: HMAC-SHA256 over the raw request body. The key is the lowercase hex STRING of sha256(secret), the 64 ASCII characters, not the 32 raw bytes, the same construction Stumble uses for its events. Share the secret with the receiver to let it verify. Worked example you can verify locally: secret = "dls_EXAMPLEsecret_0000000000000000" body = {"event":"task.done"} key = sha256(secret) hex X-Crier-Signature: sha256=HMAC-SHA256(body, key) hex A 2xx from the receiver is sent. A 408, 425, 429, 5xx, timeout, or connection failure is retried at 30 s, 2 m, 10 m, 1 h, and 6 h, then failed. Any other status, or a redirect (never followed), is failed at once: the receiver has answered. Caps: 600 per hour and 5000 per day to one receiver host. # Delivery lifecycle queued (accepted, payment settled, waiting for the loop; typically under 5 seconds) -> sent (the provider accepted it, or the receiver answered 2xx; provider_id is the Resend or Twilio message id, or the receiver's status) -> failed (permanent refusal, or the retry ladder ran out; last_error says why) -> cancelled (you cancelled before it was sent) A provider may report later (provider_status): delivered, bounced, complained, undelivered. Once a delivery is final, the destination and the content are deleted; only a masked destination, hashes, and the status remain, for 7 days. # URL policy (webhook receivers) - Schemes: http and https only. Embedded credentials (user:pass@) are refused. - Ports: 80, 443, 8080, 8443 only (error code: blocked_port). - Hosts: public only. Private, loopback, link-local, carrier-NAT, and cloud metadata addresses are refused, as are *.local / *.internal / *.localhost names and hosts that RESOLVE to private addresses (error code: blocked_host). Resolution is re-checked at connection time on every attempt. - Redirects are never followed on a delivery. # Rate limits (per client IP, per minute) status GET 120 | cancel DELETE 120 (separate bucket) | validate 30 | quote 120 | MCP 120 | idempotent replays 120 | unsubscribe page 60. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining; a 429 carries Retry-After (seconds). Per-destination caps are listed above and are refused before payment. # Free endpoints ## GET /v1/deliveries/{id} Auth: "Authorization: Bearer {secret}" header or ?secret= query param. Returns channel, status (queued|sent|failed|cancelled), to (masked), attempts, next_attempt_at, provider_id, provider_status, last_error, sent_at, and a resume_hint sentence you can drop into a fresh agent context. ## DELETE /v1/deliveries/{id} Cancels a delivery that has not been sent yet. Auth as above. Not refundable. ## POST /v1/validate {"channel": "email"|"sms"|"webhook", "message": {...the body you would send...}} Returns valid (schema), deliverable (policy, suppression, caps), and the problem if any. Sends nothing, charges nothing. ## GET /v1/quote and GET /pricing.json Machine-readable pricing and limits. GET /healthz for service health. # Errors All errors: {"error": {"code": "...", "message": "...", "hint": "...", "docs": "https://crier.vimabrosta.com/llms-full.txt"}} Codes: invalid_request, invalid_json, channel_unavailable (404: not offered on this deployment), destination_unavailable (422: bounced, complained, STOP, or unsubscribed; permanent), destination_not_opted_in (422: an SMS destination that has never texted the relay's keyword; the recipient must do that themselves), destination_rate_limited (429: the per-destination cap; Retry-After given), blocked_host, blocked_port, dns_failure, invalid_url, invalid_scheme, credentials_in_url, invalid_idempotency_key (under 16 characters), idempotency_key_reuse, in_flight, already_created, payload_too_large (413), not_found, rate_limited, payment_required. 402 means pay and retry with X-PAYMENT; 5xx means retry later. # Terms (summary) Pay-per-message, no refunds at micro-prices; the relay does not choose recipients and does not read content; destinations that opt out are honored for every sender; content is held only until a delivery is final; you are responsible for having a lawful basis to contact the people you message; we may refuse or block senders to protect recipients and the relay's standing; service by Vima Brosta LLC (contact@vimabrosta.com). Full terms: https://crier.vimabrosta.com/terms