Programs authenticate with API keys. A key acts as the member who created it, limited to the permissions you give the key. There is no token exchange: the key itself is the credential, sent on every call.
API keys are managed by administrators on API Keys (in the Developer part of the menu). Creating keys needs a plan with API keys.
Create a key
- Open API Keys and select New API Key.
- Enter a Name (up to 100 characters) and, optionally, a Description (up to 500).
- Under Module permissions, pick what the key may do, a module at a time. Each module offers a Read only preset; you can also tick single permissions. Use Search modules or permissions… to find one. The picker lists only permissions you hold yourself: “The key can do at most what you can do. If your own rights shrink later, the key loses them too.”
- Set Expires on. The default is 90 days from today, at most a year (“90 days by default, at most a year.”).
- Optionally fill IP allowlist: one subnet per line, for example
10.0.0.0/8or203.0.113.7/32. Empty means any address your organisation allows. - Leave Integration scopes (advanced) empty unless an integration’s own documentation asks for one (for example SCIM provisioning).
- Select Create Key and copy the key now.
Warning
“This key will only be shown once. Store it somewhere safe.” Hermetry stores only a hash. If you lose the key, create a new one.
If you have two-factor authentication or a passkey, Hermetry asks you to confirm it before the key is made. A key cannot be created while you are impersonating a member.
Key format
sk_live_<first 8 characters of the organisation id>_<48 random hexadecimal characters>
live keys belong to the production service, test keys to test installations, so the two cannot be mistaken for each other.
Use a key
Send the key in the Authorization header on every call:
curl "https://<your host>/tenant/<tenantId>/api/iot/devices" \
-H "Authorization: Bearer sk_live_…"
- A request with an API key is judged only as that key. A browser cookie on the same request is ignored.
- Keys work only on your organisation’s paths (
/tenant/<tenantId>/api/...). The key’s organisation must match thetenantIdin the path. - A key never works on platform, account or sign-in routes.
What a key may do
On every call, all of these must hold:
- The key holds the permission the route needs.
- The permission’s module is switched on for your organisation.
- The member who created the key holds the permission now: their role, their groups and your access policies count.
- The member is still an active member and the organisation is active.
A key acts with at most the Admin role, even when an Owner created it. So a key can never hold Owner-only permissions (for example raw device commands, firmware signing keys, dangerous device settings, rotating a webhook secret), and never permissions of the identity and access modules (sign-in, accounts, API keys, members, sessions). A key therefore cannot create or rotate keys.
Service keys
A key normally stops working when its creator leaves the organisation. An Owner can open the key and select Mark as service key: it then keeps working after its creator leaves (“Yes: survives its creator”) and acts as the Owner who marked it.
Allowed networks
A key’s IP allowlist holds up to 100 subnets in CIDR form; a single address is /32. Calls from anywhere else are refused with 403 “This API key may not be used from the current IP address.” A key without its own list uses the organisation’s default list (see Settings below). Your organisation’s own IP allow and deny lists for sign-in also apply to keys.
Expiry
Every key expires. The default is 90 days; the latest date is 365 days ahead, or less if your organisation sets a lower maximum. Expired keys stop by themselves. An administrator can also sweep expired keys (POST /api/api-keys/sweep-expired) to mark them inactive and send one api_key.expired webhook event per key.
Rotate, deactivate and revoke
Open a key to see its Identity, Usage (last used, last used IP, total verifications), Module permissions and Rotation history.
- Rotate key creates a successor with the same permissions, scopes, network list and environment. Set the Grace window (days): the old key keeps working until it ends. “0 revokes the old key immediately.” Copy the new secret at once.
- Deactivate stops a key until you Activate it again. A rotated key cannot be reactivated: “A rotated key cannot be reactivated; use its successor.”
- Revoke deletes the key: “Any integrations using this key will immediately stop working. This action cannot be undone.”
- Edit changes the key’s module permissions. You can remove any permission, and add only permissions you hold. Integration scopes can only be narrowed.
In an incident, an administrator can revoke every active key of the organisation in one step.
POST/tenant/{tenantId}/api/api-keys/{apiKeyId}/rotate
Create a successor key. The old key keeps working for graceSeconds (0 to 2592000, that is 30 days; default 86400, one day). 0 stops the old key at once. The new secret rawKey is in this answer only. Only an active, unexpired key that was not rotated before can be rotated (409 otherwise).
Request body application/json
{
"graceSeconds": 86400
}
Example curl
curl -X POST 'https://<your-host>/tenant/{tenantId}/api/api-keys/{apiKeyId}/rotate' \
-H 'Content-Type: application/json' \
-d '{"graceSeconds":86400}'
Responses
201Created
{
"key": {
"name": "Plant ERP",
"…": "…"
},
"rawKey": "sk_live_…"
}
409Conflict
{
"message": "Only an active, unexpired key that has not been rotated yet can be rotated.",
"code": "CONFLICT"
}
Manage keys over HTTP
Keys are managed by a signed-in administrator. An API key cannot create, change or rotate keys.
| Method | Path | Does |
|---|---|---|
| GET | /api/api-keys | List keys (page from 1, pageSize up to 100, sort, filters isActive, environment, scopeReview). |
| GET | /api/api-keys/permission-catalog | The permissions you may give a key, by module. |
| POST | /api/api-keys | Create a key. The secret rawKey is in this answer only. |
| PUT | /api/api-keys/{apiKeyId} | Change name, description, active state, IP allowlist, permissions, scopes. |
| DELETE | /api/api-keys/{apiKeyId} | Revoke (delete) a key. |
| POST | /api/api-keys/{apiKeyId}/rotate | Rotate a key. |
| POST | /api/api-keys/revoke-all | Deactivate every active key. |
POST/tenant/{tenantId}/api/api-keys
Create a key. Fields: name (required), description, permissions (module permission ids, for example iot_device.devices.read), scopes, environment (live or test), ipAllowlist, expiresAt (ISO 8601). Give at least one permission or scope. Without expiresAt the key expires after 90 days.
Request body application/json
{
"name": "Plant ERP",
"permissions": [
"iot_device.devices.read",
"telemetry.read"
],
"ipAllowlist": [
"203.0.113.0/24"
]
}
Example curl
curl -X POST 'https://<your-host>/tenant/{tenantId}/api/api-keys' \
-H 'Content-Type: application/json' \
-d '{"name":"Plant ERP","permissions":["iot_device.devices.read","telemetry.read"],"ipAllowlist":["203.0.113.0/24"]}'
Responses
201Created
{
"key": {
"name": "Plant ERP",
"…": "…"
},
"rawKey": "sk_live_…"
}
400Bad Request
{
"message": "Select at least one module permission or integration scope."
}
403Forbidden
{
"message": "An API key can only receive permissions you hold yourself."
}
422Unprocessable Entity
{
"message": "The expiry date must be in the future."
}
Check a key
POST/tenant/{tenantId}/api/api-keys/verify
Check a key sent in the x-api-key header. No session is needed. You may send a scope to check in the body. Every refusal of the key (unknown, inactive, expired, another organisation, missing scope, address not allowed) gives the same 401, so it does not tell which check failed. On success the answer has "valid": true and the key’s public fields.
Request body application/json
{
"scope": "read"
}
Example curl
curl -X POST 'https://<your-host>/tenant/{tenantId}/api/api-keys/verify' \
-H 'Content-Type: application/json' \
-d '{"scope":"read"}'
Responses
200OK
{
"valid": true,
"key": {
"name": "Plant ERP",
"…": "…"
}
}
400Bad Request
{
"valid": false,
"message": "scope must be a string"
}
401Unauthorized
{
"valid": false,
"message": "Invalid or expired API key."
}
429Too Many Requests
{
"valid": false,
"message": "Too many requests."
}
Settings
Administrators set the organisation’s key policy on API Keys > settings (the gear button):
| Setting | Default | Meaning |
|---|---|---|
| Max Active Keys | 0 (no limit) | How many active keys the organisation may hold at once. |
| Maximum Key Lifetime (days) | 0 (365 days) | A lower cap on how far ahead a key may expire. |
| Require Expiry on Every Key | Off | A key created through the API must name its expiresAt. |
| Tenant Subnet Allowlist | Empty (any) | Default subnets for keys without their own list. |
| Per-Key Rate Limit (requests/minute) | 0 | Calls per key per minute. 0 uses the platform default of 1,000 a minute. |
The other limits that apply to keys are in Errors and rate limits.
Watching keys
- Each key records when and from which address it was last used.
- A key unused for 14 days and then used from a new address raises an audit event (“A dormant API key was used from a new address”), at most once per key and address per hour.
- Every refused key call is written to the audit log.
- Webhooks can tell you about keys:
api_key.created,api_key.updated,api_key.rotated,api_key.deleted,api_key.expired.
See API key hardening for a checklist.

