Links

Creating legal persons as investors

This guide shows you how to create an investor account for a legal person via the API including information about the identity verification process.
In the course of this guide, we will create a simple script that shows the basic interactions with the API. At the end of this guide, you will also find the complete script for you to copy, paste and try out. Remember to insert your account specific information before running the script.

Getting started

First we are going to import the packages that we need and set up some basic variables. The client ID and client secret will be provided to you.
import binascii
import os
import requests
URL = 'https://demo.cashlink.de/api/external'
CLIENT_ID = '<your client ID>'
CLIENT_SECRET = '<your client secret>'
WALLET_ADDRESS = '<wallet address of the investor>'
Now we write a helper function that follows the OAuth protocol to retrieve an access token that we will use to authenticate the API calls.
def authenticate():
r = requests.post(f'{URL}/oauth/token', data={
'grant_type': 'client_credentials',
'scope': 'investors:write investors:read investments:write investments:read documents:write',
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET
})
assert r.status_code == 200, r.content
return r.json()['access_token']
We add an additional helper function that with every call creates a random idempotency key. In your application, you would use an idempotency key that correlates to the internal object on your side.
def make_key():
return binascii.hexlify(os.urandom(8)).decode()

Create an investor account

As we now can access the API we use it to create an investor. Note that we first need to upload the documents needed for the legal person.
if __name__ == '__main__':
headers = {
'Authorization': f'Bearer {authenticate()}',
}
# Upload documents to verify the investor's identity
r = requests.post(
f'{URL}/v2/documents/',
files={'file': ('commercial-register.pdf', b'ASD', 'application/pdf')},
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content
identity_proof_doc_id = r.json()['id']
r = requests.post(
f'{URL}/v2/documents/',
files={'file': ('shareholder-list.pdf', b'ASD', 'application/pdf')},
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content
structure_proof_doc_id = r.json()['id']
# Create investor using the uploaded files
r = requests.post(
f'{URL}/v2/investors/',
json={
'natural_person': {
'salutation': 'MR',
'forename': 'Peter',
'surname': 'Parker',
'birth_date': '2020-08-10',
'birth_place': 'New York',
'citizenship': 'DEU',
'street': 'Hauptstr. 37',
'city': 'Frankfurt',
'zip': '60316',
'country': 'DEU',
'phone': '+49 123 456 789',
'occupation': 'Apprentice',
'educational_qualification': 'Secondary school diploma'
},
'legal_person': {
'company_name': 'Test GmbH',
'street': 'Teststr. 80',
'city': 'Test',
'zip': '111111',
'country': 'DEU',
'commercial_register_number': 'HRB 12344',
'commercial_register': 'Amtsgericht München',
'company_identity_proof_id': identity_proof_doc_id,
'company_structure_proof_id': structure_proof_doc_id
},
'bank_account': {
'account_holder': 'Peter Parker',
'iban': 'DE53500105173569146251',
'bic': 'GENODEF1ERG',
'bank': 'Commerzbank Frankfurt',
'country': 'DEU',
'currency': 'EUR'
},
'is_beneficiary': True,
'pep_status': False,
'account_setup_accepted_at': '2021-05-06T07:14:13.630308+00:00',
},
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content
investor_id = r.json()['id']

Adding an identification

We also add an identification to the investor. The identification needs a protocol document that we first have to upload.
# Upload the legitimation protocol document to reference it later
r = requests.post(
f'{URL}/v2/documents/',
files={'file': ('protocol.pdf', b'ASD', 'application/pdf')},
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content
doc_id = r.json()['id']
# Create the identification for the investor using the uploaded file
r = requests.post(
f'{URL}/v2/investors/{investor_id}/identifications/',
json={
'document_type': 'PASSPORT',
'document_id': 'ID123456',
'document_issuer': 'Buergeramt Frankfurt',
'document_valid_from': '2020-08-10',
'document_valid_to': '2025-08-10',
'identity_provider': 'POSTIDENT',
'legitimation_process_id': 'string',
'legitimation_protocol_id': doc_id,
'verified_at': '2020-08-10T14:06:22.134Z'
},
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content

Adding benefitting persons

For the legal person to successfully finish the KYC/AML process, you need to provide information about the benefitting persons of the investor via the API.
# Add benefiting persons to the investor
r = requests.post(
f'{URL}/v2/investors/{investor_id}/benefiting-persons/',
json=
[
{
'salutation': 'MR',
'forename': 'Petra',
'surname': 'Parker',
'citizenship': 'AFG',
'birth_date': '2000-11-02',
'birth_place': 'Frankfurt',
'street': 'Teststr. 10',
'city': 'Frankfurt',
'zip': '60316',
'country': 'DEU',
'phone': '+49 123 456 789',
'occupation': 'Apprentice',
'educational_qualification': 'Secondary school diploma'
}
],
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content

Create wallet for investor

We can now create a wallet for that investor by providing the address of the wallet.
# create a wallet for investor ...
r = requests.post(
f'{URL}/v2/investors/{investor_id}/wallets/eth-generic/',
json={"address": "{WALLET_ADDRESS}"},
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content
wallet_id = r.json()['id']

Retrieve information

We can now use the investors endpoint to retrieve information about the investor.
# Retrieve information about the investor
r = requests.get(
f'{URL}/v2/investors/{investor_id}/',
headers={**headers}
)
assert r.status_code < 300, r.content
print(r.json())

Full script

import binascii
import os
import requests
URL = 'https://demo.cashlink.de/api/external'
CLIENT_ID = 'w7Rt7aJTNdiLTblr01Pn5DDeydo4YESiLBtzp2W3'
CLIENT_SECRET = '8Dtx8M1S845Po7CWnnHxkw0JRN9EsyLaQHNvVsWXSxrNbtCAlK9yQyiVFA5Zqe88q1UFRqSZKrGPoVljOG5VPPaOBFbHWeSTp75S7beDlkNh9xpFwwRxciw4kr4Qiinw'
def authenticate():
r = requests.post(f'{URL}/oauth/token', data={
'grant_type': 'client_credentials',
'scope': 'investors:read investors:write investments:write investments:read documents:write',
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET
})
assert r.status_code == 200, r.content
return r.json()['access_token']
def make_key():
return binascii.hexlify(os.urandom(8)).decode()
if __name__ == '__main__':
headers = {
'Authorization': f'Bearer {authenticate()}',
}
# Upload documents to verify the investor's identity
r = requests.post(
f'{URL}/v2/documents/',
files={'file': ('commercial-register.pdf', b'ASD', 'application/pdf')},
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content
identity_proof_doc_id = r.json()['id']
r = requests.post(
f'{URL}/v2/documents/',
files={'file': ('shareholder-list.pdf', b'ASD', 'application/pdf')},
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content
structure_proof_doc_id = r.json()['id']
# Create investor using the uploaded files
r = requests.post(
f'{URL}/v2/investors/',
json={
'natural_person': {
'salutation': 'MR',
'forename': 'Peter',
'surname': 'Parker',
'birth_date': '2020-08-10',
'birth_place': 'New York',
'citizenship': 'DEU',
'street': 'Hauptstr. 37',
'city': 'Frankfurt',
'zip': '60316',
'country': 'DEU',
'phone': '+49 123 456 789',
'occupation': 'Apprentice',
'educational_qualification': 'Secondary school diploma'
},
'legal_person': {
'company_name': 'Test GmbH',
'street': 'Teststr. 80',
'city': 'Test',
'zip': '111111',
'country': 'DEU',
'commercial_register_number': 'HRB 12344',
'commercial_register': 'Amtsgericht München',
'company_identity_proof_id': identity_proof_doc_id,
'company_structure_proof_id': structure_proof_doc_id
},
'bank_account': {
'account_holder': 'Peter Parker',
'iban': 'DE53500105173569146251',
'bic': 'GENODEF1ERG',
'bank': 'Commerzbank Frankfurt',
'country': 'DEU',
'currency': 'EUR'
},
'is_beneficiary': True,
'pep_status': False,
'account_setup_accepted_at': '2021-05-06T07:14:13.630308+00:00',
},
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content
investor_id = r.json()['id']
# Upload the legitimation protocol document to reference it later
r = requests.post(
f'{URL}/v2/documents/',
files={'file': ('protocol.pdf', b'ASD', 'application/pdf')},
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content
doc_id = r.json()['id']
# Create the identification for the investor using the uploaded file
r = requests.post(
f'{URL}/v2/investors/{investor_id}/identifications/',
json={
'document_type': 'PASSPORT',
'document_id': 'ID123456',
'document_issuer': 'Buergeramt Frankfurt',
'document_valid_from': '2020-08-10',
'document_valid_to': '2025-08-10',
'identity_provider': 'POSTIDENT',
'legitimation_process_id': 'string',
'legitimation_protocol_id': doc_id,
'verified_at': '2020-08-10T14:06:22.134Z'
},
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content
# Add benefiting persons to the investor
r = requests.post(
f'{URL}/v2/investors/{investor_id}/benefiting-persons/',
json=
[
{
'salutation': 'MR',
'forename': 'Petra',
'surname': 'Parker',
'citizenship': 'AFG',
'birth_date': '2000-11-02',
'birth_place': 'Frankfurt',
'street': 'Teststr. 10',
'city': 'Frankfurt',
'zip': '60316',
'country': 'DEU',
'phone': '+49 123 456 789',
'occupation': 'Apprentice',
'educational_qualification': 'Secondary school diploma'
}
],
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content
# create a wallet for investor ...
r = requests.post(
f'{URL}/v2/investors/{investor_id}/wallets/eth-generic/',
json={"address": "{WALLET_ADDRESS}"},
headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300, r.content
wallet_id = r.json()['id']
# Retrieve information about the investor
r = requests.get(
f'{URL}/v2/investors/{investor_id}/',
headers={**headers}
)
assert r.status_code < 300, r.content
print(r.json())