Update Associated Person
curl --request PATCH \
--url https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id} \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"role": "<string>",
"additional_roles": [
"<string>"
],
"residential_address": {},
"identifying_information": [
{}
]
}
'import requests
url = "https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id}"
payload = {
"role": "<string>",
"additional_roles": ["<string>"],
"residential_address": {},
"identifying_information": [{}]
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
role: '<string>',
additional_roles: ['<string>'],
residential_address: {},
identifying_information: [{}]
})
};
fetch('https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'role' => '<string>',
'additional_roles' => [
'<string>'
],
'residential_address' => [
],
'identifying_information' => [
[
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id}"
payload := strings.NewReader("{\n \"role\": \"<string>\",\n \"additional_roles\": [\n \"<string>\"\n ],\n \"residential_address\": {},\n \"identifying_information\": [\n {}\n ]\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id}")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"role\": \"<string>\",\n \"additional_roles\": [\n \"<string>\"\n ],\n \"residential_address\": {},\n \"identifying_information\": [\n {}\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"role\": \"<string>\",\n \"additional_roles\": [\n \"<string>\"\n ],\n \"residential_address\": {},\n \"identifying_information\": [\n {}\n ]\n}"
response = http.request(request)
puts response.read_bodyAssociated Persons
Update Associated Person
Correct or complete the details of a beneficial owner, director or control person
PATCH
/
v1
/
accounts
/
{account_id}
/
associated-persons
/
{person_id}
Update Associated Person
curl --request PATCH \
--url https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id} \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"role": "<string>",
"additional_roles": [
"<string>"
],
"residential_address": {},
"identifying_information": [
{}
]
}
'import requests
url = "https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id}"
payload = {
"role": "<string>",
"additional_roles": ["<string>"],
"residential_address": {},
"identifying_information": [{}]
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
role: '<string>',
additional_roles: ['<string>'],
residential_address: {},
identifying_information: [{}]
})
};
fetch('https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'role' => '<string>',
'additional_roles' => [
'<string>'
],
'residential_address' => [
],
'identifying_information' => [
[
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id}"
payload := strings.NewReader("{\n \"role\": \"<string>\",\n \"additional_roles\": [\n \"<string>\"\n ],\n \"residential_address\": {},\n \"identifying_information\": [\n {}\n ]\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id}")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"role\": \"<string>\",\n \"additional_roles\": [\n \"<string>\"\n ],\n \"residential_address\": {},\n \"identifying_information\": [\n {}\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hopnow.io/v1/accounts/{account_id}/associated-persons/{person_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"role\": \"<string>\",\n \"additional_roles\": [\n \"<string>\"\n ],\n \"residential_address\": {},\n \"identifying_information\": [\n {}\n ]\n}"
response = http.request(request)
puts response.read_bodyUpdates one associated person. Send only the fields that change; anything you omit keeps its stored value.
Requires an API key with
Omitting a field leaves it untouched. Sending it explicitly as
write access on the accounts scope. Like creation, this endpoint works only on business accounts.
The patch is merged onto the stored record and the merged result is revalidated in full, so a change can be rejected because of a field you did not send: changing role to ultimate_beneficial_owner without an ownership_percentage already on record fails.
Saving a change re-triggers virtual account group onboarding, so a requirement that was waiting on this person’s details clears on its own. A patch that changes nothing is a no-op and returns the record unchanged.
Path Parameters
string
required
The account’s external ID (starts with
acct_)string
required
The associated person’s external ID (starts with
aap_). One belonging to another account returns 404.Request Body
Every field is optional and carries the same type, format and constraint as on Create Associated Person:role, additional_roles, is_signer, first_name, last_name, middle_name, email, phone_number, date_of_birth, country_code, nationality, birth_country_code, tax_identification_number, tax_residence_country_code, position, ownership_percentage, ownership_type, residential_address, identifying_information.
string
May be changed between
ultimate_beneficial_owner, director, control_person and account_representative. account_holder is reserved and rejected. A person whose role is account_holder cannot be updated through this endpoint.string[]
Replaced wholesale. Send the full list of other roles, or
[] to remove them all; null is rejected.object
Replaced wholesale, not merged field by field. Send the complete address.
object[]
Replaced wholesale. Send the full list you want stored, not just the new entry.
null clears it, which works for the optional fields: middle_name, phone_number, position, ownership_type, ownership_percentage (clear ownership_type with it), and the tax pair. Sending null for a field that is required on create fails with 422.
Response
Returns200 with the updated person, in the same shape as Create Associated Person.
Errors
| Status | code | When |
|---|---|---|
422 | VALIDATION_ERROR | The body carries a field this endpoint does not accept; validation_errors names it |
422 | BUSINESS_RULE_VIOLATION | The account is not a business account, or the person holds the reserved account_holder role |
422 | BUSINESS_RULE_VIOLATION | role or additional_roles is set to include account_holder |
422 | VALIDATION_ERROR | The merged record fails validation: details.errors lists each offending field. Common causes: role changed to ultimate_beneficial_owner with no ownership_percentage on record, role changed to a value already in additional_roles, an ownership_type left without its percentage, or a tax number left without its residence country |
404 | RESOURCE_NOT_FOUND | account_id or person_id is unknown, or the person belongs to a different account |
Request Example
{
"email": "maya.chen@acme.example",
"ownership_percentage": "55.00",
"ownership_type": "direct",
"is_signer": true
}
Response Example
{
"id": "aap_3f8k1m5p9r2t6v4x7z0b3n5q",
"object": "associated_person",
"account_id": "acct_9f2a7c1e6a2b4f3c9b8d2e5a",
"role": "ultimate_beneficial_owner",
"additional_roles": ["control_person"],
"is_signer": true,
"first_name": "Maya",
"last_name": "Chen",
"middle_name": null,
"email": "maya.chen@acme.example",
"phone_number": "+6581234567",
"date_of_birth": "1985-06-21",
"country_code": "SG",
"nationality": "SG",
"birth_country_code": "SG",
"tax_identification_number": "S1234567D",
"tax_residence_country_code": "SG",
"position": "Director",
"ownership_percentage": "55.00",
"ownership_type": "direct",
"residential_address": {
"address_line": "10 Marina Boulevard, #23-01",
"city": "Singapore",
"region": "Central Region",
"postal_code": "018983",
"country": "SG"
},
"created": "2026-09-16T03:14:01Z",
"updated": "2026-09-16T05:02:44Z",
"created_by": "ak_9a3yfd2ro3jeoqqalbx8jew1",
"updated_by": "ak_9a3yfd2ro3jeoqqalbx8jew1"
}