API documentation
No Errands gives an agent three real-world abilities in the United States: print and mail letters and postcards, send faxes, and book appointments with local businesses. This page is the human version of the three connector briefs that agents read. All four describe the same v0 contracts.
If you only want to use this from Muse, you do not need this page. Start at Get your key instead.
The three connectors
| Connector | Brief | MCP endpoint | REST base |
|---|---|---|---|
| No Errands Mail | /connectors/muse.md | https://api.noerrands.com/c/mail/mcp |
https://api.noerrands.com/c/mail/v1 |
| No Errands Fax | /connectors/fax/muse.md | https://api.noerrands.com/c/fax/mcp |
https://api.noerrands.com/c/fax/v1 |
| No Errands Bookings | /connectors/booking/muse.md | https://api.noerrands.com/c/booking/mcp |
https://api.noerrands.com/c/booking/v1 |
They share one account, one API key, one credit balance, one error envelope and one authentication scheme. REST and MCP take the same request bodies and return the same response bodies, so pick whichever your client speaks.
| Item | Value |
|---|---|
| OpenAPI 3.1 documents | https://api.noerrands.com/c/mail/openapi.json, https://api.noerrands.com/c/fax/openapi.json, https://api.noerrands.com/c/booking/openapi.json |
| Protected resource metadata (RFC 9728) | https://api.noerrands.com/.well-known/oauth-protected-resource/c/mail/mcp, https://api.noerrands.com/.well-known/oauth-protected-resource/c/fax/mcp, https://api.noerrands.com/.well-known/oauth-protected-resource/c/booking/mcp |
| Health check | https://api.noerrands.com/health |
| Agent index | https://noerrands.com/llms.txt |
Authentication
Every call carries your key as a bearer token:
Authorization: Bearer ne_live_your_key_here
Keys look like ne_live_ or ne_test_ followed by 32
characters. We store only a SHA-256 hash and the first 12 characters, so we
cannot tell you your key if you lose it. Create a new one instead.
A key has one of two scopes:
| Scope | Can do |
|---|---|
read |
Check addresses, estimate cost, read status, list mail and faxes, read balance, create top-up links, search providers, read availability |
read,write |
Everything above, plus send letters, postcards and faxes, and create and cancel bookings |
If the key is missing or wrong you get 401 plus a
WWW-Authenticate: Bearer resource_metadata="..." header pointing at that
connector's RFC 9728 document.
Getting a key
POST https://api.noerrands.com/v1/keys - no authentication, limited to 5 requests per hour per IP.
Request: { "email": "you@example.com", "scope": "read,write" }
Response 201:
{
"account_id": "acc_...",
"api_key": "ne_live_...",
"scope": "read,write",
"balance_cents": 0,
"topup_url": "https://..."
}
The key page calls this endpoint for you. One key works for all three connectors. Email addresses are not verified in v0; treat the key, not the email, as the account identity.
Errors
Every error has the same envelope, whichever connector produced it:
{ "error": { "code": "invalid_request", "message": "..." } }
| HTTP | code |
Extra fields | Meaning |
|---|---|---|---|
| 400 | invalid_request |
field when known |
Something in the request is wrong |
| 401 | unauthorized |
Key missing, unknown, or revoked | |
| 402 | insufficient_credits |
required_cents, balance_cents, topup_url |
Not enough credits; nothing was sent |
| 403 | forbidden_scope |
A write call with a read-only key | |
| 404 | not_found |
No such record on this account | |
| 409 | idempotency_conflict |
Same idempotency_key, different body |
|
| 429 | rate_limited |
retry_after_seconds |
Slow down |
| 502 | provider_error |
An upstream provider failed; any charge was refunded |
Idempotency
Put an idempotency_key (a string up to 128 characters) on every send and every
booking. For 24 hours, a repeat of the same key with the same body returns the
original response instead of acting again. The same key with a different body
returns 409 idempotency_conflict. Use one key per human request, not per
retry.
Types
type Address = {
name: string; // 1..40 chars, required for send, optional for verify
company?: string; // <= 40
line1: string; // <= 64
line2?: string; // <= 64
city: string;
state: string; // 2-letter
zip: string; // 5 or 9 digits
country?: "US"; // US only in v0
};
type MailStatus = "created" | "processing" | "in_transit" | "delivered" | "returned" | "failed" | "cancelled";
type MailRecord = {
mail_id: string; // "ml_<id>"
kind: "letter" | "postcard";
status: MailStatus;
to: Address;
from: Address;
created_at: string; // ISO 8601
expected_delivery_date?: string; // YYYY-MM-DD
credits_charged_cents: number;
tracking_url?: string;
};
Endpoints
| MCP tool | Scope | Cost | REST |
|---|---|---|---|
verify_address |
read | free | POST /c/mail/v1/addresses/verify |
estimate_cost |
read | free | POST /c/mail/v1/estimate |
send_letter |
write | charged | POST /c/mail/v1/letters |
send_postcard |
write | charged | POST /c/mail/v1/postcards |
get_mail_status |
read | free | GET /c/mail/v1/mail/{mail_id} |
list_sent_mail |
read | free | GET /c/mail/v1/mail?limit=&cursor= |
get_balance |
read | free | GET /c/mail/v1/balance |
create_topup_link |
read | free | POST /c/mail/v1/topups |
| no tool | read | free | GET /c/mail/v1/me |
- verify_address - in
{ "address": Address }(nameoptional), out{ "deliverable": boolean, "normalized": Address, "notes": string[] }. Use thenormalizedaddress when you send. - estimate_cost - in
{ "kind": "letter" | "postcard", "pages"?: number, "color"?: boolean, "certified"?: boolean }, out{ "total_cents": number, "breakdown": [{ "item": string, "cents": number }] }. - send_letter - in
to,from, optionalsubject(<= 120 chars), exactly one ofbody_markdown,body_htmlorpdf_url(<= 20 KB), pluscolor,double_sided,certifiedandidempotency_key. Out: aMailRecordplusbalance_cents. Markdown is rendered to simple HTML; HTML is sanitized, so scripts are stripped and images load by URL. - send_postcard - in
to,from,front(textand/orimage_url),message(<= 350 characters),idempotency_key. Out: same shape assend_letter. - get_mail_status - in
{ "mail_id" }, out aMailRecordplus{ "events": [{ "at": string, "status": MailStatus, "detail"?: string }] }. - list_sent_mail - in
{ "limit"?: 1..50, "cursor"?: string }(default 20), out{ "items": MailRecord[], "next_cursor"?: string }. - get_balance - out
{ "balance_cents": number, "currency": "usd", "topup_url": string }. - create_topup_link - in
{ "amount_cents": 1000 | 2500 | 5000 }, out{ "checkout_url": string, "expires_at": string }. - GET /me - out
{ "account_id", "email", "scope", "key_prefix", "balance_cents" }.
The server validates, estimates, checks your balance, charges, then calls the
print provider. If the provider fails after the charge, the credits are
refunded and you get 502 provider_error.
| Item | Price |
|---|---|
| Letter, first page, black and white | $2.49 |
| Each additional page | $0.25 |
| Color surcharge, per letter | $0.50 |
| Certified mail surcharge | $7.50 |
| Postcard | $1.49 |
| Address check | Free |
| Cost estimate | Free |
Fax
Types
type FaxStatus = "queued" | "sending" | "delivered" | "failed";
Endpoints
| MCP tool | Scope | Cost | REST |
|---|---|---|---|
estimate_fax_cost |
read | free | POST /c/fax/v1/estimate |
send_fax |
write | charged | POST /c/fax/v1/faxes |
get_fax_status |
read | free | GET /c/fax/v1/faxes/{fax_id} |
list_faxes |
read | free | GET /c/fax/v1/faxes?limit=&cursor= |
get_balance |
read | free | GET /c/fax/v1/balance |
create_topup_link |
read | free | POST /c/fax/v1/topups |
- estimate_fax_cost - in
{ "pages": 1..50 }, out{ "total_cents", "breakdown" }. - send_fax - in
to_number(E.164, US or CA), optionalrecipient_name, exactly one ofpdf_urlorbody_markdown, optionalcover_note(<= 500 characters) andidempotency_key. Out{ "fax_id", "status", "pages_estimated", "credits_charged_cents", "balance_cents", "to_number" }. - get_fax_status - out a fax record plus
events[]. A fax that ends infailedis refunded once. - list_faxes, get_balance, create_topup_link - as in mail. The balance and top-up endpoints read and write the same ledger.
If you supply body_markdown and the configured provider will not accept
rendered HTML, the server returns 400 invalid_request with
field: "body_markdown" and a message telling you to supply a pdf_url.
Page counts come from the provider when available. Otherwise the server estimates one page per 3,000 characters and trues up the charge when the provider reports the final count, recording the adjustment on the ledger.
| Item | Price |
|---|---|
| Fax, first page | $1.49 |
| Each additional fax page | $0.49 |
| Fax cost estimate | Free |
Bookings
Free for the person booking. No call in this connector deducts credits. Merchants owe $1.00 per completed booking; see for businesses.
Endpoints
| MCP tool | Scope | REST |
|---|---|---|
search_providers |
read | POST /c/booking/v1/providers/search |
list_services |
read | GET /c/booking/v1/providers/{provider_id}/services |
get_availability |
read | POST /c/booking/v1/providers/{provider_id}/availability |
create_booking |
write | POST /c/booking/v1/bookings |
get_booking |
read | GET /c/booking/v1/bookings/{booking_id} |
cancel_booking |
write | POST /c/booking/v1/bookings/{booking_id}/cancel |
list_my_bookings |
read | GET /c/booking/v1/bookings?limit=&cursor= |
- search_providers - in
{ "query": string, "city"?: string, "state"?: string, "zip"?: string, "limit"?: 1..20 }, out{ "providers": [...] }withprovider_id,display_name,categories,address,timezone,next_available_atandservices_preview. Matching is a case-insensitive keyword match over service names and categories, filtered by city and state or ZIP prefix. Radius search does not exist in v0. - list_services - out
{ "services": [...] }withservice_id,name,duration_min,price_cents, optionaldescriptionandteam_member_ids. Cached, refreshed when older than ten minutes. - get_availability - in
{ "service_id", "date_from": "YYYY-MM-DD", "date_to": "YYYY-MM-DD", "team_member_id"? }, a window of at most 14 days. Out{ "slots": [...] }, at most 50, each withstart_at(ISO with offset),team_member_idandduration_min. - create_booking - in
provider_id,service_id,start_at, optionalteam_member_id, acustomerblock (given_name,family_name,email,phonein E.164), optionalnote(<= 200 characters) andidempotency_key. Outbooking_id,status(confirmed,pendingorcancelled),start_at,end_at,provider,serviceandcancellation_policy. - get_booking - out the same booking object.
- cancel_booking - in
{ "booking_id", "reason"? }, out the booking withstatus: "cancelled". - list_my_bookings - in
{ "limit"?, "cursor"? }, out{ "items": [booking], "next_cursor"? }for the calling account only.
Merchant endpoints
These are for business owners in a browser, not for agents:
GET https://api.noerrands.com/c/booking/merchant/connect?merchant_email=&return_to=starts Square OAuth.GET https://api.noerrands.com/c/booking/merchant/callbackis the OAuth redirect target.GET/PATCH https://api.noerrands.com/c/booking/merchant/meis merchant self-service, authenticated with a merchant session token rather than an API key.
Test it with curl
Set your key once:
export API_KEY="ne_live_your_key_here"
export MAIL="https://api.noerrands.com/c/mail/v1"
export FAX="https://api.noerrands.com/c/fax/v1"
export BOOKING="https://api.noerrands.com/c/booking/v1"
Is the service up?
curl -s https://api.noerrands.com/health
Check the account and the balance:
curl -s "$MAIL/me" -H "Authorization: Bearer $API_KEY"
curl -s "$MAIL/balance" -H "Authorization: Bearer $API_KEY"
Check a postal address (free):
curl -s "$MAIL/addresses/verify" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"address":{"name":"Ada Lovelace","line1":"1 Main St","city":"Austin","state":"TX","zip":"78701"}}'
Get a price (free):
curl -s "$MAIL/estimate" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"kind":"letter","pages":2,"color":false,"certified":false}'
curl -s "$FAX/estimate" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"pages":3}'
Send a letter (this costs money):
curl -s "$MAIL/letters" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to":{"name":"Ada Lovelace","line1":"1 Main St","city":"Austin","state":"TX","zip":"78701"},
"from":{"name":"Grace Hopper","line1":"2 Oak Ave","city":"Denver","state":"CO","zip":"80202"},
"subject":"Hello",
"body_markdown":"Dear Ada,\n\nThank you.\n\nGrace",
"idempotency_key":"letter-to-ada-2026-09-21"
}'
Send a postcard (this costs money):
curl -s "$MAIL/postcards" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to":{"name":"Ada Lovelace","line1":"1 Main St","city":"Austin","state":"TX","zip":"78701"},
"from":{"name":"Grace Hopper","line1":"2 Oak Ave","city":"Denver","state":"CO","zip":"80202"},
"front":{"text":"Greetings from Denver"},
"message":"Wish you were here.",
"idempotency_key":"postcard-to-ada-2026-09-21"
}'
Send a fax (this costs money):
curl -s "$FAX/faxes" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to_number":"+14155550123",
"recipient_name":"Records Office",
"pdf_url":"https://example.com/intake-form.pdf",
"cover_note":"Intake form for Ada Lovelace.",
"idempotency_key":"fax-to-records-2026-09-21"
}'
Find a business and see its open slots (free):
curl -s "$BOOKING/providers/search" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"haircut","city":"Austin","state":"TX","limit":5}'
curl -s "$BOOKING/providers/prv_01H8X/services" -H "Authorization: Bearer $API_KEY"
curl -s "$BOOKING/providers/prv_01H8X/availability" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"service_id":"svc_01","date_from":"2026-09-24","date_to":"2026-09-26"}'
Book it (free, but it puts a real person in a real calendar):
curl -s "$BOOKING/bookings" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider_id":"prv_01H8X",
"service_id":"svc_01",
"start_at":"2026-09-24T14:00:00-05:00",
"customer":{"given_name":"Ada","family_name":"Lovelace","email":"ada@example.com","phone":"+14155550123"},
"idempotency_key":"haircut-2026-09-24"
}'
Check on things:
curl -s "$MAIL/mail/ml_01H8X" -H "Authorization: Bearer $API_KEY"
curl -s "$FAX/faxes/fx_01H8X" -H "Authorization: Bearer $API_KEY"
curl -s "$BOOKING/bookings?limit=5" -H "Authorization: Bearer $API_KEY"
Add credits:
curl -s "$MAIL/topups" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"amount_cents":1000}'
On Windows PowerShell, use Invoke-RestMethod or wrap the JSON in single
quotes with curl.exe rather than the curl alias.
Known gaps in v0
- Email addresses on key creation are not verified.
- Mail is United States only; fax is United States and Canada; bookings are United States only.
- Mail cannot be cancelled once it is submitted to the print provider, and a fax cannot be recalled once transmitted.
- Booking search has no radius filter. Supply a city and state, or a ZIP.
- There is no reschedule call. Cancel and rebook.
- Merchant booking fees are recorded but not yet invoiced.
- Per-call payment over Stripe's Machine Payments Protocol is behind a flag and may return a stubbed challenge.
Support
Email support@noerrands.com.