title: "Authentication" description: "How to authenticate API requests — bearer tokens, key format, rotation, and errors." last_updated: "2026-05-24"

Authentication

All endpoints under https://api.boxwatch.app (except public routes) require a bearer token. There is one auth scheme for the API: send your key as Authorization: Bearer <key>.

Getting a key

Generate a key from the dashboard — see API keys for the full walkthrough.

The dashboard shows the key exactly once when created. Copy it then; only the prefix is shown after that.

Using the key

curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://api.boxwatch.app/servers

The Authorization header is the only place the API reads the token from. Query-string tokens are not accepted on authenticated endpoints.

fetch('https://api.boxwatch.app/cron-checks', {
  headers: { 'Authorization': 'Bearer ' + process.env.BOXWATCH_API_KEY },
});

Token format

Full-scope keys are 67 characters:

bw_4f3a91e8c7d2b15a8e0f47c93b6d28a05c1f9e7b3a4d68f72c5e93a17b8d04e6f

The bw_ prefix is followed by 64 hex characters generated from crypto.randomBytes(32) — 256 bits of entropy.

Two other key prefixes exist for narrower use:

  • bw_api_ — bound to a single custom API endpoint, sent via X-API-Key header or ?key= query param.
  • bw_tv_ — bound to a single TV dashboard, sent the same way.

Endpoint and TV keys do not authenticate the main resource API — they only unlock their bound public route.

Token rotation

There is no rotation endpoint. To rotate:

  1. DELETE /api-keys/:id the existing key (or revoke it in the dashboard).
  2. POST /api-keys to mint a new one.
  3. Roll consumers over to the new key.

Only one full-scope key per account is allowed at any time — you must delete the existing one before creating a replacement.

Revocation is immediate. The instant you delete a key, the next request that uses it returns 401. Plan rotations so consumers can re-read the new key before the old one disappears.

Errors

StatusBodyWhen
401{ "error": "No token provided" }Missing Authorization header.
401{ "error": "Invalid or expired token" }Token doesn't validate (wrong format, deleted, or expired).
401{ "error": "User not found" }Token's account was deleted.
500{ "error": "Authentication error" }Internal error during auth. Safe to retry.

Every error body also includes a timestamp field with the server's UTC time.

Rate limiting

Rate limits are applied per IP, not per key, and trip on aggressive bursts.

ScopeLimitNotes
Global (all requests)100 / minute / IP429 Too many requests, please slow down. Agent heartbeats are excluded.
/auth/login, /auth/register, /auth/forgot-password5 / 15 minutes / IPReturns 429 Too many attempts, please try again later.
/admin/*30 / minute / IPAdmin routes only.
/ping/:slug cron heartbeats60 / minute / slugExcess pings return 200 OK but aren't recorded.

A 429 response includes the standard RateLimit-* headers. Back off and retry.

CORS

CORS allowlist is set from the CORS_ORIGINS environment variable, comma-separated. The production deployment allows requests from the BoxWatch dashboard origin only. Credentials (Access-Control-Allow-Credentials: true) are enabled for cookie-based browser sessions.

If you're calling the API from a server (curl, Node, Python, Go, etc.) CORS does not apply. If you need cross-origin browser calls from a domain you control, contact support to request an allowlist addition.

Browser cookies

The dashboard at app.boxwatch.app authenticates with an httpOnly cookie named boxwatch_token, also accepted by the same auth middleware. This cookie is not part of the public API. Don't try to obtain or use it from scripts — your bearer token works everywhere the cookie does, with no expiry quirks.

See also

Was this page helpful?