Your API keys
Every account gets two keys. They are not interchangeable: one is safe to publish in your HTML, the other must never leave your server.
Create an account
Open /register and fill in three required fields, then press Create account & generate keys. The keys are shown once — copy both before you close the page.
| Field | Required | Example | Notes |
|---|---|---|---|
name | yes | My Blog | 2–120 characters. Your name or the site's name; never shown to visitors. |
contact_email | yes | you@example.com | The only way an operator can reach you about the site. Stored, never published by any endpoint. |
origin | yes | https://example.com | Scheme + host + optional port. No path, query or credentials. Stored lower-cased. |
The same thing from a terminal, using the registration endpoint directly:
curl -X POST https://YOUR-API-HOST/v1/sites/register \
-H 'Content-Type: application/json' \
-d '{
"name": "My Blog",
"contact_email": "you@example.com",
"origin": "https://example.com"
}'
{
"success": true,
"site": {
"id": "site_9f2c1a...",
"name": "My Blog",
"origin": "https://example.com",
"public_identifier": "pub_8d1f0c...",
"status": "active",
"expires_at": "2026-02-07T12:00:00.000Z",
"inactivity_days": 7
},
"credentials": {
"public_identifier": "pub_8d1f0c...",
"api_key": "cn_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"api_key_note": "Store this key in a server-side secret store. It cannot be retrieved again; rotate it if it is lost."
}
}
One website, one account, two keys
An account is a website. Registering the same address twice is refused with
DUPLICATE_SITE (409) — the credentials already exist, and there is no "create another account" step.
If you lost the secret key, rotate it or use the one from your secret store; do not register again.
Repeated attempts block the website permanently
Every registration request for the same address is counted, whatever its outcome. Once the count passes
REGISTRATION_ATTEMPT_LIMIT (4 by default), that website is blocked permanently:
registration, publishing and reads all return SITE_BLOCKED (403), and expiry, reactivation or
rotating a key cannot lift it. Only an operator can unblock it.
| Attempt | Result |
|---|---|
| 1 | 201 — account created, two keys returned once |
| 2 – 4 | 409 DUPLICATE_SITE, with attempts and attempts_remaining in details |
| 5 and later | 403 SITE_BLOCKED — permanent; the account's existing keys stop working too |
Use one account per website. If you run several sites, create several accounts — each one gets its own pair of keys, its own subscriber list and its own rate-limit budget.
The two keys, side by side
| Public key | Secret key | |
|---|---|---|
| Looks like | pub_8d1f0c… | cn_live_XXXXXXXXXXXXXXXXXXXXXXXX |
| Where it lives | Your website's HTML or JS bundle | Your server's environment / secret store |
| Can collect subscriptions | Yes — that is all it does | Yes |
| Can send notifications | Never | Yes |
| Can read subscriber data | No | Counts and metadata only, never endpoints |
| If it leaks | Nothing: it only identifies your site to a visitor's browser | Anyone could send notifications as you — rotate immediately |
The public key is not a "lesser secret key"
Sending always requires the secret key, and the secret key never authorises a browser to do anything a browser
cannot already do. There is no code path where a public key, an Origin header or a website id from
a request body can be promoted into an authenticated identity: the tenant is derived from the hash of the
presented secret key, and nothing else.
Storing the secret key
# server environment, one of many options
CREATOR_NOTIFY_URL=https://YOUR-API-HOST
CREATOR_NOTIFY_KEY=cn_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- Never in HTML, a JavaScript bundle,
localStorage,sessionStorage, a cookie, or a repository. - Never in client-side analytics, error trackers or chat messages.
- One key per environment is fine, but a separate account per site is cleaner: subscriptions, counts and rate limits are all per account.
Check your account
curl https://YOUR-API-HOST/v1/sites/status \
-H "Authorization: Bearer $CREATOR_NOTIFY_KEY"
The response includes expires_at, expires_in_seconds, api_key_prefix,
key_version and notification_count. It never includes the key itself or any subscriber endpoint.
Rotate or revoke
| Action | Endpoint | Effect |
|---|---|---|
| Rotate | POST /v1/sites/rotate-key | Returns a new secret key and invalidates the old one immediately. Subscribers, notifications and the registration stay intact. |
| Revoke | POST /v1/sites/revoke-key | Suspends the registration. Every authenticated call returns SITE_SUSPENDED until it is reactivated. |
| Reactivate | POST /v1/sites/reactivate | Clears an expired or suspended state and restarts the inactivity window. |
curl -X POST https://YOUR-API-HOST/v1/sites/rotate-key \
-H "Authorization: Bearer $CREATOR_NOTIFY_KEY"
The inactivity rule
A registration stays active while it publishes. If it does not send a notification for the configured window
(seven days by default), it lapses: publishing returns SITE_EXPIRED until you call
POST /v1/sites/reactivate. Your subscriber list is never deleted by expiry — expiry is about
whether the account may send, retention is a separate setting.
- Only a successful publish refreshes the timer.
- Reading your status, subscribing, health checks, documentation views and failed requests never refresh it.
- Expiry is enforced when the request is handled, and again by a background sweep, so the two can never disagree about an expired account.
Restricted deployments
If the service operator has set a registration token, the registration form asks for it under
Registration token and the API expects the same value in an x-registration-token header.
Without it, POST /v1/sites/register returns INVALID_API_KEY.
Next
- Send your first notification — the demo takes about a minute.
- Quick start — put the SDK on your site.
- API reference — request and response shapes.