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>'

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

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. Besides the company data and personal data of a person inauthority, the investor also needs a valid identification to be able to invest. If you've already identified the user, you can upload the data here. Depending on your setup, the investor might need to go through an identification process again. See Investors -> Identification for more details.

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': '2000-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',
                'is_pep': False
            },
            '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,
            '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 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',
            'is_pep': False
          }
        ],
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300, r.content

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(f'Successfully created investor with ID {investor_id}.')

Full script

import binascii
import os
import requests

URL = 'https://demo.cashlink.de/api/external'

CLIENT_ID = '<your client ID>'
CLIENT_SECRET = '<your client secret>'

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',
                'is_pep': False
            },
            '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,
            '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',
            'is_pep': False
          }
        ],
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300, r.content

    # Retrieve information about the investor
    r = requests.get(
        f'{URL}/v2/investors/{investor_id}/',
        headers={**headers}
    )
    assert r.status_code < 300, r.content
    print(f'Successfully created investor with ID {investor_id}.')

Last updated