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 binasciiimport osimport requestsURL ='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.
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.
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.
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 productr = 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 <300print(f'Successfully redeemed investment with ID {investment_id} [{status}].')
Full script
This is how the full script will look like.
import binasciiimport osimport requestsURL ='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 ...'defauthenticate(): 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.contentreturn r.json()['access_token']defmake_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 <300print(f'Successfully redeemed investment with ID {investment_id} [{status}].')