API Keys
An API key authenticates a machine, not a person. Keys are project-scoped and are what external systems present when calling REST endpoints published by pipelines.
Creating a key
Keys are managed in Settings → API keys, which requires Access settings.
- The key value is shown once, at creation. Only a hash is stored — there is no "show key" later, by design.
- Revocation is immediate.
- Give each consumer its own key. One key shared by four systems cannot be revoked without breaking three of them.
Using a key
curl -X POST https://qubiq.example.com:8090/api/v1/orders \
-H "X-API-Key: <key>" \
-H "Content-Type: application/json" \
-d '{"orderId":"SO-1042"}'
Scopes
A key carries capability scopes; a route can require some. A key without a required scope is rejected even though it is otherwise valid.
| Scope | Grants |
|---|---|
tag:read | Read tags |
tag:write | Write tags |
db:read | Run named queries |
db:write | Arbitrary SQL, stored procedures, transactions |
hist:read | Historian queries |
vars:read / vars:write | Read / write variables |
file:read / file:write | Read / write workspace files |
script:execute | Execute scripts, send messages |
The distinction between db:read and db:write is worth noting: named queries need only
db:read, while arbitrary SQL needs db:write — because arbitrary SQL may write. Building
integrations on named queries lets their keys run at a lower privilege.
The same scope model gates the scripting sandbox.
Scoping example
Key "mes-readonly"
scopes: tag:read, db:read, hist:read
GET /api/v1/line-status requires tag:read ✅
POST /api/v1/setpoint requires tag:write ❌ rejected
The MES can read everything it needs and cannot change anything, without a second gateway or a separate deployment.
Layered protection on a route
An API key is one control. Combine it with the others on the route:
| Control | Stops |
|---|---|
| API key | Unauthenticated callers |
| Required scopes | An authenticated caller exceeding its purpose |
| IP allow-list | Calls from outside the expected network |
| Rate limit + burst | Runaway clients, brute force |
| Max payload size | Memory-exhaustion attempts |
| CORS off | Browser-based cross-origin abuse |
Rotation
There is no automatic expiry, so rotate deliberately:
- Create a new key for the consumer.
- Deploy it on their side.
- Confirm traffic on the new key.
- Revoke the old one.
Rotate on a schedule you can actually keep — annually is better than a 90-day policy nobody follows — and immediately whenever someone who had access leaves.
Storing keys
- Treat a key like a password: secrets manager or environment variable, never a repository.
- Never put a key in browser-side code. A key in a web app is a public key.
- If a key is exposed, revoke first and investigate second.
Monitoring
Key usage is recorded in the audit journal. Look for:
- Calls from unexpected source addresses
- Rate-limit rejections, which usually mean a misconfigured client or an attempt
- Keys that have stopped being used — decommissioned integrations whose keys are still live
API keys versus user accounts
| API key | User account | |
|---|---|---|
| Identifies | A system | A person |
| Interactive login | No | Yes |
| Scope | Project + capability scopes | Gateway-wide role permissions |
| Revocation | Immediate | Deactivate the account |
| Use for | Integrations, machine access | People |
Never create a user account for a machine. A machine with a person's credentials makes the audit journal unusable exactly when you need it.