API Keys
Manage API keys for programmatic access. See the API Keys guide for usage details.
const TOKEN = "hv_..."; // your API keyconst headers = { Authorization: `Bearer ${TOKEN}` };const TOKEN = "hv_..."; // your API keyconst headers = { Authorization: `Bearer ${TOKEN}` };let token = "hv_..."; // your API keytoken := "hv_..." // your API key# All examples assume $TOKEN is an API key (hv_...)AUTH="Authorization: Bearer $TOKEN"Create an API key
Section titled “Create an API key”POST /admin/api-keysRequires api-keys:create permission.
interface ApiKey { id: string; name: string; key: string; key_prefix: string; permissions: string[];}
const response = await fetch("http://127.0.0.1:3000/admin/api-keys", { method: "POST", headers: { ...headers, "Content-Type": "application/json", }, body: JSON.stringify({ name: "CI Deploy", permissions: ["lexicons:read", "lexicons:create", "backfill:create"], }),});const data: ApiKey = await response.json();const response = await fetch("http://127.0.0.1:3000/admin/api-keys", { method: "POST", headers: { ...headers, "Content-Type": "application/json", }, body: JSON.stringify({ name: "CI Deploy", permissions: ["lexicons:read", "lexicons:create", "backfill:create"], }),});const data = await response.json();let client = reqwest::Client::new();let response = client .post("http://127.0.0.1:3000/admin/api-keys") .bearer_auth(token) .json(&serde_json::json!({ "name": "CI Deploy", "permissions": ["lexicons:read", "lexicons:create", "backfill:create"] })) .send() .await?;let data: serde_json::Value = response.json().await?;body := bytes.NewBufferString(`{ "name": "CI Deploy", "permissions": ["lexicons:read", "lexicons:create", "backfill:create"]}`)req, _ := http.NewRequest("POST", "http://127.0.0.1:3000/admin/api-keys", body)req.Header.Set("Authorization", "Bearer "+token)req.Header.Set("Content-Type", "application/json")resp, err := http.DefaultClient.Do(req)curl -X POST http://127.0.0.1:3000/admin/api-keys \ -H "$AUTH" \ -H "Content-Type: application/json" \ -d '{ "name": "CI Deploy", "permissions": ["lexicons:read", "lexicons:create", "backfill:create"] }'| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | A label to identify this key’s usage |
permissions | string[] | yes | Permissions to grant the key (must be a subset of the creating user’s own permissions) |
Response: 201 Created
{ "id": "550e8400-e29b-41d4-a716-446655440000", "name": "CI Deploy", "key": "hv_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", "key_prefix": "hv_a1b2c3d4", "permissions": ["lexicons:read", "lexicons:create", "backfill:create"]}The key field contains the full API key. It is only returned in this response — store it securely. The key’s effective permissions are the intersection of the permissions specified here and the creating user’s permissions at the time of each request.
List API keys
Section titled “List API keys”GET /admin/api-keysRequires api-keys:read permission.
const response = await fetch("http://127.0.0.1:3000/admin/api-keys", { headers,});const data: ApiKey[] = await response.json();const response = await fetch("http://127.0.0.1:3000/admin/api-keys", { headers,});const data = await response.json();let response = client .get("http://127.0.0.1:3000/admin/api-keys") .bearer_auth(token) .send() .await?;let data: serde_json::Value = response.json().await?;req, _ := http.NewRequest("GET", "http://127.0.0.1:3000/admin/api-keys", nil)req.Header.Set("Authorization", "Bearer "+token)resp, err := http.DefaultClient.Do(req)curl http://127.0.0.1:3000/admin/api-keys -H "$AUTH"Response: 200 OK
[ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "CI Deploy", "key_prefix": "hv_a1b2c3d4", "permissions": ["lexicons:read", "lexicons:create", "backfill:create"], "created_at": "2026-03-01T00:00:00Z", "last_used_at": "2026-03-06T12:00:00Z", "revoked_at": null }]Only returns keys belonging to the authenticated user. The full key is never included — only the prefix.
Revoke an API key
Section titled “Revoke an API key”DELETE /admin/api-keys/{id}Requires api-keys:delete permission.
const response = await fetch( "http://127.0.0.1:3000/admin/api-keys/550e8400-e29b-41d4-a716-446655440000", { method: "DELETE", headers },);const response = await fetch( "http://127.0.0.1:3000/admin/api-keys/550e8400-e29b-41d4-a716-446655440000", { method: "DELETE", headers },);client .delete("http://127.0.0.1:3000/admin/api-keys/550e8400-e29b-41d4-a716-446655440000") .bearer_auth(token) .send() .await?;req, _ := http.NewRequest("DELETE", "http://127.0.0.1:3000/admin/api-keys/550e8400-e29b-41d4-a716-446655440000", nil)req.Header.Set("Authorization", "Bearer "+token)resp, err := http.DefaultClient.Do(req)curl -X DELETE http://127.0.0.1:3000/admin/api-keys/550e8400-e29b-41d4-a716-446655440000 \ -H "$AUTH"Sets revoked_at on the key. The key remains in the database for audit purposes but can no longer authenticate.
Response: 204 No Content

