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

MethodPathReturns
GET/api/citiesEvery city with a published edition, grouped by state.
GET/api/cities-censusCensus-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/searchFull-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/territoriesWhether a town can be leased and what it costs. No query returns the taken list.
GET/api/oembedoEmbed provider for city and edition pages (rich iframe embeds).
GET/api/pricingThe whole price list as data — every product and tier, no town required.
GET/api/listing-quoteWhat an event or business listing costs in a town, and whether that town is selling.
GET/api/listing-orderWhether a listing order has been paid and whether the listing is live yet.
POST/api/subscribeSubscribe an address to a city. Free, and the only write that is not a purchase.
POST/api/listing-checkoutSubmit an event to a town's calendar. Returns a payment link; charges nothing.
POST/api/business-checkoutSubmit a business to a town's directory. Returns a payment link; charges nothing.
POST/api/operator-actionsWith `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:

  1. Price it — a GET that reserves nothing.
  2. Create the checkout — a POST that screens the request and returns a hosted checkout URL.
  3. A person pays — on our merchant of record's page. Nothing before this step moves money.
  4. Follow it — GET /api/listing-order for 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.

CodeHTTPMeaningWhat to do
city_not_found404No MailerDot city for that slugFetch 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.
conflict409Request conflicts with current stateRe-read the resource to see its current state, then decide whether to retry.
edition_not_found404No edition published for that dateFetch 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_found404No such API endpointEvery 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.
forbidden403Not permittedThe credential is valid but does not grant this action. Nothing to retry.
internal_error500Unexpected server errorRetry once; this is usually transient. If it persists, report it at https://mailerdot.com/contact with the request path and time.
invalid_json400Request body is not valid JSONSend a JSON body with `Content-Type: application/json`, then retry.
invalid_slug400City slug is not well-formedSlugs are `{city}-{state-name}`, lowercased and hyphenated — `austin-texas`, `san-francisco-california`. List valid slugs at https://mailerdot.com/api/cities.
method_not_allowed405HTTP method not allowedUse one of the methods in the `Allow` response header. Per-endpoint methods are in https://mailerdot.com/openapi.json.
missing_slug400City slug is missingPut a city slug in the path, e.g. `/api/city/austin-texas`. List valid slugs at https://mailerdot.com/api/cities.
not_found404Resource not foundCheck the identifier and retry. The available endpoints are listed at https://mailerdot.com/openapi.json.
payload_too_large413Request body is too largeSend a smaller body. The per-endpoint limit is in the `detail` field.
rate_limited429Rate limit exceededBack off and retry after the `Retry-After` header's number of seconds.
service_misconfigured503Service is misconfiguredThe 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.
unauthorized401Authentication requiredThis 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_error502An upstream service failedA third-party dependency failed or timed out. Retry with backoff.
validation_error400Request validation failedRead `issues[]` for the per-field failures, fix the request, and retry. Request shapes are documented at https://mailerdot.com/openapi.json.
weather_unavailable404No weather available for that cityThe 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