Countdown Timer API quick start

Three requests take you from nothing to a working countdown: issue a key, create a timer, read its remaining time. Everything after that is a variation on those three. Start in Sandbox — it is free with any account and runs the same code paths as Production, so nothing you learn here has to be relearned later.

Step 1 — Issue a Sandbox API key

Open the API keys page in the developer dashboard and create a Sandbox key. If you do not have an account yet, create one first — Sandbox needs no plan and no card.

The secret is displayed exactly once. We store a hash of it, not the key itself, which means we genuinely cannot show it to you again — if you lose it, issue a new one. Put it in an environment variable now rather than pasting it into a file you might commit:

Shell

export COUNTDOWNSHARE_API_KEY="cs_test_..."
A Sandbox key starts with cs_test_ and a Production key with cs_live_. The prefix is how you tell at a glance which one you are holding, and the boundary between them is enforced in the database — a Sandbox key cannot read a Production timer under any circumstances. See Sandbox and Production.

Step 2 — Create a countdown

A fixed-date countdown needs three things: a name, the type, and the deadline. The deadline must be an ISO 8601 timestamp carrying Z or a numeric UTC offset, because “2030-01-01T14:00:00” on its own does not identify a moment in time.

The Idempotency-Key header is required on creation. Send the same key twice and you get the original timer back instead of a second one — which is what you want when a request times out and you are not sure whether it landed.

Request

curl -X POST https://countdownshare.com/api/v1/timers \
  -H "Authorization: Bearer $COUNTDOWNSHARE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-001" \
  -d '{
    "name": "Product launch",
    "type": "fixed",
    "deadline_at": "2030-01-01T14:00:00Z"
  }'

201 Created

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Product launch",
    "type": "fixed",
    "status": "draft",
    "revision": 1,
    "deadline_at": "2030-01-01T14:00:00.000Z",
    "remaining_seconds": 2692800,
    "timezone": "UTC"
  },
  "request_id": "7b82b7f7-4d13-497b-9f20-58d46fd7a510"
}
Keep the ID. Every later call — status, update, pause, webhook rules, metrics — is addressed by data.id. Store it against whatever it represents in your own database: the order, the invitation, the trial, the auction lot.

Step 3 — Read the remaining time

This is the endpoint you call when you render. The remaining time is computed on the server from the authoritative clock, so a browser with the wrong system time, a phone that was asleep for an hour, and a server-rendered page all report the same number.

Read the clock

curl https://countdownshare.com/api/v1/timers/$TIMER_ID/status \
  -H "Authorization: Bearer $COUNTDOWNSHARE_API_KEY"

# {
#   "data": {
#     "status": "scheduled",
#     "server_time": "2029-12-28T14:00:00.000Z",
#     "remaining": { "total_seconds": 345600, "days": 4, ... },
#     "ended": false
#   }
# }

That is the entire loop. A new timer is a private draft until you publish it, so nothing is public yet — which is the right default while you are still wiring things up.

A complete working example

Both of these are runnable as-is once COUNTDOWNSHARE_API_KEY is set. The error handling in the Node version is worth copying rather than skipping — surfacing the code and request_id turns a failed integration from guesswork into a single support message.

Node.js

// One file. No dependencies. Node 18+.
const BASE = "https://countdownshare.com/api/v1";
const key = process.env.COUNTDOWNSHARE_API_KEY;

async function api(path, options = {}) {
  const response = await fetch(BASE + path, {
    ...options,
    headers: {
      Authorization: `Bearer ${key}`,
      "Content-Type": "application/json",
      ...options.headers,
    },
  });

  const payload = await response.json();
  if (!response.ok) {
    // The request_id is what support needs to find your call in our logs.
    throw new Error(
      `${payload.error.code}: ${payload.error.message} (${payload.request_id})`,
    );
  }
  return payload.data;
}

// 1. Create it.
const timer = await api("/timers", {
  method: "POST",
  headers: { "Idempotency-Key": "quickstart-001" },
  body: JSON.stringify({
    name: "Product launch",
    type: "fixed",
    deadline_at: "2030-01-01T14:00:00Z",
  }),
});

// 2. Read the authoritative remaining time.
const status = await api(`/timers/${timer.id}/status`);

console.log(timer.id, status.remaining.total_seconds);

Python

# One file. Requires: pip install requests
import os
import requests

BASE = "https://countdownshare.com/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['COUNTDOWNSHARE_API_KEY']}"

created = session.post(
    f"{BASE}/timers",
    headers={"Idempotency-Key": "quickstart-001"},
    json={
        "name": "Product launch",
        "type": "fixed",
        "deadline_at": "2030-01-01T14:00:00Z",
    },
)
created.raise_for_status()
timer_id = created.json()["data"]["id"]

status = session.get(f"{BASE}/timers/{timer_id}/status").json()["data"]
print(timer_id, status["remaining"]["total_seconds"])

Where to go next

Three directions from here, depending on what you are building.

You need the countdown on a page or in an email

Publish the timer and read GET /timers/{timer_id}/outputs. You get a hosted page, a responsive iframe snippet, and an animated GIF that renders live remaining time inside an inbox with no JavaScript. Embeds and hosted pages.

You need to act when the countdown ends

Do not poll for it. Register a webhook destination, attach a rule, and be called instead — including at intermediate points like “one hour remaining”. Webhooks overview.

You need something other than a fixed date

A stopwatch-style timer you can pause is a duration timer. A schedule that repeats is a recurring timer. A separate deadline per customer is a personalized timer. All four share these same endpoints.

Going to Production later is a two-line change: swap the key for a cs_live_ one and add publish: true when you create. The base URL does not change and neither does anything else.