Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.groundtech.co/llms.txt

Use this file to discover all available pages before exploring further.

Creating a wallet is asynchronous. POST /v2/wallets returns immediately with status: "creating". Deposit addresses and on-chain state are not populated synchronously — poll GET /v2/wallets/{id} until status becomes "idle".

Create a portfolio wallet

curl -X POST "$BASE_URL/v2/wallets" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "0b7e3b4d-88a5-4a75-8c0f-3b7b4f9d9f1d",
    "label": "Core Yield Portfolio",
    "strategy": {
      "allocations": [
        { "yieldSourceId": "syrup-usdc", "pct": 50 },
        { "yieldSourceId": "morpho-gauntlet-usdc", "pct": 50 }
      ]
    }
  }'
FieldRequiredDescription
requestIdYesUUID v4 idempotency key
labelNoHuman-readable wallet name
strategyYesObject containing allocations array
strategy.allocationsYesArray of { yieldSourceId, pct }. Percentages must sum to 100.
The endpoint always returns 200 OK. A first POST with a new requestId returns a minimal creating envelope (no deposit addresses, zero balances). An idempotent replay with the same requestId returns the wallet’s current state.

Creating response

{
  "id": "9d1a1c83-3a1c-4c14-9c5a-0c9a57a4a7db",
  "label": "Core Yield Portfolio",
  "status": "creating",
  "failureReason": null,
  "createdAt": "2026-02-05T08:15:00Z",
  "depositAddresses": {},
  "balance": {
    "totalUsd": "0",
    "withdrawableUsd": "0",
    "reservedUsd": "0",
    "earnedUsd": "0"
  },
  "positions": []
}

Polling for activation

Poll GET /v2/wallets/{id} every 1-2 seconds until status !== "creating":
async function waitUntilReady(walletId, apiToken) {
  while (true) {
    const res = await fetch(`${BASE_URL}/v2/wallets/${walletId}`, {
      headers: { Authorization: `Bearer ${apiToken}` },
    });
    const wallet = await res.json();
    if (wallet.status === "idle") return wallet;
    if (wallet.status === "failed") {
      throw new Error(`wallet ${walletId} failed: ${wallet.failureReason}`);
    }
    await new Promise((r) => setTimeout(r, 1500));
  }
}
See Polling for more on the poll pattern.

Ready response

Once status === "idle", the response is ready for deposits:
{
  "id": "9d1a1c83-3a1c-4c14-9c5a-0c9a57a4a7db",
  "label": "Core Yield Portfolio",
  "status": "idle",
  "failureReason": null,
  "createdAt": "2026-02-05T08:15:00Z",
  "depositAddresses": {
    "arbitrum": "0x21246509968c4d24611f414560971AEc2e3A079B",
    "base": "0x21246509968c4d24611f414560971AEc2e3A079B",
    "ethereum": "0x21246509968c4d24611f414560971AEc2e3A079B",
    "polygon": "0x21246509968c4d24611f414560971AEc2e3A079B",
    "solana": "7nYzKxM3bP4oEFbqkPmA5E2rYJ8HqKz8vFg9abc1"
  },
  "balance": {
    "totalUsd": "0",
    "withdrawableUsd": "0",
    "reservedUsd": "0",
    "earnedUsd": "0"
  },
  "positions": [
    { "id": "syrup-usdc", "kind": "yield_source", "label": "Syrup USDC", "valueUsd": "0", "pct": 50 },
    { "id": "morpho-gauntlet-usdc", "kind": "yield_source", "label": "Morpho Gauntlet USDC Prime", "valueUsd": "0", "pct": 50 }
  ]
}

Key fields

FieldDescription
status"creating" during provisioning, "idle" when ready, "withdrawal_active" / "rebalance_active" / "withdrawal_and_rebalance_active" while work is running, "failed" if setup terminated
failureReasonPopulated only when status === "failed" — short human-readable reason
depositAddressesPer-chain addresses to fund the wallet. Empty until status === "idle".
balance.totalUsdTotal economically owned wallet value
balance.withdrawableUsdAmount that can be withdrawn now
balance.reservedUsdCustomer-owned value currently reserved by active withdrawals or rebalances
balance.earnedUsdLifetime yield earned (monotonic)
positionsCurrent cash, bridge, and yield-source holdings with USD values. Yield-source positions include pct.

Failed response

If wallet setup fails terminally, GET /v2/wallets/{id} returns:
{
  "id": "9d1a1c83-3a1c-4c14-9c5a-0c9a57a4a7db",
  "label": "Core Yield Portfolio",
  "status": "failed",
  "failureReason": "Turnkey create_wallet activity ACTIVITY_STATUS_REJECTED: act_abc",
  "createdAt": "2026-02-05T08:15:00Z",
  "depositAddresses": {},
  "balance": { "totalUsd": "0", "withdrawableUsd": "0", "reservedUsd": "0", "earnedUsd": "0" },
  "positions": []
}
Failed wallets cannot be recovered by retrying the same requestId. Create a new wallet with a fresh requestId and contact support if the failure reason is unclear.

Rebalancing

Portfolio wallets are best-effort targets. Over time, or after deposits and withdrawals, holdings drift from target allocations. The system rebalances to bring positions back toward the strategy targets. Deposits first appear as cash and are then deployed into the configured yield sources.

Sandbox notes

In sandbox, deposit address keys include the network suffix (e.g. ethereum_sepolia instead of ethereum). See Supported Chains for details. The yieldSourceId values and request shapes are identical to production.