Developers

Timerbeam API

Create and manage live countdown timers from your own code — or let an AI agent do it for you. Everything the editor makes, the API makes too, within your plan’s limits. Every plan includes it.

Base URL
https://api.timerbeam.com
OpenAPI 3.1
https://api.timerbeam.com/api/openapi.json
Auth
Authorization: Bearer tb_live_…

Last updated

Quick start

  1. Create a key on the API keys page of your dashboard.
  2. Create a timer:
curl https://api.timerbeam.com/api/timers \
  -H "Authorization: Bearer tb_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "label": "Black Friday",
    "deadline": "2026-11-27T23:59",
    "timezone": "Europe/Budapest",
    "template": "cards",
    "store": true
  }'

You get the timer back — its image URL and the ready-to-paste email HTML:

{
  "id": "k7Qm2xPa9Lz3",
  "imageUrl": "https://api.timerbeam.com/s/k7Qm2xPa9Lz3.gif",
  "html": "<a href=\"…\"><img src=\"…/s/k7Qm2xPa9Lz3.gif\" width=\"570\" height=\"134\" …></a>",
  "deadline": "2026-11-27T22:59:00.000Z",
  "opens": 0,
  …
}

Put html in your email (or point an image block at imageUrl). Change the timer later with PATCH /api/timers/{id} — every email already sent shows the change on its next open.

Authentication

Send your key as Authorization: Bearer tb_live_…. A key acts as its account: its timers, its brand kits, its plan’s limits and features. A read-only key can only use GET. No key can sign in, touch billing, delete the account or manage keys — those stay in the dashboard. Revoke a key there and it stops working at once.

Without a key, POST /api/timers still works: a one-off timer (baked into its link, not editable), or with "store": true a saved timer whose manageKey — returned once — authorises its own GET/PATCH/DELETE.

Time zones

Give deadline as local time and name the zone: "deadline": "2026-11-27T23:59", "timezone": "Europe/Budapest" — daylight saving included. Or send an absolute time: ISO 8601 with an offset (2026-11-27T22:59:00Z) or unix seconds. Responses always give deadline in UTC.

Templates & brand kits

GET /api/templates lists the starting designs. Pass one as template, and a brand kit as brandKit to put it in your colours and font — in the same call:

{ "deadline": "2026-11-27T23:59", "timezone": "Europe/Budapest",
  "template": "cards", "brandKit": "<id from GET /api/brand-kits>", "store": true }

Or send a full design (see TimerDesign in the OpenAPI document). Templates marked proFont use a font from the Pro plan and up; brand kits are Business.

Evergreen timers

A countdown per recipient instead of one deadline (Pro and up, saved timers only):

{ "label": "Welcome offer", "store": true,
  "evergreen": { "durationSeconds": 172800, "start": "first-open" } }

The returned html carries ?r=RECIPIENT_ID (or ?start=START_DATE for "url-date") — swap the placeholder for your email platform’s merge tag. The email platform guides list the tags for Klaviyo, Mailchimp, HubSpot and Brevo.

Idempotency

Send an Idempotency-Key header (a UUID) with POST /api/timers. If the request is retried — a timeout, a crash, an agent trying again — the same key and body return the first answer (with Idempotent-Replayed: true) instead of creating a second timer. Keys are kept 24 hours; the same key with a different body is a 422. A replay never repeats a manage key.

Errors & limits

Every error is JSON: { "error": "<code>", "message": "<what to fix>" } — branch on error, show message.

StatusCodesMeaning
400bad_deadline, bad_units, bad_template, bad_timezone, timezone_conflict, bad_evergreen, …The input — `message` says what to fix.
401invalid_api_key, unauthorisedA revoked or mistyped key, or none where one is needed.
402plan_required, quota_exceededThe plan doesn’t include it (a Pro font, evergreen, brand kits) or its timer limit is reached.
403insufficient_scope, session_requiredA read-only key tried to write, or a key tried a browser-only endpoint.
404unknown_timer, not_foundNo such timer or kit on this account.
409limit_reachedToo many brand kits or keys.
422idempotency_key_reusedThe same Idempotency-Key with a different request.
429rate_limitedWait `Retry-After` seconds. Each key has its own budget.

Each key has its own rate budget; X-RateLimit-Remaining on every keyed response says what’s left. Plan limits (saved timers, fonts, evergreen, brand kits) apply exactly as in the editor — see pricing.

Analytics

On Business, GET /api/stats gives the account’s opens by day and by timer, and GET /api/timers/{id}/stats one timer’s — any range within the last year, every day included (zeros too), in UTC. Without from/to, the last 30 days.

curl "https://api.timerbeam.com/api/stats?from=2026-11-01&to=2026-11-30" \
  -H "Authorization: Bearer tb_live_…"
{
  "from": "2026-11-01", "to": "2026-11-30", "total": 48210,
  "days": [{ "day": "2026-11-01", "count": 0 }, …, { "day": "2026-11-27", "count": 19344 }, …],
  "timers": [{ "id": "k7Qm2xPa9Lz3", "label": "Black Friday", "opens": 41022 }, …]
}

Webhooks

On Business, Timerbeam can tell your own systems what happens — add endpoints on the Webhooks page or with POST /api/webhooks. Each event is a JSON POST:

EventWhen
timer.createdA saved timer was created — in the editor, over the API or by an agent.
timer.updatedA saved timer was changed. Rapid edits (the editor saves as you go) arrive as one event with the final state.
timer.deletedA saved timer was deleted.
timer.expiredA timer’s deadline passed. Not sent for evergreen timers, which never end for everyone at once.
usage.approaching_limitOpens this month passed 80% of the plan’s monthly limit — once a month.
usage.limit_reachedOpens this month reached the plan’s monthly limit — once a month. Timers keep working.
POST https://example.com/timerbeam-webhook
webhook-id: evt_4f0c2a…
webhook-timestamp: 1795820340
webhook-signature: v1,Zr0cBC5v…

{
  "id": "evt_4f0c2a…",
  "type": "timer.expired",
  "createdAt": "2026-11-27T22:59:04.000Z",
  "data": {
    "timer": {
      "id": "k7Qm2xPa9Lz3", "label": "Black Friday",
      "deadline": "2026-11-27T22:59:00.000Z", "expired": true,
      "imageUrl": "https://api.timerbeam.com/s/k7Qm2xPa9Lz3.gif", …
    }
  }
}

Deliveries follow Standard Webhooks, so its libraries verify them as they are. Or by hand — check the signature against the raw body, and the timestamp against replays:

import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody: the request body exactly as received, before any JSON parsing.
export function isFromTimerbeam(headers, rawBody, secret) {
  const id = headers['webhook-id'];
  const timestamp = headers['webhook-timestamp'];
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; // replayed or stale
  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const expected = createHmac('sha256', key).update(`${id}.${timestamp}.${rawBody}`).digest('base64');
  return headers['webhook-signature'].split(' ').some((part) => {
    const signature = part.split(',')[1] ?? '';
    return signature.length === expected.length && timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  });
}

Answer with any 2xx within 10 seconds. Anything else is retried after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h, then marked failed — the Webhooks page shows every delivery, its answer, and a Retry button. The same webhook-id comes with every retry, so a repeat is easy to ignore. Answer 410 Gone to turn the endpoint off. Endpoints must be public https:// URLs; redirects aren’t followed.

For AI agents

Point an agent at the OpenAPI document — every operation has an operationId, a summary and example requests — and give it a key (a read-only one, to start). The errors say what to change, local deadlines need no offset arithmetic, and Idempotency-Key makes its retries safe. A summary of the whole product for language models is at /llms.txt. Or connect it to the MCP server and skip the HTTP entirely.

MCP server

Timerbeam is also an MCP server, so Claude, Cursor and other agents can use it as a set of tools — “make a 48-hour countdown for the welcome email in our brand colours” just works. It’s the same API underneath: the same key, the same plan limits, the same errors.

URL
https://api.timerbeam.com/mcp
Transport
Streamable HTTP
Auth
OAuth sign-in, or an API key

claude.ai and ChatGPT

Add a custom connector (in claude.ai: Settings → Connectors) with this URL, then sign in to Timerbeam and allow it when asked — no key to copy:

https://api.timerbeam.com/mcp

Claude Code

In a terminal — then run /mcp in Claude Code to sign in. (Or skip the sign-in: add --header "Authorization: Bearer tb_live_…".)

claude mcp add --transport http timerbeam https://api.timerbeam.com/mcp

Cursor

In ~/.cursor/mcp.json (or .cursor/mcp.json in a project):

{
  "mcpServers": {
    "timerbeam": {
      "url": "https://api.timerbeam.com/mcp",
      "headers": { "Authorization": "Bearer tb_live_…" }
    }
  }
}

VS Code

In .vscode/mcp.json:

{
  "servers": {
    "timerbeam": {
      "type": "http",
      "url": "https://api.timerbeam.com/mcp",
      "headers": { "Authorization": "Bearer tb_live_…" }
    }
  }
}

Claude Desktop

In claude_desktop_config.json, through the mcp-remote bridge (needs Node.js):

{
  "mcpServers": {
    "timerbeam": {
      "command": "npx",
      "args": ["mcp-remote", "https://api.timerbeam.com/mcp", "--header", "Authorization:${AUTH_HEADER}"],
      "env": { "AUTH_HEADER": "Bearer tb_live_…" }
    }
  }
}

Clients that support it sign in with OAuth: you approve the app once on a Timerbeam page, choosing nothing more than “Allow”, and it appears under Connected apps on your API keys page, where you can disconnect it. Anything else sends an API key. A read-only key gives an agent the read tools only in effect — anything that would change a timer is refused.

ToolWhat it does
get_accountThe account’s plan, what it includes (fonts, evergreen timers, brand kits, the “Made with Timerbeam” strip) and how much of its limits is used.Read-only
list_templatesReady-made designs. Pass a template’s id as template to create_timer or update_timer. A proFont template needs the Pro plan and up.Read-only
list_brand_kitsThe account’s brand kits (colours and a font). Pass a kit’s id as brandKit to create_timer or update_timer.Read-only
create_brand_kitSave a brand’s colours and font as a kit (Business plan; up to 20 per account).
list_timersThe account’s saved timers, newest first, with their opens.Read-only
get_timerOne timer in full: its settings, design, image URL, embed HTML and opens.Read-only
create_timerCreate a saved countdown timer on the account. Give a deadline (or evergreen), and optionally a template and brand kit. Returns the image URL and embed HTML; get_embed_code tailors the HTML to an email platform.
update_timerChange a saved timer — send only what changes. Every email it was already sent in shows the change on its next open.
delete_timerDelete a saved timer for good. It stops showing in every email it was sent in (an invisible pixel from then on).
get_statsOpens by day (UTC) for one timer, or for the whole account with a per-timer breakdown. Defaults to the last 30 days; from/to reach back up to a year (Business plan).Read-only
get_embed_codeThe HTML to paste into an email for a timer. For an evergreen timer, name the email platform and the right merge tag is filled in.Read-only

Reference

GET/api/timers

List the account’s saved timers

  • 200 Newest first.
  • 401 No key or session.

POST/api/timers

Create a timerNo key needed

With store: true, a saved timer: editable after sending, counted toward the plan. Otherwise a one-off timer baked into a signed link. Send Idempotency-Key to make a retry safe.

  • 201 Created. A saved timer made without an account includes its manageKey — shown once.
  • 400 Invalid input — see error and message.
  • 401 An API key that is not valid.
  • 402 The plan does not include this (a timer limit, a font, evergreen).
  • 403 A read-only key.
  • 422 Idempotency-Key reused with a different request.
  • 429 Rate limited — wait Retry-After seconds.

GET/api/timers/{id}

Get a saved timer, with its opens

  • 200 The timer.
  • 404 No such timer on this account (or a wrong manage key).

PATCH/api/timers/{id}

Change a saved timer

Send only what changes. Every email it was already sent in shows the change on its next open.

  • 200 The updated timer.
  • 400 Invalid input.
  • 402 The plan does not include this.
  • 404 No such timer on this account.

DELETE/api/timers/{id}

Delete a saved timer

It stops rendering in every email it was sent in (an invisible pixel from then on).

  • 200 Deleted.
  • 404 No such timer.

GET/api/templates

List design templatesNo key needed

Pass a template’s id as template when creating a timer. proFont templates use a font from the Pro plan and up.

  • 200 Every template.

GET/api/brand-kits

List the account’s brand kits

  • 200 The kits.

POST/api/brand-kits

Create a brand kit (Business)

  • 201 Created.
  • 400 Invalid input.
  • 402 Brand kits are Business.
  • 409 At most 20 per account.

PATCH/api/brand-kits/{id}

Replace a brand kit (Business)

  • 200 Updated.
  • 402 Brand kits are Business.
  • 404 No such kit.

DELETE/api/brand-kits/{id}

Delete a brand kit

Allowed on any plan. Timers styled with it keep their colours.

  • 200 Deleted.
  • 404 No such kit.

GET/api/stats

Opens across the account, by day and by timer (Business)

Defaults to the last 30 days. from and to reach back as far as the plan keeps opens — a year on Business.

  • 200 The opens.
  • 400 A bad or out-of-reach range (bad_range).
  • 402 The analytics API is Business.

GET/api/timers/{id}/stats

One timer’s opens by day (Business)

For the account that owns the timer — not by manage key. Evergreen timers also report how many recipients have started their countdown.

  • 200 The opens.
  • 400 A bad or out-of-reach range.
  • 402 The analytics API is Business.
  • 404 No such timer on this account.

GET/api/webhooks

List webhook endpoints

  • 200 The endpoints.

POST/api/webhooks

Add a webhook endpoint (Business)

Returns the endpoint’s signing secret — keep it to verify deliveries. Up to 5 per account.

  • 201 Added.
  • 400 A bad URL or event list.
  • 402 Webhooks are Business.
  • 409 At most 5 per account.

PATCH/api/webhooks/{id}

Change an endpoint, or turn it on or off

Send only what changes. After a downgrade an endpoint can still be turned off ({"enabled": false}) or deleted.

  • 200 Updated.
  • 400 Invalid input.
  • 402 Webhooks are Business.
  • 404 No such endpoint.

DELETE/api/webhooks/{id}

Delete an endpoint and its delivery log

  • 200 Deleted.
  • 404 No such endpoint.

POST/api/webhooks/{id}/test

Send a `ping` event now

Waits for the attempt and returns the delivery, with the endpoint’s answer.

  • 201 Attempted.
  • 402 Webhooks are Business.
  • 404 No such endpoint.
  • 409 The endpoint is off.

POST/api/webhooks/{id}/rotate-secret

Replace the signing secret

The old secret stops working at once.

  • 200 The new secret.
  • 402 Webhooks are Business.
  • 404 No such endpoint.

GET/api/webhooks/{id}/deliveries

The endpoint’s recent deliveries

The last 25, newest first, kept 30 days.

  • 200 The deliveries.
  • 404 No such endpoint.

POST/api/webhooks/{id}/deliveries/{deliveryId}/retry

Send a delivery again, now

  • 200 Attempted.
  • 402 Webhooks are Business.
  • 404 No such delivery.

GET/api/capabilities

What this account’s plan includes

  • 200 The plan’s features.

Timer fields

The body of POST /api/timers and PATCH /api/timers/{id} (a PATCH sends only what changes):

FieldTypeWhat it does
deadlinestring | integerWhen it ends: ISO 8601 with an offset ("2026-11-27T22:59:00Z"), unix seconds — or, with timezone, a local "YYYY-MM-DDTHH:mm".
timezonestringAn IANA zone ("Europe/Budapest"): read deadline as local time there, daylight saving included.
storebooleanSave it (editable, counted toward the plan). Create only.
labelstringYour name for it, on the dashboard.
units"days" | "hours" | "minutes" | "seconds"[]Which units to show, largest first.
locale"en" | "hu" | "de" | "fr" | "es" | "nl" | …Label language.
hrefstring (uri)Where a click goes. Without one, a countdown page.
expiredTextstringPage title and alt text once it is over.
templatestringA template id: its design becomes the timer’s.
brandKitstringA brand kit id: the design (the template’s, or design) is put in its colours and font.
designTimerDesign
evergreenEvergreen | nullnull in a PATCH turns it back into a fixed deadline.