# Getting started with the API

This guide walks you through the Ripple Custody (Wallet) 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/get-started/console) instead.

## What you can do with the API

The Ripple Custody (Wallet) API provides programmatic access to the platform, so you can integrate digital asset custody and management into your applications. Use it to:

- Create and manage vaults and wallets
- Initiate and monitor transactions
- Configure transaction policies and address book entries
- Set up webhooks for real-time notifications


Users, devices, MPC quorums, approval groups, and API credentials are managed in the console. For the full list of operations, see the [Ripple Custody (Wallet) API reference](/products/wallet/api-docs/palisade-api/palisade-api).

To learn how API credentials work before you create one, see:

- [API credentials overview](/products/wallet/organization-and-access/api-credentials) — how API credentials secure access
- [API credentials best practices](/products/wallet/organization-and-access/api-credentials/best-practices) — security recommendations
- [Manage API credentials](/products/wallet/organization-and-access/api-credentials/manage-api-credentials) — create and manage credentials


## Prerequisites

Sign in to the Ripple Custody (Wallet) console at `https://app.sandbox.palisade.co`. See [Set up your organization](/products/wallet/get-started/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/get-started/console/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`.

For the conventions that every endpoint shares, such as token lifetime, pagination, filtering, and the error format, see [API conventions](/products/wallet/api-and-developer-experience/api-conventions).

## Step 1: Create API credentials

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

Owner or Administrator role required
Only users with the **Owner** or **Administrator** role can create API credentials. Other roles don't see **Create credentials**. If you don't see it, ask an owner or administrator in your organization.

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. Under **Permission**, build at least one permission. Each permission is built from these 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** — the specific vaults or wallets the permission applies to. Required when the scope is `vault` or `key`. An `org` scope applies to the whole organization and takes no resource.
The options offered depend on the permission set type you selected, and the console doesn't add any permissions for you. Complete each part, then 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, set the scope 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. Each credential has a single permission set type, so **Add another permission** only offers permissions from that type.

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/organization-and-access/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": "credential:read:credential::*",
  "expiresIn": 3600,
  "tokenType": "Bearer"
}
```

Every credential returns the same `scope` value, `credential:read:credential::*`. It doesn't list the credential's permissions: Ripple Custody (Wallet) checks them on the server for each request, and a request outside them returns `403`. `expiresIn` is the number of seconds until the token expires.

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 validity and renewal
Tokens last 1 hour in Sandbox and Development, and 60 seconds in Production. Reuse a token until it expires, then request a new one. Always request tokens from `/v2/credentials/oauth/token`. The endpoint caches tokens on the server and returns the same token until it expires, so `expiresIn` gives the time remaining and is often less than 3600. You don't need to cache tokens yourself. Requesting tokens any other way uses a limited issuance quota and can cause authentication failures for your organization.

## 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": "...",
  "quorumId": "00000000-0000-0000-0000-000000000000",
  "createdBy": "a55df9e0-e14a-4410-a983-12afae46662f",
  "createdAt": "2025-05-08T10:01:00.000Z",
  "updatedAt": "2025-05-08T10:01:05.000Z",
  "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",
  "reasons": [],
  "keyAlgorithm": "SECP256K1",
  "walletType": "WALLET_TYPE_UNSPECIFIED"
}
```

HSM wallets have no quorum, so `quorumId` is all zeros. 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/vaults-and-wallets/supported-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 (abridged: each `asset` carries more fields than shown):**

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

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

To filter, search, or page the balances this operation returns, see [Filter, search, and page balances](/products/wallet/vaults-and-wallets/wallets/understanding-balances#filter-search-and-page-balances).

## 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 with the `policy:create` and `policy:read` permissions.

Policy rules identify the asset by its registry ID. Read it from `asset.id` in the balances response in Step 5, for example `2::ETH`.

This example limits each transaction to 10 ETH:

```bash
curl -X POST "https://api.sandbox.palisade.co/v2/vaults/$VAULT_ID/wallets/$WALLET_ID/policy-rules" \
  -H "Authorization: Bearer $CONTROLS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "trigger": "OUTBOUND_INITIATED",
    "assetId": "2::ETH",
    "definition": {
      "kind": "LIMIT",
      "limit": {
        "amount": {
          "threshold": "10",
          "operator": "GT",
          "aggregation": "SINGLE_TX"
        }
      }
    }
  }'
```

**Response:**

```json
{
  "id": "c2655be7-795e-47d3-b4e9-2fc38b26febf",
  "organizationId": "...",
  "vaultId": "019c8a2f-1234-7abc-9def-abcdef123456",
  "walletId": "019c8a3b-5678-7def-1234-567890abcdef",
  "createdBy": "a55df9e0-e14a-4410-a983-12afae46662f",
  "trigger": "OUTBOUND_INITIATED",
  "assetId": "2::ETH",
  "matchers": [],
  "status": "RULE_CREATION_APPROVAL_PENDING",
  "active": false,
  "createdAt": "2025-05-08T10:05:00.000Z",
  "updatedAt": "2025-05-08T10:05:00.000Z",
  "definition": {
    "kind": "LIMIT",
    "limit": {
      "amount": {
        "threshold": "10",
        "operator": "GT",
        "aggregation": "SINGLE_TX"
      }
    }
  },
  "scope": "WALLET"
}
```

If the wallet already has an equivalent rule, the request returns `409` with reason `PAL006.039`.

Approval workflow
Every new policy rule starts with `status: "RULE_CREATION_APPROVAL_PENDING"` and `active: false`. If your organization has a **Policy rules** approval group, the rule stays in that status until an approver approves it in the console. If it doesn't, the rule becomes active without an approval.

Limit types
`SINGLE_TX` limits a single transaction. `ROLLING` limits the total over a time window (set `"duration": "86400s"` for 24 hours). `TOTAL` sets a lifetime cap. To hold large transactions for approvers or require a Travel Rule exchange, use a different rule kind. See [Manage policy rules via the API](/products/wallet/governance-and-controls/policies/manage-policies-api) and [Policy reference](/products/wallet/governance-and-controls/policies/policy-reference).

## 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": "...",
  "createdBy": "a55df9e0-e14a-4410-a983-12afae46662f",
  "createdAt": "2025-05-08T10:06:00.000Z",
  "updatedBy": "a55df9e0-e14a-4410-a983-12afae46662f",
  "updatedAt": "2025-05-08T10:06:00.000Z",
  "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 against the selected blockchains and returns `400` with reason `PAL013.015` if it isn't valid for one of them.

**Response:**

```json
{
  "addressId": "019d0624-b5d7-77cf-8eee-b72ceac0f71e",
  "counterpartyId": "019d0623-86d1-7aef-9134-50e8d9cb3ee5",
  "organizationId": "...",
  "createdBy": "a55df9e0-e14a-4410-a983-12afae46662f",
  "createdAt": "2025-05-08T10:07:00.000Z",
  "updatedBy": "a55df9e0-e14a-4410-a983-12afae46662f",
  "updatedAt": "2025-05-08T10:07:00.000Z",
  "custodian": {
    "name": "",
    "dId": ""
  },
  "termsAndConditionsAccepted": true,
  "details": {
    "type": "EXTERNAL",
    "externalAddress": {
      "address": "0xDESTINATION_ADDRESS",
      "name": "Recipient ETH Wallet",
      "blockchains": ["ETHEREUM"]
    }
  },
  "status": "CREATION_APPROVAL_PENDING",
  "active": false
}
```

Approval workflow
Every new address starts with `status: "CREATION_APPROVAL_PENDING"`. If your organization has approval groups configured, it stays in that status until an approver approves it. After approval, or immediately if no approval groups are configured, the address passes a compliance check before it becomes active.

## Step 8: Enable outgoing transactions

Enable outgoing transactions on the wallet. This step requires a **Wallets** credential. The request replaces all four wallet settings, so send the current value of each one: a setting you omit is set to `false`.

```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,
      "rawSigningEnabled": false,
      "sweepingEnabled": false,
      "defaultFreezeEnabled": false
    }
  }'
```

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 (abridged: the transaction object has many more fields, such as `origin`, `destination`, `feeAsset`, `freezeInfo`, `problems`, and `history`):**

```json
{
  "id": "019c94f1-cc8c-79b4-9c0f-bc7edaabc267",
  "walletId": "...",
  "vaultId": "...",
  "organizationId": "...",
  "createdAt": "2025-05-08T10:20:00.000Z",
  "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`. If the transaction ends in `REJECTED` or `FAILED`, its `problems` array says why. For example, a transfer from a wallet whose only limit rules are still pending approval is rejected with reason `PAL006.005`. See [Transaction problems](/products/wallet/api-and-developer-experience/api-conventions#transaction-problems).

The transaction moves through these statuses:

`REQUESTED` → `POLICY_CHECK_PENDING` → `POLICY_CHECK_PASSED` → `COMPILATION_PENDING` → `COMPILED` → `SIGNATURE_PENDING` → `SIGNED` → `PUBLISH_PENDING` → `PUBLISHED` → `CONFIRMATION_PENDING` → `CONFIRMED`

Approvals happen during the policy check
If the transaction needs approvals, it collects them while it's in `POLICY_CHECK_PENDING`. `POLICY_CHECK_PASSED` means the transaction passed both policy checks and approvals.

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

## Best practices

- Store API credentials securely, and never expose them in client-side code.
- Use the sandbox environment for testing before you move to production.
- Implement error handling for all API calls.
- Set up webhooks for real-time transaction status updates.


## Next steps

- [Manage webhooks](/products/wallet/integrations-and-monitoring/webhooks/manage-webhooks) for real-time transaction notifications
- [API credentials best practices](/products/wallet/organization-and-access/api-credentials/best-practices) for production security
- [Ripple Custody (Wallet) API reference](/products/wallet/api-docs/palisade-api/palisade-api) for the complete endpoint documentation


For API support, contact [Ripple Support](https://technicalsupport.ripple.com/).