Developer documentation

The iszacka.dad API

One yes/no question. Twenty-odd endpoints, four feed formats, an MCP server and an HTTP status code from RFC 8470. You're welcome.

Right now: no OpenAPI 3.1 llms.txt

Quick start

No keys, no sign-up, no SDK. If you can make an HTTP request, you can know whether Zack is a dad.

The shortest possible answer
$ curl https://iszacka.dad/api/status.txt
no
The answer, in enormous coloured letters
curl iszacka.dad
Watch it happen live (Server-Sent Events)
curl -N https://iszacka.dad/api/stream
Over DNS, because why not
dig TXT answer.iszacka.dad +short

Status

The answer, in increasing levels of verbosity. These never cache (Cache-Control: no-store), because the answer could change at any moment.

Status endpoints
MethodPathWhat it does
GET/api/statusThe answer as JSON, with the due date, days to go, the check counter and links to everything else.
GET/api/status.jsonIdentical to /api/status, for people who like file extensions.
GET/api/status.txtOne line of plain text: no, not yet or yes.
GET/api/streamServer-Sent Events: a hello on connect, then state changes, new updates and live numbers as they happen.
GET /api/status (live, as of this page load)
{
  "isZackADad": false,
  "status": "no",
  "answer": "NO",
  "sentence": "Zack is not a dad. Yet.",
  "statusChangedAt": "2026-11-07T09:04:47.227Z",
  "dueDate": null,
  "daysUntilDue": null,
  "checks": 89,
  "links": {
    "self": "https://iszacka.dad/api/status",
    "html": "https://iszacka.dad/",
    "text": "https://iszacka.dad/api/status.txt",
    "history": "https://iszacka.dad/api/history",
    "updates": "https://iszacka.dad/api/updates",
    "dad": "https://iszacka.dad/api/dad",
    "stream": "https://iszacka.dad/api/stream",
    "rss": "https://iszacka.dad/feed.xml",
    "atom": "https://iszacka.dad/atom.xml",
    "jsonFeed": "https://iszacka.dad/feed.json",
    "calendar": "https://iszacka.dad/calendar.ics",
    "badge": "https://iszacka.dad/badge.svg",
    "embed": "https://iszacka.dad/embed",
    "docs": "https://iszacka.dad/api",
    "openapi": "https://iszacka.dad/openapi.json",
    "mcp": "https://iszacka.dad/mcp"
  }
}

Fields: status is no, soon (labour has started) or yes. daysUntilDue counts whole days in Europe/London and goes negative when overdue; it is null without a due date. checks is how many times anyone has asked.

History and updates

Every status change, the daily "still no" bulletin and the occasional announcement. Cached for 30 seconds.

Update endpoints
MethodPathWhat it does
GET/api/historyEvery status the site has had, oldest first.
GET/api/updates?limit=20&before=Updates, newest first. limit is 1 to 50 (default 20); pass nextBefore as before for the next page.
GET /api/updates?limit=2 (live)
{
  "updates": [],
  "nextBefore": null,
  "next": null
}

GET /api/dad

Request one dad. Until the baby arrives this returns 425 Too Early (RFC 8470), with a Retry-After computed from the due date; afterwards it returns 201 Created with Location: /. POST works too.

Right now
HTTP/1.1 425 Too Early
Content-Type: application/json

{
  "error": "too_early",
  "status": "no",
  "isZackADad": false,
  "message": "Too early. The dad you requested has not been delivered yet.",
  "dueDate": null,
  "retryAfterSeconds": null,
  "retryAt": null
}

Retry-After counts down to midnight (London time) on the due date, drops to an hour once overdue, and to a minute during labour. Without a due date there is no Retry-After, only hope.

Feeds

The latest 50 updates in every syndication format worth having. Cached for 60 seconds, with ETags, so a polite reader gets a 304.

Feeds
MethodPathWhat it does
GET/feed.xmlRSS 2.0 (also at /rss.xml).
GET/atom.xmlAtom 1.0.
GET/feed.jsonJSON Feed 1.1.
GET/calendar.icsiCalendar: the due date as an all-day event, plus "It's happening" and "Zack became a dad" at the moments they happen. Subscribe once; it refreshes hourly.
Subscribe in your calendar app
webcal://iszacka.dad/calendar.ics
Conditional requests
curl -sI https://iszacka.dad/feed.xml | grep -i etag
curl -s -o /dev/null -w '%{http_code}\n' -H 'If-None-Match: "<etag>"' https://iszacka.dad/feed.xml   # 304

Badges and embeds

Put the answer on your README, your wiki, your intranet or your fridge (see /poster for the fridge).

Badge

Badge: is zack a dad? no Flat-square badge For-the-badge badge

Options: ?style=flat|flat-square|for-the-badge and ?label= (up to 40 characters; empty hides the label). Red for no, amber for not yet, green for yes.

Markdown
[![Is Zack a dad?](https://iszacka.dad/badge.svg)](https://iszacka.dad/)

iframe

A bare little widget that can be framed from anywhere and refreshes itself every minute. Add ?theme=light or ?theme=dark to override the system theme.

HTML
<iframe src="https://iszacka.dad/embed" title="Is Zack a dad?" width="320" height="180" style="border:0"></iframe>

Script tag

Renders the widget in a shadow DOM right where the script tag is, so your styles and ours never meet. Polls /api/status every 60 seconds (pausing while the tab is hidden).

HTML
<script src="https://iszacka.dad/embed.js" data-theme="auto" data-size="medium" async></script>

Attributes: data-theme (auto, light, dark), data-size (small, medium, large), data-target (a CSS selector to render into instead) and data-link="false" to drop the link back here.

MCP server

For AI assistants that want to ask nicely. A stateless Model Context Protocol server over Streamable HTTP at /mcp, answering with plain JSON.

Protocol versions: 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26. Version 2026-07-28 clients send per-request _meta and the MCP-Protocol-Version, Mcp-Method and Mcp-Name headers (all validated); older clients initialize first. GET /mcp returns 405: there is no stream to listen to.

MCP tools (all read-only)
ToolWhat it does
is_zack_a_dadThe answer, the sentence, when it changed and the check counter.
get_due_date_infoDue date, days to go (or overdue) and the fruit-based size comparison.
get_recent_updatesThe update stream. Arguments: limit (1 to 20), before (an update id).
get_dad_forecastA seven-day, weather-style forecast of becoming a dad.
Client configuration
{
  "mcpServers": {
    "is-zack-a-dad": {
      "type": "http",
      "url": "https://iszacka.dad/mcp"
    }
  }
}
Claude Code
claude mcp add --transport http is-zack-a-dad https://iszacka.dad/mcp
A 2026-07-28 tool call with curl
curl -s https://iszacka.dad/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: tools/call' \
  -H 'Mcp-Name: is_zack_a_dad' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"is_zack_a_dad","arguments":{},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'
A 2025-06-18 handshake with curl
curl -s https://iszacka.dad/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'

Machine-readable odds and ends

For crawlers, auditors, language models and the terminally curious.

Machine-readable files
MethodPathWhat it does
GET/openapi.jsonOpenAPI 3.1 description of the API.
GET/llms.txtA summary for language models, per llmstxt.org.
GET/humans.txtCredits, per humanstxt.org.
GET/robots.txtCrawler rules.
GET/sitemap.xmlEvery public page.
GET/.well-known/security.txtWhere to report vulnerabilities (RFC 9116). Vulnerabilities in the baby go to Jess.
GET/badge.svgThe status badge.
GET/embedThe iframe widget.
GET/embed.jsThe script-tag widget.
GET/With curl, wget, HTTPie or xh: the answer as giant ANSI art.

Elsewhere on the site

Endpoints run by other parts of the operation, listed here so this page can honestly claim to cover everything.

Other public endpoints
MethodPathWhat it does
GET/wsWebSocket: the same live messages as /api/stream, plus vibes you can send.
GET/api/liveLive numbers, the current state and the reload nonce, as JSON. For clients that poll instead of holding a WebSocket.
GET/statsAnxiety analytics: who checks, when, and from where (aggregated).
GET/api/ledgerThe Dad Ledger: a hash-chained, Ed25519-signed log of status changes.
GET/.well-known/dad-ledger.jsonThe ledger and its public key, for independent verification.
GET/og.pngThe live social preview image.
GET/api/push/keyThe VAPID public key for Web Push.
POST/api/push/subscribeSubscribe a browser to push notifications.
POST/api/push/unsubscribeUnsubscribe it again.
POST/subscribeEmail updates (double opt-in).
POST/webhooksRegister a Discord, Slack or generic webhook.

Etiquette

The small print, kept small.

  • CORS: every /api endpoint and /mcp sends Access-Control-Allow-Origin: * and answers preflight OPTIONS requests. Feeds and the badge do too.
  • Rate limits: 120 requests a minute per IP across the API. Go over and you get 429, a JSON body and a Retry-After. Polling once a minute is plenty; the baby will not arrive faster if you refresh harder.
  • Caching: status endpoints are no-store; history and updates cache for 30 seconds; feeds and the badge for 60 seconds with ETags.
  • Polling: add ?poll=1 to /api/status when a timer, not a person, is asking. You get the same answer, and the public "times checked" counter isn't inflated by your dashboard.
  • Headers: every response carries X-Is-Zack-A-Dad and, when there's a due date, X-Days-Until-Due. Server-Timing includes the estimated gestation so far, in milliseconds, because it can.
  • Privacy: the site knows the status, when it changed and the due date. That is all it will ever know, so please don't ask the API for names or weights.