This guide shows you how to use the API to create an investor and then create an investment for that investor. The API allows you to collect information about the investment process in order to document the compliant brokerage of financial products.
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 is set up for you as well during the product creation.
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. See Investors -> Identification for more details.
# Store the survey answers with the investor r = requests.post(f'{URL}/v2/investors/{investor_id}/survey/', json = {"survey_submitted_at": "2022-03-22T07:14:13.630308+00:00","survey": [] }, headers={**headers, 'X-Idempotency-Key': make_key()} )assert r.status_code <300, r.content
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. However, you could also add a custom account address of a self-custody wallet belonging to the investor.
# 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 invitation
With the investor ID at hand, we create an invitation for this investor.
# Create a basic invitation with campaign, investor and units r = requests.post(f'{URL}/v2/invitations/', json={"investor_id": investor_id,"campaign_id": CAMPAIGN_ID,"units": 100 }, headers={**headers, 'X-Idempotency-Key': make_key()} )assert r.status_code <300, r.content invitation_id = r.json()['id']
Set the survey waiver
Based on the answers to the knowledge and experience survey set for the investor, the API will return a value for the required survey waiver. In your frontend, display the survey waiver to the investor and update via the API once the investor has accepted the waiver.
# Patch the invitation with the required survey waiver r = requests.patch(f'{URL}/v2/invitations/{invitation_id}/', json = {"survey_waiver": "NO_ANSWERS", }, headers={**headers, 'X-Idempotency-Key': make_key()} )assert r.status_code <300, r.content
Create an investment
Once the survey waiver is set we can use the invitation ID to create an investment.
# Use the invitation to create an investment r = requests.post(f'{URL}/v2/investments/', json={"invitation_id": invitation_id,"signed_at": "2022-03-22T08:33:45.617Z" }, headers={**headers, 'X-Idempotency-Key': make_key()} )assert r.status_code <300, r.content investment_id = r.json()['id']
Assign wallet to investment
The investment needs a wallet. The digital securities will be credited to the wallet's account address.
# Patch investment to set wallet r = requests.patch(f'{URL}/v2/investments/{investment_id}/', json={"wallet": wallet_id }, headers={**headers} )assert r.status_code <300, r.content
Mark investment as paid
After the payment has been received for the investment, it needs to set to paid by providing the date and time of the payment.
# Patch investment to set payment date r = requests.patch(f'{URL}/v2/investments/{investment_id}/', json={"paid_at": "2022-03-23T08:33:45.617Z" }, headers={**headers} )assert r.status_code <300, r.content
You might receive an error response here, if you need to manually accept the investment via the Cashlink Studio. In this case, follow the description here to accept the investment.
Credit digital securities
Once the payment date is set and the investment has an assigned wallet, you can credit the digital securities to the wallet.
# 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}].')
Full script
This is how the full script will look like.
import binasciiimport osimport requestsURL ='https://demo.cashlink.de/api/external'CLIENT_ID ='Insert your client ID here ...'CLIENT_SECRET ='Insert your client secret here ...'CAMPAIGN_ID ='Insert the campaign 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": "Insert test email address here ...","email_confirmed": True },"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 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']# Store the survey answers with the investor r = requests.post(f'{URL}/v2/investors/{investor_id}/survey/', json = {"survey_submitted_at": "2022-03-22T07:14:13.630308+00:00","survey": [] }, 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-managed/', headers={**headers, 'X-Idempotency-Key': make_key()} )assert r.status_code <300, r.content wallet_id = r.json()['id']# Create a basic invitation with campaign, investor and units r = requests.post(f'{URL}/v2/invitations/', json={"investor_id": investor_id,"campaign_id": CAMPAIGN_ID,"units": 100 }, headers={**headers, 'X-Idempotency-Key': make_key()} )assert r.status_code <300, r.content invitation_id = r.json()['id']# Patch the invitation with the required survey waiver r = requests.patch(f'{URL}/v2/invitations/{invitation_id}/', json = {"survey_waiver": "NO_ANSWERS", }, headers={**headers, 'X-Idempotency-Key': make_key()} )assert r.status_code <300, r.content# Use the invitation to create an investment r = requests.post(f'{URL}/v2/investments/', json={"invitation_id": invitation_id,"signed_at": "2022-03-22T08:33:45.617Z" }, headers={**headers, 'X-Idempotency-Key': make_key()} )assert r.status_code <300, r.content investment_id = r.json()['id']# Patch investment to set wallet r = requests.patch(f'{URL}/v2/investments/{investment_id}/', json={"wallet": wallet_id }, headers={**headers} )assert r.status_code <300, r.content# Patch investment to set payment date r = requests.patch(f'{URL}/v2/investments/{investment_id}/', json={"paid_at": "2022-03-23T08:33:45.617Z" }, headers={**headers} )assert r.status_code <300, r.content# 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}].')