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/jsonVerify 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 key | Production key | |
|---|---|---|
| Prefix | cs_test_ | cs_live_ |
| Reaches | Sandbox data only | Production data only |
| Can publish public pages | No | Yes |
| Requests per minute | 30 | 300 |
| Keys allowed | 1 | 3 (Starter) · 10 (Growth) |
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,
});
}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.
- 01
Issue the replacement
Create a second key in the dashboard. Both are now live.
- 02
Deploy it
Update the environment variable and roll out. Traffic moves to the new key.
- 03
Confirm the switch
GET /usage/requests filtered by the old key should show no recent calls. If it does, something still holds it.
- 04
Revoke the old key
It stops working on the next request that uses it.
When authentication fails
Four distinct failures, and they mean genuinely different things. Reading the code rather than the status tells you which.
| Code | HTTP | What actually happened | Fix |
|---|---|---|---|
unauthorized | 401 | No Authorization header, or it was malformed. | Check the header is present and reads "Bearer <key>" with one space. |
invalid_api_key | 401 | The key does not exist, was revoked, or belongs to the other environment. | Confirm the prefix matches the environment you are targeting. |
forbidden | 403 | The key is valid but is not scoped for this route. | Issue a key with the scope the endpoint needs. |
suspended | 403 | The 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"
}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.