> For the complete documentation index, see [llms.txt](https://docs.teranode.group/tng-identity-documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.teranode.group/tng-identity-documentation/introduction/portal-api/guides/manage-api-keys.md).

# Manage API keys

## 1. Context

API Keys provide programmatic **access to the Identity products** (Portal API, Issuer API, Verifier API) without requiring a valid JWT access token. They allow developers and services to authenticate API requests using a simple header-based key, making them suitable for **server-to-server integrations**, CI/CD pipelines, and automated workflows.

Each API key is scoped to a set of permissions and an environment, **expires after 60 days**, and can be managed (created, listed, revoked, deleted) through the endpoints documented below.

## 2. Authentication

All endpoints mentioned support two authentication methods:

<table><thead><tr><th width="205.91796875">Method</th><th width="212.76953125">Header</th><th>Example</th></tr></thead><tbody><tr><td>JWT Access Token</td><td><code>Authorization</code></td><td><code>Authorization: Bearer eyJhbGciOi...</code></td></tr><tr><td>API Key</td><td><code>x-api-key</code></td><td><code>x-api-key: idty_...</code></td></tr></tbody></table>

To manage keys, the caller must carry the `Issuer_Admin` or `Verifier_Admin` permission.

## 3. Key constraints

<table><thead><tr><th width="310.49609375">Constraint</th><th>Value</th></tr></thead><tbody><tr><td>Name length</td><td>3 to 32 characters</td></tr><tr><td>Permissions</td><td>one or both of <code>issuerPermission</code> / <code>verifierPermission</code> (at least one required)</td></tr><tr><td>Expiration</td><td>60 days from creation</td></tr><tr><td>Environment limit</td><td>Configurable (default to 5 keys per environment)</td></tr></tbody></table>

**Permission values**

* `issuerPermission` — one of `Issuer_Admin`, `Issuer_User`, `Issuer_Auditor`
* `verifierPermission` — one of `Verifier_Admin`, `Verifier_User`, `Verifier_Auditor`

**Privilege rank** — `Admin` (3) > `User` (2) > `Auditor` (1). You cannot create a key with a permission ranked higher than the caller's own.

## 4. API endpoints

{% hint style="info" %}
Full OpenApi documentation regarding API keys is available in the [appropriate API documentation section](/tng-identity-documentation/introduction/portal-api/api-documentation.md)
{% endhint %}

### 4.1. Create API key

{% code title="Base url" %}

```
POST /api/v1/auth/identity-api-keys
```

{% endcode %}

Creates a new **API key.** The key expires 60 days after creation.

#### Request

{% code title="cURL using API key" %}

```
curl -X POST https://{host}/api/v1/auth/identity-api-keys \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "My Integration Key", "issuerPermission": "Issuer_User" }'
```

{% endcode %}

Body parameters:

* **name**<mark style="color:$danger;">**\***</mark> <sup><mark style="color:$info;">string<mark style="color:$info;"></sup> - name of the api key, can be 1 to 50 characters long
* **issuerPermission** <sup><mark style="color:$info;">enum<mark style="color:$info;"></sup> - issuer scope for the key. Required if `verifierPermission` is omitted.
* **verifierPermission** <sup><mark style="color:$info;">enum<mark style="color:$info;"></sup> - verifier scope for the key. Required if `issuerPermission` is omitted.

#### Response

{% code title="201 - created" %}

```json
{
  "id": "6f1c2e7a-9b3d-4f8a-bc21-0e5a9d4c7b10",
  "name": "My Integration Key",
  "keyMask": "idty_...2345",
  "permissions": ["Issuer_User"],
  "expiresAt": "2026-08-15T11:14:53.402Z",
  "revokedAt": null,
  "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "createdAt": "2026-06-16T11:14:53.402Z",
  "updatedAt": "2026-06-16T11:14:53.402Z",
  "rawKey": "idty_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345"
}
```

{% endcode %}

{% hint style="warning" %}
The `rawKey` field is **only returned in this response**. Store it securely immediately, as it cannot be retrieved again.
{% endhint %}

### 4.2. List API keys

{% code title="Base url" %}

```
GET /api/v1/auth/identity-api-keys
```

{% endcode %}

Returns all API keys visible to the authenticated user in the environment, newest first.

#### Request

{% code title="cURL using API key" %}

```
curl -X GET https://{host}/api/v1/auth/identity-api-keys \
  -H "x-api-key: YOUR_API_KEY"
```

{% endcode %}

#### Response

{% code title="200 - success" %}

```json
[
  {
    "id": "6f1c2e7a-9b3d-4f8a-bc21-0e5a9d4c7b10",
    "name": "My Integration Key",
    "keyMask": "idty_...2345",
    "permissions": ["Issuer_User"],
    "expiresAt": "2026-08-15T11:14:53.402Z",
    "revokedAt": null,
    "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "createdAt": "2026-06-16T11:14:53.402Z",
    "updatedAt": "2026-06-16T11:14:53.402Z"
  }
  // other api keys
]
```

{% endcode %}

### 4.3. Delete API keys

{% code title="Base url" %}

```
DELETE /api/v1/auth/identity-api-keys/:id
```

{% endcode %}

**Permanently deletes** the specified API key.

#### Request

{% code title="cURL using API key" %}

```
curl -X DELETE https://{host}/api/v1/auth/identity-api-keys/:id \
  -H "x-api-key: YOUR_API_KEY"
```

{% endcode %}

Path parameters:

* **id**<mark style="color:$danger;">**\***</mark> <sup><mark style="color:$info;">string<mark style="color:$info;"></sup> - the ID(uuid) of the API key to delete

#### Response

{% code title="204 - no content" %}

```json
// Empty response body
```

{% endcode %}

### 4.4. Revoke / re-activate API key

{% code title="Base url" %}

```
POST /api/v1/auth/identity-api-keys/:id/revoke
```

{% endcode %}

Revokes the specified API key, or resets the revocation to re-activate it. The operation is idempotent, revoking an already-revoked key returns its current state without error.

#### Request

{% code title="cURL using API key" %}

```
curl -X POST https://{host}/api/v1/auth/identity-api-keys/:id/revoke \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "isRevoked": true }'
```

{% endcode %}

Path parameters:

* **id**<mark style="color:$danger;">**\***</mark> <sup><mark style="color:$info;">integer<mark style="color:$info;"></sup> - the ID of the API key to delete

Body parameters:

* **isRevoked**<mark style="color:$danger;">**\***</mark> <sup><mark style="color:$info;">boolean<mark style="color:$info;"></sup> - `true` to revoke the key, `false` to re-activate it.

#### Response

{% code title="201 - created" %}

```json
{
  "id": "6f1c2e7a-9b3d-4f8a-bc21-0e5a9d4c7b10",
  "name": "My Integration Key",
  "keyMask": "idty_...2345",
  "permissions": ["Issuer_User"],
  "expiresAt": "2026-08-15T11:14:53.402Z",
  "revokedAt": "2026-06-16T12:00:00.000Z",
  "createdBy": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "createdAt": "2026-06-16T11:14:53.402Z",
  "updatedAt": "2026-06-16T12:00:00.000Z"
}
```

{% endcode %}

## 5. Best practices

* **Store the raw key immediately**. The `rawKey` is only returned once during creation. If lost, delete the key and create a new one.&#x20;
* **Plan for key rotation**. Keys expire after 60 days. Create a new key before the current one expires to avoid service interruption.&#x20;
* **Use `keyMask` for display**. When showing API keys in a UI, use the `keyMask` field to identify keys without exposing sensitive values.&#x20;
* **Revoke before deleting**. To gracefully decommission a key, revoke it first (`POST …/revoke` with `{ "isRevoked": true }`), verify no integrations break, then delete it.

## 6. Error responses

<table><thead><tr><th width="92.2265625">Status</th><th width="210.80078125">Error</th><th>When</th></tr></thead><tbody><tr><td><code>401</code></td><td>Bad Request</td><td>Invalid request body, name too short/long, neither <code>issuerPermission</code> nor <code>verifierPermission</code> supplied, or an invalid key ID</td></tr><tr><td><code>401</code></td><td>Unauthorized</td><td>Missing or invalid authentication header</td></tr><tr><td><code>403</code></td><td>Forbidden</td><td>Authenticated caller lacks the required claims/permissions</td></tr><tr><td><code>403</code></td><td>Forbidden — <code>identity_api_key_privilege_escalation</code></td><td>Attempting to create a key with a permission ranked higher than the caller's own</td></tr><tr><td><code>403</code></td><td>Forbidden — <code>identity_api_key_limit_exceeded</code></td><td>The environment key limit has been reached</td></tr><tr><td><code>404</code></td><td>Not found</td><td>The specified key ID does not exist</td></tr><tr><td><code>503</code></td><td>Service unavailable</td><td>An external service required to manage API keys is unavailable</td></tr></tbody></table>
