API Keys
Create, list, rotate, and delete project secret keys via /api/v1/api-keys.
The /api/v1/api-keys group manages the API keys that belong to a project — the secret keys your servers use and the publishable keys your apps ship with. See Authentication for how each key type is used to call the API.
All endpoints in this group live under /api/v1/api-keys and authenticate via the standard auth headers. They are project-scoped: key credentials carry exactly one project, and user sessions act on their first project — there is no explicit project parameter except on create.
The ApiKey object
| Field | Type | Description |
|---|---|---|
id | string | Unique key identifier (api_sk_...). Stable across rotation. |
name | string | Human-readable label you chose at creation. |
projectId | string | The project this key belongs to. |
isPublic | boolean | true for publishable keys, false for secret keys. |
prefix | string | Key prefix — vh_sk_ for secret keys, vh_pk_ for publishable keys. |
end | string | The last 4 characters of the key, for display (vh_sk_...WxYz). |
rawKey | string (optional) | The full key value. See below for when it is present. |
{
"id": "api_sk_01hxyz",
"name": "Production server",
"projectId": "prj_01hxyz",
"isPublic": false,
"prefix": "vh_sk_",
"end": "WxYz"
}ApiKey vs ApiKeyWithRawKey
Secret keys are hashed before storage — Voidhash keeps only a SHA-256 digest, never the key itself. That means the full secret key value can only exist in two responses:
- Create (
POST /api/v1/api-keys) returnsApiKeyWithRawKey— the object above withrawKeyrequired. - Rotate (
POST /api/v1/api-keys/:apiKeyId/rotate) also returnsApiKeyWithRawKeywith the new key value.
Every other read — list and get by id — returns ApiKey, where rawKey is absent for secret keys. Use prefix and end to render an identifiable placeholder like vh_sk_...WxYz in your own tooling.
Store the raw key immediately
The rawKey in the create and rotate responses is the only time you will ever see the full
secret key. It cannot be retrieved later — not by list, not by get. If you lose it, rotate the
key to get a new one.
Publishable keys are the exception: they are not secret, so rawKey is included whenever a publishable key appears in list or get responses.
Create a secret key
POST /api/v1/api-keys
| Body field | Type | Description |
|---|---|---|
name | string | Label for the key (e.g. the service that will use it). |
projectId | string | The project to create the key in. |
curl -X POST https://api.voidhash.com/api/v1/api-keys \
-H "x-api-key: <your-api-key>" \
-H "Content-Type: application/json" \
-d '{ "name": "Production server", "projectId": "prj_01hxyz" }'Returns ApiKeyWithRawKey:
{
"id": "api_sk_01hxyz",
"name": "Production server",
"projectId": "prj_01hxyz",
"isPublic": false,
"prefix": "vh_sk_",
"end": "WxYz",
"rawKey": "vh_sk_AbCdEfGhIjKlMnOpQrStUvWxYz"
}| Status | _tag | When |
|---|---|---|
403 | Api/ActionForbiddenError | The caller lacks permission on projectId. |
500 | Api/ApiKeyServiceError | Internal service failure. |
List API keys
GET /api/v1/api-keys
Lists every key in the calling credential's project — publishable keys first, then by creation time. Secret keys come back without rawKey; publishable keys include it.
curl https://api.voidhash.com/api/v1/api-keys \
-H "x-secret-key: vh_sk_..."Returns ApiKey[].
| Status | _tag | When |
|---|---|---|
403 | Api/ActionForbiddenError | No project in scope, or missing permission. |
500 | Api/ApiKeyServiceError | Internal service failure. |
Get an API key
GET /api/v1/api-keys/:apiKeyId
curl https://api.voidhash.com/api/v1/api-keys/api_sk_01hxyz \
-H "x-secret-key: vh_sk_..."Returns a single ApiKey.
| Status | _tag | When |
|---|---|---|
403 | Api/ActionForbiddenError | The key belongs to a project the caller cannot access. |
404 | Api/ApiKeyNotFoundError | No key with this id exists. |
500 | Api/ApiKeyServiceError | Internal service failure. |
Rotate a secret key
POST /api/v1/api-keys/:apiKeyId/rotate
Replaces the key material in place: a new secret is generated and stored under the same id and name, and the old key stops authenticating immediately. This is the endpoint to reach for when a key leaks.
curl -X POST https://api.voidhash.com/api/v1/api-keys/api_sk_01hxyz/rotate \
-H "x-api-key: <your-api-key>"Returns ApiKeyWithRawKey with the new rawKey, prefix, and end — same warning as create: this is the only time you see the new value.
| Status | _tag | When |
|---|---|---|
403 | Api/ActionForbiddenError | The caller lacks permission on the key's project. |
404 | Api/ApiKeyNotFoundError | No key with this id exists. |
500 | Api/ApiKeyServiceError | Internal service failure. |
Rotating a compromised key
- Call rotate on the compromised key. The leaked value is invalid the moment the call succeeds — there is no grace period.
- Copy
rawKeyfrom the response and update every server, CI secret, and environment variable that used the old value. Anything still holding the old key gets401 Api/NotAuthenticatedErroron its next request. - The
idandnameare unchanged, so nothing that references the key by id needs to change.
If the key should not exist at all anymore, delete it instead.
Delete an API key
DELETE /api/v1/api-keys/:apiKeyId
Permanently removes the key. Requests using it fail with 401 immediately. Deletion is irreversible — if a service still needs access, prefer rotate.
curl -X DELETE https://api.voidhash.com/api/v1/api-keys/api_sk_01hxyz \
-H "x-api-key: <your-api-key>"Returns an empty 200 response on success.
| Status | _tag | When |
|---|---|---|
403 | Api/ActionForbiddenError | The caller lacks permission on the key's project. |
404 | Api/ApiKeyNotFoundError | No key with this id exists. |
500 | Api/ApiKeyServiceError | Internal service failure. |
Related
- Authentication — where each key type goes in request headers
- API Overview — base URLs and shared error conventions
- Organizations & Projects — creating the project a key belongs to