Countdown Timer API authentication

Every request carries a secret API key as a bearer token. There is no OAuth flow, no token exchange, and no expiry to refresh — one header on every call. The whole security model rests on that key staying on your server, so most of this page is about the places it must never end up and what to do when one of them happens anyway.

The header

Send the key in an Authorization header using the Bearer scheme. Nothing else authenticates a request: there is no query parameter, no cookie, and no signature.

Every request

Authorization: Bearer cs_live_a1b2c3d4_...
Content-Type: application/json

Verify your key works

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

GET /usage is a good first call. It is cheap, it does not create anything, and a 200 confirms both that the key is valid and which environment it belongs to.

What an API key is, and is not

A key identifies your account and its environment. It is not a user, not a session, and not scoped to a single timer. Anyone holding it can do everything your plan allows.

Sandbox keyProduction key
Prefixcs_test_cs_live_
ReachesSandbox data onlyProduction data only
Can publish public pagesNoYes
Requests per minute30300
Keys allowed13 (Starter) · 10 (Growth)
The environment boundary is a database constraint, not a conditional in application code. A cs_test_ key presented against a Production timer ID returns not_found — not a permission error — because from that key's perspective the timer genuinely does not exist.

Properties worth knowing before you build around them

Shown once

The full secret appears only in the response that creates it. We store a hash, so recovery is impossible by design — issue a new key instead.

No expiry

Keys do not rotate themselves. A key works until you revoke it, which makes deliberate rotation your responsibility.

Revoked immediately

Revocation takes effect on the next request, with no propagation delay. Create the replacement first if you need zero downtime.

Independently logged

Every request records which key made it. GET /usage/requests filters by key, which is how you find out what a leaked key actually did.

Where the key goes

This API is server-to-server. The moment a key reaches a browser it is public, and no amount of obfuscation changes that — anyone can open the network tab.

Safe

  • An environment variable read by your server process
  • Your platform's secret store — Vercel, Railway, Fly, AWS Secrets Manager
  • A backend route handler, server action, or job worker
  • A CI secret, referenced but never echoed into build logs

Already compromised

  • Frontend JavaScript, including anything prefixed NEXT_PUBLIC_ or VITE_
  • A mobile app binary — these are trivially unpacked
  • A committed .env, even in a private repository
  • A Postman collection or screenshot shared with a colleague

Calling from the browser without exposing the key

Proxy through your own backend. The pattern below is a Next.js route handler, but the shape is the same in Express, Laravel, Rails, or Django: your frontend calls your server, your server calls us.

Server-side proxy

// app/api/countdown/[id]/route.ts — Next.js route handler
//
// The browser calls THIS route. The API key never leaves the server.
export async function GET(request, { params }) {
  const upstream = await fetch(
    `https://countdownshare.com/api/v1/timers/${params.id}/status`,
    {
      headers: {
        Authorization: `Bearer ${process.env.COUNTDOWNSHARE_API_KEY}`,
      },
      cache: "no-store",
    },
  );

  if (!upstream.ok) {
    return Response.json({ error: "unavailable" }, { status: 502 });
  }

  const { data } = await upstream.json();

  // Return only what the page needs. Do not proxy the whole payload
  // blindly — that is how internal fields end up in public HTML.
  return Response.json({
    remaining_seconds: data.remaining.total_seconds,
    ended: data.ended,
    server_time: data.server_time,
  });
}
Once a timer is published you often do not need a proxy at all. The hosted page and embed outputs are public URLs by design and require no key, which is usually the simpler answer for a front-end countdown.

Rotating a key without downtime

Because both keys work simultaneously until you revoke the old one, rotation has no window where requests fail. Do it on a schedule, and immediately if a key has been exposed.

  1. 01

    Issue the replacement

    Create a second key in the dashboard. Both are now live.

  2. 02

    Deploy it

    Update the environment variable and roll out. Traffic moves to the new key.

  3. 03

    Confirm the switch

    GET /usage/requests filtered by the old key should show no recent calls. If it does, something still holds it.

  4. 04

    Revoke the old key

    It stops working on the next request that uses it.

Step 3 is the one people skip, and it is the one that catches the forgotten cron box or the staging deploy still holding the old secret. Skipping it turns rotation into an outage.

When authentication fails

Four distinct failures, and they mean genuinely different things. Reading the code rather than the status tells you which.

CodeHTTPWhat actually happenedFix
unauthorized401No Authorization header, or it was malformed.Check the header is present and reads "Bearer <key>" with one space.
invalid_api_key401The key does not exist, was revoked, or belongs to the other environment.Confirm the prefix matches the environment you are targeting.
forbidden403The key is valid but is not scoped for this route.Issue a key with the scope the endpoint needs.
suspended403The key is fine; the account or its entitlement is not active.Check billing. Contact support if the plan is current.

401 response

HTTP/1.1 401 Unauthorized
X-Request-Id: 7b82b7f7-4d13-497b-9f20-58d46fd7a510

{
  "error": {
    "code": "invalid_api_key",
    "message": "The API key is invalid, revoked, or belongs to a different environment"
  },
  "request_id": "7b82b7f7-4d13-497b-9f20-58d46fd7a510"
}
The single most common cause of invalid_api_key on a first integration is an environment mismatch — a cs_test_ key sent at a Production timer, or the reverse. Check the prefix before you check anything else. Every code the API can return is listed under error codes.