Keys management
Create, list, rotate, and revoke API keys via the JWT-authenticated keys endpoints.
Keys management
Self-service endpoints for creating, listing, rotating, and revoking your API keys.
important: JWT auth, not key auth These endpoints require an
Authorization: Bearer <jwt>header, not anX-Api-Keyheader. The intent: the human user (logged into the dashboard) manages their keys; their machines (using a key) cannot self-create more keys.
List keys
GET /api/v1/keys/
Authorization: Bearer <jwt>Response: 200 OK
{
"keys": [
{
"id": 7,
"name": "production-server-1",
"key_prefix": "smk_aBc12dEf",
"plan": "starter",
"is_active": true,
"created_at": "2026-04-26T18:30:00Z",
"last_used_at": "2026-05-08T14:14:22Z",
"revoked_at": null
},
{
"id": 4,
"name": "old-test-key",
"key_prefix": "smk_z9YxWvUt",
"plan": "free",
"is_active": false,
"created_at": "2026-03-01T10:00:00Z",
"last_used_at": "2026-03-15T14:20:11Z",
"revoked_at": "2026-04-01T09:00:00Z"
}
]
}The raw secret is never returned, only the prefix.
Create a key
POST /api/v1/keys/
Authorization: Bearer <jwt>
Content-Type: application/json
{
"name": "production-server-1",
"plan": "starter"
}Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Human-readable label, max 100 chars |
plan | enum | free (default), starter, pro, agency, enterprise |
The plan must be at most the workspace's current subscription tier. You cannot create a Pro key on a Starter subscription.
Response: 201 Created
{
"id": 7,
"name": "production-server-1",
"raw_key": "smk_aBc12dEf3GhI4jKlMnOpQrStUvWxYz67890",
"key_prefix": "smk_aBc12dEf",
"plan": "starter",
"warning": "Store this key securely. It will NOT be shown again."
}danger: One-time disclosure
raw_keyappears in the response only once. We store the SHA-256 hash and cannot recover the original. Lose it -> revoke and create a new one.
400 Bad Request
{ "name": "Required." }{ "plan": "Plan 'pro' exceeds your subscription tier 'starter'." }Rotate a key
POST /api/v1/keys/{id}/rotate/
Authorization: Bearer <jwt>Issues a new raw key, invalidates the previous one immediately. The
key's id, name, plan, and audit history are preserved.
Response: 200 OK
{
"id": 7,
"name": "production-server-1",
"raw_key": "smk_NEW_kEyHeRe...",
"key_prefix": "smk_NEWkEy",
"plan": "starter",
"rotated_at": "2026-05-08T14:00:00Z",
"warning": "Store this key securely. It will NOT be shown again."
}Use this for the deploy new -> verify -> deploy old key removed
rotation pattern. The old hash is destroyed at rotation time, so any
client still holding the old key gets 401.
Revoke a key
DELETE /api/v1/keys/{id}/
Authorization: Bearer <jwt>Response: 204 No Content
After revocation, the key is permanently rejected with 401. The audit
trail (which calls were made) is preserved for billing and forensics.
404 Not Found
The key does not exist or does not belong to you.
Examples
cURL
# Create
curl -X POST https://api.qrstudio.agency/api/v1/keys/ \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"name":"laptop","plan":"starter"}'
# List
curl https://api.qrstudio.agency/api/v1/keys/ \
-H "Authorization: Bearer <jwt>"
# Rotate
curl -X POST https://api.qrstudio.agency/api/v1/keys/7/rotate/ \
-H "Authorization: Bearer <jwt>"
# Revoke
curl -X DELETE https://api.qrstudio.agency/api/v1/keys/3/ \
-H "Authorization: Bearer <jwt>"JavaScript (in-dashboard)
const create = await fetch("/api/v1/keys/", {
method: "POST",
credentials: "same-origin",
headers: {
"Authorization": `Bearer ${jwt}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "laptop", plan: "starter" }),
});
const { raw_key } = await create.json();
showSensitiveOnce(raw_key);