Example Guide

This guide shows you how to use the Distributor API

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. The campaign ID and product ID is set up for you as well during the product creation.

import binascii
import os
import requests

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

CLIENT_ID = 'your client ID here ...'
CLIENT_SECRET = 'your client secret here ...'

CAMPAIGN_ID = 'enter campaign id here ...'
PRODUCT_ID = 'enter product id here ...'

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. Besides personal data, 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.

if __name__ == '__main__':
    headers = {
        'Authorization': f'Bearer {authenticate()}',
    }

    # Create investor
    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",
                "is_pep": False
            },
            "bank_account": {
                "account_holder": "Peter Parker",
                "iban": 'DE53500105173569146251',
                "bic": "GENODEF1ERG",
                "bank": "Commerzbank Frankfurt",
                "country": "DEU",
                "currency": "EUR"
            },
            "tax_information": {
                "tax_identification_number": "12345678",
                "non_assessment_certificate": True
            },
            "communication": {
                "email": "Insert test email address here ...",
                "email_confirmed": True
            },
            "is_beneficiary": True
        },

        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 investor
    r = requests.post(
        f'{URL}/v2/investors/{investor_id}/identifications/',
        json={
            "document_type": "IDCARD",
            "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
    identification_id = r.json()['id']

If you want to onboard a legal person as an investor (e.g. a GmbH / AG) you need to follow another process.

Create a wallet

Investors need a wallet to receive the digital securities. In this example, we create a managed wallet for the investor. That means that the private key will remain in secure custody.

    # Create a wallet for investor
    r = requests.post(
        f'{URL}/v2/investors/{investor_id}/wallets/eth-managed/',
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300, r.content
    wallet_id = r.json()['id']

If you want to use your own custody solution or if you want your users to use their own self-custody wallet, you can use the eth-generic endpoint ({URL}/v2/investors/{investor_id}/wallets/eth-generic/) and provide an Ethereum account address in the JSON body of the request. In both cases, you will receive a wallet ID you can use to refer to the wallet in the following steps.

Create an investment

With the campaign ID, investor ID and wallet ID at hand, we create an investment for this investor with two signed documents.

    # Create an investment with signed documents
    r = requests.post(
        f'{URL}/v2/documents/',
        files={'file': ('signed_doc_1.pdf', b'ASD', 'application/pdf')},
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300, r.content
    doc_1_id = r.json()['id']

    r = requests.post(
        f'{URL}/v2/documents/',
        files={'file': ('signed_doc_2.pdf', b'ASD', 'application/pdf')},
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300, r.content
    doc_2_id = r.json()['id']

    # Create investment
    r = requests.post(
        f'{URL}/v2/investments/',
        json={
            'campaign_id': CAMPAIGN_ID,
            'investor_id': investor_id,
            'units': 300,
            'signed_at': '2021-03-12T07:14:13.630308+00:00',
            'accepted_at': '2021-03-12T07:14:13.630308+00:00',
            'paid_at': '2021-03-12T07:14:13.630308+00:00',
            'signed_documents': [{
                'document_id': doc_1_id,
                'category': 'category 1',
                'subject': 'subject 1',
            }, {
                'document_id': doc_2_id,
                'category': 'category 2',
                'subject': 'subject 2',
            }],
            'wallet': wallet_id
        },
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300, r.content
    investment_id = r.json()['id']

Credit crypto securities

After the wallet has been set for the investment, the crediting of crypto securities can also be trigged via the API. Note that this action can also be performed via the Cashlink Studio frontend and does not necessarily need to be controlled via the API.

    # Initiate credit of digital securities
    r = requests.post(
        f'{URL}/v2/investments/{investment_id}/credits/',
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300, r.content

Retrieve status information

We can now use the investments endpoint to retrieve all information about the investment.

    # Retrieve investment status
    r = requests.get(
        f'{URL}/v2/investments/{investment_id}/',
        headers={**headers}
    )
    assert r.status_code < 300, r.content
    status = r.json()['status']
    print(f'Successfully created investment with ID {investment_id} [{status}].')

Create redemption

When the contract has ended or the contract’s nominal amounts needs to be reduced because of some legal construct in the contract, the balance for that investor needs to be reduced. Such a transaction is called redemption and can be created via the redemption endpoint.

This is only possible for digital securities.

# Create redemption for investor and product
r = requests.post(
    f'{URL}/v2/redemptions/',
    json={
      'investor_id': investor_id,
      'product_id': PRODUCT_ID,
      'wallet_id': wallet_id,
      'units': 300,
    }
    headers={**headers, 'X-Idempotency-Key': make_key()}
)
assert r.status_code < 300
print(f'Successfully redeemed investment with ID {investment_id} [{status}].')

Full script

This is how the full script will look like.

import binascii
import os
import requests

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

CLIENT_ID = 'your client ID here ...'
CLIENT_SECRET = 'your client secret here ...'

CAMPAIGN_ID = 'enter campaign id here ...'
PRODUCT_ID = 'enter product id here ...'


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


def make_key():
    return binascii.hexlify(os.urandom(8)).decode()


if __name__ == '__main__':
    headers = {
        'Authorization': f'Bearer {authenticate()}',
    }

    # create investor
    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",
                "is_pep": False
            },
            "bank_account": {
                "account_holder": "Peter Parker",
                "iban": 'DE53500105173569146251',
                "bic": "GENODEF1ERG",
                "bank": "Commerzbank Frankfurt",
                "country": "DEU",
                "currency": "EUR"
            },
            "tax_information": {
                "tax_identification_number": "12345678",
                "non_assessment_certificate": True
            },
            'communication': {
                'email': '...',
                'email_confirmed': True
            },
            "is_beneficiary": True
        },

        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 investor
    r = requests.post(
        f'{URL}/v2/investors/{investor_id}/identifications/',
        json={
            "document_type": "IDCARD",
            "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
    identification_id = r.json()['id']

    # Create a wallet for investor
    r = requests.post(
        f'{URL}/v2/investors/{investor_id}/wallets/eth-managed/',
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300, r.content
    wallet_id = r.json()['id']

    # Create an investment with signed documents
    r = requests.post(
        f'{URL}/v2/documents/',
        files={'file': ('signed_doc_1.pdf', b'ASD', 'application/pdf')},
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300, r.content
    doc_1_id = r.json()['id']

    r = requests.post(
        f'{URL}/v2/documents/',
        files={'file': ('signed_doc_2.pdf', b'ASD', 'application/pdf')},
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300, r.content
    doc_2_id = r.json()['id']

    # Create investment
    r = requests.post(
        f'{URL}/v2/investments/',
        json={
            'campaign_id': CAMPAIGN_ID,
            'investor_id': investor_id,
            'units': 300,
            'signed_at': '2021-03-12T07:14:13.630308+00:00',
            'accepted_at': '2021-03-12T07:14:13.630308+00:00',
            'paid_at': '2021-03-12T07:14:13.630308+00:00',
            'signed_documents': [{
                'document_id': doc_1_id,
                'category': 'category 1',
                'subject': 'subject 1',
            }, {
                'document_id': doc_2_id,
                'category': 'category 2',
                'subject': 'subject 2',
            }],
            'wallet': wallet_id
        },
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300, r.content
    investment_id = r.json()['id']

    # Initiate credit of digital securities
    r = requests.post(
        f'{URL}/v2/investments/{investment_id}/credits/',
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300, r.content

    # Retrieve investment status
    r = requests.get(
        f'{URL}/v2/investments/{investment_id}/',
        headers={**headers}
    )
    assert r.status_code < 300, r.content
    status = r.json()['status']
    print(f'Successfully created investment with ID {investment_id} [{status}].')
    
    # Create redemption for investor and product
    r = requests.post(
        f'{URL}/v2/redemptions/',
        json={
          'investor_id': investor_id,
          'product_id': PRODUCT_ID,
          'wallet_id': wallet_id,
          'units': 300,
        }
        headers={**headers, 'X-Idempotency-Key': make_key()}
    )
    assert r.status_code < 300
    print(f'Successfully redeemed investment with ID {investment_id} [{status}].')

Last updated