Authentication
Every request carries an API key in the Authorization header:
curl https://k-message.kerneltics.com/v1/me \ -H "Authorization: Bearer km_live_a1b2c3d4e5f6..."X-API-Key: km_live_... is accepted as an alternative. Bearer is documented
because it is what HTTP clients and API tools default to.
There is no login step, no token to refresh, and no expiry unless you set one.
Creating a key
Section titled “Creating a key”Settings → API Keys → Create API key in the dashboard.
You choose a name, the scopes it carries, and optionally an expiry date, a rate limit and a list of IP addresses it may be used from.
The full key appears once, in the response to creating it, and is never retrievable again. We store only a SHA-256 hash, so we could not show it to you a second time even if asked. If you lose it, create another and revoke the old one.
Scopes
Section titled “Scopes”A key can only do what you ticked when you created it. Scopes are exact: there
is no hierarchy and no wildcard, and contacts:write does not imply
contacts:read. That is deliberate, because implied permissions are where
scope systems quietly grow holes, and ticking both boxes costs you nothing.
| Scope | Allows |
|---|---|
account:read | Read the organization and its WhatsApp numbers |
messages:send | Send text, template and media messages |
messages:read | Read conversations, history, delivery status and media |
contacts:read | List, search and read contacts |
contacts:write | Create and edit contacts and their tags |
contacts:delete | Delete contacts, and their conversation history with them |
templates:read | List templates and the variables each expects |
templates:write | Submit templates for approval and sync their status |
campaigns:read | Read campaigns and their progress |
campaigns:write | Create campaigns, add recipients, start and pause them |
webhooks:read | Read webhook endpoints and their delivery history |
webhooks:write | Create, edit, delete and test webhook endpoints |
Calling an endpoint without its scope returns 403 and names the scope you are
missing, so the fix does not require reading this table:
{ "error": { "type": "permission_error", "code": "missing_scope", "message": "This API key is missing the 'messages:send' scope. Add it in Developers, or use a key that has it.", "doc_url": "https://developers.k-message.kerneltics.com/errors#missing-scope" }}You cannot grant a scope you do not hold yourself. Someone with no campaign permission cannot mint a key that runs campaigns, because that would be an escalation route around their own role.
Keeping a key safe
Section titled “Keeping a key safe”Server-side only. A key in a browser bundle or a mobile app is a key anyone can read. Anything reaching customers must go through your own backend.
In the environment, not the repository. A key committed to git stays in the history after it is deleted.
One key per system. Separate keys for your store, your ERP and your internal tooling means you can revoke one without an outage in the others, and the “last used” timestamps tell you what is still running.
Restrict where it can be used. If your integration calls from fixed addresses, add them to the key’s allowlist. A leaked key is then unusable from anywhere else.
Set an expiry for anything temporary. A key made for a migration or an agency should stop working on its own.
Turning a key off
Section titled “Turning a key off”Two different things, on purpose:
Disable flips the key off and back on. Use it to test whether something still depends on a key before removing it.
Revoke is permanent. The key can never be used again and cannot be re-enabled. Use it when a key may have leaked. They are separate controls so nobody kills a key for good by flipping a switch they thought was reversible.
Either way, a request with that key gets a 401 naming which happened:
{ "error": { "type": "authentication_error", "code": "key_revoked", "message": "This API key was revoked and can no longer be used." }}Older keys
Section titled “Older keys”Keys beginning whm_ were issued before scopes existed. They still work with
the dashboard API but are refused on /v1, with key_wrong_surface, because
they carry no scopes and there is no safe way to guess what they should be
allowed to do.
Create a new key. There is no migration path, and that is the point: the reason these keys are refused is exactly the reason they should be replaced.