> 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/migration-guides/2.2.0-greater-than-2.3.0/migration-guide-customer-user-api-keys-identity-api-keys.md).

# Migration Guide: Customer User API Keys → Identity API Keys

{% hint style="warning" %} <mark style="color:$warning;">**Action required before Thu, 01 Oct 2026**</mark>

The legacy **Customer User API Key** endpoints are deprecated and will be **removed on 01 Oct 2026**. They are replaced by the unified **Identity API Key** endpoints. The legacy endpoints keep working until the sunset date, but plan to switch over well before then.
{% endhint %}

## 1. What's changing at a glance

<table><thead><tr><th width="201.7265625">Action</th><th width="261.96875">Legacy (deprecated)</th><th width="264.58984375">New</th></tr></thead><tbody><tr><td>Create a key</td><td><mark style="color:$info;">POST /api/v1/psp/product-manager/customer-user-api-keys</mark></td><td>POST /api/v1/auth/identity-api-keys</td></tr><tr><td>List keys</td><td><mark style="color:$info;">GET /api/v1/psp/product-manager/customer-user-api-keys</mark></td><td>GET /api/v1/auth/identity-api-keys</td></tr><tr><td>Delete a key</td><td><mark style="color:$info;">DELETE /api/v1/psp/product-manager/customer-user-api-keys/{apiKeyId}</mark></td><td>DELETE /api/v1/auth/identity-api-keys/{id}</td></tr><tr><td>Revoke / re-activate a key</td><td><mark style="color:$info;">PATCH /api/v1/psp/product-manager/customer-user-api-keys/{apiKeyId} - { status }</mark></td><td>POST /api/v1/auth/identity-api-keys/{id}/revoke - { isRevoked }</td></tr><tr><td>Get usage and limit</td><td><mark style="color:$info;">GET /api/v1/psp/product-manager/customer-user-api-keys/usage</mark></td><td>GET /api/v1/auth/identity-api-keys/usage</td></tr></tbody></table>

## 2. How to tell you're still on the deprecated API

Every response from a deprecated endpoint now includes standard deprecation headers ([RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)):

```
Deprecation: true
Sunset: Thu, 01 Oct 2026 00:00:00 GMT
Link: </api/v1/auth/identity-api-keys>; rel="successor-version"
```

Log or alert whenever you see `Deprecation: true` to find every place in your integration still calling the old endpoints. The `Link` header points to the successor for that specific operation.

## 3. Authentication

Authenticate by sending your identity API key in the `x-api-key` request header. Identity keys are formatted as `idty_` followed by 32 characters.

```
x-api-key: idty_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345
```

To manage keys (create / list / usage / delete / revoke), the calling key must carry the `Issuer_Admin` or `Verifier_Admin` permission.

## 4. New permissions model

The biggest behavioural change is **in creation**. The legacy **create** only took a `name`. The new create lets you scope each key with explicit permissions.

* Supply `issuerPermission` and/or `verifierPermission`. **At least one is required,** omitting both returns `400 Bad Request`.
  * **`issuerPermission`** ∈ `Issuer_Admin`, `Issuer_User`, `Issuer_Auditor`
  * **`verifierPermission`** ∈ `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 calling key's own.

{% hint style="warning" %} <mark style="color:$warning;">**403 · identity\_api\_key\_privilege\_escalation**</mark>

Returned when you try to mint a key with a permission outranking the calling key.
{% endhint %}

{% hint style="warning" %} <mark style="color:$warning;">**403 · identity\_api\_key\_limit\_exceeded**</mark>

Identity keys and any remaining legacy keys share one limit (default **5**).
{% endhint %}

{% hint style="info" %} <mark style="color:blue;">**Expiry**</mark>

Newly created keys expire **60 days** after creation. Check the `expiresAt` field and rotate before then.
{% endhint %}

## 5. Endpoint reference

<details>

<summary> <mark style="background-color:$success;"><strong>POST</strong></mark>  /api/v1/auth/identity-api-keys</summary>

#### **Create a key** <mark style="color:$success;">→ 201 Created</mark>

<table><thead><tr><th width="192.2578125">Field</th><th width="213.08984375">Required</th><th>Notes</th></tr></thead><tbody><tr><td><code>name</code></td><td>yes</td><td>3–32 characters</td></tr><tr><td><code>issuerPermission</code></td><td>conditional</td><td>one of the issuer permissions </td></tr><tr><td><code>verifierPermission</code></td><td>conditional</td><td>one of the verifier permissions</td></tr></tbody></table>

At least one of `issuerPermission` / `verifierPermission` is required.

```bash
curl -X POST "https://<your-host>/api/v1/auth/identity-api-keys" \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "ci-pipeline-key", "issuerPermission": "Issuer_User" }'
```

<pre class="language-json"><code class="lang-json">{
  "id": "6f1c2e7a-9b3d-4f8a-bc21-0e5a9d4c7b10",
  "name": "ci-pipeline-key",
  "keyMask": "idty_••••••••••••••••••••••••••2345",
  "permissions": ["Issuer_User"],
  "expiresAt": "2026-08-15T09:30:00.000Z",
  "revokedAt": null,
  "createdBy": "a1b2c3d4-...",
  "createdAt": "2026-06-16T09:30:00.000Z",
  "updatedAt": "2026-06-16T09:30:00.000Z",
<strong>  "rawKey": "idty_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345"
</strong>}
</code></pre>

{% hint style="warning" %} <mark style="color:$warning;">**rawKey is shown only once**</mark>

Store it securely at creation as it cannot be retrieved later.
{% endhint %}

</details>

<details>

<summary> <mark style="background-color:blue;"><strong>GET</strong></mark>  /api/v1/auth/identity-api-keys</summary>

#### **List keys** → <mark style="color:$success;">200 OK</mark>

Returns an array of keys (identity keys **and** your remaining legacy keys, mapped into the same shape), newest first.

```bash
curl "https://<your-host>/api/v1/auth/identity-api-keys" \
  -H "x-api-key: $API_KEY"
```

```json
[
  {
    "id": "6f1c2e7a-9b3d-4f8a-bc21-0e5a9d4c7b10",
    "name": "ci-pipeline-key",
    "keyMask": "idty_••••••••••••••••••••••••••2345",
    "permissions": ["Issuer_User"],
    "expiresAt": "2026-08-15T09:30:00.000Z",
    "revokedAt": null,
    "createdBy": "a1b2c3d4-...",
    "createdAt": "2026-06-16T09:30:00.000Z",
    "updatedAt": "2026-06-16T09:30:00.000Z"
  }
]
```

</details>

<details>

<summary> <mark style="background-color:$danger;"><strong>DELETE</strong></mark> /api/v1/auth/identity-api-keys/{id}</summary>

#### **Delete a key** → <mark style="color:$success;">204 No Content</mark>

```bash
curl -X DELETE "https://<your-host>/api/v1/auth/identity-api-keys/6f1c2e7a-9b3d-4f8a-bc21-0e5a9d4c7b10" \
  -H "x-api-key: $API_KEY"
```

</details>

<details>

<summary> <mark style="background-color:$success;"><strong>POST</strong></mark> /api/v1/auth/identity-api-keys/{id}/revoke</summary>

#### **Revoke or re-activate a key** → <mark style="color:$success;">201 Created</mark>

Replaces the legacy `PATCH … { status }`. Send `isRevoked: true` to revoke, or `isRevoked: false` to re-activate. Idempotent: revoking an already-revoked key returns its current state without error.

```bash
curl -X POST "https://<your-host>/api/v1/auth/identity-api-keys/6f1c2e7a-9b3d-4f8a-bc21-0e5a9d4c7b10/revoke" \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "isRevoked": true }'
```

Response is the updated key (same shape as the list item; `revokedAt` is set when revoked).

</details>

<details>

<summary> <mark style="background-color:blue;"><strong>GET</strong></mark>  /api/v1/auth/identity-api-keys/usage</summary>

#### **Get usage** → <mark style="color:$success;">200 OK</mark>

```bash
curl "https://<your-host>/api/v1/auth/identity-api-keys/usage" \
  -H "x-api-key: $API_KEY"
```

```json
{ "used": 2, "limit": 5 }
```

`used` counts both identity and remaining legacy keys against the shared `limit`.

</details>

## 6. Key differences and gotchas

{% hint style="info" %} <mark style="color:blue;">**UUID key IDs**</mark>

Identity keys are identified by a UUID (e.g. `6f1c2e7a-9b3d-4f8a-bc21-0e5a9d4c7b10`), not a number. If your code assumes a numeric ID, update it.
{% endhint %}

{% hint style="info" %} <mark style="color:blue;">**Revoke replaces status updates**</mark>

Old: `PATCH …/{id}` with `{ "status": "Active" | "Disabled" }`.&#x20;

New: `POST …/{id}/revoke` with `{ "isRevoked": true | false }`.
{% endhint %}

{% hint style="danger" %} <mark style="color:$danger;">**Keys created via the deprecated POST can't be managed via the deprecated endpoints**</mark>

The deprecated create now issues an identity key with a **UUID** ID. The deprecated `DELETE`/`PATCH` endpoints accept only legacy **numeric** IDs, passing a UUID returns:&#x20;

```
400 Bad Request
"This api key is managed by the identity api key system.
 Manage it via the identity api key endpoints under auth/identity-api-keys."
```

**Any key you create from now on must be managed through the new `/api/v1/auth/identity-api-keys` endpoints.** Migrate your create calls first.
{% endhint %}

## 7. Migration checklist

{% stepper %}
{% step %}

### Find every legacy call

grep for `customer-user-api-keys`, or watch for the `Deprecation: true` header.
{% endstep %}

{% step %}

### Swap the base path

to `/api/v1/auth/identity-api-keys`&#x20;
{% endstep %}

{% step %}

### Handle UUID IDs&#x20;

wherever you store or pass a key ID.
{% endstep %}

{% step %}

### Update create calls

send `issuerPermission` and/or `verifierPermission`, with sufficient privilege rank.
{% endstep %}

{% step %}

### Switch revoke

from `PATCH { status }` to `POST …/revoke { isRevoked }`.
{% endstep %}

{% step %}

### Store `rawKey` at creation

It is shown only once.
{% endstep %}

{% step %}

### Confirm the shared key limit

via `GET /api/v1/auth/identity-api-keys/usage`.
{% endstep %}

{% step %}

### Complete migration before 01 Oct 2026

After which, the legacy endpoints are removed.
{% endstep %}
{% endstepper %}
