Create Associated Person
curl --request POST \
--url https://api.hopnow.io/v1/accounts/{account_id}/associated-persons \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"role": "<string>",
"additional_roles": [
"<string>"
],
"is_signer": true,
"first_name": "<string>",
"last_name": "<string>",
"middle_name": "<string>",
"email": "<string>",
"phone_number": "<string>",
"date_of_birth": "<string>",
"country_code": "<string>",
"nationality": "<string>",
"birth_country_code": "<string>",
"tax_identification_number": "<string>",
"tax_residence_country_code": "<string>",
"position": "<string>",
"ownership_percentage": "<string>",
"ownership_type": "<string>",
"residential_address": {},
"identifying_information": [
{}
]
}
'import requests
url = "https://api.hopnow.io/v1/accounts/{account_id}/associated-persons"
payload = {
"role": "<string>",
"additional_roles": ["<string>"],
"is_signer": True,
"first_name": "<string>",
"last_name": "<string>",
"middle_name": "<string>",
"email": "<string>",
"phone_number": "<string>",
"date_of_birth": "<string>",
"country_code": "<string>",
"nationality": "<string>",
"birth_country_code": "<string>",
"tax_identification_number": "<string>",
"tax_residence_country_code": "<string>",
"position": "<string>",
"ownership_percentage": "<string>",
"ownership_type": "<string>",
"residential_address": {},
"identifying_information": [{}]
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
role: '<string>',
additional_roles: ['<string>'],
is_signer: true,
first_name: '<string>',
last_name: '<string>',
middle_name: '<string>',
email: '<string>',
phone_number: '<string>',
date_of_birth: '<string>',
country_code: '<string>',
nationality: '<string>',
birth_country_code: '<string>',
tax_identification_number: '<string>',
tax_residence_country_code: '<string>',
position: '<string>',
ownership_percentage: '<string>',
ownership_type: '<string>',
residential_address: {},
identifying_information: [{}]
})
};
fetch('https://api.hopnow.io/v1/accounts/{account_id}/associated-persons', 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",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'role' => '<string>',
'additional_roles' => [
'<string>'
],
'is_signer' => true,
'first_name' => '<string>',
'last_name' => '<string>',
'middle_name' => '<string>',
'email' => '<string>',
'phone_number' => '<string>',
'date_of_birth' => '<string>',
'country_code' => '<string>',
'nationality' => '<string>',
'birth_country_code' => '<string>',
'tax_identification_number' => '<string>',
'tax_residence_country_code' => '<string>',
'position' => '<string>',
'ownership_percentage' => '<string>',
'ownership_type' => '<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"
payload := strings.NewReader("{\n \"role\": \"<string>\",\n \"additional_roles\": [\n \"<string>\"\n ],\n \"is_signer\": true,\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"middle_name\": \"<string>\",\n \"email\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"date_of_birth\": \"<string>\",\n \"country_code\": \"<string>\",\n \"nationality\": \"<string>\",\n \"birth_country_code\": \"<string>\",\n \"tax_identification_number\": \"<string>\",\n \"tax_residence_country_code\": \"<string>\",\n \"position\": \"<string>\",\n \"ownership_percentage\": \"<string>\",\n \"ownership_type\": \"<string>\",\n \"residential_address\": {},\n \"identifying_information\": [\n {}\n ]\n}")
req, _ := http.NewRequest("POST", 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.post("https://api.hopnow.io/v1/accounts/{account_id}/associated-persons")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"role\": \"<string>\",\n \"additional_roles\": [\n \"<string>\"\n ],\n \"is_signer\": true,\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"middle_name\": \"<string>\",\n \"email\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"date_of_birth\": \"<string>\",\n \"country_code\": \"<string>\",\n \"nationality\": \"<string>\",\n \"birth_country_code\": \"<string>\",\n \"tax_identification_number\": \"<string>\",\n \"tax_residence_country_code\": \"<string>\",\n \"position\": \"<string>\",\n \"ownership_percentage\": \"<string>\",\n \"ownership_type\": \"<string>\",\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")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.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 \"is_signer\": true,\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"middle_name\": \"<string>\",\n \"email\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"date_of_birth\": \"<string>\",\n \"country_code\": \"<string>\",\n \"nationality\": \"<string>\",\n \"birth_country_code\": \"<string>\",\n \"tax_identification_number\": \"<string>\",\n \"tax_residence_country_code\": \"<string>\",\n \"position\": \"<string>\",\n \"ownership_percentage\": \"<string>\",\n \"ownership_type\": \"<string>\",\n \"residential_address\": {},\n \"identifying_information\": [\n {}\n ]\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"object": "<string>",
"account_id": "<string>",
"role": "<string>",
"additional_roles": [
"<string>"
],
"is_signer": true,
"first_name": "<string>",
"last_name": "<string>",
"middle_name": "<string>",
"email": "<string>",
"phone_number": "<string>",
"date_of_birth": "<string>",
"country_code": "<string>",
"nationality": "<string>",
"birth_country_code": "<string>",
"tax_identification_number": "<string>",
"tax_residence_country_code": "<string>",
"position": "<string>",
"ownership_percentage": "<string>",
"ownership_type": "<string>",
"residential_address": {},
"created": "<string>",
"updated": "<string>",
"created_by": "<string>",
"updated_by": "<string>"
}Associated Persons
Create Associated Person
Add a beneficial owner, director or control person to a business account
POST
/
v1
/
accounts
/
{account_id}
/
associated-persons
Create Associated Person
curl --request POST \
--url https://api.hopnow.io/v1/accounts/{account_id}/associated-persons \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"role": "<string>",
"additional_roles": [
"<string>"
],
"is_signer": true,
"first_name": "<string>",
"last_name": "<string>",
"middle_name": "<string>",
"email": "<string>",
"phone_number": "<string>",
"date_of_birth": "<string>",
"country_code": "<string>",
"nationality": "<string>",
"birth_country_code": "<string>",
"tax_identification_number": "<string>",
"tax_residence_country_code": "<string>",
"position": "<string>",
"ownership_percentage": "<string>",
"ownership_type": "<string>",
"residential_address": {},
"identifying_information": [
{}
]
}
'import requests
url = "https://api.hopnow.io/v1/accounts/{account_id}/associated-persons"
payload = {
"role": "<string>",
"additional_roles": ["<string>"],
"is_signer": True,
"first_name": "<string>",
"last_name": "<string>",
"middle_name": "<string>",
"email": "<string>",
"phone_number": "<string>",
"date_of_birth": "<string>",
"country_code": "<string>",
"nationality": "<string>",
"birth_country_code": "<string>",
"tax_identification_number": "<string>",
"tax_residence_country_code": "<string>",
"position": "<string>",
"ownership_percentage": "<string>",
"ownership_type": "<string>",
"residential_address": {},
"identifying_information": [{}]
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
role: '<string>',
additional_roles: ['<string>'],
is_signer: true,
first_name: '<string>',
last_name: '<string>',
middle_name: '<string>',
email: '<string>',
phone_number: '<string>',
date_of_birth: '<string>',
country_code: '<string>',
nationality: '<string>',
birth_country_code: '<string>',
tax_identification_number: '<string>',
tax_residence_country_code: '<string>',
position: '<string>',
ownership_percentage: '<string>',
ownership_type: '<string>',
residential_address: {},
identifying_information: [{}]
})
};
fetch('https://api.hopnow.io/v1/accounts/{account_id}/associated-persons', 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",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'role' => '<string>',
'additional_roles' => [
'<string>'
],
'is_signer' => true,
'first_name' => '<string>',
'last_name' => '<string>',
'middle_name' => '<string>',
'email' => '<string>',
'phone_number' => '<string>',
'date_of_birth' => '<string>',
'country_code' => '<string>',
'nationality' => '<string>',
'birth_country_code' => '<string>',
'tax_identification_number' => '<string>',
'tax_residence_country_code' => '<string>',
'position' => '<string>',
'ownership_percentage' => '<string>',
'ownership_type' => '<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"
payload := strings.NewReader("{\n \"role\": \"<string>\",\n \"additional_roles\": [\n \"<string>\"\n ],\n \"is_signer\": true,\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"middle_name\": \"<string>\",\n \"email\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"date_of_birth\": \"<string>\",\n \"country_code\": \"<string>\",\n \"nationality\": \"<string>\",\n \"birth_country_code\": \"<string>\",\n \"tax_identification_number\": \"<string>\",\n \"tax_residence_country_code\": \"<string>\",\n \"position\": \"<string>\",\n \"ownership_percentage\": \"<string>\",\n \"ownership_type\": \"<string>\",\n \"residential_address\": {},\n \"identifying_information\": [\n {}\n ]\n}")
req, _ := http.NewRequest("POST", 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.post("https://api.hopnow.io/v1/accounts/{account_id}/associated-persons")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"role\": \"<string>\",\n \"additional_roles\": [\n \"<string>\"\n ],\n \"is_signer\": true,\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"middle_name\": \"<string>\",\n \"email\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"date_of_birth\": \"<string>\",\n \"country_code\": \"<string>\",\n \"nationality\": \"<string>\",\n \"birth_country_code\": \"<string>\",\n \"tax_identification_number\": \"<string>\",\n \"tax_residence_country_code\": \"<string>\",\n \"position\": \"<string>\",\n \"ownership_percentage\": \"<string>\",\n \"ownership_type\": \"<string>\",\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")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.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 \"is_signer\": true,\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"middle_name\": \"<string>\",\n \"email\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"date_of_birth\": \"<string>\",\n \"country_code\": \"<string>\",\n \"nationality\": \"<string>\",\n \"birth_country_code\": \"<string>\",\n \"tax_identification_number\": \"<string>\",\n \"tax_residence_country_code\": \"<string>\",\n \"position\": \"<string>\",\n \"ownership_percentage\": \"<string>\",\n \"ownership_type\": \"<string>\",\n \"residential_address\": {},\n \"identifying_information\": [\n {}\n ]\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"object": "<string>",
"account_id": "<string>",
"role": "<string>",
"additional_roles": [
"<string>"
],
"is_signer": true,
"first_name": "<string>",
"last_name": "<string>",
"middle_name": "<string>",
"email": "<string>",
"phone_number": "<string>",
"date_of_birth": "<string>",
"country_code": "<string>",
"nationality": "<string>",
"birth_country_code": "<string>",
"tax_identification_number": "<string>",
"tax_residence_country_code": "<string>",
"position": "<string>",
"ownership_percentage": "<string>",
"ownership_type": "<string>",
"residential_address": {},
"created": "<string>",
"updated": "<string>",
"created_by": "<string>",
"updated_by": "<string>"
}An associated person is a natural person behind a business entity: an ultimate beneficial owner, a director, or someone who exercises control. Virtual account onboarding needs them for two reasons: the provider verifies each person, and one of them signs the provider agreements.
Associated persons exist only on business accounts: set the account’s identity to
type: "business" through Update Account first, or this endpoint returns 422 BUSINESS_RULE_VIOLATION.
Requires an API key with write access on the accounts scope.
Who signs the agreements
Setis_signer: true on at least one person. A business virtual account group cannot complete its agreements without one: Submit Agreement Actions requires a signer_id, and the person it names must carry this flag. Setting it on more than one person is allowed. You choose which one signs at the time.
Path Parameters
string
required
The account’s external ID (starts with
acct_)Request Body
string
required
The person’s role on the entity:
ultimate_beneficial_owner, director, control_person or account_representative. account_holder is reserved for Hop’s own use and is rejected here.Hop submits exactly the roles you declare. It does not add one for you: a large owner is not treated as a control person, and a signer is not treated as an account representative.string[]
default:"[]"
Other roles the same person holds, from the same values as
role, for example an owner who is also the control person. Must not repeat role or list a value twice.boolean
default:"false"
Whether this person may sign agreements on behalf of the entity. It gives signing authority only; declare an account representative with the
account_representative role.string
required
Max 100 characters.
string
required
Max 100 characters.
string
Max 100 characters.
string
required
Contact email for the person. Must be a valid address.
string
E.164 format, for example
+6581234567. Max 20 characters.string
required
ISO 8601 date, for example
1985-06-21.string
required
ISO 3166-1 alpha-2 country of residence.
string
required
ISO 3166-1 alpha-2 nationality.
string
required
ISO 3166-1 alpha-2 country of birth.
string
Tax identification number, 1 to 255 characters. Must be sent together with
tax_residence_country_code. Supplying one without the other is rejected.string
ISO 3166-1 alpha-2 country of tax residence. Paired with
tax_identification_number, as above.string
Position held at the business, for example
Director. Max 255 characters.string
Ownership share as a percentage, from 0 to 100, at most 5 digits with 2 decimal places. Required when
role or additional_roles includes ultimate_beneficial_owner, optional otherwise. A value outside 0 to 100 returns 422.string
direct or indirect: whether the share is held directly or through other entities. Only accepted together with ownership_percentage. When a percentage is set, the type is needed before verification can complete.object
required
The person’s home address. Fields:
address_line and city required, country required as an ISO 3166-1 alpha-2 code, region and postal_code optional. The provider may later require postal_code, so supply it when the country has one.object[]
default:"[]"
Identity document reference numbers. Each entry carries
type (for example passport, national_id, drivers_license, max 100 characters), number (max 100 characters) and an optional issuing_country as an alpha-2 code.Reference numbers only: document files go through Upload Document against the requirement that asks for them.identifying_information is stored for submission to the provider and is not returned on any response.
Response
Returns201 with the created person.
string
Associated person identifier (starts with
aap_), and the value to pass as signer_id when responding to agreements.string
Always returns
"associated_person"string
Parent account ID
string
ultimate_beneficial_owner, director, control_person or account_representativestring[]
Other roles the person holds. Empty when they hold only
roleboolean
Whether the person may be named as
signer_id on an agreement responsestring
First name
string
Last name
string
Middle name, or
nullstring
Contact email, or
nullstring
Contact phone in E.164, or
nullstring
ISO 8601 date, or
nullstring
Country of residence, alpha-2
string
Nationality, alpha-2, or
nullstring
Country of birth, alpha-2, or
nullstring
Tax identification number, or
nullstring
Country of tax residence, alpha-2, or
nullstring
Position at the business, or
nullstring
Ownership share as a percentage, or
nullstring
direct or indirect, or nullobject
Residential address, or
nullstring
ISO 8601 timestamp when created
string
ISO 8601 timestamp when last updated
string
ID of the API key that created the person (starts with
ak_)string
Actor that last updated the person, or
nullErrors
| Status | code | When |
|---|---|---|
422 | BUSINESS_RULE_VIOLATION | The account is not a business account. details.account_identity_type carries what it is, or null when no identity has been set yet |
422 | BUSINESS_RULE_VIOLATION | role or additional_roles includes account_holder, which is reserved |
422 | VALIDATION_ERROR | A field is missing or malformed: ownership_percentage absent for an ultimate beneficial owner, ownership_type without ownership_percentage, additional_roles repeating a role, tax_identification_number without tax_residence_country_code, a country code that is not alpha-2, a phone that is not E.164, or an invalid email |
403 | ACCESS_DENIED | The account exists but belongs to another organization |
404 | RESOURCE_NOT_FOUND | account_id is unknown or deleted |
Request Example
{
"role": "ultimate_beneficial_owner",
"additional_roles": ["control_person"],
"is_signer": true,
"first_name": "Maya",
"last_name": "Chen",
"email": "maya@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": "60.00",
"ownership_type": "direct",
"residential_address": {
"address_line": "10 Marina Boulevard, #23-01",
"city": "Singapore",
"region": "Central Region",
"postal_code": "018983",
"country": "SG"
},
"identifying_information": [
{"type": "passport", "number": "E1234567A", "issuing_country": "SG"}
]
}
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@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": "60.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-16T03:14:01Z",
"created_by": "ak_9a3yfd2ro3jeoqqalbx8jew1",
"updated_by": null
}