Skip to content

Keys & security

A CanaryLytics project has exactly one live key (cly_live_…) with full access, and optionally one read-only key (cly_read_…) that can read stats but never write or change anything. The live key stays secret; the read-only key is the one that can appear in public HTML.

Capability cly_live_<32 hex> cly_read_<32 hex>
Per project exactly one zero or one
Dashboard login yes yes (read-only mode)
GET /v1/* reads yes yes
Widget yes yes
POST /v1/events yes 403 read_only_key
Dashboard settings mutations yes forbidden

Only the hex HMAC-SHA256(key, KEY_HMAC_SECRET) hash is persisted. The plaintext key is shown exactly once, at creation or rotation, and is never logged. Authenticating a request is a single indexed lookup: hash the Bearer token and match it against the live-key-hash and read-key-hash columns; whichever matched decides whether the request gets "full" or "read" access. A database dump alone is useless without the separate KEY_HMAC_SECRET.

Every query the API runs is hard-scoped WHERE project_id = <the authenticated project>. There is no cross-project read path, regardless of which key flavor authenticated the request.

From the dashboard’s Settings page:

  • Rotate key replaces the live key immediately. The old key stops resolving the moment the new hash is written, with no grace period. The new plaintext is shown once.
  • Create/replace read key mints a cly_read_… key. Minting over an existing one revokes the old one, since a project holds at most one read key at a time.
  • Revoke read key removes it entirely. Browser reads that were using it (the widget, SDK clients) stop working immediately; the project still operates on the live key alone.
cly_live_5f3a9c2e1b7d4a80f6c3e9d2a1b4c7e0
cly_read_a1b2c3d4e5f60718293a4b5c6d7e8f90

Both are a fixed prefix (cly_live_ or cly_read_) followed by 32 lowercase hex characters (16 random bytes). An Authorization: Bearer … header carrying anything else is rejected before it ever reaches a database lookup.