AI API keys
A key is what authenticates a call and what limits it. This resource manages your AI project's keys, including the one making the request. Only a hash of the secret is stored, so a key is shown in full exactly once, in the response that creates it.
Endpoints
| Method | Path | Scope |
|---|---|---|
| GET | /v1/ai/key/list | key:read |
| GET | /v1/ai/key/detail/current | key:read |
| GET | /v1/ai/key/detail/<key_id> | key:read |
| POST | /v1/ai/key/create | key:write |
| PATCH | /v1/ai/key/detail/<key_id> | key:write |
| DELETE | /v1/ai/key/detail/<key_id> | key:write |
<key_id> is a key identifier, prefixed papi_. current is a reserved word in that position: it stands for the key the request is made 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 | sk-project-... followed by the last four characters of the secret, so you can recognise it. |
scope | array | The scopes the key grants. See Your first AI API call. |
status | string | activated or suspended. 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 account of your project that created the key, or an empty string when no account of your project did. |
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 shows you again.
List the keys
GET /v1/ai/key/list returns your project's keys, newest first, without the revoked ones. It accepts limit and offset.
curl https://api.ultineo.com/v1/ai/key/list \
-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": 1789209200,
"created_by": "cons_8e2b45a10c9df7631b4a",
"created_at": 1788414400,
"updated_at": 1789209200
}
],
"total": 1
}
} Read the calling key
GET /v1/ai/key/detail/current is the quickest way to find out what your key may do, without knowing its identifier.
curl https://api.ultineo.com/v1/ai/key/detail/current \
-H "Authorization: Bearer sk-project-YOUR_KEY" GET /v1/ai/key/detail/<key_id> reads any key of your project the same way. A key of another project, or a revoked key, gives 404 and API key not found.
Create a key
POST /v1/ai/key/create creates a new key for the same project.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | A name to recognise the key by, up to 200 characters. |
scope | array | No | The scopes to grant. Defaults to ["*"], which is refused unless your own key holds *. |
expires_at | integer | No | When the key should stop working, in Unix seconds, in the future. Omit it or send 0 for a key that never expires. |
curl -X POST https://api.ultineo.com/v1/ai/key/create \
-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_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": 1789209800,
"updated_at": 1789209800,
"key": "sk-project-7Qd1xR0mZ8pKvT3sLb…"
}
} 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 your own key does not hold is refused rather than silently trimmed.
Important — Because leaving
scopeout means["*"], a key that does not itself hold*must send an explicitscopelist: leaving it out is a403, even though the parameter is optional.
| Code | Message | Cause |
|---|---|---|
| 422 | name is required. | No name was sent. |
| 422 | name must be at most 200 characters. | The name is too long. |
| 400 | scope must be a non-empty array. | scope is a string, an object or an empty list. |
| 403 | Cannot create a key with wider scope than the calling key. | You asked for * while your own key is narrower. |
| 403 | The API key is missing the required scope: <scope> | You asked for a scope your own key does not hold. |
| 422 | expires_at must be in the future. | The expiry date has already passed. |
A scope that is not written in the scope grammar (*, <resource>:*, <resource>:read or <resource>:write) is refused with 422 and a message that recalls that grammar.
Rename, suspend or reactivate a key
PATCH /v1/ai/key/detail/<key_id> changes a key's name, its status, or both.
| Parameter | Type | Description |
|---|---|---|
name | string | A new name, up to 200 characters. |
status | string | suspended to stop the key without deleting it, activated to bring it back. |
curl -X PATCH https://api.ultineo.com/v1/ai/key/detail/papi_b6294fc0e178ad35c9b2 \
-H "Authorization: Bearer sk-project-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "suspended" }' The answer is the updated key. A suspended key answers 403 on every AI path until you reactivate it.
| Code | Message | Cause |
|---|---|---|
| 400 | Nothing to update: send name and/or status. | Neither field was sent. |
| 422 | status must be activated or suspended. | status is another value. |
| 422 | Cannot suspend the API key currently in use. | You tried to suspend the key the request is made with. |
| 404 | API key not found. | The key is not one of your project's keys, or it was revoked. |
Revoke a key
DELETE /v1/ai/key/detail/<key_id> revokes the key at once. Any later call made with it answers 403 and The API key doesn't have permissions to perform the request., the same answer an unknown key gets. A revoked key cannot be reactivated.
curl -X DELETE https://api.ultineo.com/v1/ai/key/detail/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 new key, move your traffic onto it, then revoke the old key with the new one.