# Create and manage identities

Identity Management v3
This topic describes the v3 identity model. **Identity Management v3** is recommended for all new integrations.

The supported payment corridors are:

- US • USD
- EU • EUR
- MX • MXN
- BR • BRL
- CO • COP
- CA • CAD
- GB • GBP
- NG • NGN


This tutorial shows how to use the **Identity Management v3** identity endpoints to:

- Create a new identity
- Retrieve an identity by ID (and by version)
- List identities with filters and pagination
- Update identity details
- Deactivate an identity


It builds on the model described in [Payment identities](/products/payments-direct-2/api-docs/concepts/payment-identities) and focuses on the v3 identity schema.

internalId
A key behavior in v3 is that **at most one ACTIVE identity** in your environment can use a given `internalId`. Attempts to create or update an identity so that its `internalId` collides with another ACTIVE identity result in **409 Conflict**.

## Before you begin

To follow these examples, you need:

- Base URL for the Identity Management (for example, `https://{base-url}`)
- An access token with the following scopes (or equivalent):
  - `participants:create `for creating identities
  - `participants:read` for reading and listing identities
  - `participants:update` for updating and deactivating identities
- The v3 identity model from **Payment identities** (INDIVIDUAL vs BUSINESS and ORIGINATOR vs BENEFICIARY)


In all examples below, replace:

- `{base-url}` with your actual PII base URL
- `<access_token>` with a valid OAuth2 access token
- `{identity-id} `with a real `identityId` returned by the API


## Create an identity

Use `POST /v3/identities` to create a new identity.

At minimum, you must provide:

- `identityType` – INDIVIDUAL or BUSINESS
- `paymentRole` – ORIGINATOR or BENEFICIARY
- Either individual or business details, depending on `identityType`
- `internalId` for ORIGINATOR identities (required by corridor rules)


For **BENEFICIARY** identities, `internalId` is optional, but recommended if you want to link beneficiaries to your own customer records.

Deduplication via internalId
Identity Management v3 enforces that at most one **ACTIVE** identity per organization can use a given `internalId`.

- If you try to create an identity and the provided `internalId` is already used by another **ACTIVE** identity, the request fails with **409 Conflict**.
- You can handle this error in your integration to surface “duplicate customer” conditions or guide users to reuse the existing identity.


Optionally, you can also:

- Set a friendly `nickName` and `tags`
- Add `validatePayoutRails` to validate the identity against specific payout rails (for example, `US_ACH`, `BR_PIX`, `MX_SPEI`)


**Example: Create an INDIVIDUAL ORIGINATOR**


```bash
curl -X POST "https://{base-url}/v3/identities" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "identityType": "INDIVIDUAL",
    "paymentRole": "ORIGINATOR",
    "internalId": "customer-12345-uuid",
    "nickName": "Alice Sender USD",
    "tags": ["sender", "priority"],
    "validatePayoutRails": ["US_ACH"],
    "individual": {
      "firstName": "Alice",
      "lastName": "Chen",
      "dateOfBirth": "1990-05-14",
      "citizenship": "US",
      "address": {
        "streetAddress": ["123 Main Street"],
        "city": "San Francisco",
        "stateOrProvince": "CA",
        "postalCode": "94105",
        "country": "US"
      }
    }
  }'
```

**Response (201 Created)**


```json
{
  "identityId": "99254c4f-f207-4792-a846-06928825018c",
  "version": "1"
}
```

Deduplication via internalId
If you send another `POST /v3/identities` with the same `internalId` while this identity is still ACTIVE, the service returns:

- **409 Conflict** – indicating that another **ACTIVE** identity already uses that `internalId`.


Handle 409 as a signal that you should look up and reuse the existing identity instead of creating a duplicate.

**Example: Create a BUSINESS BENEFICIARY**


```bash
curl -X POST "https://{base-url}/v3/identities" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "identityType": "BUSINESS",
    "paymentRole": "BENEFICIARY",
    "internalId": "counterparty-7890",
    "nickName": "Widgets Org MX",
    "tags": ["beneficiary", "mx"],
    "validatePayoutRails": ["MX_SPEI"],
    "business": {
      "businessName": "Widgets Org",
      "incorporationCountry": "US",
      "registration": [
        {
          "number": "123ABC",
          "type": "INCORPORATION_CERTIFICATE"
        }
      ],
      "email": "fake@example.com",
      "phone": "+1234567890",
      "address": {
        "streetAddress": ["123 Example MA"],
        "city": "Boston",
        "stateOrProvince": "MS",
        "postalCode": "12345",
        "country": "US"
      }
    }
  }'
```

**Response (201 Created)**


```json
{
  "identityId": "0116bacc-ffbf-4fa2-a29c-ecd9ea346806",
  "version": "1"
}
```

Missing or invalid PII
If any required PII is missing or invalid for the rails in `validatePayoutRails`, the API returns a 400 error describing which fields failed validation.

**Example: Validation failure for missing required fields**

If you try to create an identity with `validatePayoutRails: ["US_ACH"]` but omit required fields like `dateOfBirth` or `identityDocuments`, you'll receive:

**Response (400 Bad Request)**


```json
{
  "code": "VALIDATION_ERROR",
  "title": "Identity validation failed",
  "description": "The identity is missing required fields for the specified payout rails.",
  "details": [
    {
      "field": "individual.dateOfBirth",
      "message": "Date of birth is required for US_ACH beneficiary identities"
    },
    {
      "field": "individual.identityDocuments",
      "message": "At least one identity document (SSN or Tax ID) is required for US_ACH beneficiary identities"
    }
  ]
}
```

To fix this, add the missing fields and retry the request.

## Handling `internalId` conflicts (409 Conflict)

When you create or update an identity with an `internalId` that's already used by another **ACTIVE** identity, the API returns **409 Conflict**.

This is a **deduplication feature** that prevents you from accidentally creating multiple identities for the same customer.

### Example: Duplicate `internalId` on create

**Request:**


```bash
curl -X POST "https://{base-url}/v3/identities" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "identityType": "INDIVIDUAL",
    "paymentRole": "BENEFICIARY",
    "internalId": "customer-12345-uuid",
    "individual": {
      "firstName": "Bob",
      "lastName": "Smith",
      "address": {
        "streetAddress": ["456 Oak Ave"],
        "city": "Austin",
        "stateOrProvince": "TX",
        "postalCode": "78701",
        "country": "US"
      }
    }
  }'
```

**Response (409 Conflict)**


```json
{
  "code": "CONFLICT",
  "title": "Identity conflict",
  "description": "An identity with the provided internalId already exists.",
  "details": [
    {
      "field": "internalId",
      "message": "An ACTIVE identity with internalId 'customer-12345-uuid' already exists (identityId: 99254c4f-f207-4792-a846-06928825018c)"
    }
  ]
}
```

### How to handle 409 Conflict

When you receive a 409 Conflict:

1. **Look up the existing identity** using the `identityId` from the error message
2. **Reuse the existing identity** instead of creating a new one
3. **Update the existing identity** if needed (for example, to add new PII or change `validatePayoutRails`)
4. **Investigate your upstream systems** if you're unexpectedly getting conflicts (this may indicate duplicate customer records in your CRM or database)


**Example: Look up the existing identity**


```bash
curl -X GET "https://{base-url}/v3/identities/99254c4f-f207-4792-a846-06928825018c" \
  -H "Authorization: Bearer <access_token>"
```

If the existing identity is correct, use it for your payment or financial instrument creation. If it needs updates, use `PUT /v3/identities/{identity-id}` to modify it.

### Example: Duplicate `internalId` on update

**Request:**


```bash
curl -X PUT "https://{base-url}/v3/identities/1d927c62-45fa-4f42-b9f8-8210e1a111bb" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "internalId": "customer-99999"
  }'
```

If `customer-99999` is already used by a different ACTIVE identity:

**Response (409 Conflict)**


```json
{
  "code": "CONFLICT",
  "title": "Identity conflict",
  "description": "The provided internalId already exists on a different identity.",
  "details": [
    {
      "field": "internalId",
      "message": "An ACTIVE identity with internalId 'customer-99999' already exists (identityId: 7ea3399c-1234-5678-8d8f-d320ea406630)"
    }
  ]
}
```

### Best practices for `internalId` conflicts

- **Use stable upstream keys** – Use your CRM customer ID, core banking customer ID, or KYC master record ID as `internalId`
- **Implement idempotent create logic** – When creating identities, catch 409 errors and look up the existing identity instead of failing
- **Avoid PII in `internalId`** – Don't use emails, phone numbers, or national IDs directly; use opaque identifiers
- **Monitor conflicts** – Track 409 errors in your logs to identify data quality issues in upstream systems


## Get an identity by ID (and version)

Use `GET /v3/identities/{identity-id}` to retrieve a specific identity.

- If you omit the version query parameter, the latest version is returned.
- To retrieve a specific version, include `?version={version-number}`.


**Example: Get the latest version**


```bash
curl -X GET "https://{base-url}/v3/identities/9d839f58-7fd3-4913-a27e-48d31973d3f9" \
  -H "Authorization: Bearer <access_token>"
```

**Response (200 OK)**


```json
{
  "identityId": "9d839f58-7fd3-4913-a27e-48d31973d3f9",
  "identityState": "ACTIVE",
  "nickName": "nickName",
  "tags": ["tag1"],
  "version": "2",
  "schemaVersion": "1.0.0",
  "createdAt": "2025-10-01T18:46:41.833Z",
  "updatedAt": "2025-10-01T18:46:47.430Z",
  "identityType": "BUSINESS",
  "paymentRole": "BENEFICIARY",
  "internalId": "counterparty-7890",
  "business": {
    "businessName": "Widgets Org",
    "address": {
      "streetAddress": ["123 Example MA"],
      "country": "US",
      "city": "Boston",
      "stateOrProvince": "MS",
      "postalCode": "12345"
    },
    "email": "fake@example.com",
    "phone": "+1234567890",
    "registration": [
      {
        "number": "123ABC",
        "type": "INCORPORATION_CERTIFICATE"
      }
    ],
    "incorporationCountry": "US"
  }
}
```

**Example: Get a specific version**


```bash
curl -X GET "https://{base-url}/v3/identities/9d839f58-7fd3-4913-a27e-48d31973d3f9?version=1" \
  -H "Authorization: Bearer <access_token>"
```

Versioned lookups
Use versioned lookups when you need to:

- Compare changes over time for audit
- Reconcile payments that used an earlier version of an identity


If the identity or version does not exist, the service returns **404 Not Found**.

## Get an identity by internal ID

Use `GET /v3/identities/by-internal-id/{internal-id}` to look up an identity directly by your client-provided `internalId`, without needing to know the server-generated `identityId` first.

This endpoint is especially useful for:

- Resolving the `identityId` of an existing customer identity when you only have your own reference (for example, a CRM customer ID).
- Implementing idempotent create-or-fetch workflows without storing `identityId` separately.
- Handling 409 Conflict responses by immediately looking up the conflicting identity.


**Example: Look up an identity by internal ID**


```bash
curl -X GET "https://{base-url}/v3/identities/by-internal-id/customer-12345-uuid" \
  -H "Authorization: Bearer <access_token>"
```

**Response (200 OK)**


```json
{
  "identityId": "99254c4f-f207-4792-a846-06928825018c",
  "identityState": "ACTIVE",
  "identityType": "INDIVIDUAL",
  "paymentRole": "ORIGINATOR",
  "internalId": "customer-12345-uuid",
  "nickName": "Alice Sender USD",
  "tags": ["sender", "priority"],
  "version": "1",
  "schemaVersion": "1.0.0",
  "createdAt": "2025-10-01T18:46:41.833Z",
  "updatedAt": "2025-10-01T18:46:47.430Z",
  "individual": {
    "firstName": "Alice",
    "lastName": "Chen",
    "dateOfBirth": "1990-05-14",
    "citizenship": "US",
    "address": {
      "streetAddress": ["123 Main Street"],
      "city": "San Francisco",
      "stateOrProvince": "CA",
      "postalCode": "94105",
      "country": "US"
    }
  }
}
```

Only ACTIVE identities
This endpoint returns the identity only if it is currently **ACTIVE**. If the identity exists but has been deactivated or blocked, the service returns **404 Not Found**.

The lookup is scoped to your organization. `internalId` values from other organizations do not conflict.

Error responses:

- **404 Not Found** – no ACTIVE identity with the specified `internalId` exists in your organization


## List identities

Use `GET /v3/identities` to retrieve identities for your organization with optional filters and pagination.

Supported query parameters:

- `payment-role` – filter by payment role (ORIGINATOR or BENEFICIARY)
- `nick-name` – filter by nickname (exact match)
- `limit` – maximum number of identities to return (1–100, default 10)
- `next-token` – opaque token returned from a previous response, used to fetch the next page


**Example: List identities by role**


```bash
curl -X GET "https://{base-url}/v3/identities?payment-role=BENEFICIARY&limit=2" \
  -H "Authorization: Bearer <access_token>"
```

**Response (200 OK)**


```json
{
  "data": [
    {
      "identityId": "0116bacc-ffbf-4fa2-a29c-ecd9ea346806",
      "identityType": "BUSINESS",
      "paymentRole": "BENEFICIARY",
      "createdAt": "2025-10-01T16:14:13.200Z",
      "updatedAt": "2025-10-01T16:14:15.763Z",
      "identityState": "ACTIVE",
      "nickName": "testNickName",
      "tags": ["tag1"],
      "version": 2,
      "schemaVersion": "1.0.0",
      "internalId": "counterparty-7890"
    },
    {
      "identityId": "1d927c62-45fa-4f42-b9f8-8210e1a111bb",
      "identityType": "INDIVIDUAL",
      "paymentRole": "ORIGINATOR",
      "createdAt": "2025-10-01T17:20:05.111Z",
      "updatedAt": "2025-10-01T17:20:06.500Z",
      "identityState": "ACTIVE",
      "nickName": "Alice Originator",
      "tags": ["sender", "priority"],
      "version": 1,
      "schemaVersion": "1.0.0",
      "internalId": "customer-12345-uuid"
    }
  ],
  "nextToken": "eyJrZXkxIjoidmFsdWUifQ=="
}
```

If you receive a `nextToken`, pass it back as `next-token` to fetch the next page:


```bash
curl -X GET "https://{base-url}/v3/identities?payment-role=BENEFICIARY&limit=2&next-token=eyJrZXkxIjoidmFsdWUifQ==" \
  -H "Authorization: Bearer <access_token>"
```

No identities found
If no identities match your filters, the service returns **404 Not Found**.

## Update an identity

Use `PUT /v3/identities/{identity-id}` to update an existing identity.

Each successful update creates a **new identity version**. The `identityId` never changes, and previous versions remain available for retrieval and audit. The request body supports **partial updates**. Only include the fields you want to change.

You can update:

- Metadata: `nickName`, `tags`
- PII: any field within `individual` or `business`
- `internalId` (must remain unique across all ACTIVE identities)
- `validatePayoutRails` (re-validates PII against the specified rails)


`identityType` and `paymentRole` cannot be changed after creation.

For complete examples, error handling, and guidance on updating BLOCKED identities, see [Update an identity](/products/payments-direct-2/api-docs/tutorials/update-an-identity).

## Deactivate an identity

Use `DELETE /v3/identities/{identity-id}` to deactivate an identity.

Deactivation:

- Sets the identity’s state to **DEACTIVATED**
- Deactivates the identity’s associated **financial instruments**
- Prevents any **new payments** from using this identity
- Keeps historical versions available for audit


Permanent
Deactivation is **permanent**; you cannot reactivate a deactivated identity.

**Example: Deactivate an identity**


```bash
curl -X DELETE "https://{base-url}/v3/identities/146f3c51-c313-47ce-b6f2-691c5a238b3e" \
  -H "Authorization: Bearer <access_token>"
```

**Response (204 No Content) – the identity is successfully deactivated.**

Error responses:

- **400 Bad Request** – malformed request or invalid `identityId` format
- **404 Not Found** – identity does not exist
- **422 Unprocessable Entity** – identity is already deactivated or cannot be deactivated (for example, due to internal constraints)


## Error handling and validation

All identity endpoints use a standard error response schema with:

- **code** – machine-readable error code
- **title** – short human-readable summary
- **description** – detailed explanation and remediation hints


Common cases:

- **400** – missing or invalid fields in the request, or identity failing corridor-specific validation rules
- **404** – identity not found (for a specific ID, version, or query)
- **409** – `internalId` conflict (another ACTIVE identity already uses the same `internalId`)
- **422** – invalid lifecycle transition (for example, attempting to deactivate an already deactivated identity)
- **500** – internal processing error; retry or contact support if persistent


Refer to the general [Error handling](/products/payments-direct-2/api-docs/error-codes/api-errors) section of the API docs for the full list of error codes.

## Next steps

- For a deeper explanation of the identity model, see [Payment identities](/products/payments-direct-2/api-docs/concepts/payment-identities).
- For full update examples and guidance on immutable versioning, payout rail validation, and BLOCKED identity reactivation, see [Update an identity](/products/payments-direct-2/api-docs/tutorials/update-an-identity).
- To learn how to attach payout account details to identities (bank accounts, wallets, local payout rails), continue with [Create and manage financial instruments](/products/payments-direct-2/api-docs/tutorials/create-and-manage-financial-instruments).