Skip to main content

Authentication

All requests require a Bearer token in the Authorization header.
curl -X GET "$BASE_URL/v2/portfolio-wallets" \
  -H "Authorization: Bearer $BRAID_API_TOKEN"
To get your API token, contact your Braid account representative or generate one from the Braid dashboard. Tokens are scoped to your organization. Keep them secret — store them in environment variables or a secret manager, never in client-side code. If the token is missing or invalid, the API returns 401:
{
  "error": "Unauthenticated request",
  "code": "unauthenticated"
}

Environments

EnvironmentBase URL
Sandboxhttps://sandbox.trybraid.xyz
Productionhttps://production.trybraid.xyz
Sandbox uses test networks while you integrate. Production processes live funds. Swap the base URL without changing request shapes. Sandbox networks: ethereum → Ethereum Sepolia, solana → Solana devnet. The API uses canonical chain names in both environments; sandbox maps them to testnets internally.

Testing in sandbox

  • Get test USDC — use the Circle faucet for Sepolia USDC (Ethereum) or devnet USDC (select Solana devnet).
  • Simulated positionsusdz is simulated in sandbox since USDZ is not available on testnets. The positionKey and request shapes are identical to production.
  • Same API shapes — request and response formats are identical between sandbox and production. Only base URLs and underlying networks differ.
  • End-to-end flow — create a wallet, deposit test USDC, check balances, initiate a withdrawal, and track it to completion. The full lifecycle works in sandbox.

Versioning

All endpoints are prefixed with /v2/. This is the current and only supported version. Breaking changes (removed fields, changed types, altered semantics) will result in a new version prefix. Additive changes (new fields, new endpoints, new enum values) are made in-place and do not increment the version. Build your client to tolerate unknown JSON fields.

Response format

  • Field names are camelCase (withdrawalAmount, positionKey).
  • Enum values are lower_snake_case (stablecoin_yield, overcollateralized_lending).
  • USD amounts are fixed-precision decimal strings ("65000.00", "0.00"), never raw floats.
  • Position identifiers use positionKey (camelCase): usdz, syrupUsdc, rlp, ustb.
  • Timestamps are ISO 8601 UTC ("2026-02-05T08:15:00.000Z").
  • Durations are ISO 8601 ("PT2H", "PT24H"). See Yield Sources for details.
No SDK is currently offered. Integrate directly via HTTP. We do not currently have client libraries — the curl examples on each page are the canonical reference.

Pagination

List endpoints use cursor-based pagination. Pass limit and cursor as query parameters:
curl "$BASE_URL/v2/portfolio-wallets/$WALLET_ID/withdrawals?limit=25" \
  -H "Authorization: Bearer $BRAID_API_TOKEN"
The response includes nextCursor. Pass it as cursor to fetch the next page. When nextCursor is null, you’ve reached the end.
{
  "data": [ ... ],
  "nextCursor": "eyJjcmVhdGVkQXQiOi..."
}
curl "$BASE_URL/v2/portfolio-wallets/$WALLET_ID/withdrawals?limit=25&cursor=$NEXT_CURSOR" \
  -H "Authorization: Bearer $BRAID_API_TOKEN"
Additional query parameters supported on list endpoints:
ParamDefaultDescription
limit25Items per page (1–100)
cursorOpaque cursor from nextCursor
sortcreatedAtSort field
orderdescasc or desc
createdAtGteISO 8601 lower-bound filter
statusRepeatable status filter (e.g. status=processing&status=completed)
The activity feed uses a slightly different pagination shape: responses contain hasMore (boolean) instead of nextCursor. The cursor query parameter still works the same way.

Idempotency

Create and withdrawal endpoints accept a requestId (UUID v4). Retrying with the same requestId and payload returns the original response without creating duplicates. If the requestId was already used with a different payload, the API returns 409 request_id_conflict.
curl -X POST "$BASE_URL/v2/portfolio-wallets" \
  -H "Authorization: Bearer $BRAID_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "f0a3afc3-1e67-4c0c-a8e1-3f5d8fb9a23b",
    "label": "Treasury Portfolio",
    "positions": [
      { "positionKey": "usdz", "targetWeightBps": 6000 },
      { "positionKey": "rlp", "targetWeightBps": 4000 }
    ]
  }'
When you receive a 409, fetch the existing resource rather than retrying the create.

Rate limits

Requests are rate-limited per organization:
TierLimitWindow
Read (GET)200 requests60 seconds
Write (POST, PATCH, DELETE)50 requests60 seconds
When rate-limited, the API returns 429 with a Retry-After header:
{
  "error": "Rate limit exceeded. Max 200 read requests per 60s.",
  "code": "rate_limit_exceeded",
  "retryAfter": 60
}
Every response includes rate limit headers:
HeaderDescription
X-RateLimit-LimitMax requests for this tier
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the window resets
Retry-AfterSeconds to wait (only on 429)
Implement exponential backoff when you receive 429. For high-throughput integrations, use webhooks instead of polling.

Error handling

Errors are JSON with a human-readable error string and a machine-readable code:
{
  "error": "Missing required fields: destinationAddress, withdrawalAmount, token, chain, requestId",
  "code": "validation_error"
}
Use code for programmatic branching. Use error for logging.

Error code reference

CodeHTTPWhen
validation_error400Invalid request body or parameters
unknown_parameters400Unexpected query or body parameters
unsupported_chain400Chain not supported for the requested operation
unsupported_token400Token not supported
invalid_destination_address400Malformed destination address
precision_overflow400Too many decimal places (max 6 for USDC)
invalid_position_weight400Weight out of range or weights do not sum to 10,000 bps
invalid_cursor400Malformed pagination cursor
unauthenticated401Missing or invalid Bearer token
wallet_not_found404Wallet ID does not exist or does not belong to your organization
withdrawal_not_found404Withdrawal ID does not exist
deposit_not_found404Deposit ID does not exist
rate_limit_exceeded429Too many requests — back off and retry
insufficient_funds409Not enough unreserved balance
duplicate_request_id409requestId already used with the same payload
request_id_conflict409requestId already used with a different payload
withdrawal_not_cancellable409Withdrawal in a non-cancellable state
withdrawal_already_cancelled409Withdrawal already cancelled
withdrawal_payout_in_progress409Cannot cancel — payout already broadcast
internal_error500Server error — retry with exponential backoff
wallet_creation_failed502Upstream custody provider failure
service_temporarily_unavailable503Temporary outage — retry after a short delay

Support

If you encounter 500, 502, or 503 errors, retry with exponential backoff. For persistent issues, contact your Braid account representative. Webhook delivery is best-effort — build your integration to tolerate missed events by polling as a backstop.

Next steps

  • Quickstart — create a wallet and withdraw in 5 minutes
  • Create a Wallet — wallet creation, lifecycle, and response shape
  • Webhooks — real-time event notifications