MailerDot Developer Documentation
The MailerDot API, MCP server, and agent-readable resources — every U.S. city we publish, its daily editions, weather, and census facts. Mostly reads — subscribing and the listing checkouts write — with no key, no sign-up, and CORS open.
Base URL: https://mailerdot.com · OpenAPI 3.1 spec · MCP server · This page as markdown
Authentication
None. No key, no token, no account, no sign-up — including for the endpoints that sell things: a checkout is created anonymously and the buyer identifies themselves on the payment page. Most endpoints are read-only; the four that write are POST /api/subscribe (free) and the three purchase endpoints under Buying things, none of which can charge anyone. There is no sandbox because there is nothing to break: the read endpoints cannot mutate anything and the purchase endpoints stop at a payment link, so you can develop against production directly.
Quickstart
# 1. Find a city
curl -s 'https://mailerdot.com/api/cities'
# 2. Read its latest edition as markdown
curl -s 'https://mailerdot.com/austin-texas.md'
# 3. Search every archive
curl -s 'https://mailerdot.com/api/search?q=farmers+market&limit=3'City slugs
Every endpoint is keyed by a slug of the form `{city}-{state-name}`, lowercased and hyphenated — `austin-texas`, `san-francisco-california`, `new-york-new-york`. The state is the full name, not the postal abbreviation. Fetch https://mailerdot.com/api/cities for the authoritative list.
API endpoints
| Method | Path | Returns |
|---|---|---|
GET | /api/cities | Every city with a published edition, grouped by state. |
GET | /api/cities-census | Census-tracked cities, filterable by population and state. |
GET | /api/city/{slug} | One city: subscriber count, archive index, census snapshot, events, directory. |
GET | /api/archive/{slug}/{date} | One past edition. Append `.md` to the date for markdown instead of JSON. |
GET | /api/search | Full-text search across every archived edition, with highlighted snippets. |
GET | /api/weather/{slug} | Today's forecast — the stored snapshot the edition used, or a live fetch. |
GET | /api/facts/{slug} | Schema.org Dataset facts for a city: newsletter metadata plus ACS demographics. |
GET | /api/territories | Whether a town can be leased and what it costs. No query returns the taken list. |
GET | /api/oembed | oEmbed provider for city and edition pages (rich iframe embeds). |
GET | /api/pricing | The whole price list as data — every product and tier, no town required. |
GET | /api/listing-quote | What an event or business listing costs in a town, and whether that town is selling. |
GET | /api/listing-order | Whether a listing order has been paid and whether the listing is live yet. |
POST | /api/subscribe | Subscribe an address to a city. Free, and the only write that is not a purchase. |
POST | /api/listing-checkout | Submit an event to a town's calendar. Returns a payment link; charges nothing. |
POST | /api/business-checkout | Submit a business to a town's directory. Returns a payment link; charges nothing. |
POST | /api/operator-actions | With `action: "guest-checkout"`, returns a payment link to lease a town as its operator. |
Full parameters and response schemas: https://mailerdot.com/openapi.json
Buying things
No endpoint here can charge anyone. Every purchase call ends at a hosted checkout URL on our merchant of record's domain, and a person has to open it and enter card details before money moves. Nothing publishes until payment clears (paid tiers) or a person approves it (free tier).
Both products work the same way:
- Price it — a
GETthat reserves nothing. - Create the checkout — a
POSTthat screens the request and returns a hosted checkout URL. - A person pays — on our merchant of record's page. Nothing before this step moves money.
- Follow it —
GET /api/listing-orderfor a listing; a town lease activates on the payment webhook.
Town lease (operator)
Exclusive commercial rights to one U.S. town's daily newsletter. The edition writes and sends itself — an operator never writes a word — and the operator takes half the net on every listing sold in their town.
- Price: $10/month for the first 1,000 subscribers, then +$5/month per additional 1,000.
- Quote:
GET /api/territories?city=Austin&state=Texas - Buy:
POST /api/operator-actions — body: {"action":"guest-checkout","city":"Austin","state":"Texas"} - Before anything publishes: The town is held for a short reservation window and released if checkout is not completed. The email entered at checkout becomes the operator account.
Event listing
An event on a town's public calendar, and on the featured tier, inside the daily email.
- Price: Free (reviewed by a person), $10 flat, or $25 featured. One-off, not a subscription.
- Quote:
GET /api/listing-quote?slug=austin-texas - Buy:
POST /api/listing-checkout - Before anything publishes: The compliance screen runs inside the request, so a rejected submission is answered with a reason and never gets a payment link. A paid listing publishes only after payment clears; a free one only after a person approves it.
Business directory listing
A business in a town's directory for 30 days, and on the featured tier, a weekly mention in the daily email.
- Price: Free (reviewed by a person), $10 flat, or $25 featured. One-off, not a subscription.
- Quote:
GET /api/listing-quote?slug=austin-texas - Buy:
POST /api/business-checkout - Before anything publishes: Same screen-then-pay contract as an event listing. The 30-day run starts the day payment clears.
A listing checkout answers 200 for every screened outcome — exactly one of checkoutUrl, rejected, held or queued is present. Branch on which field is there, not on the status: “the answer is no” is a successful request.
Error responses
Every failure answers with application/problem+json (RFC 9457) — never an HTML page, on any path under /api/, including ones that do not exist. Branch on code; it is stable. resolution says what to do next, and issues[] lists per-field failures on a validation error.
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
city_not_found | 404 | No MailerDot city for that slug | Fetch https://mailerdot.com/api/cities for every city with a published edition. A U.S. city with no MailerDot edition yet has no data to return; subscribing to it creates one. |
conflict | 409 | Request conflicts with current state | Re-read the resource to see its current state, then decide whether to retry. |
edition_not_found | 404 | No edition published for that date | Fetch https://mailerdot.com/api/city/{slug} for the dates that exist. Editions publish each morning in the city's own timezone, so today's may not exist yet. |
endpoint_not_found | 404 | No such API endpoint | Every public endpoint is listed in https://mailerdot.com/openapi.json and https://mailerdot.com/.well-known/api-catalog. Human-readable docs: https://mailerdot.com/developers. |
forbidden | 403 | Not permitted | The credential is valid but does not grant this action. Nothing to retry. |
internal_error | 500 | Unexpected server error | Retry once; this is usually transient. If it persists, report it at https://mailerdot.com/contact with the request path and time. |
invalid_json | 400 | Request body is not valid JSON | Send a JSON body with `Content-Type: application/json`, then retry. |
invalid_slug | 400 | City slug is not well-formed | Slugs are `{city}-{state-name}`, lowercased and hyphenated — `austin-texas`, `san-francisco-california`. List valid slugs at https://mailerdot.com/api/cities. |
method_not_allowed | 405 | HTTP method not allowed | Use one of the methods in the `Allow` response header. Per-endpoint methods are in https://mailerdot.com/openapi.json. |
missing_slug | 400 | City slug is missing | Put a city slug in the path, e.g. `/api/city/austin-texas`. List valid slugs at https://mailerdot.com/api/cities. |
not_found | 404 | Resource not found | Check the identifier and retry. The available endpoints are listed at https://mailerdot.com/openapi.json. |
payload_too_large | 413 | Request body is too large | Send a smaller body. The per-endpoint limit is in the `detail` field. |
rate_limited | 429 | Rate limit exceeded | Back off and retry after the `Retry-After` header's number of seconds. |
service_misconfigured | 503 | Service is misconfigured | The deployment is missing required configuration and cannot serve the API. This is our problem, not the caller's — retry later or report it at https://mailerdot.com/contact. |
unauthorized | 401 | Authentication required | This endpoint is not part of the public API. Sign in at https://mailerdot.com/operators — the public, unauthenticated surface is documented at https://mailerdot.com/developers. |
upstream_error | 502 | An upstream service failed | A third-party dependency failed or timed out. Retry with backoff. |
validation_error | 400 | Request validation failed | Read `issues[]` for the per-field failures, fix the request, and retry. Request shapes are documented at https://mailerdot.com/openapi.json. |
weather_unavailable | 404 | No weather available for that city | The city resolved but neither a stored snapshot nor a live forecast was available. Retry later; weather is refreshed with each morning's edition. |
API versioning and deprecation
- The current version is **v1**. Every endpoint answers at both `/api/…` and `/api/v1/…` — the same handler, so the two can never drift. Pin to `/api/v1/` if you want a URL that cannot change meaning; use the bare `/api/` path if you would rather track the current version.
- Every `/api/*` response carries an `API-Version` header naming the version that served it.
- Breaking changes get a new prefix (`/api/v2/…`) rather than being made in place. `v1` keeps answering as it always did.
- Additive changes are not breaking and land in `v1` without a bump: a new endpoint, a new optional response field, a new error `code`. **Tolerate unknown fields** — that is the one thing a client has to do to stay compatible.
- A retirement, if one ever happens, is signalled with a `Deprecation` header (RFC 9745) and a `Sunset` header (RFC 8594) on the affected endpoint, at least 180 days apart, plus a `Link: <…>; rel="deprecation"` pointing at the replacement. Nothing is deprecated today, so no endpoint emits those headers.
- The unversioned `/api/` prefix is not deprecated and is not scheduled for removal.
Webhooks
- MailerDot does not send outbound webhooks today. There is no endpoint to register a callback URL with, and no signing secret to verify.
- To follow a listing order to completion, poll `GET /api/listing-order?id={orderId}`. It reports `status`, and `published` once the listing is live. Payment confirmation arrives by our own webhook from the payment provider, so a paid order becomes `published` within seconds.
- To follow a town's editions, use the RSS feed at `/feed.xml`, or poll `GET /api/city/{slug}` — the `archives` array grows by one each morning.
- The `/api/creem-webhook` endpoint in this codebase is *inbound*: it receives signed callbacks from our payment provider. It is not for integrators and is not part of the public API.
- If outbound webhooks would make a real integration possible for you, say so at /contact — that is the signal that would get them built.
Markdown for agents
Every page has a markdown twin. Append .md to the path, or send Accept: text/markdown. Responses carry an X-Markdown-Tokens header with a rough token estimate.
https://mailerdot.com/{slug}.md— A city page: latest edition, archive index, census table.https://mailerdot.com/{slug}/{YYYY-MM-DD}.md— One dated edition, in full.https://mailerdot.com/cities.md— The whole city directory.https://mailerdot.com/states/{state}.md— Every tracked city in one state, by population.https://mailerdot.com/feed.md— The 30 most recent editions across all cities.https://mailerdot.com/developers.md— This page.
MCP server
https://mailerdot.com/mcp speaks the Model Context Protocol over streamable HTTP, unauthenticated. Point any MCP client at it:
{
"mcpServers": {
"mailerdot": {
"url": "https://mailerdot.com/mcp"
}
}
}Tools:
list_cities— Every city with an active newsletter, grouped by state.get_city— City overview: subscribers, census demographics, recent editions.get_archive— Read a past edition as markdown.search_archives— Full-text search across every edition.get_weather— Today's weather for a city.subscribe_city(writes) — Subscribe a reader's email to a city's free daily digest. Sends mail — confirm with the reader first.get_town_quote— Whether a town can be leased, and the monthly price.start_town_checkout(writes) — Payment link to lease a town. Charges nothing.get_listing_prices— What an event or business listing costs in a town.create_event_listing(writes) — Submit an event; returns a payment link. Charges nothing.create_business_listing(writes) — Submit a business; returns a payment link. Charges nothing.get_listing_order— Whether an order is paid and the listing is live.
The tools marked (writes) are the commerce ones. They are annotated readOnlyHint: false so a client can gate them, and they return a payment link rather than making a purchase.
It also serves resources/list, resources/templates/list, resources/read, and prompts/list.
Machine-readable discovery
- OpenAPI 3.1 specification
application/json— The full contract: every endpoint, schema, and error code. Generate a client from it. - MCP server
application/json— Model Context Protocol over streamable HTTP. Tools, resources, and prompts. - API catalog (RFC 9727)
application/linkset+json— Linkset pointing at everything on this list. The standard discovery entry point. - Agent Skills index
application/json— A SKILL.md an agent can load to learn the URL shapes without crawling for them. - Agent instructions (when to use this)
text/markdown— The jobs MailerDot is the right tool for, the call that answers each, and what it is not for. - Pricing as data
application/json— Everything MailerDot charges for, priced from the same module the checkout path bills from. - llms.txt
text/plain— What MailerDot is, in one page, written for a language model. - llms-full.txt
text/plain— The long form: product, editorial policy, operator terms, and API notes. - RSS feed
application/rss+xml— The most recent editions across every city. - Sitemap index
application/xml— Every indexable URL: cities, states, and dated editions.
Limits and conventions
- No API key, no account, no rate limit on the read endpoints. Be reasonable and cache what you fetch — responses carry real `Cache-Control` headers, so honouring them costs you nothing.
- Every read endpoint sends `Access-Control-Allow-Origin: *`, so browser-side agents can call them directly.
- Markdown responses carry an `X-Markdown-Tokens` header with a rough token estimate, so you can decide whether to fetch a full edition or stop at the summary.
- Editions publish each morning in the city's own timezone. Asking for today's edition before it publishes returns a 404 with the `edition_not_found` code, not the previous day's content.
- Coverage is the United States only.
Support
- Questions and bug reports: https://mailerdot.com/contact — we reply within 3 business days
- Security reports: security.txt
- Terms · Privacy