Programmatically provision, rotate, and revoke API keys via the management API.
Management (provisioning) keys let you automate API key lifecycle operations — minting keys for new users, capping them, rotating them, revoking compromised ones.
Available now in the dashboard. Create and manage them at Settings → Management keys (/dashboard/settings/management-keys): create a named key (the raw mk-… value is shown once at creation — copy it then; only the prefix is shown afterwards), see your keys with their prefix and status, and revoke any of them. They use the same one-way hashing as API keys, so the secret is never recoverable after creation.
Authenticate with your management key (Bearer mk-…) to create, list, change and delete your API keys programmatically. A key is named by its hash — the SHA-256 of the key, in lowercase hex:
# Create an API key with a 2,000 DA monthly cap (the raw sk-… is returned once)
curl https://opendunes.com/v1/keys \
-H "Authorization: Bearer $OPENDUNES_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "ci-bot", "limit": 2000, "limit_reset": "monthly" }'
# List your keys
curl https://opendunes.com/v1/keys \
-H "Authorization: Bearer $OPENDUNES_MANAGEMENT_KEY"
# Delete a key
curl -X DELETE https://opendunes.com/v1/keys/<hash> \
-H "Authorization: Bearer $OPENDUNES_MANAGEMENT_KEY"Keys minted this way belong to the management key's owner; the raw API key is shown only on creation.
The dashboard at /dashboard/keys is the right place for day-to-day key management. The Management API is for:
Management API calls are authenticated with a special management key — distinct from ordinary API keys. Management keys have elevated permissions and should be stored in a secrets manager, never in application code or environment files alongside API keys.
Management keys are issued from Settings → Management Keys — the value is shown once at creation.
Authorization: Bearer $OPENDUNES_MANAGEMENT_KEYA management key is refused on a model route, and an API key is refused on a management route: both answer 401.
| Method | Path | Does |
|---|---|---|
GET | /v1/keys | lists your active API keys, 100 at a time from offset; include_disabled=true adds the disabled ones |
POST | /v1/keys | creates one — name, and optionally limit, limit_reset, expires_at, storage |
GET | /v1/keys/{hash} | reads one, without its value |
PATCH | /v1/keys/{hash} | changes name, limit, limit_reset, storage or disabled |
DELETE | /v1/keys/{hash} | deletes one |
The same management key also reads your balance at GET /v1/credits and your usage at GET /v1/activity, and manages guardrails and observability destinations.
POST /v1/keys
Content-Type: application/json
Authorization: Bearer $OPENDUNES_MANAGEMENT_KEY
{
"name": "ci-production",
"limit": 2000,
"limit_reset": "monthly"
}The answer is 201, with the full key value in key — store it immediately, it will not be shown again:
{
"data": {
"hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"name": "ci-production",
"label": "sk-a1B2c…",
"disabled": false,
"limit": 2000,
"limit_remaining": 2000,
"limit_reset": "monthly",
"usage": 0,
"usage_daily": 0,
"usage_weekly": 0,
"usage_monthly": 0,
"storage": true,
"created_at": "2026-09-24T09:12:03+00:00",
"updated_at": "2026-09-24T09:12:03+00:00",
"expires_at": null
},
"key": "sk-…"
}limit is a spending cap in DA; null or absent for none. The expiry is set only at creation.
PATCH /v1/keys/{hash}
Content-Type: application/json
Authorization: Bearer $OPENDUNES_MANAGEMENT_KEY
{
"limit": 5000,
"disabled": true
}A disabled key answers 401 until it is enabled again with "disabled": false.
DELETE /v1/keys/{hash}The answer is { "deleted": true }. Deletion is immediate: any request using this key answers 401 from then on. A hash that is not one of your keys is a 404.
The safest way to rotate a key is:
POST /v1/keys to create the new keyDELETE /v1/keys/{old_hash} to delete the old keyThis is a zero-downtime rotation — both keys are valid during the overlap window.
There is no cap on the number of management calls, as there is none on model requests: provisioning keys in a loop needs no arrangement with support. See Limits.