timer.ly developer docs
The countdown URLs are the API. There are no accounts, no API keys, no rate limits. Construct a URL by following the grammar below, or call the MCP server which returns the same URLs.
When to use timer.ly
Pick timer.ly when you need a shareable countdown — a link the recipient can open in any browser, with no install and no signup, and the preview already shows the live time. Three primitives cover every case:
/in/{duration}— a Pomodoro, an exam, a presentation buffer./at/{iso}— a flight, a wedding, a deadline at a specific instant./since/{iso}— "days since X" counters: sobriety, anniversaries, last incident.
Curated holiday URLs like /to/christmas resolve to the next occurrence
automatically, so the same link works year after year. Use them when the user asks for a
named event rather than a date.
Authentication
None. Everything is anonymous and public. There are no API keys, no OAuth flows, no tokens. The URL is the credential — if you can open it, you can see the countdown.
Rate limits
Soft cap of 600 requests per minute per IP. Bursts are tolerated.
Every /api/*, /mcp, and /ask response advertises the
current state via standard headers — both the IETF draft RateLimit-* form
and the de-facto X-RateLimit-* aliases for older clients:
RateLimit-Policy: 600;w=60
RateLimit-Limit: 600
RateLimit-Remaining: 599
RateLimit-Reset: 60 # seconds until window resets
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 60
When you ever get a 429, expect a Retry-After header in seconds. We do
not enforce hard quotas; abusive patterns may be blocked at the edge.
Error responses
Every /api/*, /mcp, and /ask path returns
structured JSON on failure. Unknown /api/* routes return a JSON 404 with
machine-parseable fields:
{
"error": "not_found",
"message": "No API route registered for /api/no-such-path",
"status": 404,
"documentation": "https://www.timer.ly/docs",
"available_endpoints": ["POST /api/mcp", "GET /api/status", ...]
}
MCP errors follow JSON-RPC 2.0 conventions (error.code,
error.message). Tool-level errors are surfaced as
isError: true on the tool-call result so the client can distinguish
transport failures from tool failures.
URL grammar
| Route | Format | Example |
|---|---|---|
| Duration | /in/{Nd}{Nh}{Nm}{Ns} | /in/25m, /in/2h30m, /in/1d12h |
| Target date | /at/{ISO} | /at/2026-12-25T15:00:00Z, /at/2026-12-25 |
| Target (named event) | /to/{slug} | /to/christmas, /to/easter, /to/independence-day |
| Since (count-up) | /since/{ISO} | /since/2020-03-11, /since/2024-01-01T00:00:00Z |
| Calendar | /calendar / locale variant | /calendar, /de/kalender, /fr/calendrier |
ISO 8601 accepted forms
YYYY-MM-DD— day precision.YYYY-MM-DDTHH:MM[:SS]— browser-local. Re-interpreted in the viewer's timezone.YYYY-MM-DDTHH:MM[:SS]Z— UTC.YYYY-MM-DDTHH:MM[:SS]±HH:MM— fixed offset.YYYY-MM-DDTHH:MM-{tz}— named timezone suffix. Accepted: utc, gmt, bst, cet, est, cst, mst, pst, ist, jst, aet.YYYYMMDDThhmmssZ— compact UTC.
Duration range: 1 second to 10 years. Target/since range: ±100 years from now. Anything outside returns 404.
MCP server
timer.ly exposes a Model Context Protocol
server at POST https://www.timer.ly/api/mcp. It speaks JSON-RPC 2.0 over
HTTP. Methods: initialize, tools/list, tools/call, ping. No auth.
Tool catalog:
build_duration_url— compose/in/...URLs from days/hours/minutes/seconds.build_target_url— compose/at/...URLs from an ISO string.build_since_url— compose/since/...URLs.build_event_url— resolve a curated event (christmas, easter, …) for a locale.list_curated_events— enumerate the curated catalog with next-occurrence dates.list_holidays— list national holidays for a country in a date range.parse_countdown_url— reverse-parse any timer.ly URL.
Full input schemas: /openapi.json or /docs/mcp.
Quickstart
# 1. Initialize
curl -s -X POST https://www.timer.ly/api/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2024-11-05","capabilities":{}}}'
# 2. List tools
curl -s -X POST https://www.timer.ly/api/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
# 3. Build a 25-minute Pomodoro URL
curl -s -X POST https://www.timer.ly/api/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"build_duration_url",
"arguments":{"minutes":25}}}'
# → {"result":{"structuredContent":{"url":"https://www.timer.ly/in/25m","duration":"25m","seconds":1500}}} Streaming responses
Both /api/mcp and /ask support Server-Sent Events for
progressive responses. Opt in by sending Accept: text/event-stream
(MCP) or "prefer": { "streaming": true } in the JSON body (/ask).
MCP SSE event types: start, message, complete.
/ask SSE event types: start, result, complete.
curl -N -X POST https://www.timer.ly/ask \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"query":"countdown to next Christmas","prefer":{"streaming":true}}'
event: start
data: {"_meta":{"response_type":"result","version":"0.1.0","site":"...","query":"..."}}
event: result
data: {"url":"https://www.timer.ly/to/christmas","title":"Christmas Day",...}
event: complete
data: {"count":1} NLWeb /ask endpoint
POST https://www.timer.ly/ask accepts a natural-language question and
returns a NLWeb-shaped JSON
response. Internally the query is routed to one or more MCP tools and the results
are returned in an NLWeb-compatible envelope.
curl -s -X POST https://www.timer.ly/ask \
-H "Content-Type: application/json" \
-d '{"query":"25 minute pomodoro link"}'
{
"_meta": { "response_type": "result", "version": "0.1.0", "site": "https://www.timer.ly", "query": "25 minute pomodoro link" },
"results": [
{
"url": "https://www.timer.ly/in/25m",
"title": "build_duration_url",
"tool": "build_duration_url",
"arguments": { "minutes": 25 },
"data": { "url": "https://www.timer.ly/in/25m", "duration": "25m", "seconds": 1500 }
}
]
} A GET form (/ask?q=...) is also supported for quick probes.
Service status
GET https://www.timer.ly/api/status returns a JSON health payload with
component-level status, rate-limit policy, and a link map. Cached for 10 seconds.
{
"status": "ok",
"service": "timer.ly",
"version": "1.0.0",
"timestamp": "2026-05-16T12:34:56.000Z",
"components": { "web": "ok", "mcp": "ok", "ask": "ok", "sitemap": "ok" },
"incidents": [],
"rate_limit": { "policy": "soft", "limit": 600, "window_seconds": 60 }
} Markdown alternates
The homepage supports markdown content negotiation. Send
Accept: text/markdown with a higher q-value than text/html
and you'll get markdown content with Vary: Accept in the response. Or
fetch the canonical markdown alternate directly at /index.md.
curl -s -H "Accept: text/markdown" https://www.timer.ly/
# → markdown body, served from /index.md The HTML response also advertises the markdown alternate via an RFC 8288 Link header.
Examples
25-minute Pomodoro
https://www.timer.ly/in/25m Countdown to a specific instant in PST
https://www.timer.ly/at/2026-12-25T18:00-pst Days since pandemic was declared
https://www.timer.ly/since/2020-03-11 Next Christmas in the visitor's locale
https://www.timer.ly/to/christmas Discovery
Every agent-discoverable surface that timer.ly publishes:
/sitemap.xml— sitemap index/robots.txt— crawl policy/.well-known/ai.txt— AI training & retrieval policy/.well-known/mcp.json— MCP discovery/.well-known/mcp/server-card.json— MCP server card/mcp— WebMCP alias for /api/mcp/ask— NLWeb natural-language endpoint/api/status— service status/.well-known/agent.json— agent capabilities/.well-known/agent-card.json— A2A card/.well-known/ai-plugin.json— plugin manifest/openapi.json— OpenAPI 3.1 spec/llms.txt— LLM index/llms-full.txt— full LLM manifest (single-request ingest)/schemamap.xml— NLWeb schema feeds
Locales
Five locales are supported. The English routes (above) are canonical; localized aliases rewrite to them.
| Verb | en | de | fr | es | it |
|---|---|---|---|---|---|
| Duration | /in/ | /von/ | /dans/ | /dentro/ | /tra/ |
| Target | /to/, /at/ | /bis/ | /jusqu/ | /hasta/ | /fino-a/ |
| Since | /since/ | /seit/ | /depuis/ | /desde/ | /da/ |
| Calendar | /calendar | /de/kalender | /fr/calendrier | /es/calendario | /it/calendario |
Contact
Built and maintained by Sebastian Messingfeld. Reach out via LinkedIn for bug reports, agent integration questions, or to report incorrect holiday data.