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.
Live key vs read-only key
Section titled “Live key vs read-only key”| 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 |
How keys are stored
Section titled “How keys are stored”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.
Rotation
Section titled “Rotation”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.
Key format
Section titled “Key format”cly_live_5f3a9c2e1b7d4a80f6c3e9d2a1b4c7e0cly_read_a1b2c3d4e5f60718293a4b5c6d7e8f90Both 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.