First Steps as an API User

In this guide, we'll walk you through the essential processes to perform authenticated requests on Ledger Enterprise APIs as an API Operator. Follow these steps to obtain the necessary authentication token and explore some common API functionalities.

Step 1: Obtain Authentication Token

To access Ledger Enterprise Apis as an API Operator, you need to obtain an authentication token by making a POST request to the authentication endpoint. Use the following example:

bashjson
Copy
Copied
curl --request POST \
  --url https://api.vault.ledger.com/auth/token \
  --header 'content-type: application/json' \
  --header 'x-ledger-workspace: minivault' \
  --data '{"api_key_id": "yqjrwdngm15ml12u4opscq","api_key_secret": "Hc5mFFhnpeMoj6kZsCQXOphPMawIWAGnUxE8bvw5Kv0"}'
Copy
Copied
{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ4ek5yelNjeVNjM0J4RlhjaklaNWNtV0paSG1jRUhVU2VGVExKTXVwbl9jIn0.eyJleHAiOjE3MDU1MDQ1NzksImlhdCI6MTcwNTUwNDI3OSwianRpIjoiZDQwY2E2MzUtN2VjYS00ZmRkLTgwMWItNDY0YmZmMzBkZjI4IiwiaXNzIjoiaHR0cHM6Ly9ibHVlLWJhZGdlci00MzMubWluaXZhdWx0LmxlZGdlci1zYnguY29tL2tleWNsb2FrL3JlYWxtcy9taW5pdmF1bHQiLCJzdWIiOiJmOTczOTJlZC1hNjVmLTQ4ZGQtYjkwZi03NTk2NGFiN2ZjNmYiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJyZXZhdWx0Iiwic2Vzc2lvbl9zdGF0ZSI6ImY5Yjk1Nzk5LTM1NTMtNDQ5ZC1hNGZkLTIxNDYzNzY1Mjc5YiIsInNjb3BlIjoiIiwic2lkIjoiZjliOTU3OTktMzU1My00NDlkLWE0ZmQtMjE0NjM3NjUyNzliIiwid29ya3NwYWNlIjoibWluaXZhdWx0IiwicHViX2tleSI6IjA0NzAwZTUwOGM4NTdhYTg5ZDlmZmZjNmViNzJiZjZmMjVkYzdiMGM1NGIxMzc4OTIxYmJiYjY3MWUzY2U3Y2M0YmU3ZWRkOGExOTgyYmM5MjkyMGY0NjY5MzFmOWYzN2Y2NTBjMDcwZDc5ZGVlMWI4OTZmZjQwODdmNzg5MDk4MDAifQ.MxTA8JkgJbNyHevZTihdUSthCZlezu7lUAsa09invGy5tWXO56lTXAiNRXc2rQB4AwljzrT1RUrCTIkT1ZGe36GUt3ganJOAiQzFUPdjm-lJC0GC14dHI0CLcP6tJMKR6ISBUkermdwPXmuWRrb8wAlH3b7kXVE-2nsEl5G5oPSqcwIPgie0tSwecg_pP0HNGtX0aoRS7dfEj6dreRC21Gxt5Sy2BtF3u1LltfgiOmoHvTGZkpz3xNektUdyDNIPke0flcOS12t1OakuoxA6Vgh7UyRamwU30-WjoYEZT_073b0Y6mKlzBYZ6i61vnMaS_1KNjPlovqeraIlTDs4Pw",
    "expires_in": 300,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1MjA3Y2IwZC1mZmMwLTRiY2MtYjg1Mi1mYjIyNTFkODA2NDUifQ.eyJleHAiOjE3MDU1MDYwNzksImlhdCI6MTcwNTUwNDI3OSwianRpIjoiZDVmZGE0YWQtZmU5MC00NDA4LWFhMTAtODgwNzhkYzg2YzdiIiwiaXNzIjoiaHR0cHM6Ly9ibHVlLWJhZGdlci00MzMubWluaXZhdWx0LmxlZGdlci1zYnguY29tL2tleWNsb2FrL3JlYWxtcy9taW5pdmF1bHQiLCJhdWQiOiJodHRwczovL2JsdWUtYmFkZ2VyLTQzMy5taW5pdmF1bHQubGVkZ2VyLXNieC5jb20va2V5Y2xvYWsvcmVhbG1zL21pbml2YXVsdCIsInN1YiI6ImY5NzM5MmVkLWE2NWYtNDhkZC1iOTBmLTc1OTY0YWI3ZmM2ZiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJyZXZhdWx0Iiwic2Vzc2lvbl9zdGF0ZSI6ImY5Yjk1Nzk5LTM1NTMtNDQ5ZC1hNGZkLTIxNDYzNzY1Mjc5YiIsInNjb3BlIjoiIiwic2lkIjoiZjliOTU3OTktMzU1My00NDlkLWE0ZmQtMjE0NjM3NjUyNzliIn0.jzPFvjntSSXCwAyGuADwDJLTgFcIlEwZoiCcN8TxEno",
    "session_state": "f9b95799-3553-449d-a4fd-21463765279b",
    "token_type": "Bearer"
}

This request returns an access_token, which you will use in subsequent authenticated requests.

Step 2: List Accounts

Now that you have the access token, you can use it to list the accounts you have access to of this workspace. Make a GET request to the search accounts endpoint:

bashjson
Copy
Copied
curl --request GET \
  --url https://api.vault.ledger.com/accounts \
  --header 'authorization: Bearer {{access_token}}' \
  --header 'content-type: application/json' \
  --header 'x-ledger-workspace: minivault'
Copy
Copied
{
  "edges": [
    {
      "cursor": 0,
      "node": {
        "address": null,
        "available_balance": "5719662",
        "balance": "5719662",
        "coin_fields": null,
        "contract_address": null,
        "created_by": 5,
        "created_on": "2023-12-13T20:27:18.887023+00:00",
        "currency": "bitcoin_testnet",
        "derivation_path": "84'/1'/3'",
        "governance_rules": [
        ...
        ],
        "id": 3,
        "index": 3,
        "labels": [],
        "last_request": 24,
        "name": "Exchange TBTC",
        "parent": null,
        "pending_balance": "0",
        "status": "ACTIVE",
        "type": "Bitcoin",
        "xpub": null
      }
    },
    {
      "cursor": 1,
      "node": {
        "address": null,
        "available_balance": "20298",
        "balance": "20298",
        "coin_fields": null,
        "contract_address": null,
        "created_by": 5,
        "created_on": "2023-12-13T20:26:57.395657+00:00",
        "currency": "bitcoin_testnet",
        "derivation_path": "84'/1'/1'",
        "governance_rules": [
        ...
        ],
        "id": 2,
        "index": 1,
        "labels": [],
        "last_request": 23,
        "name": "ClientA COL TBTC 1",
        "parent": null,
        "pending_balance": "0",
        "status": "ACTIVE",
        "type": "Bitcoin",
        "xpub": null
      }
    }
  ],
  "page_info": {
    "count": 2,
    "has_next_page": false
  }
}

This request returns a list of accounts along with their details, including balance, currency, and status. This endpoint is paginated, to learn more about how pagination work please refer the the API documentation.

Step 3: Create a Transaction

To create a transaction,

Building your request payload:

To build a payload to create a new outgoing transaction request, we just need to know:

  • the sender account, in our case let's use one account from the previous payload; the account named ClientA COL TBTC 1 , id 2 .
  • the recipient address , let's use tb1q5tsjcyz7xmet07yxtumakt739y53hcttmntajq
  • the amount we want to send, let's say 2 tBTC

We will need to create CREATE_TRANSACTION request type for the requests endpoint. Then in data we will be passing the transaction intention following the specifications depending on each type of transaction, since we are using a bitcoin testnet account, we need to be follwing the "transaction_type": "BITCOIN_LIKE_SEND",, we know this from the account object we received earlier of "type": "Bitcoin",.

You can check the requests endpoint on how to specify the fees_strategy for each transaction_type. In our case we will follow the standard fee priotity model, FAST, to make sure our transaction is processed quickly by the node.

We also need to build up the transaction_data object, in this object you will specify more precisly your intention, it's redondent yet it allows a better security validation of the challenge later on. In our case we need to :

  • A currency should be the same as the one in your account in this case bitcoin_testnet , this allow the HSM to validate he is signing for the good derivation path and prevent an attacker to switch this derivation path to your main bitcoin account without you knowing.
  • Sender account_name wich is the unique identifier of your account on the HSM, in our case ClientA COL TBTC 1
  • A max_fees it's the highest amount of fees in the blockchain asset your are willing to pay for in this transaction, this is used to assert the transaction at a signature level when HSM sees that the crafted Tx is spending more in fees that what you specify here. And since fees can vary from the type you create the tx to the time it get signed by all of the other operator, it's generally good to include a buffer, the estimate fees endpoint and following section help you select this value. You can also set your own based on the max amount you are willing to pay it's acting as an extra security
  • The recipient , use the address you want to send BTC to tb1q5tsjcyz7xmet07yxtumakt739y53hcttmntajq
  • The amount you want to send in the smallest unit of this asset. You can use the get currency by name endpoint to get all of the information about the currency you are using. To convert your 2tBTC in tsat you need to get the magnitude of the currency bitcoin_testnet . In our case the tBTC magnitude is 8 it means that I need to multiply the amount in tBTC by this magnitude to get the amount for this payload in satoshis (tsat) so 2 * 10^8 = 200000000 .

The resulting request payload is the following:

Copy
Copied
{
  "data": {
    "account_id": "2",
    "type": "CREATE_TRANSACTION",
    "transaction_data": {
      "account_name": "ClientA COL TBTC 1",
      "amount": "200000000",
      "currency": "bitcoin_testnet",
      "max_fees": ""{{max_fees_from_estimate_fees_endpoint}}"",
      "recipient": "tb1q5tsjcyz7xmet07yxtumakt739y53hcttmntajq"
    },
    "transaction_type": "BITCOIN_LIKE_SEND"
  },
  "fees_strategy": {
      "data": {
        "speed": "FAST"
      },
      "type": "SPEED"      
    }
}

Estimate & define fees:

The use the same payload to compute the recomended fees and maxfees by calling the [transactions/estimate-fees endpoint](/openapi/leapi/tag/Transactions/paths/~1transactions~1estimate-fees/post/) endpoint. You can also define custom fees and advanced options please refer to the fees & speed section of the help center for more informations.

bashjson
Copy
Copied
curl --request POST \
  --url https://api.vault.ledger.com/transactions/estimate-fees \
  --header 'authorization: Bearer {{access_token}}' \
  --header 'content-type: application/json' \
  --header 'x-ledger-workspace: minivault' \
  --data '{
    "account_id": "2",
    "type": "CREATE_TRANSACTION",
    "transaction_data": {
      "account_name": "ClientA COL TBTC 1",
      "amount": "200000000",
      "currency": "bitcoin_testnet",
      "max_fees": ""{{max_fees_from_estimate_fees_endpoint}}"",
      "recipient": "tb1q5tsjcyz7xmet07yxtumakt739y53hcttmntajq"
    },
    "transaction_type": "BITCOIN_LIKE_SEND"
  },
  "fees_strategy": {
      "data": {
        "speed": "FAST"
      },
      "type": "SPEED"
            }
        }
    }
}'
Copy
Copied
{
  "coin_fields": {
    "UTXO_limit": 150,
    "fees_per_byte": 0,
    "type": "Bitcoin"
  },
  "max_fees": "211",
  "max_fees_buffer_factor": 1.5
}

If the response status is 200, the response payload will have the attribute max_fees, you can use it in your inital payload, for the following step we will refer to this value with max_fees_from_estimate_fees_endpoint.

POST Create Transaction request:

Then create the actual transaction request with the same payload except that the max_fees value must be relevant, use the one extracted in the previous step.

bashjson
Copy
Copied
curl --request POST \
  --url https://api.vault.ledger.com/requests \
  --header 'authorization: Bearer {{access_token}}' \
  --header 'content-type: application/json' \
  --header 'x-ledger-workspace: minivault' \
  --data '{
    "account_id": "2",
    "type": "CREATE_TRANSACTION",
    "transaction_data": {
      "account_name": "ClientA COL TBTC 1",
      "amount": "200000000",
      "currency": "bitcoin_testnet",
      "max_fees": ""{{max_fees_from_estimate_fees_endpoint}}"",
      "recipient": "tb1q5tsjcyz7xmet07yxtumakt739y53hcttmntajq"
    },
    "transaction_type": "BITCOIN_LIKE_SEND"
  },
  "fees_strategy": {
      "data": {
        "speed": "FAST"
      },
      "type": "SPEED"
            }
        }
    }
}'
Copy
Copied
{
  "created_by": {
    "id": 24
  },
  "created_on": "2024-01-24T08:07:08.007892+00:00",
  "expired_at": "2024-01-31T08:07:08.007837+00:00",
  "id": 29,
  "status": "PENDING_APPROVAL",
  "target_id": 305,
  "target_type": "BITCOIN_LIKE_TRANSACTION",
  "type": "CREATE_TRANSACTION"
}

Once the transaction is created, it can be approved by the different opeators in the transaction rules. Transaction rules can vary from an account to the other. It'll affect how you create transactions as the amount per transaction can be limited (Amount ranges) as well as the possible list of allowed recipients (Whitelisted addresses). To find out which rules have been defined in an account, go to Accounts > Account dashboard > Rules tab.

Traget Id

The attribute target_id of a request in our example of type CREATE_TRANSACTION provides the id of the transaction.

Step 4: Approve a request

Once you created your transaction request following step 3, you need to approve the challenge as the request creator before other operator are notified to approve. To approve a request, you'll need to follow these steps:

Get the challenge

Given a request_id, first need to fetch the HSM challenge :

bashjson
Copy
Copied
curl --request GET \
  --url https://api.vault.ledger.com/requests/{{request_id}}/challenge \
  --header 'authorization: Bearer {{access_token}}' \
  --header 'content-type: application/json' \
  --header 'x-ledger-workspace: minivault'
Copy
Copied
{
"challenge": "eyJhbnRpcmVwbGF5IjoiM0JCMzdERkIxREYxRkQ5REUzNEZDQUZCQkVGNjZDQjVDMUVEMTU3MzEzNTRGQjAxNDZEQ0MyMjk3NjdEMzVCMSIsImRhdGEiOnsidHJhbnNhY3Rpb25fZGF0YSI6eyJhY2NvdW50X25hbWUiOiJDbGllbnRBIENPTCBUQlRDIDEiLCJhbW91bnQiOiIxMDAiLCJjdXJyZW5jeSI6ImJpdGNvaW4gdGVzdG5ldCIsIm1heF9mZWVzIjoiMjExIiwicmVjaXBpZW50IjoidGIxcTl3ZXh3OXB6amtsajd5cWxkNmczbGVqeHpyMHdndndoengwbmVrIn0sInRyYW5zYWN0aW9uX3R5cGUiOiJCSVRDT0lOX0xJS0VfU0VORCJ9LCJ0eXBlIjoiQVBQUk9WRV9UUkFOU0FDVElPTiJ9",
"id": 29
}

Decoding the challenge

Decode the challenge to validate that the instructions HSM received is the same as the one you passed in your request:

PythonJavascriptExample
Copy
Copied
import jwt

// decode the challenge
challenge_data_bytes = jwt.utils.base64url_decode(challenge)
Copy
Copied
import * as crypto from 'crypto';
import * as jwt from 'jsonwebtoken';

// decode the challenge
const decodedString = atob(dataToSign);
const jsonObject = JSON.parse(decodedString);
Copy
Copied
{
  "antireplay": "3BB37DFB1DF1FD9DE34FCAFBBEF66CB5C1ED15731354FB0146DCC229767D35B1",
  "data": {
      "transaction_data": {
          "account_name": "ClientA COL TBTC 1",
          "amount": "200000000",
          "currency": "bitcoin testnet",
          "max_fees": "211",
          "recipient": "tb1q9wexw9pzjklj7yqld6g3lejxzr0wgvwhzx0nek"
      },
      "transaction_type": "BITCOIN_LIKE_SEND"
  },
  "type": "APPROVE_TRANSACTION"
}
warning

This is an important step, when decoding the challenge from the HSM this need to be the exact same infromation the transaction creator passed in transaction_data.

Carefully review that what you are about to sign is the right instructions matching your inital intention. This challenge is the trusted data signed by your operator that will allow the HSM to sign the true transaction. For example, if you see that the recipient is not the same address you requested this is a sign that there is a potential man in the middle attack, regardless of what the request was the true singed transaction will be send to the recipient from this challenge. Don't trust, verify.

Signing the challnge

Sign the challenge with your user private key

PythonJavascriptExample
Copy
Copied
import jwt

def sign_challenge(challenge: bytes, private_key_hex: str) -> str:
    private_key_bytes = bytes.fromhex(private_key_hex)
    challenge_data_bytes = jwt.utils.base64url_decode(challenge)
    jws = jwt.PyJWS()
    jws: str = jws.encode(challenge_data_bytes, private_key_bytes, algorithm="ES256")
    return jws
Copy
Copied
import * as crypto from 'crypto';
import * as jwt from 'jsonwebtoken';

// // Data to be signed
const challenge = "eyJhbnRpcmVwbGF5IjoiM0JCMzdERkIxREYxRkQ5REUzNEZDQUZCQkVGNjZDQjVDMUVEMTU3MzEzNTRGQjAxNDZEQ0MyMjk3NjdEMzVCMSIsImRhdGEiOnsidHJhbnNhY3Rpb25fZGF0YSI6eyJhY2NvdW50X25hbWUiOiJDbGllbnRBIENPTCBUQlRDIDEiLCJhbW91bnQiOiIxMDAiLCJjdXJyZW5jeSI6ImJpdGNvaW4gdGVzdG5ldCIsIm1heF9mZWVzIjoiMjExIiwicmVjaXBpZW50IjoidGIxcTl3ZXh3OXB6amtsajd5cWxkNmczbGVqeHpyMHdndndoengwbmVrIn0sInRyYW5zYWN0aW9uX3R5cGUiOiJCSVRDT0lOX0xJS0VfU0VORCJ9LCJ0eXBlIjoiQVBQUk9WRV9UUkFOU0FDVElPTiJ9"

// Json to be sign
const decodedString = atob(challenge);
const jsonObject = JSON.parse(decodedString);
console.log("----- Json to be signed ----")
console.log(jsonObject);
console.log("----------------------------")

// Load the private key
const private_key_bytes = `-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEILMAPH5tb765vxNGiAmAaeBvFZnRtzTqfG/ZWQHXxv1NoAoGCCqGSM49\nAwEHoUQDQgAE5iRVj/FjRl3RLNmY0E8os9nyo/lxsII/WiAgNMq8z7tGFZ+G55g4\nqQwYCbfDE9in554X5KVTJqn2EDFl95QNPA==\n-----END EC PRIVATE KEY-----`
const privateKey = crypto.createPrivateKey({
  key: private_key_bytes,
  format: 'pem'
});
const privateKeyPem = privateKey.export({ format: 'pem', type: 'sec1' });
console.log('\nLoaded Private Key (PEM/SEC1):\n', privateKeyPem);

// Sign the data using the private key
const decodedData = Buffer.from(challenge, 'base64').toString('hex');
const jws = jwt.sign(Buffer.from(decodedData, 'hex'), privateKeyPem, {
  algorithm: 'ES256',
  header: {
    alg: 'ES256',
    typ: 'JWT'
  }
});

console.log("----- JWS Token ----")
console.log(jws);
console.log("--------------------")
Copy
Copied
yJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhbnRpcmVwbGF5IjoiM0JCMzdERkIxREYxRkQ5REUzNEZDQUZCQkVGNjZDQjVDMUVEMTU3MzEzNTRGQjAxNDZEQ0MyMjk3NjdEMzVCMSIsImRhdGEiOnsidHJhbnNhY3Rpb25fZGF0YSI6eyJhY2NvdW50X25hbWUiOiJDbGllbnRBIENPTCBUQlRDIDEiLCJhbW91bnQiOiIxMDAiLCJjdXJyZW5jeSI6ImJpdGNvaW4gdGVzdG5ldCIsIm1heF9mZWVzIjoiMjExIiwicmVjaXBpZW50IjoidGIxcTl3ZXh3OXB6amtsajd5cWxkNmczbGVqeHpyMHdndndoengwbmVrIn0sInRyYW5zYWN0aW9uX3R5cGUiOiJCSVRDT0lOX0xJS0VfU0VORCJ9LCJ0eXBlIjoiQVBQUk9WRV9UUkFOU0FDVElPTiJ9.F4NFOkcoQaAdxRwLQ_-QUal-XbBNhg-yjiTW_ycLmqA-wLrmpRpJ0owxjcf_QAkLyxWlkGpxH9gaymvsAl2KDA

with privatekeybytes the PEM format bytes of the private key

Copy
Copied
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDN4pKvYZtwGLC/XiUKdUjPJJGTRd1MQxVKsiaWEAY1OoAoGCCqGSM49
AwEHoUQDQgAEdcIngZ7X7X5sipnIuP3rt1w6mg3V9LQE4txm5cx0tvaDxon+W6Kx
CBtTMvPCR5D9a9Nab2cNEjvKePWyzOHqMg==
-----END EC PRIVATE KEY-----
JSON Web Signature details

And jws the signed challenge / jws object. The resulting jws consists of three parts: the JWS Header, the JWS Payload, and the JWS Signature. The three parts are base64url-encoded and concatenated in that order being separated by period ('.') characters.

Approve the request

Once you signed the challenge use it to post your approval on the approve request endpoint:

bashjson
Copy
Copied
curl --request POST \
  --url https://api.vault.ledger.com/requests/{{request_id}}/approve \
  --header 'authorization: Bearer {{access_token}}' \
  --header 'content-type: application/json' \
  --header 'x-ledger-workspace: <workspace name>' \
  --data '{
      "jws": "{{jws}}"
  }'
Copy
Copied
{
"created_by": {
  "id": 24
},
"created_on": "2024-01-24T08:07:08.007892+00:00",
"expired_at": "2024-01-31T08:07:08.007837+00:00",
"id": 29,
"status": "PENDING_APPROVAL",
"target_id": 305,
"target_type": "BITCOIN_LIKE_TRANSACTION",
"type": "CREATE_TRANSACTION"
}

If the response status is 200, the response payload will be the request with its new status, in our case we are still waiting for another approval from a second user so the status stays in PENDING_APPROVAL.

Step 5: Additional Functionalities

Explore additional functionalities, such as getting an account balance, subscribing to Vault events via web-hooks, getting request info, and getting the number of request approvers. Refer to the provided examples for detailed instructions.

5.1. How to Get an Account Balance

To retrieve the balance of a specific account, you can make a GET request to either of the following endpoints:

Get Account & balances

To get an account balance, one can either call the get account by ID and extract balance or available_balance

bashjson
Copy
Copied
curl --request GET \
  --url https://api.vault.ledger.com/accounts/{{account_id}} \
  --header 'authorization: Bearer {{access_token}}' \
  --header 'content-type: application/json' \
  --header 'x-ledger-workspace: minivault'
Copy
Copied
{
  "address": null,
  "available_balance": "9981",
  "balance": "10292",
  "coin_fields": null,
  "contract_address": null,
  "created_by": 5,
  "created_on": "2024-01-24T07:36:17.591696+00:00",
  "currency": "bitcoin_testnet",
  "derivation_path": "84'/1'/1'",
  "governance_rules": [
    {
      "id": 2,
      "index": 0,
      "name": "Rule 1",
      "rules": [
        {
          "data": [
            {
              "group": 1,
              "quorum": 1
            },
            {
              "group": 2,
              "quorum": 1
            },
            {
              "group": 3,
              "quorum": 1
            }
          ],
          "id": 2,
          "type": "MULTI_AUTHORIZATIONS"
        }
      ]
    },
    {
      "id": 3,
      "index": 1,
      "name": "Rule 2",
      "rules": [
        {
          "data": [
            {
              "group": 2,
              "quorum": 1
            },
            {
              "group": 1,
              "quorum": 1
            },
            {
              "group": 3,
              "quorum": 1
            }
          ],
          "id": 3,
          "type": "MULTI_AUTHORIZATIONS"
        }
      ]
    }
  ],
  "id": 2,
  "index": 1,
  "labels": [],
  "last_request": 27,
  "name": "ClientA COL TBTC 1",
  "parent": null,
  "pending_balance": "311",
  "status": "ACTIVE",
  "type": "Bitcoin",
  "xpub": null
}

or GET /accounts/{account_id}/balances and extract total or available. Not that some account time offer more granularity in the response payload.

bashjson
Copy
Copied
curl --request GET \
  --url https://api.vault.ledger.com/accounts/{{account_id}}/balances \
  --header 'authorization: Bearer {{access_token}}' \
  --header 'content-type: application/json' \
  --header 'x-ledger-workspace: minivault'
Copy
Copied
{
  "available": 9981,
  "pending": 311,
  "total": 10292
}

5.2. How to Subscribe to Vault Events via Web-hooks

Stay informed about events related to accounts, groups, requests, transactions, users, or whitelists by subscribing to web-hooks. Configure notifications using the following example:

bashjson
Copy
Copied
curl --request PUT \
  --url https://api.vault.ledger.com/notifications/configuration \
  --header 'authorization: Bearer {{access_token}}' \
  --header 'content-type: application/json' \
  --header 'x-ledger-workspace: minivault' \
  --data '{
    "all": {
      "webhook": {
        "secret": "secret", 
        "url": "{{notification_endpoint}}"
      }
    }
  }
Copy
Copied
{
  "all": {
    "webhook": {
      "secret": "secret",
      "url": "https://webhook.site/9773a594-4764-4807-8921-100133557f82"
    }
  }
}

With notification_endpoint a valid HTTPS endpoint that accepts POST queries with the payload similar too:

Copy
Copied
{
  "payload_type": "TRANSACTION",
  "event_type": "NEW_TRANSACTION_HAS_BEEN_RECEIVED",
  "id": 260
}

For more details on how to use this endpoint please refer to the API documentation.

Replace {{notification_endpoint}} with a valid HTTPS endpoint that accepts POST queries. You'll receive notifications similar to the following payload:

Copy
Copied
{
  "payload_type": "TRANSACTION",
  "event_type": "NEW_TRANSACTION_HAS_BEEN_RECEIVED",
  "id": 260
}

5.3. How to get information about a request

Retrieve information about a specific request by querying the GET request by Id endpoint:

bashjson
Copy
Copied
curl --request GET \
  --url https://api.vault.ledger.com/requests/{{request_id}} \
  --header 'authorization: Bearer {{access_token}}' \
  --header 'content-type: application/json' \
  --header 'x-ledger-workspace: minivault'
Copy
Copied
{
  "created_by": 24,
  "created_on": "2024-01-24T08:07:08.007892+00:00",
  "expires_at": "2024-01-31T08:07:08.007837+00:00",
  "id": 29,
  "status": "PENDING_APPROVAL",
  "target_id": 305,
  "target_type": "TRANSACTION",
  "type": "CREATE_TRANSACTION"
}
Traget Id

The attribute target_id of a request in our example of type CREATE_TRANSACTION provides the id of the transaction.

You can also search requests for example of type CREATE_TRANSACTION or in status PENDING_APPROVAL to get the list of request pending approval of some operators.

bashjson
Copy
Copied
curl --location 'https://blue-badger-433.minivault.ledger-sbx.com/api-gateway/requests?status=PENDING_APPROVAL&type=CREATE_TRANSACTION' \
--header 'X-Ledger-Workspace: minivault' \
--header 'authorization: Bearer {{access_token}}'
Copy
Copied
{
  "edges": [
    {
      "cursor": 0,
      "node": {
        "created_by": 24,
        "created_on": "2024-01-24T08:07:08.007892+00:00",
        "expires_at": "2024-01-31T08:07:08.007837+00:00",
        "id": 29,
        "status": "PENDING_APPROVAL",
        "target_id": 305,
        "target_type": "TRANSACTION",
        "type": "CREATE_TRANSACTION"
      }
    }
  ],
  "page_info": {
    "count": 1,
    "has_next_page": false
  }
}

5.4. How to get detail of who already approved a request

To get the number of request approvers, call the following endpoint:

bashjson
Copy
Copied
curl --request GET \
  --url https://api.vault.ledger.com/requests/{{request_id}}/governance-status \
  --header 'authorization: Bearer {{access_token}}' \
  --header 'content-type: application/json' \
  --header 'x-ledger-workspace: minivault'
Copy
Copied
{
    "governance_steps": [
        {
            "approvals": [
                {
                    "created_on": "2024-01-25T13:17:27.083210+00:00",
                    "type": "APPROVE",
                    "user": {
                        "id": null
                    }
                }
            ],
            "approvers": [
                {
                    "id": null
                },
                {
                    "id": 27
                },
                {
                    "id": null
                },
                {
                    "id": null
                },
                {
                    "id": null
                }
            ],
            "group": {
                "id": 11
            },
            "quorum": 1,
            "step_index": 0
        }
    ],
    "is_complete": true,
    "request": {
        "created_by": {
            "id": null
        },
        "created_on": "2024-01-25T13:15:10.322027+00:00",
        "current_step_index": 1,
        "expired_at": "2024-02-01T13:15:10.321913+00:00",
        "id": 62,
        "status": "SUBMITTED",
        "target_id": 435,
        "target_type": "ETHEREUM_LIKE_TRANSACTION",
        "type": "CREATE_TRANSACTION"
    }
}

Looking at the json I can see that request id 62 is completely approved "is_complete": true,, with one approval from an operator that is anonymized from my scope, we can also see that there where 4 potential approvers for this request from the approvers array, we know that all theses approvers where part of groupe 11. From "quorum": 1, and "step_index": 0 we learn that this request only needed 1 approval out of all of the groupe members for it to be submited.

Congratulations! You've completed the first steps as an API User. Feel free to explore more functionalities and reach out if you have any questions or need further assistance. Happy coding!

Copyright © Ledger Enterprise Platform 2023. All right reserved.