Zodia Custody API Documentation (3.8.3)

Introduction

This document outlines the required steps to integrate with Zodia using our API.
Document created on April 20th, 2021. Last updated on August 1st, 2024.

Contact: customerservice@zodia.io

What's New?

  • Updated Custody Generate Wallet Address in /v3/api/custody/wallets/addresses/generate
  • New API endpoint allows to query Staking reporting data in /v3/api/ase/staking/report/data

Getting Started

  • Zodia's API is RESTful over HTTPS and consumes JSON payload
  • Transport level security is TLS 1.2.
  • Requests must be digitally signed using SHA256withRSA (minimum 2048 bits)
  • Zodia's API requires at least two users: a maker (creation request) and a checker (approval request)
  • The examples below in Python require the libraries:
    • cryptography that can be installed with pip: pip install cryptography
    • requests that can be installed with pip: pip install requests

Company Onboarding For API Interaction

Your company must be onboarded before you can invoking the APIs.
To complete this setup, generate a key pair for your company. We will refer to these keys as:

  • company_pri_key for the private key
  • company_pub_key for the public key

These keys must be RSA keys (encoded in base 64) with a minimum size of 2048 bits.
Once generated, share the public key company_pub_key with customerservice@zodia.io

WARNING: You must never share the private key company_pri_key with anyone including Zodia staff. This private key must be stored securely

How To Generate Keys For A Company

Below is an example of how to generate a key pair for the company named ZTEST:

import os
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa

def generate_rsa_keypair(company):
    rsa_private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())
    private_key_pem = rsa_private_key.private_bytes(serialization.Encoding.PEM,
                                                serialization.PrivateFormat.PKCS8,
                                                serialization.NoEncryption())
    with open(os.path.join("keys", company + ".private.pem"), "wb") as private_key_out:
        private_key_out.write(private_key_pem)
        
    rsa_public_key = rsa_private_key.public_key()
    public_key_pem = rsa_public_key.public_bytes(serialization.Encoding.PEM,
                                              serialization.PublicFormat.SubjectPublicKeyInfo)
    with open(os.path.join("keys", company + ".public.pem"), "wb") as public_key_out:
        public_key_out.write(public_key_pem)

if __name__ == '__main__':
    generate_rsa_keypair("ZTEST")
    # Private key "company_pri_key" can be found in the file "ZTEST.private.pem"
    # Public key "company_pub_key" can be found in the file "ZTEST.public.pem"

Company API Users

At least two API users must be added to your company's account.

Important note

If the company has already been set up as explained above in the document, then Zodia customer service cannot create any API accounts on the created company. Only your company user maker will be able to create these two required API accounts.
However, if the company has not already been set up on Zodia UI, then Zodia onboarding team can create the company with the two required API accounts. Other user accounts can also be created by Zodia team at the time of the new company creation.

By convention, the API users should have an id starting with api-. Examples: api-maker and api-checker
To onboard these users, you must generate a key pair for each API user. We will refer to these keys as:

  • api_user_pri_key for the private key
  • api_user_pub_key for the public key

Each API user is composed of three pieces of information:

  • an api user email (just an identifier)
  • an Elliptic Curve public key (encoded in base 64)

Share these 2 pieces of information with customerservice@zodia.io

Applying the two "How-to" steps below results in the following example:

  • api user email: api-maker@zodia.io
  • public key: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeo/e0vtlHo44xeAOvDxZtlmnyKgkQrvoq/M2kpawqGOO+7FrUPktZm+wxqzE/Etta5F8sewpqYK5RgCbXtplSA==

WARNING: You must never share the private key api_user_pri_key with anyone including Zodia staff. This private key must be stored securely

How To Generate Keys For An API User

Please note that the Elliptic Curve that must be used to generate those keys is SECP256R1
Below is an example of how to generate a key pair for the user whose email is api-maker@zodia.io:

import os
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec

def generate_ec_keypair(user):
    ec_private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
    private_key_pem = ec_private_key.private_bytes(serialization.Encoding.PEM,
                                                serialization.PrivateFormat.PKCS8,
                                                serialization.NoEncryption())
    with open(os.path.join("keys", user + ".private.pem"), "wb") as private_key_out:
        private_key_out.write(private_key_pem)
        
    ec_public_key = ec_private_key.public_key()
    public_key_pem = ec_public_key.public_bytes(serialization.Encoding.PEM,
                                              serialization.PublicFormat.SubjectPublicKeyInfo)
    with open(os.path.join("keys", user + ".public.pem"), "wb") as public_key_out:
        public_key_out.write(public_key_pem)

if __name__ == "__main__":
    generate_ec_keypair("[email protected]")
    # Private key "api_user_pri_key" can be found in the file "[email protected]"
    # Private key "api_user_pub_key" can be found in the file "[email protected]"

Alternatively, you can also generate the user keys using below shell commands,

$ openssl ecparam -name prime256v1 -genkey -noout -out ./private-key.pem
$ openssl ec -in ./private-key.pem -pubout -out ./public-key.pem

Rate Limiting

In order to provide a high-quality service, Zodia's API is rate limited. The default limits are:

  • 1 request per second per endpoint
  • 128 requests per day per endpoint

Please reach out to Zodia if you wish to adjust the rate limit for given endpoints.
If the rate limit is exceeded, the API will respond with the status code HTTP 429 Too Many Requests. Please refer to the section Error codes.

FAQs

How to retrieve the next page records from the retrieve list API ?

The retrieve list APIs respond with total number of records in the response. You can use paginationLimit & paginationOffset in the request payload to work with pagination.

For example to retrieve the wallets, (2nd Page)

{
    "currencies": ["BTC"],
    "paginationLimit" : 10,
    "paginationOffset" : 1  // 0 - First Page, 1 - Second Page so on...
}

Do you notify events so we can consume ?

Throughout any request or a transaction life cycle, Zodia produces a variety of status change events such as incoming transfer, outgoing transfer, wallet creation. Zodia can assist you in subscribing to relevant events so that you are informed when you need to take any action on your end.

Zodia currently support two types of subscriptions,

  • REST Webhook
  • JMS

What are the supported testnets for Ethereum & Bitcoin (Preprod)?

ethereum-testnet-sepolia and bitcoin-testnet

Authentication

Zodia's API authentication requires each request to be signed.
These requests must be signed using company_pri_key, the private key of your company.
All REST requests must contain the following headers:

  1. company-identifier is the identifier provided to you during the setup of your company on the Zodia Platform.
  2. request-identifier is an unique randomly generated identifier
  3. request-timestamp is the timestamp (milliseconds elapsed since the unix epoch) of when the request is submitted. Please note that the request should be submitted within 5 minutes
  4. signature is the message signature(using the SHA256withRSA algorithm). Please refer to the section Signing requests.

All request bodies should have content type application/json and be valid JSON.

Signing the payload as maker or authoriser

Some API calls require an additional signing process with the maker/authoriser private key, typically when the maker confirms/authoriser approves an instruction (see section "4. Confirm instruction as maker" below). Before submitting the instruction to the HSM, you will need to sign the request payload with api_user_pri_key (API user private key).
The content that must be signed is the stringified value of a part of this payload.
For more details about a stringified value, see json-stable-stringify

The signed payload must not include optional and empty fields

The content to sign is the stringified value of the object named request and the resulting signature string should substitute placeholder $$REPLACE.

Assume the request payload is

{
  "request": {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
  },
  "signature": "$$REPLACE$$"
}

Below is an example of how to sign it in Python:

import os, base64, json
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec

def stringify_transaction_details():
    transaction_details = {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
  }

    return json.dumps(transaction_details, sort_keys=True, separators=(',', ':'))

def sign_transaction_details(details, user):
    with open(os.path.join("keys", user + ".private.pem"), "rb") as private_key_in:
        ec_private_key = serialization.load_pem_private_key(private_key_in.read(),
                                                            password=None,
                                                            backend=default_backend())
        signed_payload = ec_private_key.sign(str.encode(details), ec.ECDSA(hashes.SHA256()))
    b64_signed_payload = base64.b64encode(signed_payload).decode('UTF-8')
    return b64_signed_payload

def create_transaction_payload(details, signature):
    payload = {
            "request": json.loads(details),
            "signature": signature
        }
    }
    return json.dumps(payload, sort_keys=True, separators=(',', ':'))

if __name__ == "__main__":
    stringified_details = stringify_transaction_details()
    user_signature = sign_transaction_details(stringified_details, "[email protected]")
    transaction_payload = create_transaction_payload(stringified_details, user_signature)

    print(transaction_payload)
    

You will obtain the payload below (prettified for the documentation) to be submitted to the API:

{
      "request": {
        "key1": "value1",
        "key2": "value2",
        "key3": "value3"
      },
      "signature": "MEQCIHuLPOFmbnjWnJ0l6QpYRLMh/6LTwIV6LNlGXRiChProAiBjDQCsvbsGxb6pw9Il/m8zIFmZ+Wh3QFwBmIZJCOk8kA=="
    }

Signing Requests

We mandate that all requests are signed. Concatenate the following values using : as a separator:

Note GET requests will end with a :, for example

ZTEST:c730a02f-105d-4899-87aa-b1b8280e4a7e:1620141458133:https://dummy.server/v3/api/servicedesk/create:

The content of the header signature is generated by signing the string above with company_pri_key.
Example of code showing how to sign the request:

import os, base64, time, uuid, requests
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

def sign(data_to_sign, private_key):
    data_bytes = str.encode(data_to_sign)
    signature = private_key.sign(data_bytes, padding.PKCS1v15(), hashes.SHA256())
    return base64.b64encode(signature).decode('UTF-8')

def sign_for_zodia(company_identifier, url, payload):
    request_identifier = str(uuid.uuid4())
    request_timestamp = str(int(time.time() * 1000))
    data_to_sign = ":".join([company_identifier, request_identifier, request_timestamp, url, payload])
    
    print(data_to_sign)   # ZTEST:c730a02f-105d-4899-87aa-b1b8280e4a7e:1620141458133:https://dummy.server/v3/api/servicedesk/create:{}

    with open(os.path.join("keys", company_identifier + ".private.pem"), "rb") as private_key_in:
        rsa_private_key = serialization.load_pem_private_key(private_key_in.read(),
                                                             password=None,
                                                             backend=default_backend())

        signature = sign(data_to_sign, rsa_private_key)
    return company_identifier, request_identifier, request_timestamp, signature

if __name__ == "__main__":
    full_url = "https://dummy.server/v3/api/servicedesk/create"
    payload = json.dumps({})
    headers = sign_for_zodia("ZTEST", full_url, "{}")

    response = requests.post(full_url, headers={
        "company-identifier": headers[0],         # ZTEST
        "request-identifier": headers[1],         # c730a02f-105d-4899-87aa-b1b8280e4a7e
        "request-timestamp": headers[2],          # 1620141458133
        "signature": headers[3]                   # aZBZNDeDJomgRBZFhV24MbdlyfSiqJuCMgz5mSl+dpxtDDgbadTuin0z920eBD2YFP5g3ccakguQUXPMuLp4Umyet3hGYXb2GiMkKSIA8XocdF8uG2xReSZq+JSbuDSO7yQcxXVK10A6mL2f/zJuTFBRl20jegiHOBcbDlAOUkWS3Vam3KRLA/Nd8ZwOhK6XZbtZWGz0AW9obE7cpEwqUEposucx7J462XAaM9Duh+CF1ALhuo67G0hLYAtwqryRVhUdBvVrqoWgGu3quxfIYgG/8okYv2hHjzBtIfo2VREi4TlgsRXvGPQpSI534S8o9laa5Ddq1f6N2u1sXkE97g==
    }, data = payload)

Digital Asset Service Desk Usage

This section describes an end-to-end flow to create a wallet, whitelist a beneficiary, whitelist an address for a beneficiary and instruct a transfer.

The APIs aim to be self service, you can check the list of available products and services to fetch the request payload templates based on your account's permissions.

Here is an example :

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/products'
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data-raw 
'{
  "products": [
  ],
  "productIds": [
    "0x0013"
  ],
  "serviceIds": [
    "0x0013-001"
  ],
  "serviceNames": [
  ],
  "requestTypes": [
  ]
}'

Response

HTTP 200

{
  "items": [
    {
      "product": "CUSTODY",
      "productId": "0x0013",
      "serviceId": "0x0013-001",
      "serviceName": "Custody Wallet Management",
      "requestType": "CREATE",
      "description": "Custody Wallet",
      "template": {
        "name": "Wallet_Name",
        "currency": "ETH | BTC",
        "walletOwnerId": "Wallet_Beneficiary_ID"
      }
    }
  ]
}

The template object you see on the response above is what will be requested for any service request creation.

The sample APIs calls below omit authentication information for simplicity, see more about Signing requests.

Step 1: Create a wallet

1. Instruction to create a wallet

Create an Ethereum wallet with name FUND123

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/create'
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data-raw 
'{
    "serviceId": "0x0013-001",
    "payload": {
        "name": "FUND123",
        "currency": "ETH"
    }
}'

Response

HTTP 200

{
  "requestId": "SERV-REQ-0RV7UQ2D56",
  "pluginDetail": {
    "entityId": "ZODCS-NOBENF-E3WB8MB4EI",
    "details": [
      {
        "name": "FUND123",
        "currency": "ETH",
        "isDeFi": false
      }
    ]
  }
}

2. Submit service request

Use the request ID obtained in the previous request to submit the instruction.

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/submit' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-0RV7UQ2D56"
}'

Response

HTTP 200

3. Retrieve instruction to sign as maker

Retrieve the HSM instruction to be signed by the maker

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/pending' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-0RV7UQ2D56",
}'

Response

HTTP 200

{
  "request": {
    ...
  },
  "signature": "$$REPLACE$$"
}

4. Confirm instruction as maker

Sign the 'request' element with the maker private key and insert the resulting string in 'signature'. The signed instruction is submitted directly to the HSM.

Request

curl --location 'https://hostname.zodia.io/v3/api/servicedesk/approve' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-0RV7UQ2D56",
  "request": {
    ...
  },
  "signature": "MEUCIQDqOsThmTIPlSyqPt2bWYC5FsahAxby/wUjOfdOpnATBgIgdszq9Gnbx8SyTYUcSjTW2OPmnB1a7PPxFO1ReKMWFo0="
}'

Response

HTTP 200

5. Retrieve instruction to sign as authoriser

Request

curl --location 'https://hostname.zodia.io/v3/api/servicedesk/pending' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-0RV7UQ2D56"
}'

Response

{
  "request": {
    ...,
    "type": "Approve|Reject"
  },
  "signature": "$$REPLACE$$"
}

6. Approve instruction as authoriser

To approve an instruction set type to Approve. To reject an instruction, set type to Reject and set a rejectReason.

Request

curl --location 'https://hostname.zodia.io/v3/api/servicedesk/approve' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-0RV7UQ2D56",
  "request": {
    ...,
    "type": "Approve"
  },
  "signature": "MEUCIQCP9Sqzh0jBj0WW++7oVwQyxpSTPfjhB2G7lNjjvom+LwIgcfU521JS3sUFVRHyUhjQGCgQbkb4P4IP/zzcGOxfUEA="
}'

Response

HTTP 200

Step 2: Add a beneficiary

1. Create instruction to add a beneficiary

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/create'
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data-raw 
'{
    "serviceId": "0x0007-004",
    "payload": {
        "entityType": "INDIVIDUAL",
        "beneficiaryName": "John Smith",
        "registeredAddress": {
            "line1": "10 Downing Street",
            "city": "London",
            "country": "United Kingdom"
        },
        "operatingAddress": {
            "line1": "10 Downing Street",
            "city": "London",
            "country": "United Kingdom"
        }
    }
}'

Response

HTTP 200

{
    "requestId": "SERV-REQ-Y3S874B6IC",
    "pluginDetail": {
        "entityId": "BNF-T20046-JQAPQMNOYA",
        "details": [
            {
                "entityType": "INDIVIDUAL",
                "legalName": "John Smith",
                "registeredAddress": {
                    "line1": "10 Downing Street",
                    "city": "London",
                    "country": "United Kingdom"
                },
                "operatingAddress": {
                    "line1": "10 Downing Street",
                    "city": "London",
                    "country": "United Kingdom"
                }
            }
        ]
    }
}

2. Submit service request

Use the request id obtained in the previous request to submit the instruction

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/submit' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-Y3S874B6IC",
}'

Response

HTTP 200

3. Retrieve instruction to sign as maker

Retrieve the HSM instruction to be signed by the maker

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/pending' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-Y3S874B6IC",
}'

Response

HTTP 200

{
    "request": {
        ...
    },
    "signature": "$$REPLACE$$"
}

4. Confirm instruction as maker

Sign the 'request' element with the API maker private key, insert the string in 'signature'. The signed instruction is submitted directly to the HSM.

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/approve' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-Y3S874B6IC",
  "request": {
    ...
  },
  "signature": "MEQCIAkTlKFSm8IDOS7o0eklI7aU9MvmS6vQUM6NElw9R3ebAiAPd8TMNh6g3QtzDYEPOFxa1EYCr3o+a5ypHa8/RLrN2w=="
}'

Response

HTTP 200

NOTE: Authoriser approval is not required when whitelisting beneficiaries

Step 3: Whitelist an address for beneficiary

1. Create instruction to whitelist an address associated with a beneficiary

Whitelist an ETH address linked with beneficiary John Smith which will be use for deposit and withdrawal

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/create' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
    "serviceId": "0x0007-007",
    "payload": {
        "address": "0x8dC847Af872947Ac18d5d63fA646EB65d4D99560",
        "blockchain": "ETH",
        "beneficiaryId": "BNF-T20046-WGG997I15N",
        "notes": "Fund A",
        "hostedAddress": true,
        "vaspId": "06ea2d8f-fa02-48f6-87af-c71ea58452as",
        "addressPurpose": [
            "INCOMING, OUTGOING"
        ]
    }
}'

Response

HTTP 200

{
    "requestId": "SERV-REQ-99WPBB0FAG",
    "pluginDetail": {
        "entityId": "467f0fbf-7cd0-4196-b8be-7a33ac66f4f6",
        "details": [
            {
                "cryptoAddress": "0x8dC847Af872947Ac18d5d63fA646EB65d4D99560",
                "currency": "ETH",
                "managingVasp": "06ea2d8f-fa02-48f6-87af-c71ea58452as",
                "beneficiaryId": "BNF-T20046-WGG997I15N",
                "notes": "Fund A",
                "addressPurpose": "INCOMING, OUTGOING"
            },
            {
                "beneficiaryId": "BNF-T20046-WGG997I15N",
                "entityType": "INDIVIDUAL",
                "legalName": "John Smith",
                "legalEntityName": "John Smith",
                "registeredAddress": {
                    "line1": "10 Downing Street",
                    "city": "London",
                    "country": "United Kingdom"
                },
                "operatingAddress": {
                    "line1": "10 Downing Street",
                    "city": "London",
                    "country": "United Kingdom"
                }
            }
        ]
    }
}

2. Submit service request

Use the request ID obtained in the previous request to submit the instruction.

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/submit' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-99WPBB0FAG"
}'

Response

HTTP 200

3. Retrieve instruction to sign as maker

Retrieve the HSM instruction to be signed by the maker

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/pending' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-99WPBB0FAG",
}'

Response

HTTP 200

{
    "request": {
        ...
    },
    "signature": "$$REPLACE$$"
}

4. Confirm instruction as maker

Sign the 'request' element with the API maker private key, insert the string in 'signature'. The signed instruction is submitted directly to the HSM.

Request

curl --location 'https://hostname.zodia.io/v3/api/servicedesk/approve' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-99WPBB0FAG",
  "request": {
    ...
  },
  "signature": "MEUCIHlH4Zs4zPrhofU9+KsLVLEEcfw6ENHgk7OHLRXhKVJmAiEA8TfaVVjc0XCdnGa8TrRtdmkVQWA5WwJCDNq0CWH02mY="
}'

Response

HTTP 200

5. Retrieve instruction to sign as authoriser

Request

curl --location 'https://hostname.zodia.io/v3/api/servicedesk/pending' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-99WPBB0FAG"
}'

Response

{
    "request": {
        ...,
      "type": "Approve|Reject"
    },
    "signature": "$$REPLACE$$"
}

6. Approve instruction as authoriser

To approve an instruction set type to Approve. To reject an instruction, set type to Reject and set a rejectReason.

Request

curl --location 'https://hostname.zodia.io/v3/api/servicedesk/approve' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-99WPBB0FAG",
  "request": {
    ...,
    "type": "Approve"
  },
  "signature": "MEYCIQCkuiFDtdtl+cbeMewrGgwRycTNuRHmHVrhzR3MlDqX0AIhALIiT+KoYjxlDKtVpwcvXQbL0MuXE/wAeXl4DIulMp3m"
}'

Response

HTTP 200

Step 4: Instruct a transfer from a wallet to a whitelisted address

1. Create transfer instruction

We assumed the wallet has been funded prior to initiating the transfer.

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/create'
--header 'Content-Type: application/json' \
--data-raw 
'{
  "serviceId": "0x0014-007",
  "payload": {
    "sender": {
      "type": "WALLETID",
      "value": "ZODCS-NOBENF-E3WB8MB4EI"
    },
    "destination": {
      "type": "BENEFICIARYADDRESSID",
      "value": "467f0fbf-7cd0-4196-b8be-7a33ac66f4f6"
    },
    "amount": "50000",
    "subtractFee": false
  }
}'

Response

HTTP 200

{
    "requestId": "SERV-REQ-A02R2928OC",
    "pluginDetail": {
        "entityId": "TRO-LUXOR-P01HT6O1UT",
        "details": [
            {
              "sender": {
                "type": "WALLETID",
                "value": "ZODCS-NOBENF-E3WB8MB4EI"
              },
              "destination": {
                "type": "BENEFICIARYADDRESSID",
                "value": "467f0fbf-7cd0-4196-b8be-7a33ac66f4f6"
              },
              "amount": "50000",
              "subtractFee": false
            }
        ]
    }
}

2. Submit service request

Use the request id obtained in the previous request to submit the instruction.

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/submit' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-A02R2928OC"
}'

Response

HTTP 200

3. Retrieve instruction to sign as maker

Retrieve the HSM instruction to be signed by the maker

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/pending' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-A02R2928OC",
}'

Response

HTTP 200

{
  "request": {
    ...
  },
  "signature": "$$REPLACE$$"
}

4. Confirm transfer instruction as maker

Sign the 'request' element with the API maker private key, insert the string in 'signature'

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/approve'
--header 'Content-Type: application/json' \
--data-raw 
{
  "requestId": "SERV-REQ-A02R2928OC",
  "request": {
    ...
  },
  "signature": "MEYCIQDgag6BsAjQoKe7BPObXUsCC09X2gk0yrLYtxp8qg+R9QIhAJOT+fD3MrjmHHOKD1PO9nLcKhnUrQYSx/w022fO1z9p"
}

Response

HTTP 200

5. Retrieve instruction to sign as authoriser

Request

curl --location 'https://hostname.zodia.io/v3/api/servicedesk/pending' \
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data '{
  "requestId": "SERV-REQ-A02R2928OC"
}'

Response

{
  "requestId": "SERV-REQ-A02R2928OC",
  "request": {
    ...,
    "type": "Approve|Reject"
  },
  "signature": "MEQCIA/KgOFOxWQzeTpyZerRjcxucXBgdWBFzBdcczEqurkOAiAwcTIFg2XLgE5pfvV+decUKLEzejk1zYe/DFVxW55pdg=="
}

6a. Approve transfer instruction as authoriser

To approve an instruction set type to Approve. To reject an instruction, set type to Reject and set a rejectReason.

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/approve'
--header 'Content-Type: application/json' \
--data-raw 
{
  "requestId": "SERV-REQ-A02R2928OC",
  "request": {
    ...,
    "type": "Approve"
  },
   "signature": "MEQCID0XsCYDvVpo2eLD88LH4CpygowzUVAQGpkqhjbMZuMNAiB6rypnraKaKwRsarWSKJGYnx31NfrBQGekUj6yc7wfTg=="
}

Response

HTTP 200

6b. Reject transfer instruction as authoriser

To reject any type of instruction, set type to Reject and specify a rejectReason

Request

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/approve'
--header 'Content-Type: application/json' \
--data-raw 
{
  "requestId": "SERV-REQ-A02R2928OC",
  "request": {
    ...,
    "type": "Reject",
    "rejectReason": "Unknown request"
  },
   "signature": "MEQCID0XsCYDvVpo2eLD88LH4CpygowzUVAQGpkqhjbMZuMNAiB6rypnraKaKwRsarWSKJGYnx31NfrBQGekUj6yc7wfTg=="
}

Response

HTTP 200

Error Codes

Zodia's API offers a number of error codes to facilitate your usage and troubleshooting.
Zodia uses HTTP response codes to indicate the success or failure of an API request. In general:

  • 200 indicate success
  • 4xx range indicate a failure given the information provided(e.g. a required parameter was omitted, etc...)
  • 500 indicate an error with Zodia's servers

In addition to these HTTP response codes, Zodia provides in the response payload:

  • an error code starting with ER- that should be shared with Zodia for troubleshooting
  • a human-readable message providing more details about the errors

The table below describes some errors you may encounter:

HTTP status code Error code Error message Description
400 ER-101 Missing mandatory field: field_name A mandatory field is missing in the payload received by Zodia
400 ER-102 Entity with id: entity_id does not exist You are trying to use an entity (company, wallet, transaction, etc…) that does not exist
400 ER-103 Size of the field: field_name must be between min_value and max_value Length of the value provided does not match the expected size
400 ER-104 Entity with id: entity_id already exists You are trying to create an entity (company, wallet, transaction, etc…) which already exists
400 ER-107 Field: field_name must not be blank
  • Field is missing
  • Value of the field is empty
400 ER-108 Field: field_name does not match the expected pattern: regexp_value You sent a value that does not match the pattern expected by Zodia
400 ER-111 Value of the field: field_name is not a supported cryptocurrency The cryptocurrency provided is not currently supported by Zodia
400 ER-112 Field: field_name must not be empty You sent an empty value/list while Zodia is expecting at least one value
400 ER-113 Entity with id: entity_id is deactivated You are trying to use a deactivated entity
400 ER-114 Field: field_name does not match with user's company The value you provided for the field (usually "domainId") does not match your company's identifier
400 ER-115 Field: field_name does not match with resource parameter: path_parameter The value you provided in the payload does not match the path parameter used in the resource
400 ER-116 Value of the field: field_name must contain only digits You sent a value that is not a numeric value
400 ER-117 Value of the field: field_name must be equal to value You must send a value strictly equal to the value expected by Zodia
400 ER-121 Field: field_name must be greater than or equal to value You must send a value greater than or equal to the value expected by Zodia
400 ER-122 Field: field_name must be less than or equal to value You must send a value lesser than or equal to the value expected by Zodia
400 ER-124 Only max_value minutes of drift are allowed Zodia allows 5 minute of drift between the signed timestamp provided and the instant that Zodia receives the query
500 ER-128 currency_value is not a known currency Zodia was not able to verify the provided currency
500 ER-200 Internal Error Internal service error
400 ER-202 Header: header must not be blank
  • Header is missing
  • Value of the header is empty
500 ER-206 Unable to fetch data Zodia was not able to fetch the data requested
400 ER-212 Approval timeout Transaction approval expires
400 ER-214 Transaction amount greater than available amount or fee amount The transfer instruction cannot be created as there are not sufficient funds to pay for the amount and/or fee
400 ER-234 Entity with id: value is not ready to be submitted The id provided not allowed to be submitted
400 ER-253 Not enough funds The transfer instruction cannot be created as there are not sufficient funds to pay for the amount and/or fee
400 ER-255 Insufficient funds available to cover transfer and fee The transfer instruction cannot be created as there are not sufficient funds to pay for the amount and/or fee
400 ER-260 You are attempting to send to an invalid address The transfer instruction has been rejected due to sender or recipient details
400 ER-501 Transaction creation is rejected The transfer instruction has been rejected due to sender or recipient details
400 ER-502 Mismatch between transaction's wallet currency and field: field_name The cryptocurrency you provided for the field is not consistent with the wallet used for the transaction
400 ER-503 Value of the field: field_name is not adapted to this endpoint You provided a value that is not supported by the endpoint you used. Example: "preburn" while using the endpoint "/api/core/transactions"
400 ER-504 Mismatch between field: field1_name and field: field2_name The fields "field1" and "field2" must have the same value
400 ER-505 Mismatch between transaction's wallet and field: field_name The value you provided for the field does not match the wallet used for the transaction
400 ER-506 Mismatch between transaction's company and field: field_name The value you provided does not match the company used for the transaction
400 ER-512 Eth transaction fee-included not allowed You can't submit a transaction ETH included fees
400 ER-601 Ledger wallet is not compatible with this operation You are trying to initiate a contract-related transaction while using a wallet that does not support this operation
400 ER-604 Error retrieving the wallet address Zodia was not able to retrieve the address associated to this wallet
400 ER-615 The sender and recipient cannot be the same The provided transfer sender and transfer recipient cannot be the same
401 ER-210 Signature verification failed Zodia was not able to verify the signature you provided. Either because:
  • private key that signed the data does not match with public key shared with Zodia
  • Data signed is wrong

Please refer to the section Authentication
401 ER-211 Access to this resource is denied The entitlements defined by Zodia don’t allow you to access this resource
404 Endpoint doesn't exist You are trying to reach an endpoint that does not exist
405 ER-204 Available methods : available_methods You must choose from the available methods for an endpoint. Example: GET,POST
429 ER-208 Rate limit or daily quota exceeded Too many request submitted in a given time period, exceeded the number of requests allowed
400 ER-903 Invalid parameter: filter You must provide a valid filter value
400 ER-1123 This user is already link to a group change : value You must provide a different group for this user
401 ER-1200 Invalid company Id The value you provided does not match the any valid company Id
400 ER-1201 Invalid notification Id The value you provided does not match the any valid notification Id
400 ER-1650 Invalid entity ID The value you provided does not match the any valid entity Id
400 ER-1902 You need to deactivate all active addresses before deactivating the beneficiary All active addresses must be deactivated first
500 ER-001 Unexpected error: error Zodia encountered an unexpected error and can not fulfill the request
Please contact Zodia with the error's identifier beginning with the keyword "ERR"

Unit Reference

The units used in the API are the smallest denomination for a currency i.e. Satoshi, Wei, Litoshi.

Status Reference

Status in API responses can be mapped to human-readable messages using the following reference tables:

Digital Asset Service Desk

Status Description
DRAFT Request is created and not yet submitted
SUBMITTED Request is submitted and maker has 120 seconds to confirm
PENDING CONFIRMATION Request is awaiting maker confirmation
PENDING AUTHORISATION Request is awaiting approvals
CONFIRMED Request is completed
CONFIRMATION TIMEOUT Request was not confirmed by the maker within 120 seconds
APPROVAL TIMEOUT Request was not approved within the allocated time and expired
REJECTED BY AUTHORISER Request was rejected by authoriser
REJECTED BY SYSTEM Request was rejected by system

Wallet

Status Description
ACTIVE Wallet is active, funds are available to spend
DEACTIVATED Wallet is deactivated, funds are frozen

Transfer

Status Description
INITIATED Transfer is waiting to be posted on chain
POSTED ON CHAIN Transfer is posted on-chain
CONFIRMATION COMPLETED Transfer is confirmed after x blocks
FAILED Transfer has failed and has been posted on chain
UNDER INVESTIGATION Transfer is under investigation by Zodia
PENDING UNLOCK Incoming transfer detected on-chain and pending system approval
UNLOCKED Incoming transfer is approved
REJECTED BY SYSTEM Incoming transfer rejected by system

Interchange Guide

Zodia Custody’s Interchange service enables you to trade with your assets directly from a fully segregated institution-grade trading wallet, so they remain your property, completely isolated and protected until trade settlement.

Interchange enables participating venues to establish limits based on your assets mirrored on a wallet held by Zodia Custody, thereby eliminating the need to pre-fund venues or exchanges and to allocate assets across multiple venues instantly, safely and simply, without delay or exposure.

interchange

Approval modes

Join approval: This is also called as Joint Control. Both client & trading venue have the joint control on the settlement. The trading venue submits the instruction & the client authorises it.

One-party approval(client or venue): Client or Trading venue have full control on the settlement.

The client and trading venue can mutually agree to change the approval mode at any time.

interchange-delegations

Getting Started with Interchange (Trading Venue)

Trading Venue needs to follow the steps below,

Settlement

The process refers to the transfer of funds between different parties (buyers and sellers) after a trade is executed.
Settlement Receivable: Transferring funds from a client’s trading wallet to a venue.
Settlement Payable: Transferring funds from a venue to a client’s trading wallet.

settlement

Instruct Settlement

Trading Venue instructs a settlement. Settlement can be requested by creating a service request as below, then the request can be further submitted, approved same as the steps mentioned here creating a transaction .

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/create'
--header 'Content-Type: application/json' \
--data-raw 
'{
  "serviceId": "0x0003-003",
  "payload": {
    "sender": {
      "type": "WALLETID | WALLETREFERENCE",
      "value": "ZODCS-NOBENF-E3WB8MB4EI"
    },
    "destination": {
      "type": "ENDPOINT | WALLETID | WALLETREFERENCE",
      "value": "467f0fbf-7cd0-4196-b8be-7a33ac66f4f6"
    },
    "currency": "<ETH | BTC>",
    "venueId": "<VENUE_ID>",
    "amount": "100",
    "subtractFee": false
  }
}'

Do you support batch transactions ?

Yes, we do support batch transactions for settlements on UTXO based currencies like Bitcoin.

Below is the settlement payload sample with multiple destinations,

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/create'
--header 'Content-Type: application/json' \
--data-raw 
'{
  "serviceId": "0x0003-003",
  "payload": {
    "sender": {
      "type": "WALLETID | WALLETREFERENCE",
      "value": "ZODCS-NOBENF-E3WB8MB4EI"
    },
    "destinations": [
    {
      "type": "ENDPOINT | WALLETID | WALLETREFERENCE",
      "value": "467f0fbf-7cd0-4196-b8be-7a33ac66f4f6"
    },
    {
    "type": "ENDPOINT | WALLETID | WALLETREFERENCE",
    "value": "467f0fbf-7cd0-4196-b8be-ft6tfuy7676"
    }
    ],
        
    "currency": "<ETH | BTC>",
    "venueId": "<VENUE_ID>",
    "amount": "100",
    "subtractFee": false
  }
}'

What is batch transaction ?

Transaction batching is the process of combining multiple transactions together into a single transaction to save on gas fees and improve efficiency on the blockchain.

Getting Started with Interchange (Client)

Client needs to follow the steps below,

For example, you can create a Ethereum trading wallet with name TradingFund123,
then proceed with submit & approval steps same as here Wallet Creation Flow

curl --request POST 'https://hostname.zodia.io/v3/api/servicedesk/create'
--header 'submitter-id: [email protected]' \
--header 'Content-Type: application/json' \
--data-raw 
'{
    "serviceId": "0x0003-001",
    "payload": {
        "name": "TradingFund123",
        "currency": "ETH",
        "tradingVenueId": "VENUEID123",
        "accountUuid": "test",
        "userUuid": "test"
    }
}'
  • Client need to work with Zodia customer service to link a trading wallet with the Trading Venue. As a pre-requisite, Client needs to have a direct trading relationship with Trading Venue.
  • To fund the trading wallet, the client needs to transfer assets from their custody wallet. Refer Create a transaction

Difference between custody wallet and trading wallet

A custody wallet is owned by the client, with (the client) having full operational control of their custody wallets. Whereas the trading wallets, the client remains the owner or beneficiary of these assets, but operational control is partially given to the partner trading venue.

What is Withdrawal process?

Client initiates this request when wishes to transfer the funds from their trading wallet to their custody wallet.
Client needs to Create a transaction to withdraw funds from their trading wallet.

Typical Client workflow on Zodia platform look like?

Create Trading Wallet → Deposit into Trading Wallet → Withdraw from Trading Wallet

Can a Client use Interchange with multiple trading venues?

Yes, the client will need to create a separate trading wallet for each venue.

Can a Client initiate settlement?

It is possible with Full Control delegation.

Can a Client create multiple trading wallets for a venue ?

Yes.

Digital Asset Service Desk

One stop shop for instructing transfers, create wallets, whitelist beneficiaries and more. The general idea of the Digital Asset Service Desk is to be self-service. Consult the list of available products and services to find out what you are allowed to do and what payloads are required to create service requests.

Retrieve products and services

Available products and services are derived from the entitlements of the user

header Parameters
submitter-id
string
Request Body schema: application/json
products
Array of strings

Search by product offering i.e. NETWORK_MGMT, TRADING, CUSTODY

productIds
Array of strings

Search by product IDs

serviceIds
Array of strings

Search by service IDs

serviceNames
Array of strings

Search by service names. Note the search text needs to match the given service names

requestTypes
Array of strings

Search by request type i.e. CREATE, UPDATE or DEACTIVATE

Responses

Request samples

Content type
application/json
{
  • "products": [
    ],
  • "productIds": [
    ],
  • "serviceIds": [
    ],
  • "serviceNames": [
    ],
  • "requestTypes": [
    ]
}

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Create service request

header Parameters
submitter-id
string
Request Body schema: application/json
serviceId
string

Unique service ID for a given operation

endToEndId
string <uuid>

Use for request tracking purposes

object

Each service has an unique payload template. Check the list of products and services to find the correct one. This sample is for the creation of a custody wallet

Responses

Request samples

Content type
application/json
{
  • "serviceId": "0x0013-001",
  • "endToEndId": "adb61a09-f295-41c4-b4c0-b61daa0cf479",
  • "payload": {
    }
}

Response samples

Content type
application/json
{
  • "requestId": "string",
  • "pluginDetail": {
    }
}

Submit service request

header Parameters
submitter-id
string
Request Body schema: application/json
requestId
string

Service Request ID. Obtained when the service request was created

Responses

Request samples

Content type
application/json
{
  • "requestId": "string"
}

Response samples

Content type
application/json
{
  • "timestamp": "2021-10-27T14:30:13.912Z",
  • "title": "Method Not Allowed",
  • "status": 415,
  • "details": [
    ]
}

Retrieve the instruction to sign

header Parameters
submitter-id
string
Request Body schema: application/json
requestId
string

Service Request ID. Obtained when the service request was created

Responses

Request samples

Content type
application/json
{
  • "requestId": "string"
}

Response samples

Content type
application/json
{
  • "request": { },
  • "signature": "string"
}

Approve or reject an instruction

header Parameters
submitter-id
string
Request Body schema: application/json
requestId
required
string

Service Request ID

request
required
object

As a maker you don't need to modify this element. As an authoriser you need to explicitly specify the approval or rejection of the request

signature
required
string

Signature of the request field above

Responses

Request samples

Content type
application/json
{
  • "requestId": "string",
  • "request": { },
  • "signature": "MEUCIC3VIuw4pfk+BLnZrk1qklGS9phAlQFSQoAnlhw59x7cAiEAm5nq8ANlHcRNcONj5FXXl1v0EK5U8gZyQ22geFSsFL8="
}

Response samples

Content type
application/json
{
  • "timestamp": "2021-10-27T14:30:13.912Z",
  • "title": "Method Not Allowed",
  • "status": 415,
  • "details": [
    ]
}

List service requests

Search past and present service requests

header Parameters
submitter-id
string
Request Body schema: application/json
productIds
required
Array of strings

Search by product IDs

serviceIds
required
Array of strings

Search by service IDs

requestTypes
Array of strings

Search by request types i.e. CREATE, UPDATE, DEACTIVATE

requestIds
Array of strings

Search by request IDs

endToEndIds
Array of strings

Search by customer provided end to end IDs

statuses
Array of strings

Search by statuses. Refer to section Status Reference for the full list

modifiedAt
Array of strings
details
Array of strings
entityIds
Array of strings

Search by entity IDs. Possible values are wallet IDs, transactions IDs (not exhaustive)

companyIds
Array of strings
paginationLimit
integer <int32>

Number of items in response (default is 10)

paginationOffset
integer <int32>
object

Responses

Request samples

Content type
application/json
{
  • "productIds": [
    ],
  • "serviceIds": [
    ],
  • "requestTypes": [
    ],
  • "requestIds": [
    ],
  • "endToEndIds": [
    ],
  • "statuses": [
    ],
  • "modifiedAt": [
    ],
  • "details": [
    ],
  • "entityIds": [
    ],
  • "companyIds": [
    ],
  • "paginationLimit": 0,
  • "paginationOffset": 0,
  • "sort": {
    }
}

Response samples

Content type
application/json
{
  • "total": 0,
  • "items": [
    ]
}

Retrieve audit trail

header Parameters
submitter-id
string
Request Body schema: application/json
requestIds
Array of strings

Search by request IDs

paginationLimit
number

Number of items in response (default is 10)

paginationOffset
number
object

Responses

Request samples

Content type
application/json
{
  • "requestIds": [
    ],
  • "paginationLimit": 0,
  • "paginationOffset": 0,
  • "sort": {
    }
}

Response samples

Content type
application/json
[
  • {
    }
]

Beneficiary and Address Management

Retrieve authorised vasps, whitelisted beneficiaries and addresses.

Retrieve onboarded VASPs

header Parameters
submitter-id
string
Request Body schema: application/json
ids
Array of strings

Search by IDs of VASPs

names
Array of strings

Search by names of the VASPs

travelRuleMethods
Array of strings

Search by Travel Rule methods such as MANUAL, TRUST, etc.

paginationLimit
number

Number of items in response (default is 10)

paginationOffset
number
object

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ],
  • "names": [
    ],
  • "travelRuleMethods": [
    ],
  • "paginationLimit": 0,
  • "paginationOffset": 0,
  • "sort": {
    }
}

Response samples

Content type
application/json
{
  • "total": 0,
  • "items": [
    ]
}

Retrieve beneficiaries

header Parameters
submitter-id
string
Request Body schema: application/json
statuses
Array of strings

Search by statuses i.e. ACTIVE or DEACTIVATED

beneficiaryIds
Array of strings

Search by specific beneficiary IDs

beneficiaryNames
Array of strings

Search by beneficiary names

entityTypes
Array of strings

Search by type i.e. INDIVIDUAL or ORGANISATION

companyIds
Array of strings

Search by company IDs

paginationLimit
number

Number of items in response (default is 10)

paginationOffset
number
object

Responses

Request samples

Content type
application/json
{
  • "statuses": [
    ],
  • "beneficiaryIds": [
    ],
  • "beneficiaryNames": [
    ],
  • "entityTypes": [
    ],
  • "companyIds": [
    ],
  • "paginationLimit": 0,
  • "paginationOffset": 0,
  • "sort": {
    }
}

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "total": 0
}

Retrieve addresses

header Parameters
submitter-id
string
Request Body schema: application/json
statuses
Array of strings

Search by status i.e. ACTIVE or DEACTIVATED

beneficiaryIds
Array of strings

Search by beneficiary IDs

cryptoAddressIds
Array of strings

Search by address IDs

addresses
Array of strings

Search by on-chain addresses

blockchains
Array of strings

Search by blockchain i.e. BTC or ETH

vaspIds
Array of strings

Search by VASP IPs

addressPurposes
Array of strings

Search by purpose of address i.e. INCOMING, OUTGOING or both

hostedAddress
boolean

Specify if the address hosted or managed by a vasp

paginationLimit
number

Number of items in response (default is 10)

paginationOffset
number
object

Responses

Request samples

Content type
application/json
{
  • "statuses": [
    ],
  • "beneficiaryIds": [
    ],
  • "cryptoAddressIds": [
    ],
  • "addresses": [
    ],
  • "blockchains": [
    ],
  • "vaspIds": [
    ],
  • "addressPurposes": [
    ],
  • "hostedAddress": true,
  • "paginationLimit": 0,
  • "paginationOffset": 0,
  • "sort": {
    }
}

Response samples

Content type
application/json
[
  • {
    }
]

Retrieve transfer rules

header Parameters
submitter-id
string
Request Body schema: application/json
ids
Array of strings

Search by transfer rule IDs

walletIds
Array of strings

Search by wallet IDs on which transfer rules are applied

statuses
Array of strings
Items Enum: "ACTIVE" "DEACTIVATED"

Search by status

companyIds
Array of strings

Search by company IDs

enforcingCompanyIds
Array of strings

Search by enforcing company IDs

transferRuleTypes
Array of strings
Items Enum: "DESTINATION" "TRANSFER_AMOUNT"

Search by transfer rule types

createdBy
Array of strings

Search by transfer rule creator

updatedAt
string

Search by time of last update

paginationLimit
number

Number of items in response (default is 10)

paginationOffset
number

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ],
  • "walletIds": [
    ],
  • "statuses": [
    ],
  • "companyIds": [
    ],
  • "enforcingCompanyIds": [
    ],
  • "transferRuleTypes": [
    ],
  • "createdBy": [
    ],
  • "updatedAt": "2021-05-11T04:43:43Z",
  • "paginationLimit": 0,
  • "paginationOffset": 0
}

Response samples

Content type
application/json
[
  • {
    }
]

Custody

Retrieve information on custody wallets, balances and transfers.

Retrieve wallets

header Parameters
submitter-id
string
Request Body schema: application/json
ids
Array of strings

Search by wallet IDs

names
Array of strings

Search by wallet names

walletOwnerIds
Array of strings

Search by wallet owner IDs

statuses
Array of strings

Search by wallet status i.e. ACTIVE or DEACTIVATED

companyIds
Array of strings
currencies
Array of strings
createdBy
Array of strings
paginationLimit
number

Number of items in response (default is 10)

paginationOffset
number
object

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ],
  • "names": [
    ],
  • "walletOwnerIds": [
    ],
  • "statuses": [
    ],
  • "companyIds": [
    ],
  • "currencies": [
    ],
  • "createdBy": [
    ],
  • "paginationLimit": 0,
  • "paginationOffset": 0,
  • "sort": {
    }
}

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "total": 0
}

Retrieve an address for a wallet

Use for receiving deposits from external sources

header Parameters
submitter-id
string
Request Body schema: application/json
walletId
required
string

Generate/retrieve an address for a given wallet ID

currencyId
string

Currency ID for multi-ledger wallet; will throw '400' error if the currency is not supported by the wallet

subClient
string <= 128 characters

Use for validation; will throw an error if the sub-client is already existing on any wallet address under unified wallet

Responses

Request samples

Content type
application/json
{
  • "walletId": "string",
  • "currencyId": "string",
  • "subClient": "string"
}

Response samples

Content type
application/json
{
  • "address": "string",
  • "ledger": "string",
  • "walletId": "string",
  • "walletOwner": {
    },
  • "custodian": {
    },
  • "integrityDetails": {
    }
}

Returns list of generated addresses of wallets

header Parameters
submitter-id
string
Request Body schema: application/json
addresses
Array of strings

Search by on-chain addresses

walletIds
Array of strings

Search by wallet IDs

currencies
Array of strings

Search by currencies

paginationLimit
number

Number of items in response (default is 10)

paginationOffset
number
object

Responses

Request samples

Content type
application/json
{
  • "addresses": [
    ],
  • "walletIds": [
    ],
  • "currencies": [
    ],
  • "paginationLimit": 0,
  • "paginationOffset": 0,
  • "sort": {
    }
}

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "total": 0
}

Retrieve transactions

header Parameters
submitter-id
string
Request Body schema: application/json
walletIds
Array of strings

Search by wallet IDs

destinations
Array of strings

Search by destination values. Acceptable values are wallet ID, beneficiary/self address ID, Fireblocks connection ID and LNURL

statuses
Array of strings

Search by transfer status. Refer to section Status Reference for list of statuses

currencies
Array of strings

Search by currencies

amounts
Array of strings

Search transactions by ledger amounts

ledgerRefs
Array of strings

Search by transaction hash

companyIds
Array of strings

Search by company IDs

fromDate
string <date-time>

Search from a given date. Datetime format in ISO 8601

toDate
string <date-time>

Search to a given date. Datetime format in ISO 8601

paginationLimit
number

Number of items in response (default is 10)

paginationOffset
number
object

Responses

Request samples

Content type
application/json
{
  • "walletIds": [
    ],
  • "destinations": [
    ],
  • "statuses": [
    ],
  • "currencies": [
    ],
  • "amounts": [
    ],
  • "ledgerRefs": [
    ],
  • "companyIds": [
    ],
  • "fromDate": "2021-05-11T04:43:43Z",
  • "toDate": "2021-05-11T04:43:43Z",
  • "paginationLimit": 0,
  • "paginationOffset": 0,
  • "sort": {
    }
}

Response samples

Content type
application/json
{
  • "total": 0,
  • "items": [
    ]
}

Retrieve supported currencies

header Parameters
submitter-id
string

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Retrieve ledger fees per currency

header Parameters
submitter-id
string

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Retrieve fee details of a ledger

header Parameters
submitter-id
string

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Retrieve wallet balances

header Parameters
submitter-id
string
Request Body schema: application/json
walletId
required
string

Id of the wallet

hideZeroBalance
required
boolean

Hide addresses when their balances is 0. (Only available for UTXO wallets.)

Responses

Request samples

Content type
application/json
{
  • "walletId": "string",
  • "hideZeroBalance": true
}

Response samples

Content type
application/json
[
  • {
    }
]

Interchange

Retrieve information on trading wallets, vaults, balances and transfers.

Retrieve all wallets

header Parameters
submitter-id
string
Request Body schema: application/json
ids
Array of strings

Search by wallet IDs

venueIds
Array of strings

Search by venue IDs

names
Array of strings

Search by wallet names

statuses
Array of strings

Search by wallet status i.e. ACTIVE or DEACTIVATED

companyIds
Array of strings
currencies
Array of strings
createdBy
Array of strings
paginationLimit
number

Number of items in response (default is 10)

paginationOffset
number
object

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ],
  • "venueIds": [
    ],
  • "names": [
    ],
  • "statuses": [
    ],
  • "companyIds": [
    ],
  • "currencies": [
    ],
  • "createdBy": [
    ],
  • "paginationLimit": 0,
  • "paginationOffset": 0,
  • "sort": {
    }
}

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "total": 0
}

Retrieve all vaults

header Parameters
submitter-id
string
Request Body schema: application/json
ids
Array of strings

Search by vault IDs

venueIds
Array of strings

Search by venue IDs

names
Array of strings

Search by vault names

statuses
Array of strings

Search by vault status i.e. ACTIVE or DEACTIVATED

companyIds
Array of strings
createdBy
Array of strings
paginationLimit
number

Number of items in response (default is 10)

paginationOffset
number
object

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ],
  • "venueIds": [
    ],
  • "names": [
    ],
  • "statuses": [
    ],
  • "companyIds": [
    ],
  • "createdBy": [
    ],
  • "paginationLimit": 0,
  • "paginationOffset": 0,
  • "sort": {
    }
}

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "total": 0
}

Retrieve an address for a wallet

Use for receiving deposits from external sources

header Parameters
submitter-id
string
Request Body schema: application/json
walletId
string

Generate/retrieve an address for a given wallet ID

Responses

Request samples

Content type
application/json
{
  • "walletId": "string"
}

Response samples

Content type
application/json
{
  • "address": "string",
  • "ledger": "string",
  • "walletId": "string",
  • "walletOwner": {
    },
  • "custodian": {
    },
  • "integrityDetails": {
    }
}

Return list of generated addresses

header Parameters
submitter-id
string
Request Body schema: application/json
addresses
Array of strings

Search by on-chain addresses

walletIds
Array of strings

Search by wallet IDs

currencies
Array of strings

Search by currencies

paginationLimit
number

Number of items in response (default is 10)

paginationOffset
number
object

Responses

Request samples

Content type
application/json
{
  • "addresses": [
    ],
  • "walletIds": [
    ],
  • "currencies": [
    ],
  • "paginationLimit": 0,
  • "paginationOffset": 0,
  • "sort": {
    }
}

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "total": 0
}

Retrieve an address for a vault

Use for receiving deposits from external sources

header Parameters
submitter-id
string
Request Body schema: application/json
walletId
string

Generate/retrieve an address for a given vault ID

ledger
string

Generate/retrieve an address for a given vault with a given currency

Responses

Request samples

Content type
application/json
{
  • "walletId": "string",
  • "ledger": "string"
}

Response samples

Content type
application/json
{
  • "address": "string",
  • "ledger": "string",
  • "walletId": "string",
  • "walletOwner": {
    },
  • "custodian": {
    },
  • "integrityDetails": {
    }
}

Retrieve transactions

header Parameters
submitter-id
string
Request Body schema: application/json
ids
Array of strings

Search by transaction IDs

walletIds
Array of strings

Search by wallet IDs

statuses
Array of strings

Search by transfer status. Refer to section Status Reference for list of statuses

currencies
Array of strings

Search by currencies

amounts
Array of strings

Search transactions by ledger amounts

ledgerRefs
Array of strings

Search by transaction hash

companyIds
Array of strings

Search by company IDs. If you are a venue use this field to filter per client

paginationLimit
number

Number of items in response (default is 10)

paginationOffset
number
object

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ],
  • "walletIds": [
    ],
  • "statuses": [
    ],
  • "currencies": [
    ],
  • "amounts": [
    ],
  • "ledgerRefs": [
    ],
  • "companyIds": [
    ],
  • "paginationLimit": 0,
  • "paginationOffset": 0,
  • "sort": {
    }
}

Response samples

Content type
application/json
{
  • "total": 0,
  • "items": [
    ]
}

Reporting

Get Staking Report Data.

Get Staking Report Data

Available staking Report Data

header Parameters
submitter-id
string
Request Body schema: application/json
walletIds
Array of strings

List of wallet id

fromDate
required
string

Earliest date of data returned

toDate
required
string

Latest date of data returned

currency
required
string

Currency of the validator

voteAddresses
Array of strings

(For Solana only) List of vote address the stake account is delegated to

Responses

Request samples

Content type
application/json
{
  • "walletIds": [
    ],
  • "fromDate": "string",
  • "toDate": "string",
  • "currency": "string",
  • "voteAddresses": [
    ]
}

Response samples

Content type
application/json
[
  • {
    }
]

Wallets

Return all wallets

Responses

Response samples

Content type
application/json
{
  • "wallets": [
    ]
}

Create new wallet

Request Body schema: application/json
name
required
string [ 1 .. 128 ] characters ^[a-zA-Z0-9 -]+$

Display name of the wallet

currency
required
string

Currency of the wallet

beneficiaryId
string [ 14 .. 32 ] characters

ID of the beneficiary of the wallet

submitterEmail
required
string

Mail of the user creating the wallet

walletId
required
string [ 1 .. 40 ] characters <companyId>-[NOBENF|<beneficiaryId>]-API-*

ID of the wallet

Responses

Request samples

Content type
application/json
{
  • "name": "Demo wallet",
  • "currency": "string",
  • "beneficiaryId": "BNF-ZTEST-0DFUSJPF7A",
  • "submitterEmail": "[email protected]",
  • "walletId": "ZTEST-NOBENF-849P8XKKAO"
}

Response samples

Content type
application/json
{
  • "id": "string"
}

Return a single wallet by ID

path Parameters
walletId
required
string

Responses

Response samples

Content type
application/json
{
  • "walletId": "ZTEST-NOBENF-849P8XKKAO",
  • "walletName": "Demo wallet",
  • "walletCompany": "ZTEST",
  • "createdBy": "[email protected]",
  • "status": "ACTIVE",
  • "valid": true,
  • "registeredComplianceKey": true,
  • "blockchain": "string",
  • "assetBalances": [
    ],
  • "createdAt": "2021-08-03T18:17:57Z",
  • "updatedAt": "2021-08-03T18:17:57Z"
}

Return a wallet address for a given currency

path Parameters
walletId
required
string

Responses

Response samples

Content type
application/json
{
  • "address": "string",
  • "ledger": "string",
  • "walletId": "string",
  • "walletOwner": {
    },
  • "custodian": {
    },
  • "integrityDetails": {
    }
}

Get the payload to create new wallet

path Parameters
walletId
required
string

Responses

Response samples

Content type
application/json
{
  • "request": {
    },
  • "signature": "MEUCIC3VIuw4pfk+BLnZrk1qklGS9phAlQFSQoAnlhw59x7cAiEAm5nq8ANlHcRNcONj5FXXl1v0EK5U8gZyQ22geFSsFL8="
}

Submit the payload to create new wallet

path Parameters
walletId
required
string
Request Body schema: application/json
required
object

Details of the wallet being created

signature
required
string

Signature of the "request" field above

Responses

Request samples

Content type
application/json
{
  • "request": {
    },
  • "signature": "MEUCIC3VIuw4pfk+BLnZrk1qklGS9phAlQFSQoAnlhw59x7cAiEAm5nq8ANlHcRNcONj5FXXl1v0EK5U8gZyQ22geFSsFL8="
}

Response samples

Content type
application/json
{
  • "id": "string"
}

Get the payload to approve/reject new wallet

path Parameters
walletId
required
string
approverId
required
string

Responses

Response samples

Content type
application/json
{
  • "request": {
    },
  • "signature": "MEUCIC3VIuw4pfk+BLnZrk1qklGS9phAlQFSQoAnlhw59x7cAiEAm5nq8ANlHcRNcONj5FXXl1v0EK5U8gZyQ22geFSsFL8="
}

Submit the payload to approve/reject new wallet

path Parameters
walletId
required
string
Request Body schema: application/json
required
object

Details of the request being approved/rejected

signature
required
string

Signature of the "request" field above

Responses

Request samples

Content type
application/json
{
  • "request": {
    },
  • "signature": "MEUCIC3VIuw4pfk+BLnZrk1qklGS9phAlQFSQoAnlhw59x7cAiEAm5nq8ANlHcRNcONj5FXXl1v0EK5U8gZyQ22geFSsFL8="
}

Response samples

Content type
application/json
{
  • "id": "string"
}

Get incoming transactions by wallet ID

path Parameters
walletId
required
string
query Parameters
limit
integer [ 1 .. 1024 ]
Default: 10

Number of results per request

from
string
Examples:
  • from=2021-03-29T11:34:53Z - ISO-8601 date/time in UTC
  • from=2021-03-29 - ISO-8601 date

Start and inclusive date/time of the query

to
string
Examples:
  • to=2021-03-29T11:34:53Z - ISO-8601 date/time in UTC
  • to=2021-03-29 - ISO-8601 date

End and inclusive date/time of the query

Responses

Response samples

Content type
application/json
{
  • "transfers": [
    ]
}

Get outgoing transactions by wallet ID

path Parameters
walletId
required
string
query Parameters
limit
integer [ 1 .. 1024 ]
Default: 10

Number of results per request

from
string
Examples:
  • from=2021-03-29T11:34:53Z - ISO-8601 date/time in UTC
  • from=2021-03-29 - ISO-8601 date

Start and inclusive date/time of the query

to
string
Examples:
  • to=2021-03-29T11:34:53Z - ISO-8601 date/time in UTC
  • to=2021-03-29 - ISO-8601 date

End and inclusive date/time of the query

Responses

Response samples

Content type
application/json
{
  • "transfers": [
    ]
}

Transactions

Create new outgoing transaction

Request Body schema: application/json
walletId
required
string [ 1 .. 40 ] characters

id of the wallet you want to use

transactionId
required
string [ 1 .. 40 ] characters

id of transaction which will be created

amount
required
string

amount to send in Unit

currency
string
Default: "the wallet currency"

currency of the transfer

subtractFee
required
boolean

subtract fee

recipientId
required
string

to address

description
string

a quick description of the transaction

submitterId
required
string

email of submitter

Responses

Request samples

Content type
application/json
{
  • "walletId": "ZTEST-NOBENF-849P8XKKAO",
  • "transactionId": "TRI-ZTEST-63Q4DZ0KJP",
  • "amount": "100",
  • "currency": "eth",
  • "subtractFee": true,
  • "recipientId": "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt",
  • "description": "First transaction",
  • "submitterId": "[email protected]"
}

Response samples

Content type
application/json
{
  • "id": "string"
}

Get the payload to create new outgoing transaction

path Parameters
transactionId
required
string

Responses

Response samples

Content type
application/json
{
  • "request": {
    },
  • "signature": "MEUCIC3VIuw4pfk+BLnZrk1qklGS9phAlQFSQoAnlhw59x7cAiEAm5nq8ANlHcRNcONj5FXXl1v0EK5U8gZyQ22geFSsFL8="
}

Submit the payload to create new outgoing transaction

Request Body schema: application/json
required
object

Details of the transaction being created

signature
required
string

Signature of the "request" field above

Responses

Request samples

Content type
application/json
{
  • "request": {
    },
  • "signature": "MEUCIC3VIuw4pfk+BLnZrk1qklGS9phAlQFSQoAnlhw59x7cAiEAm5nq8ANlHcRNcONj5FXXl1v0EK5U8gZyQ22geFSsFL8="
}

Response samples

Content type
application/json
{
  • "id": "string"
}

Get the payload to approve/reject new outgoing transaction

path Parameters
transactionId
required
string
approverId
required
string

Responses

Response samples

Content type
application/json
{
  • "request": {
    },
  • "signature": "MEUCIC3VIuw4pfk+BLnZrk1qklGS9phAlQFSQoAnlhw59x7cAiEAm5nq8ANlHcRNcONj5FXXl1v0EK5U8gZyQ22geFSsFL8="
}

Submit the payload to approve/reject new outgoing transaction

Request Body schema: application/json
required
object

Details of the request being approved/rejected

signature
required
string

Signature of the "request" field above

Responses

Request samples

Content type
application/json
{
  • "request": {
    },
  • "signature": "MEUCIC3VIuw4pfk+BLnZrk1qklGS9phAlQFSQoAnlhw59x7cAiEAm5nq8ANlHcRNcONj5FXXl1v0EK5U8gZyQ22geFSsFL8="
}

Response samples

Content type
application/json
{
  • "id": "string"
}

Get an outgoing transaction by ID

path Parameters
transactionId
required
string

Responses

Response samples

Content type
application/json
{
  • "companyId": "ZTEST",
  • "fromWallet": "ZTEST-NOBENF-849P8XKKAO",
  • "toAddress": "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt",
  • "consumerMetadata": "metadata",
  • "transferId": "TRO-API-ZTEST-4001",
  • "transferStatus": "CONFIRMED",
  • "amountLedger": 641400,
  • "feeType": "HIGH",
  • "feeAmountLedger": 165,
  • "ledgerRef": "fd1c22fd097501606fddbc7d22e5009d45e9e6cb7115ad109a46f8319a385bd4",
  • "createdBy": "[email protected]",
  • "createdAt": "2021-08-03T18:17:57Z",
  • "currency": "string",
  • "feeCurrency": "string"
}

Return transactions statistics

query Parameters
from
string
Examples:
  • from=2021-03-29T11:34:53Z - ISO-8601 date/time in UTC
  • from=2021-03-29 - ISO-8601 date

Start and inclusive date/time of the query

to
string
Examples:
  • to=2021-03-29T11:34:53Z - ISO-8601 date/time in UTC
  • to=2021-03-29 - ISO-8601 date

End and inclusive date/time of the query

Responses

Response samples

Content type
application/json
{
  • "from": "2021-08-03T18:17:57Z",
  • "to": "2021-08-03T18:17:57Z",
  • "transferOutStats": {
    },
  • "transferInStats": {
    }
}

Get an incoming transaction by ID

path Parameters
transactionId
required
string

Responses

Response samples

Content type
application/json
{
  • "companyId": "ZTEST",
  • "fromAddress": [
    ],
  • "toWallet": "ZTEST-NOBENF-849P8XKKAO",
  • "toAddress": "2Mz5ZH9e6Q53QfK2E1Azaos3Vq62niibhc1",
  • "transferId": "TRI-ZTEST-63Q4DZ0KJP",
  • "transferStatus": "PENDING_UNLOCK",
  • "amountLedger": 641400,
  • "ledgerRef": "2f6e77f8c4313d0925f71acf19a659398115bdf903e3af06b3b1776e756c096f",
  • "actionId": "TX_IN_ACTION-ZTEST-7LE2JCPZ3D",
  • "method": "TRANSFER",
  • "createdAt": "2021-08-03T18:17:57Z",
  • "currency": "string"
}

Miscellaneous

Ping GET

Responses

Response samples

Content type
application/json
{
  • "id": "ZODIA",
  • "from": "ZTEST",
  • "date": "2021-08-03T18:17:57Z",
  • "timeUTC": 1620708223000
}

Network Fees

Get the estimated fee rate by currency

path Parameters
currency
required
string

Currency

Responses

Response samples

Content type
application/json
{
  • "ledgerId": "BTC",
  • "ledgerAmount": "1"
}

Retrieve fee details of a ledger

header Parameters
submitter-id
string

Responses

Response samples

Content type
application/json
[
  • {
    }
]