# Getting started with the API

This guide walks you through the Wallet-as-a-Service (Palisade) API from authentication to your first confirmed transaction. Every step includes a complete `curl` command you can run against the sandbox environment.

Console alternative
Prefer a visual interface? Follow the [console getting-started guide](/products/wallet/getting-started/getting-started) instead.

## Prerequisites

Sign in to the Wallet-as-a-Service (Palisade) console at `https://app.sandbox.palisade.co`. See [Creating your organisation](/products/wallet/introduction/set-up-your-organization) if this is your first login.

If you plan to use MPC wallets, set up at least one quorum first. See [Configure devices and quorums](/products/wallet/getting-started/configure-devices-and-quorums). For sandbox testing, skip this and use HSM wallets instead.

## Base URL

All examples in this guide use the sandbox environment:


```
https://api.sandbox.palisade.co
```

For production, replace with `https://api.palisade.co`.

## Step 1: Create API credentials

Generate API credentials in the console to authenticate your API requests.

1. Navigate to **Settings** > **API credentials**.
2. Click **Create credentials**. This opens the **Permissions set** page.
3. Under **Permission set type**, select **Wallets**. Each type provides a different set of permissions:


| Permission set type | What it grants |
|  --- | --- |
| **Wallets** | Manage wallets and vaults |
| **Transactions** | Manage transactions |
| **Controls** | Manage policies and addresses |
| **Monitoring** | Webhooks and auditing |


1. Under **Name and description**, enter a **Name** (for example, "Sandbox API - Wallets"). The **Description** is optional.
2. Under **IP addresses**, select **All IP addresses** for sandbox testing. In production, use **Limited IP addresses** and enter a comma-separated list of IPs or CIDRs.
3. Review the default permissions. Each permission is built from four components:
  - **Type** — the resource category (`balances`, `key`, or `vault`)
  - **Action** — the operation (`create`, `delete`, `read`, or `update`)
  - **Scope** — how broadly the permission applies (`org`, `vault`, or `key`)
  - **Resource** — a specific vault or wallet (optional — leave blank to apply to all resources in the scope)
The permission set type pre-populates sensible defaults. Click **Add another permission** to add more, or **Remove** to delete one. Since this is your first credential and you haven't created vaults or wallets yet, keep the scope set to **org** so the credential works for all resources you create later.
4. Click **Generate credentials**.
5. Copy the `clientId` and `clientSecret`.


Store credentials securely
The `clientSecret` is shown only once. Store it in a secrets manager or environment variable — never commit it to source control.

Repeat steps 2–8 to create credentials for **Transactions** and **Controls** — you need all three permission set types to complete this walkthrough. You can also create a single credential and add permissions from multiple types using **Add another permission**.

Scoping credentials in production
Once you have vaults and wallets, you can create narrower credentials scoped to specific resources. For example, a credential with `vault` scope and a specific vault selected under **Resource** only has access to that vault. See [API credentials best practices](/products/wallet/user-interface/api/api-credentials-best-practices).

## Step 2: Authenticate

Exchange your API credentials for a Bearer token.


```bash
curl -X POST https://api.sandbox.palisade.co/v2/credentials/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "YOUR_CLIENT_ID",
    "clientSecret": "YOUR_CLIENT_SECRET"
  }'
```

**Response:**


```json
{
  "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "scope": "balances:read:org:id=...  vault:create:org:id=...  key:read:org:id=...",
  "expiresIn": 3600,
  "tokenType": "Bearer"
}
```

The `scope` field lists each granted permission in the format `type:action:scope:id=orgId:*`.

Authenticate each credential and export the tokens. The examples in this guide use `$TOKEN` for the Wallets credential, `$CONTROLS_TOKEN` for Controls, and `$TX_TOKEN` for Transactions:


```bash
export TOKEN="<Wallets accessToken>"
export CONTROLS_TOKEN="<Controls accessToken>"
export TX_TOKEN="<Transactions accessToken>"
```

Token expiry
Tokens expire after 1 hour (`expiresIn: 3600`). Request a new token before the current one expires.

## Step 3: Create a vault

A vault groups related wallets together. Create one to hold your first wallet.


```bash
curl -X POST https://api.sandbox.palisade.co/v2/vaults \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sandbox Testing",
    "description": "Vault for API getting-started walkthrough"
  }'
```

**Response:**


```json
{
  "id": "019c8a2f-1234-7abc-9def-abcdef123456",
  "organizationId": "21c81319-5b83-45f9-b648-42055084af15",
  "createdBy": "a55df9e0-e14a-4410-a983-12afae46662f",
  "createdAt": "2025-05-08T10:00:00.000Z",
  "updatedAt": "2025-05-08T10:00:00.000Z",
  "name": "Sandbox Testing",
  "description": "Vault for API getting-started walkthrough"
}
```

Save the vault `id` — you need it for the next step.


```bash
export VAULT_ID="019c8a2f-1234-7abc-9def-abcdef123456"
```

## Step 4: Create a wallet

Create an Ethereum wallet inside your vault. This example uses HSM (available in sandbox only) so you do not need a quorum.


```bash
curl -X POST https://api.sandbox.palisade.co/v2/vaults/$VAULT_ID/wallets \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ETH Sandbox Wallet",
    "description": "Ethereum wallet for API testing",
    "blockchain": "ETHEREUM",
    "keystore": "HSM"
  }'
```

The initial response has `status: "CREATED"` with an empty `address`. HSM wallets are provisioned within seconds. Poll the wallet until `status` changes to `PROVISIONED`:


```bash
curl -X GET "https://api.sandbox.palisade.co/v2/vaults/$VAULT_ID/wallets/$WALLET_ID" \
  -H "Authorization: Bearer $TOKEN"
```

**Response (provisioned):**


```json
{
  "id": "019c8a3b-5678-7def-1234-567890abcdef",
  "vaultId": "019c8a2f-1234-7abc-9def-abcdef123456",
  "organizationId": "...",
  "name": "ETH Sandbox Wallet",
  "description": "Ethereum wallet for API testing",
  "address": "0x8dCd82eC41C41627D987830128198aff4A392D89",
  "publicKey": "04b05c60b4ac57b85c8d69a98409c5c5b4590d77...",
  "keystore": "HSM",
  "blockchain": "ETHEREUM",
  "settings": {
    "enabled": false,
    "rawSigningEnabled": false,
    "sweepingEnabled": false,
    "defaultFreezeEnabled": false
  },
  "status": "PROVISIONED",
  "keyAlgorithm": "SECP256K1"
}
```

Note that `settings.enabled` is `false` — the wallet is deposit-only until you explicitly enable outgoing transactions in a later step.

Save the wallet `id` and `address`:


```bash
export WALLET_ID="019c8a3b-5678-7def-1234-567890abcdef"
export WALLET_ADDRESS="0x8dCd82eC41C41627D987830128198aff4A392D89"
```

MPC wallets in production
For production wallets, set `"keystore": "MPC"` and include `"quorumId": "YOUR_QUORUM_ID"`. MPC wallets require quorum device approval before moving to `PROVISIONED`.

Supported blockchains
Replace `ETHEREUM` with any supported blockchain. See [Blockchains and tokens](/products/wallet/user-interface/blockchains-and-tokens) for the full list.

## Step 5: Fund your wallet

Your wallet starts with a zero balance. Send testnet funds to the wallet `address` from a faucet or another wallet before proceeding.

For Ethereum testnet faucets, search for "Sepolia faucet" and send testnet ETH to your wallet address.

Verify the balance:


```bash
curl -X GET "https://api.sandbox.palisade.co/v2/vaults/$VAULT_ID/wallets/$WALLET_ID/balances" \
  -H "Authorization: Bearer $TOKEN"
```

**Response:**


```json
{
  "currencyCode": "USD",
  "aggregatedFiatValue": "0",
  "balances": [
    {
      "asset": {
        "symbol": "ETH",
        "name": "Ethereum",
        "blockchain": "ETHEREUM",
        "standard": "NATIVE",
        "decimals": 18
      },
      "balance": "0",
      "availableBalance": "0",
      "pendingBalance": "0",
      "frozenBalance": "0"
    }
  ]
}
```

Wait until `balance` shows a non-zero amount before continuing.

## Step 6: Create a transaction policy

Wallets are deposit-only by default. Create a policy rule to permit outgoing transactions. This step requires a **Controls** credential.

This example creates a per-transaction limit of 10 ETH:


```bash
curl -X PUT "https://api.sandbox.palisade.co/v2/vaults/$VAULT_ID/wallets/$WALLET_ID/policy-rules/limits" \
  -H "Authorization: Bearer $CONTROLS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "limitQty": "10",
    "limitType": "PER_TX",
    "symbol": "ETH"
  }'
```

**Response:**


```json
{
  "id": "c2655be7-795e-47d3-b4e9-2fc38b26febf",
  "organizationId": "...",
  "walletId": "019c8a3b-5678-7def-1234-567890abcdef",
  "vaultId": "019c8a2f-1234-7abc-9def-abcdef123456",
  "limitQty": "10",
  "limitType": "PER_TX",
  "asset": {
    "symbol": "ETH",
    "name": "Ethereum",
    "blockchain": "ETHEREUM"
  },
  "status": "LIMIT_CREATION_APPROVAL_PENDING",
  "active": false,
  "matchers": []
}
```

Approval workflow
If your organisation has approval groups configured, the policy starts with `status: "LIMIT_CREATION_APPROVAL_PENDING"` and `active: false` until an approver approves it in the console. If no approval groups are configured, the policy auto-transitions to active.

Policy types
`PER_TX` limits a single transaction. `ROLLING_DURATION` limits the total over a time window (set `"duration": "86400s"` for 24 hours). `CONSTANT` sets a lifetime cap. See [Policy reference](/products/wallet/user-interface/policies/policies-reference) for the complete list of options.

## Step 7: Add a destination address

Register an external destination in the address book before you can send to it. This step requires a **Controls** credential.

### Create a counterparty

The `details` field requires a `type` (`INDIVIDUAL`, `ORGANIZATION`, or `DAPP`) and the corresponding detail object:


```bash
curl -X POST https://api.sandbox.palisade.co/v2/counterparties \
  -H "Authorization: Bearer $CONTROLS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Test Recipient",
    "description": "Sandbox test counterparty",
    "details": {
      "type": "INDIVIDUAL",
      "individual": {
        "firstName": "Test",
        "lastName": "Recipient"
      }
    }
  }'
```

**Response:**


```json
{
  "id": "019d0623-86d1-7aef-9134-50e8d9cb3ee5",
  "organizationId": "...",
  "name": "Test Recipient",
  "description": "Sandbox test counterparty",
  "details": {
    "type": "INDIVIDUAL",
    "individual": {
      "firstName": "Test",
      "lastName": "Recipient"
    }
  },
  "addressCount": 0
}
```

Save the counterparty `id`:


```bash
export COUNTERPARTY_ID="019d0623-86d1-7aef-9134-50e8d9cb3ee5"
```

Counterparty types
Use `ORGANIZATION` with a `legalName` field for businesses, or `DAPP` for decentralized applications.

### Add a blockchain address


```bash
curl -X POST "https://api.sandbox.palisade.co/v2/counterparties/$COUNTERPARTY_ID/addresses" \
  -H "Authorization: Bearer $CONTROLS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "termsAndConditionsAccepted": true,
    "details": {
      "type": "EXTERNAL",
      "externalAddress": {
        "address": "0xDESTINATION_ADDRESS",
        "name": "Recipient ETH Wallet",
        "blockchains": ["ETHEREUM"]
      }
    }
  }'
```

Replace `0xDESTINATION_ADDRESS` with a valid Ethereum address. The API validates the address checksum against the selected blockchain.

**Response:**


```json
{
  "addressId": "019d0624-b5d7-77cf-8eee-b72ceac0f71e",
  "counterpartyId": "019d0623-86d1-7aef-9134-50e8d9cb3ee5",
  "organizationId": "...",
  "termsAndConditionsAccepted": true,
  "details": {
    "type": "EXTERNAL",
    "externalAddress": {
      "address": "0xDESTINATION_ADDRESS",
      "name": "Recipient ETH Wallet",
      "blockchains": ["ETHEREUM"]
    }
  },
  "status": "CREATION_APPROVAL_PENDING",
  "active": false
}
```

Approval workflow
If your organisation has approval groups configured, the address starts with `status: "CREATION_APPROVAL_PENDING"` until an approver approves it. If no approval groups are configured, the address auto-transitions to active.

## Step 8: Enable outgoing transactions

Enable outgoing transactions on the wallet. This step requires a **Wallets** credential.


```bash
curl -X PUT "https://api.sandbox.palisade.co/v2/vaults/$VAULT_ID/wallets/$WALLET_ID/settings" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "settings": {
      "enabled": true
    }
  }'
```

The response is the full wallet object with `settings.enabled` now set to `true`.

## Step 9: Send a transaction

Send 0.01 ETH to the approved destination address. This step requires a **Transactions** credential.


```bash
curl -X POST "https://api.sandbox.palisade.co/v2/vaults/$VAULT_ID/wallets/$WALLET_ID/transactions/transfer" \
  -H "Authorization: Bearer $TX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destinationAddress": "0xDESTINATION_ADDRESS",
    "symbol": "ETH",
    "qty": "0.01"
  }'
```

Replace `0xDESTINATION_ADDRESS` with the address you registered in Step 7.

**Response:**


```json
{
  "id": "019c94f1-cc8c-79b4-9c0f-bc7edaabc267",
  "walletId": "...",
  "vaultId": "...",
  "status": "REQUESTED",
  "action": "PALISADE_TRANSFER",
  "asset": {
    "symbol": "ETH"
  },
  "blockchain": "ETHEREUM",
  "destinationAddress": "0xDESTINATION_ADDRESS",
  "originAddress": "0x8dCd82eC41C41627D987830128198aff4A392D89",
  "qty": "0.01"
}
```

Save the transaction `id`:


```bash
export TX_ID="019c94f1-cc8c-79b4-9c0f-bc7edaabc267"
```

## Step 10: Track the transaction

Poll the transaction until it reaches a terminal status (`CONFIRMED`, `REJECTED`, or `FAILED`). Use a **Transactions** credential:


```bash
curl -X GET "https://api.sandbox.palisade.co/v2/vaults/$VAULT_ID/wallets/$WALLET_ID/transactions/$TX_ID" \
  -H "Authorization: Bearer $TX_TOKEN"
```

The `status` field progresses through the lifecycle until it reaches `CONFIRMED`.

The transaction moves through these statuses:

`REQUESTED` → `POLICY_CHECK_PENDING` → `POLICY_CHECK_PASSED` → `APPROVAL_CHECK_PENDING` → `APPROVAL_CHECK_PASSED` → `SIGNATURE_PENDING` → `SIGNED` → `PUBLISH_PENDING` → `PUBLISHED` → `CONFIRMATION_PENDING` → `CONFIRMED`

Use webhooks instead of polling
For production integrations, set up [webhooks](/products/wallet/user-interface/integrations/manage-webhooks) to receive real-time status updates instead of polling.

## Next steps

- [Manage webhooks](/products/wallet/user-interface/integrations/manage-webhooks) for real-time transaction notifications
- [API credentials best practices](/products/wallet/user-interface/api/api-credentials-best-practices) for production security
- [Wallet-as-a-Service (Palisade) API reference](/products/wallet/api-docs/palisade-api/palisade-api) for the complete endpoint documentation