API keys
A key is what authenticates a call and what scopes it. This resource manages the project's keys, including the one making the request. The raw secret is never stored — only its SHA-256 hash — so a key is shown in full exactly once, in the response that creates it.
To create your first key from the workspace instead, see API keys.
Endpoints
| Method | Path | Scope |
|---|---|---|
| GET | /v1/key | key:read |
| GET | /v1/key/current | key:read |
| GET | /v1/key/<key_id> | key:read |
| POST | /v1/key | key:write |
| DELETE | /v1/key/<key_id> | key:write |
<key_id> is a key identifier, prefixed papi_. current is a reserved word in that position and resolves to the key the request is authenticated with.
The API key object
| Field | Type | Description |
|---|---|---|
id | string | The key identifier, prefixed papi_. |
object | string | Always api_key. |
name | string | The key's name. |
key_preview | string | The literal prefix sk-project-... followed by the last four characters of the secret, for recognising it. |
scope | array | The scopes the key grants. See below. |
status | string | Only an activated key authenticates. |
expires_at | integer | When the key stops working, in Unix seconds, or null for a key that never expires. |
last_used_at | integer | When the key last authenticated a call, or null. |
created_by | string | The consumer who created the key. |
created_at | integer | Creation time, in Unix seconds. |
updated_at | integer | Last change, in Unix seconds. |
The secret itself appears in no read response. key_preview is the only part of it the API will show you again.
List the keys
GET /v1/key returns the project's keys, newest first.
curl https://api.thinkhx.com/v1/key \
-H "Authorization: Bearer sk-project-YOUR_KEY" {
"code": 200,
"title": "OK",
"response": {
"object": "list",
"data": [
{
"id": "papi_4e07c1b8a35df926104c",
"object": "api_key",
"name": "Production backend",
"key_preview": "sk-project-...a7e1",
"scope": ["reasoning:*", "app:read", "usage:read"],
"status": "activated",
"expires_at": null,
"last_used_at": 1754209200,
"created_by": "cons_8e2b45a10c9df7631b4a",
"created_at": 1751414400,
"updated_at": 1754209200
}
],
"total": 1
}
} Read the calling key
GET /v1/key/current is the quickest way to find out what your key may do, without knowing its identifier.
curl https://api.thinkhx.com/v1/key/current \
-H "Authorization: Bearer sk-project-YOUR_KEY" A key of another project gives 404 and API key not found.
Create a key
POST /v1/key mints a new key for the same project.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | A name to recognise the key by. |
scope | array | no | The scopes to grant. Defaults to ["*"], which is refused unless your own key holds *. See below. |
expires_at | integer | no | When the key should stop working, in Unix seconds. Omit or send 0 for a key that never expires. |
curl -X POST https://api.thinkhx.com/v1/key \
-H "Authorization: Bearer sk-project-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Nightly batch",
"scope": ["reasoning:read", "reasoning:write", "usage:read"]
}' {
"code": 201,
"title": "Created",
"response": {
"id": "papi_b6294fc0e178ad35c9b2",
"object": "api_key",
"name": "Nightly batch",
"key": "sk-project-7Qd1xR0mZ8pKvT3sLb...",
"key_preview": "sk-project-...Lb92",
"scope": ["reasoning:read", "reasoning:write", "usage:read"],
"status": "activated",
"expires_at": null,
"last_used_at": null,
"created_by": "cons_8e2b45a10c9df7631b4a",
"created_at": 1754209800,
"updated_at": 1754209800
}
} Important —
keyis present in this one response and nowhere else. Store it before you discard the response. A lost secret cannot be recovered, only replaced by creating another key.
A key can never grant more than the key that created it. Asking for a scope the caller does not itself hold is refused rather than silently trimmed.
Important — Nothing is narrowed for you, and that includes the default. Because omitting
scopemeans["*"], a key that does not itself hold*must send an explicitscopearray: leaving the parameter out is a403, even though it is not a required field.
| Code | Message | Cause |
|---|---|---|
| 422 | name is required. | No name was sent. |
| 400 | scope must be an array. | scope was sent as a string or an object. |
| 403 | Cannot create a key with wider scope than the calling key. | A requested scope is outside the caller's own. |
Revoke a key
DELETE /v1/key/<key_id> revokes the key immediately. Any call made with it afterwards answers 403 and The API key doesn't have permissions to perform the request. — the same answer an unknown key gets. 401 is reserved for a key that had an expiry and reached it. See Errors.
curl -X DELETE https://api.thinkhx.com/v1/key/papi_b6294fc0e178ad35c9b2 \
-H "Authorization: Bearer sk-project-YOUR_KEY" {
"code": 200,
"title": "OK",
"response": {
"deleted": true,
"id": "papi_b6294fc0e178ad35c9b2"
}
} The key you are calling with is refused with 422 and Cannot revoke the API key currently in use., so a rotation cannot lock you out halfway. Create the replacement, move your traffic onto it, then revoke the old key with the new one.