Skip to main content
This guide walks through the complete self-directed investing lifecycle: account creation, KYC, bank linking, funding, trading, and withdrawal. By the end, you’ll have a working integration in the sandbox.
Prerequisites: API credentials from the sandbox environment. A terminal or API client (cURL, Postman, or similar).
Set these variables once — every example below references them:
API_KEY="YOUR_API_KEY"
API_SECRET="YOUR_API_SECRET"
BASE_URL="https://test-service.bluumfinance.com/v1"
AUTH=$(echo -n "$API_KEY:$API_SECRET" | base64)

Step 1 — Create an investment account

When a user in your app is ready to invest, create an individual account on their behalf. This collects identity, contact information, regulatory disclosures, and agreements.
curl -X POST "$BASE_URL/accounts" \
  -H "Authorization: Basic $AUTH" \
  -H "Content-Type: application/json" \
  -d '{
    "account_type": "individual",
    "management_type": "self_directed",
    "contact": {
      "email_address": "jane.doe@example.com",
      "phone_number": "+14155551234",
      "street_address": ["456 Oak Avenue"],
      "city": "San Francisco",
      "state": "CA",
      "postal_code": "94102",
      "country": "US"
    },
    "identity": {
      "first_name": "Jane",
      "last_name": "Doe",
      "date_of_birth": "1990-05-15",
      "tax_id": "987-65-4321",
      "tax_id_type": "SSN",
      "country_of_citizenship": "US",
      "country_of_birth": "US",
      "country_of_tax_residence": "US",
      "funding_source": ["employment_income"]
    },
    "disclosures": {
      "is_control_person": false,
      "is_affiliated_exchange_or_finra": false,
      "is_politically_exposed": false,
      "immediate_family_exposed": false
    },
    "agreements": [
      {
        "agreement": "account_agreement",
        "agreed": true,
        "signed_at": "2025-06-15T10:30:00Z",
        "ip_address": "203.0.113.42"
      },
      {
        "agreement": "customer_agreement",
        "agreed": true,
        "signed_at": "2025-06-15T10:30:00Z",
        "ip_address": "203.0.113.42"
      }
    ]
  }'
The response returns an account with status: "ACTIVE". Store the id — you’ll need it for every subsequent call.
ACCOUNT_ID="3d0b0e65-35d3-4dcd-8df7-10286ebb4b4b"
See Accounts for details on account types, required fields, and lifecycle states.

Step 2 — Upload KYC documents

Identity verification requires supporting documents. Upload a government-issued ID (passport, driver’s license).
curl -X POST "$BASE_URL/documents/accounts/$ACCOUNT_ID/upload" \
  -H "Authorization: Basic $AUTH" \
  -F "document_type=id_verification" \
  -F "file=@/path/to/drivers-license.jpg"
{
  "document_id": "doc_a1b2c3d4e5f6g7h8",
  "account_id": "3d0b0e65-35d3-4dcd-8df7-10286ebb4b4b",
  "document_type": "id_verification",
  "upload_status": "processing",
  "uploaded_at": "2025-06-15T10:35:00Z"
}
Check the status until it changes to approved:
curl -X GET "$BASE_URL/documents/doc_a1b2c3d4e5f6g7h8" \
  -H "Authorization: Basic $AUTH"
In sandbox, documents are auto-approved. In production, use webhooks to receive notifications instead of polling.

Before the user can deposit funds, they need to link a bank account. This is a two-step flow using Plaid Link. Generate a token your frontend uses to launch the Plaid Link widget:
curl -X POST "$BASE_URL/accounts/$ACCOUNT_ID/funding-sources/plaid/link-token" \
  -H "Authorization: Basic $AUTH" \
  -H "Content-Type: application/json" \
  -d '{ "enable_hosted_link": false }'
{
  "status": "success",
  "data": {
    "link_token": "link-sandbox-abc123def456",
    "hosted_link_url": null
  }
}
Set enable_hosted_link: true for a Plaid-hosted redirect flow instead of embedding the widget.

3b. Exchange the public token

After the user completes Plaid Link, your frontend receives a public_token. Send it to Bluum to finalize the connection:
curl -X POST "$BASE_URL/accounts/$ACCOUNT_ID/funding-sources/plaid/connect" \
  -H "Authorization: Basic $AUTH" \
  -H "Content-Type: application/json" \
  -d '{ "public_token": "public-sandbox-abc123def456" }'
Store the returned item.itemId and item.accounts[0].accountId — you’ll need them for deposits and withdrawals.
PLAID_ITEM_ID="item_abc123"
PLAID_ACCOUNT_ID="acc_1234567890"

Step 4 — Deposit funds

Fund the account via ACH using the linked bank account:
curl -X POST "$BASE_URL/accounts/$ACCOUNT_ID/deposits" \
  -H "Authorization: Basic $AUTH" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: dep-$(uuidgen)" \
  -d '{
    "amount": "5000.00",
    "currency": "USD",
    "method": "ach",
    "description": "Initial account funding",
    "plaid_options": {
      "item_id": "'"$PLAID_ITEM_ID"'",
      "account_id": "'"$PLAID_ACCOUNT_ID"'"
    }
  }'
Deposit status progresses: pendingprocessingreceivedcompleted.
Always include an Idempotency-Key header on deposit and withdrawal requests to prevent duplicate transfers on retry.
You can also use manual_bank_transfer as the method — the response includes bank details and a reference code your user provides to their bank. See Deposits & Withdrawals for all funding methods.

Step 5 — Place a buy order

Once the deposit is completed and the wallet is funded, the user can trade.

Market buy — by quantity

Buy 10 shares of AAPL at the current market price:
curl -X POST "$BASE_URL/trading/accounts/$ACCOUNT_ID/orders" \
  -H "Authorization: Basic $AUTH" \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "AAPL",
    "side": "buy",
    "type": "market",
    "time_in_force": "day",
    "qty": "10"
  }'

Market buy — by dollar amount (fractional shares)

Invest exactly $1,000 worth of GOOGL:
curl -X POST "$BASE_URL/trading/accounts/$ACCOUNT_ID/orders" \
  -H "Authorization: Basic $AUTH" \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "GOOGL",
    "side": "buy",
    "type": "market",
    "time_in_force": "day",
    "notional": "1000.00"
  }'

Response

{
  "id": "ord_x9y8z7a6b5c4d3e2",
  "account_id": "3d0b0e65-35d3-4dcd-8df7-10286ebb4b4b",
  "symbol": "AAPL",
  "qty": "10",
  "side": "buy",
  "type": "market",
  "time_in_force": "day",
  "status": "accepted",
  "filled_qty": "0",
  "average_price": "0.00",
  "submitted_at": "2025-06-15T14:30:00Z"
}
Order status progresses: acceptedfilled (or partially_filled, canceled, rejected). Check order status:
curl -X GET "$BASE_URL/trading/accounts/$ACCOUNT_ID/orders/ord_x9y8z7a6b5c4d3e2" \
  -H "Authorization: Basic $AUTH"
See Trading for limit, stop, and trailing stop orders.

Step 6 — Check positions

After an order fills, view the account’s holdings:
curl -X GET "$BASE_URL/trading/accounts/$ACCOUNT_ID/positions" \
  -H "Authorization: Basic $AUTH"
[
  {
    "id": "pos_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "account_id": "3d0b0e65-35d3-4dcd-8df7-10286ebb4b4b",
    "symbol": "AAPL",
    "asset_id": "asset_9f8e7d6c-5b4a-3210-fedc-ba0987654321",
    "currency": "USD",
    "quantity": "10",
    "average_cost_basis": "178.50",
    "total_cost_basis": "1785.00",
    "current_price": "179.25",
    "market_value": "1792.50",
    "unrealized_pl": "7.50",
    "unrealized_pl_percent": "0.0042",
    "price_source": "ALPACA",
    "price_confidence": "REAL_TIME",
    "price_timestamp": "2025-06-15T14:30:00Z",
    "last_transaction_at": "2025-06-15T14:00:00Z",
    "created_at": "2025-06-15T14:00:00Z",
    "updated_at": "2025-06-15T14:30:00Z"
  }
]

Step 7 — Withdraw funds

Sell positions first, then withdraw proceeds to the linked bank account:
# Sell the AAPL position
curl -X POST "$BASE_URL/trading/accounts/$ACCOUNT_ID/orders" \
  -H "Authorization: Basic $AUTH" \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "AAPL",
    "side": "sell",
    "type": "market",
    "time_in_force": "day",
    "qty": "10"
  }'

# Withdraw to linked bank account
curl -X POST "$BASE_URL/accounts/$ACCOUNT_ID/withdrawals" \
  -H "Authorization: Basic $AUTH" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wdr-$(uuidgen)" \
  -d '{
    "amount": "1000.00",
    "currency": "USD",
    "method": "ach",
    "description": "Withdrawal to checking account",
    "plaid_options": {
      "item_id": "'"$PLAID_ITEM_ID"'",
      "account_id": "'"$PLAID_ACCOUNT_ID"'"
    }
  }'
Withdrawal status progresses: pendingprocessingsubmittedcompleted.

Summary

StepEndpointWhat it does
1POST /accountsCreate investment account with identity, contact, disclosures
2POST /documents/accounts/{id}/uploadUpload KYC documents for verification
3aPOST /accounts/{id}/funding-sources/plaid/link-tokenGet Plaid Link token for bank connection UI
3bPOST /accounts/{id}/funding-sources/plaid/connectExchange Plaid public token to finalize connection
4POST /accounts/{id}/depositsDeposit funds via ACH or manual bank transfer
5POST /trading/accounts/{id}/ordersPlace a buy order
6GET /trading/accounts/{id}/positionsView current holdings
7POST /accounts/{id}/withdrawalsWithdraw proceeds to linked bank account

Next steps