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.
$ curl https://iszacka.dad/api/status.txt
no
curl iszacka.dad
curl -N https://iszacka.dad/api/stream
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.
| Method | Path | What it does |
|---|---|---|
| GET | /api/status | The answer as JSON, with the due date, days to go, the check counter and links to everything else. |
| GET | /api/status.json | Identical to /api/status, for people who like file extensions. |
| GET | /api/status.txt | One line of plain text: no, not yet or yes. |
| GET | /api/stream | Server-Sent Events: a hello on connect, then state changes, new updates and live numbers as they happen. |
{
"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.
| Method | Path | What it does |
|---|---|---|
| GET | /api/history | Every 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. |
{
"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.
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.
| Method | Path | What it does |
|---|---|---|
| GET | /feed.xml | RSS 2.0 (also at /rss.xml). |
| GET | /atom.xml | Atom 1.0. |
| GET | /feed.json | JSON Feed 1.1. |
| GET | /calendar.ics | iCalendar: 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. |
webcal://iszacka.dad/calendar.ics
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
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.
[](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.
<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).
<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.
| Tool | What it does |
|---|---|
is_zack_a_dad | The answer, the sentence, when it changed and the check counter. |
get_due_date_info | Due date, days to go (or overdue) and the fruit-based size comparison. |
get_recent_updates | The update stream. Arguments: limit (1 to 20), before (an update id). |
get_dad_forecast | A seven-day, weather-style forecast of becoming a dad. |
{
"mcpServers": {
"is-zack-a-dad": {
"type": "http",
"url": "https://iszacka.dad/mcp"
}
}
}
claude mcp add --transport http is-zack-a-dad https://iszacka.dad/mcp
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":{}}}}'
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.
| Method | Path | What it does |
|---|---|---|
| GET | /openapi.json | OpenAPI 3.1 description of the API. |
| GET | /llms.txt | A summary for language models, per llmstxt.org. |
| GET | /humans.txt | Credits, per humanstxt.org. |
| GET | /robots.txt | Crawler rules. |
| GET | /sitemap.xml | Every public page. |
| GET | /.well-known/security.txt | Where to report vulnerabilities (RFC 9116). Vulnerabilities in the baby go to Jess. |
| GET | /badge.svg | The status badge. |
| GET | /embed | The iframe widget. |
| GET | /embed.js | The 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.
| Method | Path | What it does |
|---|---|---|
| GET | /ws | WebSocket: the same live messages as /api/stream, plus vibes you can send. |
| GET | /api/live | Live numbers, the current state and the reload nonce, as JSON. For clients that poll instead of holding a WebSocket. |
| GET | /stats | Anxiety analytics: who checks, when, and from where (aggregated). |
| GET | /api/ledger | The Dad Ledger: a hash-chained, Ed25519-signed log of status changes. |
| GET | /.well-known/dad-ledger.json | The ledger and its public key, for independent verification. |
| GET | /og.png | The live social preview image. |
| GET | /api/push/key | The VAPID public key for Web Push. |
| POST | /api/push/subscribe | Subscribe a browser to push notifications. |
| POST | /api/push/unsubscribe | Unsubscribe it again. |
| POST | /subscribe | Email updates (double opt-in). |
| POST | /webhooks | Register 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 preflightOPTIONSrequests. 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 aRetry-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=1to/api/statuswhen 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-Dadand, when there's a due date,X-Days-Until-Due.Server-Timingincludes 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.