API keys
Programmatic access for your own services and CI — scoped to a role, optionally to a workspace, and shown exactly once.
An API key authenticates a service or a pipeline, not a person and not an agent. Agents authenticate with a key pair at the gateway; people authenticate with a session. API keys are for your deployment tooling, your CI, and your own backend services talking to the control plane.
#Create one
bashcurl -X POST https://veefore.example.com/v1/api-keys \
-H "Authorization: Bearer $EXISTING_KEY" \
-H 'content-type: application/json' \
-d '{
"name": "ci-policy-gate",
"role_id": "role_01JAV…",
"workspace_id": "wsp_01JAV…",
"expires_in_days": 90
}'
json{
"data": {
"api_key": { "id": "apk_01JAV…", "name": "ci-policy-gate", "created_at": "…" },
"token": "aos_sk_…",
"note": "Store this key now. It cannot be retrieved again."
}
}
`token` is shown exactly once
Only a hash is stored. There is no endpoint that returns it again, and there never will be. Capture it from this response or create a new key.
Note the field name: the secret is token, not secret. api_key is the
metadata record.
namestringrequiredWhat this key is for. It appears in the audit trail on every action the key takes, so make it specific —
ci-policy-gatebeatsci.role_idstringrequiredThe role whose permissions the key carries.
GET /v1/rolesto list them.workspace_idstringoptionalScope the key to one workspace. Do this unless the key genuinely needs organization-wide reach.
expires_in_daysnumberoptional1 to 730. Set it. A key with no expiry is a key that outlives the reason it was created.
#Use one
bashcurl -s https://veefore.example.com/v1/agents \
-H "Authorization: Bearer aos_sk_…" \
-H 'x-veefore-workspace: wsp_01JAV…' \
-H 'x-veefore-environment: env_01JAV…'
Same header as a session token. The scope headers are optional — omit them and you get the organization-wide view, subject to the key's permissions and its workspace scope.
#Pick the smallest role
An API key's permissions are its role's permissions. The roles and their contents are in Permissions and roles.
| Key purpose | Role |
|---|---|
| CI policy gate | DEVELOPER, or a custom role with policy:read + policy:simulate |
| Deployment pipeline | Custom: policy:read, policy:activate, environment:set_mode |
| Monitoring and dashboards | VIEWER |
| Audit export to a SIEM | Custom: audit:read, audit:export |
| Agent provisioning service | Custom: agent:create, agent:read, tool:read |
Never `OWNER`
OWNER holds every permission including organization:delete. A key in a CI
environment variable is a key that is in your CI provider's storage, in build
logs if anyone echoes the environment, and in whatever a compromised build step
can read.
Create a custom role with exactly the permissions the key needs. It takes two minutes and it bounds the damage.
Two further protections apply automatically and are worth knowing about:
High-impact permissions cannot come from a wildcard. agent:revoke,
policy:activate, security:emergency_action and the rest must appear on the
role explicitly.
Step-up permissions are unavailable to API keys. Step-up requires a human re-authenticating, and an API key has no human. A key holding a step-up permission still cannot perform the operation — which means a CI key cannot activate a policy on its own. That is deliberate: activation is a decision, not a build step.
#Store it properly
| Where | Verdict |
|---|---|
| Your CI provider's encrypted secret store | Correct |
| Your cloud provider's secret manager | Correct |
| An environment variable injected at runtime | Acceptable |
| A file in the repository | Never |
A .env that is gitignored |
Risky — easy to commit by accident |
| Checked into a Dockerfile or image layer | Never |
npm run quality:audit fails the build on a committed secret, which catches
some of this. Do not rely on it as the control.
#Rotate and revoke
bashcurl -s https://veefore.example.com/v1/api-keys \
-H "Authorization: Bearer $EXISTING_KEY"
curl -X POST https://veefore.example.com/v1/api-keys/apk_01ABC/revoke \
-H "Authorization: Bearer $EXISTING_KEY"
Revocation is immediate. To rotate, create the new key, deploy it, then revoke the old one — in that order, so there is no window where nothing works.
Rotate on a schedule and immediately on any suspicion. Set expires_in_days so
expiry happens even when nobody remembers.
#Everything is attributed
Every action an API key takes is recorded against that key, so the audit trail
distinguishes "the CI gate read the policy" from "a developer read the policy".
principal.type is API_KEY, which is also a policy field:
json{ "kind": "condition", "id": "c-principal", "field": "principal.type", "operator": "eq", "value": "API_KEY" }
Useful for rules that should apply to automation but not to people, or the reverse.