Authentication
Every request is authenticated with an API key sent in the Authorization header,
using the Bearer scheme.
curl https://api.joinotify.com/templates \
-H 'Authorization: Bearer sk_live_xxx'
Token format
sk_live_AbCdEf0123456789abcdefgh
│ │ └── 32 random characters (192 bits of entropy)
│ └── environment: live or test
└── fixed prefix
The first 14 characters form the prefix (sk_live_AbCdEf). It is shown in the
dashboard and in listings so you can identify a key without revealing the secret.
Meta's documentation shows examples with ?access_token= in the query string. That
does not work on Joinotify and never will — URL parameters leak into server logs,
browser history and Referer headers.
# Correct
curl https://api.joinotify.com/v1/{WABA_ID} \
-H 'Authorization: Bearer sk_live_xxx'
# Does not work
curl 'https://api.joinotify.com/v1/{WABA_ID}?access_token=sk_live_xxx'
Creating a key
Through the dashboard, under API keys, or through the API itself with an existing key:
curl -X POST https://api.joinotify.com/keys \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "name": "erp-integration", "environment": "live" }'
{
"data": {
"id": "key_01J8ZK...",
"name": "erp-integration",
"prefix": "sk_live_AbCdEf",
"environment": "live",
"createdAt": "2026-07-27T14:02:11.482Z",
"secret": "sk_live_AbCdEf0123456789abcdefgh"
},
"warning": "Store the \"secret\" now — it will not be shown again."
}
The secret field appears only in that response. We store only the SHA-256 hash of
the secret — there is no way to recover it later.
Editing a key
Rename the key, or change which numbers it reaches, without swapping the secret:
curl -X PATCH https://api.joinotify.com/keys/key_01J8ZK... -H 'Authorization: Bearer sk_live_xxx' -H 'Content-Type: application/json' -d '{ "allowedPhoneNumberIds": ["1555000111", "1555000222"] }'
This is the path for whoever connected a second WhatsApp and wants the key already in
production to send through it too: the sk_… stays the same, so no .env, plugin or
no-code flow has to be updated. The new reach applies from the very next request.
allowedPhoneNumberIds replaces the whole list — send the numbers the key should reach,
not just the new ones. An empty list gives the key every number in the account,
including the ones you connect later. Omitting the field leaves the reach untouched.
environment is not editable: it is baked into the secret itself (sk_live_… /
sk_test_…). To switch environments, create another key. An already revoked key returns
409 key_revoked — revocation has no undo.
Listing and revoking
# List (never returns secrets)
curl https://api.joinotify.com/keys \
-H 'Authorization: Bearer sk_live_xxx'
# Revoke
curl -X DELETE https://api.joinotify.com/keys/key_01J8ZK... \
-H 'Authorization: Bearer sk_live_xxx'
Revoking takes effect immediately: the next request with that key already gets
401 authentication. Revoking an already revoked key returns 200 — the operation is
idempotent, so repeating it breaks nothing.
Good practice
One key per integration. ERP, storefront and chatbot with separate keys. When one leaks, you revoke only that one.
Rotate periodically. Create the new key, publish it, confirm traffic has moved (the
dashboard shows lastUsedAt per key) and only then revoke the old one. Since both
coexist, rotation has no downtime.
Never in the front end. The key grants full access to your account. It lives on the server, in an environment variable or a secrets manager — never in browser code, a mobile app or a repository.
Leaked? Revoke first. Revoking takes a second; investigating can take hours.
Authentication errors
| HTTP code | type | Cause |
|---|---|---|
401 | authentication | Missing header, invalid format, unknown or revoked key. |
403 | tenant_inactive | The account is suspended or cancelled. Contact support. |
{
"error": {
"message": "Invalid API key.",
"type": "authentication"
}
}
The live and test environments
Keys can be created as live or test. Today the prefix exists so you can tell
credentials apart in your own systems and logs.
There is no isolated test environment: an sk_test_ key sends real messages and is
billed like any other. To test without impact, use a destination number you control.