> Toden documentation. For the full index fetch /llms.txt. Every page is available as Markdown by appending .md to its URL. Identity verification uses /api/kyc, Risk Intelligence uses /api/v1; they are separate contracts with different error shapes. Never invent an endpoint, field or enum value: read the API reference page for the operation first.

# Update part of an entity

PATCH https://sandbox-api.toden.co/api/identity/entities/{externalUserId}
Content-Type: application/json

Changes only the fields you send. An absent key is left alone and `null` clears a nullable field. `externalUserId` and `type` cannot be changed, and unknown keys are rejected.

On an entity verified by Toden, `fullName`, `dateOfBirth`, `nationality`, `idType`, `idNumber` and `kycProvenance` are locked; a request touching any of them is refused and names the fields. Changing the verified document on an externally verified entity runs identity resolution again.

Side effects: when at least one field changed, the `entity.updated` webhook fires with the names of the changed fields (never their values) and the call is recorded in your audit log. A changed wallet set queues wallet screening again.

**Error codes**

| `error` | Status | Meaning |
|---|---|---|
| `invalid_request` | 400 | The body failed validation. `details` carries the field-level breakdown. |
| `field_not_editable` | 400 | The request tried to change identity facts on an entity verified by Toden. `fields` lists them. |
| `entity_not_found` | 404 | No live entity has this reference in this organisation and environment. |
| `secret_key_required` | 403 | A publishable key was used. This API accepts secret keys only. |
| `environment_mismatch` | 403 | The key belongs to an environment this host does not serve. |
| `rate_limited` | 429 | The request rate limit for this key was reached. Retry after a short wait. |
| `rate_limit_store_unavailable` | 503 | Rate limiting is temporarily unavailable. Retry shortly. |

A missing or unknown key returns `401` with a plain sentence in `error` rather than a code.

Requires a secret (`sk_`) key. A publishable key receives `403 secret_key_required`.

Reference: https://docs.toden.co/api-reference/verification/entities/update-entity

## Authentication

- `Authorization` header (bearer token, required) — Send the API key as a Bearer token. Each operation states whether a publishable key is enough or a secret key is required.

## Servers

- `https://sandbox-api.toden.co` (Sandbox, default)
- `https://api.toden.co` (Production)

## Request

### Path parameters

- `externalUserId` (string, required) — Your own stable reference for the person or business, as sent when the entity was registered.

### Body (application/json)

This endpoint expects an EntityUpdateRequest.

- `metadata` (map from string to any, optional) — Your own correlation fields for the entity. Sending it replaces the stored object. At most 16 KiB of JSON, four levels deep, 100 keys in total, 50 keys per object, 20 items per array, keys up to 64 characters and strings up to 1,000 characters.
- `kycSource` (string, optional, nullable) — A label for where the external verification came from, such as the name of your previous provider.
- `kycProvenance` (enum, optional) — Move the entity between unverified and externally verified. `EXTERNAL_VERIFIED` requires a non-empty `kycSource` in the same request.
  - Allowed values: `EXTERNAL_VERIFIED`, `UNVERIFIED`
- `profile` (EntityProfilePatch, optional) — The profile fields to change. An absent key is left alone and `null` clears a nullable field. Unknown keys are rejected.

## Response

### 200

Success

- `entityId` (string, required) — The Toden identifier of the entity.
- `externalUserId` (string, required) — Your reference, echoed back.
- `identityId` (string, required, nullable) — The identifier of the shared identity the entity is linked to, or `null` when it has not been resolved to one.
- `changedFields` (list of string, required) — The names of the fields that actually changed. Empty when the request changed nothing.
- `screening` (EntityScreeningEnrolment, required, nullable) — Set only when the wallet set changed and wallet screening was queued again; otherwise `null`.

## Errors

### 400 Bad Request Error

The request body, query or path was invalid.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

### 401 Unauthorized Error

The Authorization header is missing, or the API key is unknown or revoked.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

### 403 Forbidden Error

The key may not call this endpoint, or the organisation may not use this feature.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

### 404 Not Found Error

The resource does not exist for this organisation and environment.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

### 429 Too Many Requests Error

A rate limit was reached. Retry after a short wait.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

### 503 Service Unavailable Error

The service is temporarily unavailable.

- `error` (string, required) — A stable machine-readable code such as `invalid_input`. Authentication failures (401) carry a short sentence here instead of a code.
- `message` (string, optional) — A human-readable explanation, safe to log. The wording may change.

## Types

### EntityProfilePatch

The profile fields to change. An absent key is left alone and `null` clears a nullable field. Unknown keys are rejected.

- `fullName` (string, optional, nullable) — The full name of the person, or the registered name of the business.
- `dateOfBirth` (string, optional, nullable) — The date of birth, as a string such as `1990-01-01`.
- `nationality` (string, optional, nullable) — The nationality or country, ideally as an ISO 3166-1 alpha-2 code.
- `idType` (string, optional, nullable) — The type of identity document the number belongs to, such as `bvn` or `passport`.
- `idNumber` (string, optional, nullable) — The identity document number. Together with `idType` it drives identity resolution on a verified entity.
- `address` (map from string to any, optional, nullable) — The address you hold, as free-form JSON. It is recorded as declared address evidence for the entity.
- `email` (string, optional, nullable) — An email address you hold for the entity. Recorded as declared contact evidence.
- `phone` (string, optional, nullable) — A phone number you hold for the entity. Recorded as declared contact evidence.
- `declaredMonthlyVolume` (double, optional, nullable) — The monthly transaction volume the customer declared.
- `declaredVolumeCurrency` (string, optional, nullable) — The ISO 4217 currency code the declared volume is stated in. Lower case input is upper-cased.
- `declaredTxnTypes` (list of string, optional) — The kinds of transaction the customer declared they expect to make.
- `walletAddresses` (list of EntityProfilePatchWalletAddressesItems, optional) — Crypto wallet addresses attached to the entity. Omit the key to leave the stored set unchanged; send an array (including an empty one) to replace it. Wallets are risk attributes for wallet screening, never identity keys.

### EntityScreeningEnrolment

- `status` (enum, required) — Always `ENQUEUED`: the entity is enrolled and its screens are queued to run.
  - Allowed values: `ENQUEUED`
- `types` (list of enum, required) — Every screening type the entity is enrolled in after this call. `PEP` is skipped for businesses and `WALLET` applies only when a wallet address is attached.
  - Allowed values: `SANCTIONS`, `PEP`, `ADVERSE_MEDIA`, `WALLET`

### EntityProfilePatchWalletAddressesItems

- `address` (string, required) — The wallet address.
- `chain` (string, optional) — The network the address belongs to, in your own naming.
- `label` (string, optional) — Your own label for the wallet.

## Examples

**Request**

```json
{
  "metadata": {
    "accountId": "acc_7732",
    "tier": "gold"
  },
  "profile": {
    "nationality": "GH",
    "declaredMonthlyVolume": 750000
  }
}
```

**Response**

```json
{
  "entityId": "ent_01j9x2k4m8",
  "externalUserId": "user_42",
  "identityId": "myz_01j9x2k4m8",
  "changedFields": [
    "metadata",
    "nationality",
    "declaredMonthlyVolume"
  ],
  "screening": null
}
```

**SDK Code**

```python
import requests

url = "https://sandbox-api.toden.co/api/identity/entities/externalUserId"

payload = {
    "metadata": {
        "accountId": "acc_7732",
        "tier": "gold"
    },
    "profile": {
        "nationality": "GH",
        "declaredMonthlyVolume": 750000
    }
}
headers = {
    "Authorization": "Bearer <apiKey>",
    "Content-Type": "application/json"
}

response = requests.patch(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://sandbox-api.toden.co/api/identity/entities/externalUserId';
const options = {
  method: 'PATCH',
  headers: {Authorization: 'Bearer <apiKey>', 'Content-Type': 'application/json'},
  body: '{"metadata":{"accountId":"acc_7732","tier":"gold"},"profile":{"nationality":"GH","declaredMonthlyVolume":750000}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://sandbox-api.toden.co/api/identity/entities/externalUserId"

	payload := strings.NewReader("{\n  \"metadata\": {\n    \"accountId\": \"acc_7732\",\n    \"tier\": \"gold\"\n  },\n  \"profile\": {\n    \"nationality\": \"GH\",\n    \"declaredMonthlyVolume\": 750000\n  }\n}")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("Authorization", "Bearer <apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://sandbox-api.toden.co/api/identity/entities/externalUserId")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"metadata\": {\n    \"accountId\": \"acc_7732\",\n    \"tier\": \"gold\"\n  },\n  \"profile\": {\n    \"nationality\": \"GH\",\n    \"declaredMonthlyVolume\": 750000\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.patch("https://sandbox-api.toden.co/api/identity/entities/externalUserId")
  .header("Authorization", "Bearer <apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"metadata\": {\n    \"accountId\": \"acc_7732\",\n    \"tier\": \"gold\"\n  },\n  \"profile\": {\n    \"nationality\": \"GH\",\n    \"declaredMonthlyVolume\": 750000\n  }\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://sandbox-api.toden.co/api/identity/entities/externalUserId', [
  'body' => '{
  "metadata": {
    "accountId": "acc_7732",
    "tier": "gold"
  },
  "profile": {
    "nationality": "GH",
    "declaredMonthlyVolume": 750000
  }
}',
  'headers' => [
    'Authorization' => 'Bearer <apiKey>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://sandbox-api.toden.co/api/identity/entities/externalUserId");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"metadata\": {\n    \"accountId\": \"acc_7732\",\n    \"tier\": \"gold\"\n  },\n  \"profile\": {\n    \"nationality\": \"GH\",\n    \"declaredMonthlyVolume\": 750000\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "metadata": [
    "accountId": "acc_7732",
    "tier": "gold"
  ],
  "profile": [
    "nationality": "GH",
    "declaredMonthlyVolume": 750000
  ]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://sandbox-api.toden.co/api/identity/entities/externalUserId")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```