Managing API Keys
Overview
API keys authenticate machine-to-machine traffic: the Edge Agent pushing readings, a script sending data straight to the ingestion endpoint, a dashboard tool reading your machines, or an integration that works with your data on its own. Unlike a user login, an API key is a long-lived credential meant for non-interactive systems.
Interactive access by a person uses a short-lived Bearer JWT instead. See Authentication.
Two kinds of key
| Kind | Use it for | What it can reach |
|---|---|---|
edge_agent (default) | The Edge Agent, ingestion scripts, read-only dashboard tools | Only what its scopes allow (below) |
service_account | An integration that needs to work with your data the way a team member does | Whatever its role allows, through the same endpoints a person in that role uses |
The kind is fixed when the key is created.
Edge-agent scopes
| Scope | Grants |
|---|---|
ingest | POST /api/v1/ingest: send readings |
agent | GET and POST /api/v1/agent/heartbeat: the Edge Agent's check-in |
read:machines | GET /api/v1/machines and GET /api/v1/machines/{machine_id} |
read:sensors | GET /api/v1/machines/{machine_id}/readings, /readings/latest, and /summary |
A key created without scopes gets ingest and agent, which is what the Edge Agent needs. You can also pass a preset name instead of a list: edge_agent (ingest, agent) or read_only (read:machines, read:sensors). A key with a read scope reads every machine in your company; per-user machine-group restrictions do not apply to it.
An edge-agent key cannot manage anything: users, alert rules, notification channels, integrations, and other settings always need a signed-in user or a service account.
Service accounts
A service-account key acts as a member of your company with the role you give it (admin, operator, viewer, or one of your custom roles). It can call whatever a person in that role can, with one exception: it can never manage users, roles, API keys, or sign-in settings, however broad its role. That way a leaked key cannot create new credentials that outlive its own revocation.
Creating a key
Create keys from the Client Portal (Settings → API Keys) or from the API. In both cases, only an account administrator can create a key.
POST /api/v1/settings/api-keys
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | A descriptive name (1–255 chars) |
kind | string | No | edge_agent (default) or service_account |
scopes | string[] | No | Edge-agent keys only. A list of scopes, or one preset name. Defaults to ingest and agent |
role | string | Service accounts only | The role the account acts with. Required for a service account, refused for an edge-agent key |
expires_at | string (ISO 8601) | No | When the key stops working. Omit it for a key that does not expire |
An edge-agent key for a plant:
curl -X POST "https://api.haltless.io/api/v1/settings/api-keys" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "edge-agent-plant-1" }'
A service account for a maintenance integration:
curl -X POST "https://api.haltless.io/api/v1/settings/api-keys" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "cmms-sync", "kind": "service_account", "role": "operator", "expires_at": "2027-06-30T00:00:00Z" }'
Response (201 Created):
{
"id": "b1e7c0a2-3d4e-4f5a-9b6c-7d8e9f0a1b2c",
"name": "edge-agent-plant-1",
"key": "<your-new-api-key>",
"scopes": ["ingest", "agent"],
"tenant_id": "7d8e9f0a-1b2c-4f5a-9b6c-b1e7c0a23d4e",
"created_at": "2026-08-05T10:00:00Z",
"kind": "edge_agent",
"role": null
}
The key value is returned only once, at creation. Store it in a secrets manager immediately. If you lose it, revoke the key and create a new one; there is no way to retrieve it later.
Using a key
Send the key in the X-API-Key header on every request:
curl -X POST "https://api.haltless.io/api/v1/ingest" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "readings": [] }'
| Response | Meaning |
|---|---|
401 Unauthorized | The key is unknown, revoked, or past its expires_at |
403 Forbidden | The key is valid but lacks the scope (edge agent) or the role permission (service account) for this request |
402 Payment Required | An edge-agent key while your company's account is suspended. See Errors |
To push readings with a key, see Direct Ingestion. To configure the Edge Agent with a key, see the Edge Agent configuration guide.
Listing keys
List the keys on your account. The secret is never returned. You get only a short, non-sensitive prefix so you can tell keys apart:
curl "https://api.haltless.io/api/v1/settings/api-keys" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
[
{
"id": "b1e7c0a2-3d4e-4f5a-9b6c-7d8e9f0a1b2c",
"tenant_id": "7d8e9f0a-1b2c-4f5a-9b6c-b1e7c0a23d4e",
"name": "edge-agent-plant-1",
"key_prefix": "Xk3vQ9pL...",
"scopes": ["ingest", "agent"],
"created_at": "2026-08-05T10:00:00Z",
"is_active": true,
"expires_at": null,
"last_used_at": "2026-08-06T08:12:44Z",
"kind": "edge_agent",
"role": null
}
]
last_used_at shows when the key last authenticated a request, which helps you find keys nobody uses any more.
Changing a key
PATCH /api/v1/settings/api-keys/{key_id} changes a key's name, expires_at, and either its scopes (edge-agent keys) or its role (service accounts). Send only the fields you want to change. Omitting expires_at keeps the current expiry; sending "expires_at": null makes the key never expire. Sending scopes to a service account, or role to an edge-agent key, returns 422.
The secret itself cannot be changed. To replace it, rotate the key (below).
Revoking a key
Revoke a key the moment it is compromised, retired, or replaced:
curl -X DELETE "https://api.haltless.io/api/v1/settings/api-keys/KEY_ID" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Revocation takes effect immediately. Any request using the revoked key receives 401 Unauthorized. Revoking a service-account key also deactivates its account.
Rotating keys with zero downtime
Because more than one key can be active at once, rotation needs no downtime:
- Create a new key with a fresh name (for example,
edge-agent-plant-1-2026-q3). - Deploy the new key to the agent or system that uses it.
- Confirm data is still flowing on the new key (
last_used_atupdates). - Revoke the old key once you have confirmed the new one is in use.
Security best practices
- Treat keys as secrets. Never commit them to version control or paste them into tickets. Use environment variables or a secrets manager.
- One key per system. Don't share a single key across multiple agents; per-system keys make rotation and revocation surgical.
- Grant the least access. Give edge-agent keys only the scopes they need, and service accounts the narrowest role that works.
- Use descriptive names. Encode the system and environment in the name so you can identify a key at a glance.
- Set an expiry, and rotate. An
expires_aton keys for temporary integrations, and quarterly rotation for the rest, are sensible defaults. - Revoke on exposure. If a key ever appears in a log, error message, or repository, revoke it immediately and issue a new one.
Rate limits
Ingestion is limited to 600 requests per minute per network address, so agents and scripts behind the same plant address share that budget; because each request can carry up to 1,000 readings, this supports high sustained throughput. Requests made with a service-account key count toward your company's read and write limits, like a signed-in user's. See Rate Limiting.
POST /api/v1/api-keys and GET /api/v1/api-keys from earlier versions of this API still work: they create an edge-agent key with the default scopes and list your keys. Use /api/v1/settings/api-keys for new integrations, since it supports every option above.