# Bluum Finance API Documentation > Bluum Finance is an investment infrastructure API for fintech companies. Embed self-directed trading or full-service wealth management into your product with accounts, wallets, compliance, trading, and portfolio management. Base URLs: - Sandbox: https://test-service.bluumfinance.com/v1 - Production: https://service.bluumfinance.com/v1 Authentication: HTTP Basic Auth with API Key (username) and API Secret (password). ## Welcome to Bluum Understand what Bluum does, how it works, and which integration path is right for your product. Bluum Finance is an investment infrastructure API for fintech companies. We sit between your application and the financial system — handling account onboarding, identity verification, custodian routing, order execution, wallet management, and regulatory compliance. You build the user experience. Bluum provides the rails. ## How it works ``` ┌─────────────┐ ┌─────────────────┐ ┌──────────────────┐ │ Your App │ ───► │ Bluum API │ ───► │ Financial System │ │ (Frontend) │ ◄─── │ (Orchestrator) │ ◄─── │ (Custodians, │ └─────────────┘ └─────────────────┘ │ Banks, KYC) │ └──────────────────┘ ``` Your application makes REST calls to the Bluum API. Bluum orchestrates the downstream operations — creating brokerage accounts with custodians, routing orders to exchanges, initiating ACH transfers, running identity verification — and returns clean, normalized responses. Webhook events notify you in real-time when asynchronous operations complete (order fills, deposit arrivals, KYC decisions). ## Two integration models Bluum supports two ways to embed investing, depending on your product: | | Self-Directed Investing | Wealth Management | |---|---|---| | **What it is** | Your users decide what to buy and sell | Bluum's engine manages portfolios on behalf of your users | | **Account type** | `individual` with `management_type: "self_directed"` | `individual` with `management_type: "advised"` | | **Who places trades** | End user (through your UI) | Strategy engine (via portfolios and auto-invest) | | **Wealth APIs needed** | No | Yes (profiles, risk, goals, portfolios) | | **Typical products** | Trading apps, invest tabs, stock gifting, round-ups | Robo-advisors, financial planner apps, employee benefits | | **Time to first trade** | ~30 min in sandbox | ~1 hour in sandbox | Both models share the same foundation: accounts, compliance, wallets, funding, and market data. Wealth Management adds a layer of financial planning, risk assessment, and portfolio automation on top. ## Core objects These are the building blocks you'll work with across both integration models: | Object | What it represents | |--------|--------------------| | **Account** | An investment account for one end user. Holds identity, contact info, disclosures, and agreements. | | **Wallet** | The cash balance for an account. Receives deposits, funds trades, and disburses withdrawals. | | **Deposit / Withdrawal** | A money movement into or out of a wallet. Supports ACH (via Plaid) and manual bank transfer. | | **Funding Source** | A linked bank account (via Plaid) used for ACH deposits and withdrawals. | | **Order** | A buy or sell instruction. Supports market, limit, stop, and trailing stop types. | | **Position** | A holding in a specific asset — quantity, cost basis, current value, and P&L. | | **Asset** | A tradable instrument (stock, ETF). Searchable by symbol, name, or asset class. | | **Webhook** | A registered URL that receives event notifications (order filled, deposit completed, etc.). | Wealth Management adds: | Object | What it represents | |--------|--------------------| | **Investor Profile** | Comprehensive financial picture — demographics, employment, tax, insurance, estate planning, preferences. | | **Risk Assessment** | A questionnaire-based risk tolerance score that drives portfolio construction. | | **Goal** | A financial objective (retirement, home purchase) with target amount and timeline. | | **Financial Plan** | A generated plan that maps goals to investment strategies. | | **Portfolio** | A managed collection of holdings governed by an Investment Policy Statement. | | **Auto-Invest** | A recurring schedule that automatically invests into a portfolio. | ## Authentication All API requests use **HTTP Basic Authentication**. Your API Key is the username, your API Secret is the password. ```bash curl -X GET 'https://test-service.bluumfinance.com/v1/assets?asset_class=us_equity' \ -H 'Authorization: Basic '$(echo -n 'YOUR_API_KEY:YOUR_API_SECRET' | base64) ``` You'll get separate credentials for sandbox and production. See [Authentication](/platform/authentication) for details. ## Environments | Environment | Base URL | Purpose | |-------------|----------|---------| | **Sandbox** | `https://test-service.bluumfinance.com/v1` | Development and testing. Data resets nightly. Simulated order fills. | | **Production** | `https://service.bluumfinance.com/v1` | Live operations. Real money and securities. Requires compliance approval. | Start in sandbox — no approval needed. See [Sandbox Environment](/get-started/sandbox) for setup details. ## Next steps Create an account, fund it, and place your first trade in under 30 minutes. Set up an investor profile, run risk assessment, and create a managed portfolio. ## Quick Start: Self-Directed Investing Create an account, fund it, and place your first trade — end to end in the sandbox. 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](/get-started/sandbox). A terminal or API client (cURL, Postman, or similar). Set these variables once — every example below references them: ```bash 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. ```bash cURL 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" } ] }' ``` ```javascript Node.js const credentials = Buffer.from(`${apiKey}:${apiSecret}`).toString('base64'); const response = await fetch(`${baseUrl}/accounts`, { method: 'POST', headers: { 'Authorization': `Basic ${credentials}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ 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' } ] }) }); const account = await response.json(); console.log(account.id); // Save this — used in every subsequent call ``` ```python Python import requests import base64 credentials = base64.b64encode(f'{api_key}:{api_secret}'.encode()).decode() response = requests.post( f'{base_url}/accounts', headers={ 'Authorization': f'Basic {credentials}', 'Content-Type': 'application/json' }, json={ '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' } ] } ) account = response.json() account_id = account['id'] # Save this ``` The response returns an account with `status: "ACTIVE"`. Store the `id` — you'll need it for every subsequent call. ```bash ACCOUNT_ID="3d0b0e65-35d3-4dcd-8df7-10286ebb4b4b" ``` See [Accounts](/platform/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). ```bash 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" ``` ```json { "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`: ```bash curl -X GET "$BASE_URL/documents/doc_a1b2c3d4e5f6g7h8" \ -H "Authorization: Basic $AUTH" ``` In sandbox, documents are auto-approved. In production, use [webhooks](/platform/webhooks) to receive notifications instead of polling. --- ## Step 3 — Link a bank account via Plaid Before the user can deposit funds, they need to link a bank account. This is a two-step flow using Plaid Link. ### 3a. Create a Plaid Link token Generate a token your frontend uses to launch the Plaid Link widget: ```bash 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 }' ``` ```json { "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: ```bash 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. ```bash PLAID_ITEM_ID="item_abc123" PLAID_ACCOUNT_ID="acc_1234567890" ``` --- ## Step 4 — Deposit funds Fund the account via ACH using the linked bank account: ```bash 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: `pending` → `processing` → `received` → `completed`. 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](/guides/self-directed/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: ```bash 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: ```bash 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 ```json { "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: `accepted` → `filled` (or `partially_filled`, `canceled`, `rejected`). Check order status: ```bash curl -X GET "$BASE_URL/trading/accounts/$ACCOUNT_ID/orders/ord_x9y8z7a6b5c4d3e2" \ -H "Authorization: Basic $AUTH" ``` See [Trading](/platform/trading) for limit, stop, and trailing stop orders. --- ## Step 6 — Check positions After an order fills, view the account's holdings: ```bash curl -X GET "$BASE_URL/trading/accounts/$ACCOUNT_ID/positions" \ -H "Authorization: Basic $AUTH" ``` ```json [ { "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: ```bash # 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: `pending` → `processing` → `submitted` → `completed`. --- ## Summary | Step | Endpoint | What it does | |------|----------|--------------| | 1 | `POST /accounts` | Create investment account with identity, contact, disclosures | | 2 | `POST /documents/accounts/{id}/upload` | Upload KYC documents for verification | | 3a | `POST /accounts/{id}/funding-sources/plaid/link-token` | Get Plaid Link token for bank connection UI | | 3b | `POST /accounts/{id}/funding-sources/plaid/connect` | Exchange Plaid public token to finalize connection | | 4 | `POST /accounts/{id}/deposits` | Deposit funds via ACH or manual bank transfer | | 5 | `POST /trading/accounts/{id}/orders` | Place a buy order | | 6 | `GET /trading/accounts/{id}/positions` | View current holdings | | 7 | `POST /accounts/{id}/withdrawals` | Withdraw proceeds to linked bank account | ## Next steps - [Platform Concepts](/platform/accounts) — Understand the data model in depth - [Webhooks](/platform/webhooks) — Set up real-time event notifications - [Going Live](/get-started/go-live) — Production readiness checklist ## Quick Start: Wealth Management Set up an investor profile, run a risk assessment, create a financial plan, and launch a managed portfolio. This guide walks through the wealth management lifecycle: account creation, investor profiling, risk assessment, financial planning, portfolio construction, and automated investing. By the end, you'll have a managed portfolio running in the sandbox. **Prerequisites:** API credentials from the [sandbox environment](/get-started/sandbox). Familiarity with the [self-directed quick start](/get-started/quick-start-self-directed) (account creation and funding are the same). ```bash 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 Create an `individual` account (rather than `trading`) for wealth management: ```bash curl -X POST "$BASE_URL/accounts" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "account_type": "individual", "management_type": "advised", "contact": { "email_address": "alex.chen@example.com", "phone_number": "+14155559876", "street_address": ["789 Elm Street"], "city": "Austin", "state": "TX", "postal_code": "78701", "country": "US" }, "identity": { "first_name": "Alex", "last_name": "Chen", "date_of_birth": "1985-03-22", "tax_id": "123-45-6789", "tax_id_type": "SSN", "country_of_citizenship": "US", "country_of_birth": "US", "country_of_tax_residence": "US", "funding_source": ["employment_income", "investments"] }, "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" } ] }' ``` Store the account ID: ```bash ACCOUNT_ID="3d0b0e65-35d3-4dcd-8df7-10286ebb4b4b" ``` Complete KYC (Step 2 of the [self-directed quick start](/get-started/quick-start-self-directed#step-2--upload-kyc-documents)) and bank linking (Step 3) before continuing. Fund the account with at least $10,000 for a meaningful portfolio. --- ## Step 2 — Build the investor profile The investor profile captures the user's full financial picture. This data drives risk assessment, financial planning, and portfolio construction. ```bash curl -X PUT "$BASE_URL/wealth/accounts/$ACCOUNT_ID/profile" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "demographics": { "marital_status": "married", "number_of_dependents": 2 }, "employment": { "status": "employed", "employer": "Acme Corp", "occupation": "Software Engineer", "annual_income": "185000.00", "income_stability": "stable" }, "tax": { "filing_status": "married_filing_jointly", "federal_tax_bracket": "24", "state_tax_rate": "0" }, "investment_preferences": { "investment_experience": "intermediate", "investment_horizon": "long_term", "liquidity_needs": "low", "esg_preference": true }, "cash_flow": { "monthly_expenses": "8500.00", "monthly_savings": "3500.00", "emergency_fund_months": 6 } }' ``` The profile is modular — you can update individual sections as you collect data from your UI. See [Investor Profile](/platform/wealth/investor-profile) for all available sections (demographics, employment, tax, insurance, estate planning, partner details). --- ## Step 3 — Run a risk assessment Submit the risk questionnaire to determine the investor's risk tolerance. This score drives portfolio allocation. ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/risk-assessments" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "questionnaire_id": "default-risk-v1", "responses": [ { "question_id": "investment_goal", "answer_id": "growth" }, { "question_id": "time_horizon", "answer_id": "20_years" }, { "question_id": "risk_tolerance", "answer_id": "moderate" }, { "question_id": "reaction_to_loss", "answer_id": "hold_and_wait" }, { "question_id": "income_needs", "answer_id": "none" }, { "question_id": "investment_knowledge", "answer_id": "intermediate" } ] }' ``` ```json { "id": "ra_f1e2d3c4b5a69870", "account_id": "3d0b0e65-35d3-4dcd-8df7-10286ebb4b4b", "risk_score": 65, "risk_category": "moderate_growth", "recommended_equity_allocation": 70, "recommended_fixed_income_allocation": 25, "recommended_alternatives_allocation": 5, "created_at": "2025-06-15T11:00:00Z" } ``` View the current assessment and summary: ```bash # Current assessment curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/risk-assessments/current" \ -H "Authorization: Basic $AUTH" # Summary across all assessments curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/risk-assessments/summary" \ -H "Authorization: Basic $AUTH" ``` See [Risk Assessment](/platform/wealth/risk-assessment) for the full questionnaire structure and scoring model. --- ## Step 4 — Create goals Define the investor's financial objectives. Goals anchor the financial plan. ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/goals" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "name": "Retirement", "type": "retirement", "target_amount": "2000000.00", "target_date": "2050-01-01", "priority": "high", "current_savings": "150000.00", "monthly_contribution": "2000.00" }' ``` ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/goals" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "name": "College Fund - Emma", "type": "education", "target_amount": "250000.00", "target_date": "2040-09-01", "priority": "medium", "current_savings": "25000.00", "monthly_contribution": "500.00" }' ``` --- ## Step 5 — Generate a financial plan The financial plan maps the investor's goals, risk profile, and financial situation into an actionable strategy: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/financial-plan" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "include_goals": true, "include_tax_optimization": true, "planning_horizon_years": 25 }' ``` View the plan summary: ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/financial-plan/summary" \ -H "Authorization: Basic $AUTH" ``` --- ## Step 6 — Create an Investment Policy Statement The IPS formalizes the investment strategy based on the financial plan and risk assessment: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/investment-policy" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "risk_profile": { "risk_tolerance": "moderate", "risk_score": 5, "volatility_tolerance": "medium" }, "time_horizon": { "years": 20, "category": "long_term" }, "investment_objectives": { "primary": "capital_appreciation", "secondary": ["income_generation"], "target_annual_return": "7.00" }, "target_allocation": { "equities": { "target_percent": "50.00", "min_percent": "40.00", "max_percent": "60.00" }, "fixed_income": { "target_percent": "25.00", "min_percent": "20.00", "max_percent": "30.00" }, "alternatives": { "target_percent": "5.00", "min_percent": "0.00", "max_percent": "10.00" } }, "constraints": { "liquidity_requirements": { "minimum_cash_percent": "5.00", "emergency_fund_months": 6 }, "tax_considerations": { "tax_loss_harvesting": true, "tax_bracket": "24", "prefer_tax_advantaged": true }, "rebalancing_policy": { "frequency": "quarterly", "threshold_percent": "5.00", "tax_aware": true } } }' ``` Validate the IPS before proceeding: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/investment-policy/validate" \ -H "Authorization: Basic $AUTH" ``` --- ## Step 7 — Create a managed portfolio Create a portfolio governed by the IPS: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "name": "Alex Growth Portfolio", "initial_investment": "10000.00" }' ``` ```bash PORTFOLIO_ID="pf_a1b2c3d4e5f6g7h8" ``` View portfolio details: ```bash # Summary curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/summary" \ -H "Authorization: Basic $AUTH" # Holdings curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/holdings" \ -H "Authorization: Basic $AUTH" # Performance curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/performance" \ -H "Authorization: Basic $AUTH" ``` --- ## Step 8 — Set up auto-invest Configure recurring investments into the portfolio: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/auto-invest" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "portfolio_id": "'"$PORTFOLIO_ID"'", "amount": "2000.00", "frequency": "monthly", "day_of_month": 1, "funding_source": "wallet" }' ``` Enable dividend reinvestment (DRIP): ```bash curl -X PUT "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/drip" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "enabled": true }' ``` --- ## Step 9 — Monitor with insights Check portfolio insights and recommendations: ```bash # Insights (alerts, opportunities) curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/insights" \ -H "Authorization: Basic $AUTH" # Recommendations curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/recommendations" \ -H "Authorization: Basic $AUTH" # Tax optimization opportunities curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/tax-optimization" \ -H "Authorization: Basic $AUTH" ``` --- ## Summary | Step | Endpoint | What it does | |------|----------|--------------| | 1 | `POST /accounts` | Create an `individual` account | | 2 | `PUT /wealth/accounts/{id}/profile` | Build comprehensive investor profile | | 3 | `POST /wealth/accounts/{id}/risk-assessments` | Determine risk tolerance and allocation | | 4 | `POST /wealth/accounts/{id}/goals` | Define financial objectives | | 5 | `POST /wealth/accounts/{id}/financial-plan` | Generate an investment strategy | | 6 | `POST /wealth/accounts/{id}/investment-policy` | Formalize the IPS | | 7 | `POST /wealth/accounts/{id}/portfolios` | Create a managed portfolio | | 8 | `POST /wealth/accounts/{id}/auto-invest` | Set up recurring investments | | 9 | `GET /wealth/accounts/{id}/insights` | Monitor and optimize | ## Next steps - [Wealth Management Overview](/platform/wealth/overview) — Understand the wealth data model - [Portfolio Management](/platform/wealth/portfolios) — Rebalancing, performance, and holdings - [Going Live](/get-started/go-live) — Production readiness checklist ## Sandbox Environment Set up and use the Bluum sandbox for development and testing. The sandbox is a fully functional replica of the Bluum production environment. It simulates order execution, deposit processing, and KYC verification — without touching real money or securities. ## Sandbox vs Production | | Sandbox | Production | |---|---|---| | **Base URL** | `https://test-service.bluumfinance.com/v1` | `https://service.bluumfinance.com/v1` | | **Data persistence** | Resets nightly at midnight UTC | Permanent | | **Order execution** | Simulated fills (instant for market orders) | Real exchange execution | | **Deposits** | Simulated ACH processing | Real bank transfers | | **KYC verification** | Auto-approved in most cases | Real identity verification | | **Rate limit** | 10 requests/second | 25 requests/second | | **Access** | Immediate after signup | Requires compliance approval | ## Getting credentials Visit the [Bluum Finance dashboard](https://dashboard.bluumfinance.com/login) and sign up with your email and company information. Navigate to **Settings > API Keys** in the dashboard. Click **Create API Key** and copy both the API Key and API Secret immediately — the secret is only shown once. Store your API secret securely. If you lose it, you'll need to generate a new key pair. ```bash curl -X GET 'https://test-service.bluumfinance.com/v1/assets?asset_class=us_equity&tradable=true' \ -H 'Authorization: Basic '$(echo -n 'YOUR_API_KEY:YOUR_API_SECRET' | base64) ``` A successful response returns a JSON array of tradable assets. ## Setting up Postman The Bluum Postman collection includes pre-configured requests for every endpoint. 1. Import the collection from our [Postman documentation](https://documenter.getpostman.com/view/40898431/2sBXVo97hh) 2. Set environment variables: - `baseUrl` = `https://test-service.bluumfinance.com/v1` - `apiKey` = your sandbox API key - `apiSecret` = your sandbox API secret 3. The collection handles Base64 encoding automatically See [Postman Collection](/resources/postman) for detailed setup instructions. ## Sandbox behaviors ### Order execution Market orders fill instantly at the current simulated price. Limit orders fill when the simulated price crosses the limit. Orders placed outside market hours queue until the next simulated open. ### Deposits and withdrawals ACH deposits via Plaid transition through `pending` → `processing` → `completed` within seconds. Manual bank transfer deposits remain in `pending` until you explicitly trigger completion (useful for testing status transitions). ### KYC verification Document uploads are auto-approved in sandbox. To test rejection scenarios, use specific test values documented in your dashboard. ### Webhooks Webhook events fire in sandbox just like production. Register a webhook endpoint to receive real-time notifications during development. Tools like [webhook.site](https://webhook.site) or [ngrok](https://ngrok.com) are useful for local testing. ## Environment variable setup Use environment variables to switch between sandbox and production without code changes: ```bash # Sandbox export BLUUM_BASE_URL="https://test-service.bluumfinance.com/v1" export BLUUM_API_KEY="your_sandbox_key" export BLUUM_API_SECRET="your_sandbox_secret" ``` ```javascript const BASE_URL = process.env.BLUUM_BASE_URL; const credentials = Buffer.from( `${process.env.BLUUM_API_KEY}:${process.env.BLUUM_API_SECRET}` ).toString('base64'); ``` Never hardcode credentials. Use a secret manager (AWS Secrets Manager, HashiCorp Vault, Doppler) or environment variables. ## Next steps - [Quick Start: Self-Directed Investing](/get-started/quick-start-self-directed) — Make your first trade - [Quick Start: Wealth Management](/get-started/quick-start-wealth) — Set up a managed portfolio - [Going Live](/get-started/go-live) — Production readiness checklist ## Going Live Production readiness checklist and deployment guide. This page covers everything you need to move from sandbox to production. ## Production readiness checklist Before requesting production access, confirm you've completed each item: Your integration handles the full lifecycle you plan to support in production: - Account creation and KYC verification - Bank account linking and funding - Order placement, status tracking, and fills - Withdrawal processing - Error handling for all failure scenarios - Webhook event processing (if using webhooks) All deposit and withdrawal requests include an `Idempotency-Key` header to prevent duplicate transfers on retry. Your integration handles: - `401` authentication failures (credential rotation, expiry) - `429` rate limit responses (exponential backoff with jitter) - `400` validation errors (surface to users or log for debugging) - `500` server errors (retry with backoff, alert on repeated failures) - Network timeouts (retry with idempotency key) See [Error Handling](/platform/errors) for the complete error model. If you use webhooks: - Endpoint is HTTPS only - Verify webhook signatures before processing - Respond with `2xx` within 30 seconds - Handle duplicate deliveries idempotently - Alert on delivery failures See [Webhooks](/platform/webhooks) for implementation details. - API keys and secrets are in a secret manager (not code, not config files) - Credentials are scoped to service accounts, not individual developers - You have a key rotation process ready Contact your Bluum account manager to confirm: - Your KYC document collection meets regulatory requirements - Required disclosures and agreements are presented to end users - Your record-keeping meets applicable regulations ## Requesting production access 1. Email `support@bluum.finance` with: - Your company name and dashboard account email - A description of your product and integration model (Self-Directed or Wealth Management) - Confirmation that you've completed the readiness checklist above 2. Bluum reviews your sandbox integration and compliance setup 3. Upon approval, you receive production API credentials ## Deploying to production Switch your base URL and credentials to production values: ```bash export BLUUM_BASE_URL="https://service.bluumfinance.com/v1" export BLUUM_API_KEY="your_production_key" export BLUUM_API_SECRET="your_production_secret" ``` Verify connectivity with a read-only request: ```bash curl -X GET "$BLUUM_BASE_URL/assets?asset_class=us_equity&tradable=true" \ -H "Authorization: Basic $(echo -n "$BLUUM_API_KEY:$BLUUM_API_SECRET" | base64)" ``` Confirm you receive a `200` response. If you see `401`, verify both key and secret are updated. Watch the first few production operations closely: - First account creation and KYC flow - First deposit and order - Webhook delivery to your production endpoint Log `X-Request-Id` headers from every response for troubleshooting. ## Credential rotation Rotate API keys every 90 days: 1. Generate a new key pair in the dashboard 2. Deploy the new credentials to your production environment 3. Verify connectivity with the new credentials 4. Revoke the old key pair in the dashboard Overlap old and new keys during rotation. Generate the new key, deploy it, verify it works, then revoke the old one. Never revoke before verifying the new key. ## Observability ### Request logging Log every API request and response, including: - HTTP method and path - Response status code - `X-Request-Id` header (include in support tickets) - Response time ### Monitoring Set up alerts for: - Elevated error rates (5xx responses) - Rate limit hits (429 responses) - Orders stuck in `accepted` status for longer than expected - Webhook delivery failures ## Support | Channel | Use for | |---------|---------| | `support@bluum.finance` | Credentials, access, compliance, general questions | | `ope@bluumfinance.com` | Integration help, technical questions | | Emergency trading desk: `+1 (415) 555-0112` | Urgent production issues during market hours (24/5) | ## Authentication How to authenticate with the Bluum Finance API using HTTP Basic Auth. All Bluum API requests use **HTTP Basic Authentication**. Your API Key is the username and your API Secret is the password. ## How it works 1. Concatenate your API Key and Secret with a colon: `API_KEY:API_SECRET` 2. Base64-encode the result 3. Send it in the `Authorization` header as `Basic ` ```bash cURL curl -X GET 'https://test-service.bluumfinance.com/v1/assets' \ -H 'Authorization: Basic '$(echo -n 'YOUR_API_KEY:YOUR_API_SECRET' | base64) ``` ```javascript Node.js const credentials = Buffer.from(`${apiKey}:${apiSecret}`).toString('base64'); const response = await fetch('https://test-service.bluumfinance.com/v1/assets', { headers: { 'Authorization': `Basic ${credentials}` } }); ``` ```python Python import requests, base64 credentials = base64.b64encode(f'{api_key}:{api_secret}'.encode()).decode() response = requests.get( 'https://test-service.bluumfinance.com/v1/assets', headers={'Authorization': f'Basic {credentials}'} ) ``` ## Required headers | Header | Required | Description | |--------|----------|-------------| | `Authorization` | Always | `Basic ` | | `Content-Type` | POST/PUT | `application/json` for JSON payloads, `multipart/form-data` for file uploads | | `Idempotency-Key` | Deposits/Withdrawals | Unique key to prevent duplicate operations on retry | ## Credentials You receive separate credentials for each environment: | Environment | Base URL | How to get | |-------------|----------|------------| | Sandbox | `https://test-service.bluumfinance.com/v1` | Dashboard → Settings → API Keys | | Production | `https://service.bluumfinance.com/v1` | Issued after compliance approval | The API Secret is shown only once when created. Store it immediately in a secret manager. If lost, generate a new key pair. ## Security best practices - Store credentials in a secret manager (AWS Secrets Manager, HashiCorp Vault, Doppler) — never in code or config files - Rotate keys every 90 days - Scope credential access to service accounts, not individual developers - Use environment variables to switch between sandbox and production - Never log or transmit credentials in plaintext ## Common authentication errors | Error Code | Meaning | Resolution | |------------|---------|------------| | `BLUM-401-001` | Missing Authorization header | Include `Authorization: Basic ` | | `BLUM-401-002` | Malformed credentials | Verify Base64 encoding of `API_KEY:API_SECRET` | | `BLUM-401-003` | Invalid API key or secret | Check credentials are correct and not revoked | | `BLUM-401-004` | API key inactive | Contact support to activate your key | ## Accounts Investment accounts — types, lifecycle, required fields, and relationships to other objects. An **Account** represents an investment account for one end user. It's the root object that connects to wallets, orders, positions, and (for wealth management) investor profiles and portfolios. ## Account types | Type | Use case | Wealth APIs | Auto-creates | |------|----------|-------------|-------------| | `individual` | Personal investment account — most common type | Depends on `management_type` | Wallet (+ Investor Profile if `advised`) | | `joint` | Joint investment account shared by two or more individuals | Depends on `management_type` | Wallet (+ Investor Profile if `advised`) | | `corporate` | Corporate/business investment account | Depends on `management_type` | Wallet (+ Investor Profile if `advised`) | ### Management type The `management_type` field controls whether the account is self-directed or professionally managed: | Management type | Description | Wealth APIs | |-----------------|-------------|-------------| | `self_directed` | User decides what to buy and sell (default) | No | | `advised` | Managed portfolios, financial planning, robo-advisory | Yes | For example, a self-directed personal account uses `account_type: "individual"` with `management_type: "self_directed"` (the default). A wealth-managed account uses `account_type: "individual"` with `management_type: "advised"`. For backward compatibility, `trading` is accepted as an `account_type` value and maps to `individual` with `management_type: "self_directed"`. ## Account lifecycle ``` Create Account → KYC Verification → Active → Trading │ (orders, deposits, │ withdrawals) ▼ Inactive / Suspended ``` | Status | Meaning | |--------|---------| | `ACTIVE` | Account is open and can trade (sandbox accounts are immediately active) | | `INACTIVE` | Account is deactivated | | `SUSPENDED` | Account temporarily suspended | ## Required fields ### Contact | Field | Type | Required | Description | |-------|------|----------|-------------| | `email_address` | string | Yes | User's email | | `phone_number` | string | Yes | E.164 format (e.g., `+14155551234`) | | `street_address` | string[] | Yes | Street address lines | | `city` | string | Yes | City | | `state` | string | Yes | State/province code | | `postal_code` | string | Yes | Postal/ZIP code | | `country` | string | Yes | ISO 3166-1 alpha-2 (e.g., `US`) | ### Identity | Field | Type | Required | Description | |-------|------|----------|-------------| | `first_name` | string | Yes | Legal first name | | `last_name` | string | Yes | Legal last name | | `date_of_birth` | string | Yes | `YYYY-MM-DD` format | | `tax_id` | string | Yes | SSN or ITIN | | `tax_id_type` | string | Yes | `SSN` or `ITIN` | | `country_of_citizenship` | string | Yes | ISO 3166-1 alpha-2 | | `country_of_birth` | string | Yes | ISO 3166-1 alpha-2 | | `country_of_tax_residence` | string | Yes | ISO 3166-1 alpha-2 | | `funding_source` | string[] | Yes | Source of funds (e.g., `employment_income`, `investments`) | ### Disclosures All boolean, all required: - `is_control_person` — Is the user a control person of a publicly traded company? - `is_affiliated_exchange_or_finra` — Is the user affiliated with a stock exchange or FINRA? - `is_politically_exposed` — Is the user a politically exposed person? - `immediate_family_exposed` — Is an immediate family member politically exposed? ### Agreements At least `account_agreement` and `customer_agreement` must be signed: | Field | Type | Description | |-------|------|-------------| | `agreement` | string | Agreement type | | `agreed` | boolean | Must be `true` | | `signed_at` | string | ISO 8601 timestamp | | `ip_address` | string | IP address of the signer | ### Additional account fields These optional fields configure trading behavior and tax treatment: | Field | Type | Default | Description | |-------|------|---------|-------------| | `tax_advantaged` | boolean | `false` | Whether the account has tax-advantaged status (IRA, ISA, RRSP) | | `tax_designation` | string | `null` | Jurisdiction-specific designation (e.g., `traditional_ira`, `roth_ira`, `isa`, `rrsp`) | | `trading_type` | string | `margin` | Trading type: `margin` or `cash` | | `enabled_assets` | string[] | `["us_equity"]` | Asset classes enabled: `us_equity`, `us_option`, `crypto`, `bonds`, `etf`, `mutual_funds` | ## Relationships ``` Account ├── Wallet (cash balance, deposits, withdrawals) ├── Orders (buy/sell instructions) ├── Positions (current holdings) ├── Documents (KYC uploads) ├── Funding Sources (linked bank accounts) └── [Wealth Management] ├── Investor Profile ├── Risk Assessments ├── Goals & Life Events ├── Financial Plan ├── Investment Policy Statement └── Portfolios ``` ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/accounts` | Create an account | | `GET` | `/accounts/{account_id}` | Get account details | | `GET` | `/accounts` | List all accounts | | `GET` | `/accounts/{account_id}/wallets` | Get account wallets | | `GET` | `/accounts/{account_id}/transactions` | Get transaction history | ## Compliance & KYC Identity verification, document requirements, and compliance workflows. Every investment account must complete Know Your Customer (KYC) verification before trading. Bluum handles the compliance pipeline — you collect documents from the user and submit them through the API. ## How KYC works ``` Upload Documents → Processing → Approved / Rejected │ (auto-approved in sandbox) ``` 1. Create an account with identity and contact information 2. Upload identity documents (government-issued ID, proof of address) 3. Bluum verifies the documents against the identity information 4. Account is approved or rejected In sandbox, documents are auto-approved. In production, verification typically completes within minutes but may take up to 24 hours for manual review. ## Document types | Type | Description | Required | |------|-------------|----------| | `id_verification` | Government-issued photo ID (passport, driver's license, national ID) | Yes | | `proof_of_address` | Utility bill, bank statement, or tax document showing current address | Situational | | `w9_form` | W-9 tax form | Situational | ## Upload a document ```bash 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" ``` Supported file formats: JPEG, PNG, PDF. Maximum file size: 10MB. ## Document statuses | Status | Meaning | |--------|---------| | `processing` | Document uploaded, verification in progress | | `approved` | Verification successful | | `rejected` | Verification failed — upload a new document | ## Checking document status Poll the document endpoint or use webhooks: ```bash # Get a specific document curl -X GET "$BASE_URL/documents/{document_id}" \ -H "Authorization: Basic $AUTH" # List all documents for an account curl -X GET "$BASE_URL/documents/accounts/$ACCOUNT_ID/upload?status=approved" \ -H "Authorization: Basic $AUTH" ``` Use [webhooks](/platform/webhooks) to receive real-time notifications when document verification completes, rather than polling. ## Handling rejections When a document is rejected, the response includes a reason. Common rejection reasons: | Reason | Resolution | |--------|-----------| | Document is blurry or illegible | Re-upload with higher quality image | | Document is expired | Upload a current, non-expired document | | Name mismatch | Ensure the name on the document matches the account identity | | Document type not accepted | Use a supported government-issued photo ID | Prompt the user to upload a replacement document. There is no limit on upload attempts. ## Compliance workflow statuses For more complex compliance scenarios, Bluum uses a multi-step compliance workflow: | Status | Meaning | |--------|---------| | `IN_PROGRESS` | Compliance checks are running | | `PENDING_REVIEW` | Requires manual review | | `APPROVED` | All checks passed | | `REJECTED` | One or more checks failed | | `SUSPENDED` | Account compliance suspended | | `EXPIRED` | Compliance window expired, re-verification needed | ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/documents/accounts/{account_id}/upload` | Upload a document | | `GET` | `/documents/{document_id}` | Get document status | | `GET` | `/documents/accounts/{account_id}/upload` | List account documents | ## Wallets Cash balance management — wallet types, holds, and ledger entries. A **Wallet** holds the cash balance for an investment account. Deposits flow into the wallet, trades debit from it, and withdrawals send funds back to the user's bank account. Every account gets a wallet automatically when created. ## Wallet types Bluum supports three wallet types, depending on your integration model: | Type | Balance source | Best for | |------|---------------|----------| | **Hosted** | Bluum manages the balance directly | Most integrations — Bluum tracks all cash | | **BYO (Bring Your Own)** | Your system provides the balance via an endpoint | Partners who manage their own ledger | | **Omnibus** | Computed from an immutable double-entry ledger | Partners needing full audit trail | Most integrations use the **Hosted** wallet type. BYO and Omnibus are available for partners with specific operational requirements. ## How money flows ``` Bank Account ──deposit──► Wallet ──buy order──► Positions ▲ │ │ │ sell order ◄───────────────┘ │ ──withdrawal──► Bank Account ``` 1. **Deposit** — Money enters the wallet from a linked bank account 2. **Buy order** — Wallet balance decreases; a hold is placed during execution 3. **Sell order** — Proceeds return to the wallet after the order fills 4. **Withdrawal** — Money leaves the wallet to the user's bank account ## Wallet holds When an order is placed, Bluum creates a **hold** on the wallet to reserve funds for the trade. This prevents the user from spending the same money twice. | Hold status | Meaning | |-------------|---------| | `ACTIVE` | Funds reserved, order in progress | | `RELEASED` | Order canceled, funds returned to available balance | | `CAPTURED` | Order filled, funds debited | | `EXPIRED` | Hold expired (order timed out) | ## Balance fields | Field | Description | |-------|-------------| | `balance` | Total wallet balance (available + held) | | `available_balance` | Balance available for new trades or withdrawals | | `reserved_balance` | Balance reserved by active holds | ``` balance = available_balance + reserved_balance ``` ## Transactions Every wallet operation creates a transaction record: | Transaction type | Description | |-----------------|-------------| | `deposit` | Funds received from bank account | | `withdrawal` | Funds sent to bank account | | `buy` | Funds debited for order execution | | `sell` | Proceeds credited from order fill | | `fee` | Trading commission or platform fee | ```bash # Get wallet details curl -X GET "$BASE_URL/accounts/$ACCOUNT_ID/wallets" \ -H "Authorization: Basic $AUTH" # Get transaction history curl -X GET "$BASE_URL/accounts/$ACCOUNT_ID/transactions" \ -H "Authorization: Basic $AUTH" ``` ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/accounts/{account_id}/wallets` | Get wallet details and balance | | `GET` | `/accounts/{account_id}/transactions` | List wallet transactions | ## Funding & Transfers Deposit and withdrawal methods, transfer lifecycle, and Plaid integration. Funding covers how money moves between your user's bank account and their investment wallet. Bluum supports ACH transfers via Plaid and manual bank transfers. ## Deposit methods | Method | How it works | Settlement | |--------|-------------|------------| | **ACH via Plaid** (`ach`) | Automated pull from linked bank account | 1-3 business days | | **Manual Bank Transfer** (`manual_bank_transfer`) | User initiates a transfer from their bank using provided instructions | Varies (1-5 business days) | ## Plaid Link integration Before using ACH deposits, the user must link a bank account through Plaid Link. This is a two-step process: ### 1. Create a Link token Your backend requests a Plaid Link token from Bluum: ```bash 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 }' ``` ### 2. Exchange the public token Your frontend launches Plaid Link with the token. After the user selects their bank, Plaid returns a `public_token` to your frontend. Send it to Bluum: ```bash 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-abc123" }' ``` The response includes the linked bank account details (`itemId`, `accountId`) needed for deposits and withdrawals. ``` Your Frontend Your Backend Bluum API │ │ │ │ │──── Create Link Token ────►│ │ │◄─── link_token ───────────│ │◄─── Launch Plaid Link ────────│ │ │──── User selects bank ────────►│ │ │──── public_token ─────────────►│ │ │ │──── Exchange Token ───────►│ │ │◄─── Bank account details ──│ ``` Set `enable_hosted_link: true` for a Plaid-hosted redirect flow. The response includes a `hosted_link_url` you redirect the user to, instead of embedding the Plaid Link widget. ## Deposit lifecycle ``` pending → processing → received → completed │ failed ``` | Status | Meaning | |--------|---------| | `pending` | Deposit created, not yet initiated | | `processing` | ACH transfer initiated with the bank | | `received` | Funds received, awaiting final settlement | | `completed` | Funds available in wallet | | `failed` | Transfer failed (insufficient funds, bank rejection) | ## Withdrawal lifecycle ``` pending → processing → submitted → completed │ │ canceled failed ``` Withdrawals in `pending` status can be canceled. ## Manual bank transfers For `manual_bank_transfer` deposits, the response includes bank details and a reference code: ```json { "method_details": { "referenceCode": "BLUUM-ABC123XY", "bankDetails": { "bankName": "Choice Financial Group", "accountName": "Bluum Finance, Inc.", "accountNumber": "202534766488", "routingNumber": "091311229", "instructions": "Include reference code \"BLUUM-ABC123XY\" in your transfer memo." } }, "expires_at": "2025-06-22T10:45:00.000Z" } ``` Display the bank details and reference code to your user. The deposit expires at `expires_at` if funds are not received. ### Wire details PDF Download a formatted PDF with wire transfer instructions for a manual bank transfer deposit: ```bash curl -X GET "$BASE_URL/accounts/$ACCOUNT_ID/deposits/$DEPOSIT_ID/wire-details" \ -H "Authorization: Basic $AUTH" \ --output wire-details.pdf ``` The PDF includes domestic transfer details (and international SWIFT details if configured). Only available for deposits with method `manual_bank_transfer`. ## Idempotency Always include an `Idempotency-Key` header on deposit and withdrawal requests: ```bash curl -X POST "$BASE_URL/accounts/$ACCOUNT_ID/deposits" \ -H "Idempotency-Key: dep-unique-request-id-123" \ ... ``` If a request is retried with the same idempotency key, Bluum returns the original response instead of creating a duplicate transfer. See [Idempotency](/platform/idempotency) for details. ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/accounts/{id}/funding-sources/plaid/link-token` | Create Plaid Link token | | `POST` | `/accounts/{id}/funding-sources/plaid/connect` | Exchange Plaid public token | | `GET` | `/accounts/{id}/funding-sources` | List linked funding sources | | `POST` | `/accounts/{id}/deposits` | Create a deposit | | `GET` | `/accounts/{id}/deposits/{deposit_id}` | Get deposit status | | `POST` | `/accounts/{id}/deposits/{deposit_id}/cancel` | Cancel a pending deposit | | `GET` | `/accounts/{id}/deposits/{deposit_id}/wire-details` | Download wire details PDF | | `POST` | `/accounts/{id}/withdrawals` | Create a withdrawal | | `GET` | `/accounts/{id}/withdrawals/{withdrawal_id}` | Get withdrawal status | | `POST` | `/accounts/{id}/withdrawals/{withdrawal_id}/cancel` | Cancel a pending withdrawal | ## Trading Order types, lifecycle, fractional shares, and market hours. Trading lets your users buy and sell securities through the Bluum API. You submit orders, Bluum routes them to the appropriate exchange via our custodian network, and you receive status updates as orders execute. ## Order types | Type | Description | Required fields | |------|-------------|----------------| | `market` | Execute immediately at current market price | `qty` or `notional` | | `limit` | Execute only at the specified price or better | `qty`, `limit_price` | | `stop` | Trigger a market order when price hits stop level | `qty`, `stop_price` | | `stop_limit` | Trigger a limit order when price hits stop level | `qty`, `stop_price`, `limit_price` | | `trailing_stop` | Stop price trails market price by a fixed amount or percent | `qty`, `trail_price` or `trail_percent` | ## Order sides | Side | Description | |------|-------------| | `buy` | Purchase shares | | `sell` | Sell shares you own | ## Quantity vs Notional You can specify order size two ways: - **`qty`** — Number of shares (e.g., `"10"` for 10 shares) - **`notional`** — Dollar amount (e.g., `"1000.00"` to invest exactly $1,000) Notional orders result in fractional share purchases when the dollar amount doesn't divide evenly into whole shares. Only available for `market` orders. ## Time in force | Value | Meaning | |-------|---------| | `day` | Valid for the current trading day. Canceled at market close if unfilled. | | `gtc` | Good 'til canceled. Remains active until filled or explicitly canceled. | | `ioc` | Immediate or cancel. Fill what you can immediately, cancel the rest. | | `fok` | Fill or kill. Fill the entire order immediately, or cancel entirely. | ## Order lifecycle ``` accepted → filled │ │ │ partially_filled │ canceled / rejected ``` | Status | Meaning | |--------|---------| | `accepted` | Order received and queued for execution | | `partially_filled` | Some shares executed, remainder still active | | `filled` | Order fully executed | | `canceled` | Order canceled (by user or system) | | `rejected` | Order rejected (insufficient funds, invalid symbol, market closed) | ## Placing an order ```bash 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" }' ``` ### Response ```json { "id": "ord_x9y8z7a6b5c4d3e2", "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" } ``` ## Market hours US equity markets are open Monday–Friday, 9:30 AM – 4:00 PM Eastern Time, excluding holidays. - Market orders placed outside market hours queue until the next open - Limit and stop orders with `gtc` remain active across sessions - Use the [Market Data](/platform/assets) endpoints to check market status and calendar ## Commission and fees You can attach a commission to orders: | Field | Type | Description | |-------|------|-------------| | `commission` | string | Commission amount (e.g., `"1.00"`) | | `commission_type` | string | Calculation method: `notional` (per order, default), `qty` (per share, pro-rated), `bps` (basis points, up to 2 decimals) | Commission fields are optional. When set, they appear in the order response as well. ## Client order IDs Use `client_order_id` to assign your own identifier to an order for reconciliation: ```json { "symbol": "MSFT", "side": "buy", "type": "limit", "qty": "5", "limit_price": "350.00", "time_in_force": "gtc", "client_order_id": "my-order-001", "commission": "1.00", "commission_type": "notional" } ``` ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/trading/accounts/{id}/orders` | Place an order | | `GET` | `/trading/accounts/{id}/orders` | List orders for an account | | `GET` | `/trading/orders/{order_id}` | Get order details | ## Assets & Market Data Tradable instruments, asset search, quotes, bars, and market calendar. **Assets** are the tradable instruments available through Bluum — stocks, ETFs, and other securities. **Market Data** provides real-time and historical pricing, market status, and trading calendars. ## Asset classes | Class | Description | Markets | |-------|-------------|---------| | `us_equity` | US-listed stocks and ETFs | XNAS (NASDAQ), XNYS (NYSE) | ## Searching assets Find assets by symbol, name, or filters: ```bash # Search by symbol or name curl -X GET "$BASE_URL/assets/search?q=apple" \ -H "Authorization: Basic $AUTH" # List tradable US equities curl -X GET "$BASE_URL/assets/list?asset_class=us_equity&tradable=true" \ -H "Authorization: Basic $AUTH" # Get a specific asset curl -X GET "$BASE_URL/assets/AAPL" \ -H "Authorization: Basic $AUTH" ``` ### Asset object ```json { "id": "6c5b2403-24a9-4b55-a3dd-5cb1e4b50da6", "symbol": "AAPL", "name": "Apple Inc.", "class": "us_equity", "exchange": "NASDAQ", "status": "active", "tradable": true, "fractionable": true } ``` Key fields: - `tradable` — Whether the asset can be traded through Bluum - `fractionable` — Whether fractional shares are supported (enables notional orders) ## Market data ### Quotes Get the latest quote for an asset: ```bash curl -X GET "$BASE_URL/market-data/assets/AAPL/quote" \ -H "Authorization: Basic $AUTH" ``` ### Historical bars (OHLCV) Get historical price data: ```bash curl -X GET "$BASE_URL/market-data/assets/AAPL/bars?timeframe=1Day&start=2025-01-01&end=2025-06-15" \ -H "Authorization: Basic $AUTH" ``` ### Snapshots Get a complete market snapshot for an asset (quote + latest bar + previous close): ```bash curl -X GET "$BASE_URL/market-data/assets/AAPL/snapshot" \ -H "Authorization: Basic $AUTH" ``` ### Charts Get chart-ready data for an asset: ```bash curl -X GET "$BASE_URL/assets/chart?symbol=AAPL&timeframe=1Day&period=1M" \ -H "Authorization: Basic $AUTH" ``` ## Market status and calendar Check if a market is currently open and get the trading calendar: ```bash # List all markets curl -X GET "$BASE_URL/market-data/markets" \ -H "Authorization: Basic $AUTH" # Check if NYSE is open curl -X GET "$BASE_URL/market-data/markets/XNYS/status" \ -H "Authorization: Basic $AUTH" # Get trading calendar (open/close times, holidays) curl -X GET "$BASE_URL/market-data/markets/XNYS/calendar?start=2025-06-01&end=2025-06-30" \ -H "Authorization: Basic $AUTH" ``` ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/assets/search` | Search assets by name or symbol | | `GET` | `/assets/list` | List assets with filters | | `GET` | `/assets/{symbol}` | Get asset details | | `GET` | `/assets/chart` | Get chart data | | `GET` | `/market-data/assets/{symbol}/quote` | Latest quote | | `GET` | `/market-data/assets/{symbol}/bars` | Historical OHLCV bars | | `GET` | `/market-data/assets/{symbol}/snapshot` | Market snapshot | | `GET` | `/market-data/markets` | List markets | | `GET` | `/market-data/markets/{code}/status` | Market open/closed status | | `GET` | `/market-data/markets/{code}/calendar` | Trading calendar | ## Positions Track holdings, cost basis, market value, and unrealized P&L. A **Position** represents the user's holding in a specific asset — the quantity held, average cost basis, current market value, and unrealized profit or loss. Positions are created automatically when orders fill and updated in real-time as market prices change. ## Position object ```json { "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": "182.30", "market_value": "1823.00", "unrealized_pl": "38.00", "unrealized_pl_percent": "0.0213", "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" } ``` | Field | Description | |-------|-------------| | `id` | Unique position identifier | | `account_id` | Account that holds this position | | `symbol` | Ticker symbol | | `asset_id` | Unique asset identifier | | `currency` | ISO 4217 currency code | | `quantity` | Number of shares held (can be fractional) | | `average_cost_basis` | Volume-weighted average purchase price | | `total_cost_basis` | `quantity * average_cost_basis` | | `current_price` | Latest market price | | `market_value` | `quantity * current_price` | | `unrealized_pl` | `market_value - total_cost_basis` | | `unrealized_pl_percent` | Unrealized P&L as a percentage | | `price_source` | Where the price came from (`ALPACA`, `NAYA`, `DATABASE`, `UNAVAILABLE`) | | `price_confidence` | Freshness of the price (`REAL_TIME`, `DELAYED`, `END_OF_DAY`, `STALE`, `UNAVAILABLE`) | | `price_timestamp` | When the price was last updated | | `last_transaction_at` | When the position was last affected by a transaction | | `created_at` | When the position was created | | `updated_at` | When the position was last updated | ## Querying positions ```bash # All positions for an account curl -X GET "$BASE_URL/trading/accounts/$ACCOUNT_ID/positions" \ -H "Authorization: Basic $AUTH" # Specific position curl -X GET "$BASE_URL/trading/accounts/$ACCOUNT_ID/positions/$POSITION_ID" \ -H "Authorization: Basic $AUTH" ``` ## How positions update | Event | Effect on position | |-------|-------------------| | Buy order fills | Creates or increases position; recalculates `average_cost_basis` | | Sell order fills | Decreases position; removes if fully sold | | Market price changes | Updates `current_price`, `market_value`, `unrealized_pl` | | Stock split | Adjusts `quantity` and `average_cost_basis` proportionally | ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/trading/accounts/{id}/positions` | List all positions | | `GET` | `/trading/accounts/{id}/positions/{position_id}` | Get position details | ## Wealth Management Overview How Bluum's wealth management APIs work together — profiles, risk, planning, and portfolios. The Wealth Management APIs extend Bluum's core investing infrastructure with financial planning, risk assessment, portfolio construction, and automated investing. They're designed for products that offer managed investment experiences — robo-advisors, financial planner apps, and employee benefits platforms. ## Object relationships The wealth objects build on each other in a logical sequence: ``` Account (individual) └── Investor Profile (financial picture) ├── Risk Assessment (risk tolerance → allocation) ├── Goals & Life Events (financial objectives) ├── Dependents (family context) ├── External Accounts (outside holdings) │ └── Financial Plan (strategy) └── Investment Policy Statement (rules) └── Portfolio (managed holdings) ├── Auto-Invest (recurring investments) ├── DRIP (dividend reinvestment) ├── Rebalancing (maintain target allocation) └── Insights & Recommendations ``` Each layer informs the next. The investor profile feeds the risk assessment, which drives the financial plan, which produces the IPS, which governs the portfolio. ## The wealth lifecycle ### 1. Collect the investor profile Gather the user's financial picture: demographics, employment, income, tax situation, insurance, estate plans, and investment preferences. The profile is modular — you can collect sections progressively as the user completes your onboarding flow. See [Investor Profile](/platform/wealth/investor-profile). ### 2. Assess risk tolerance Submit a risk questionnaire to determine how much volatility the investor can tolerate. The assessment produces a risk score and recommended asset allocation split (equity/fixed income/alternatives). See [Risk Assessment](/platform/wealth/risk-assessment). ### 3. Define goals Create financial goals (retirement, education, home purchase) with target amounts and timelines. Record life events (marriage, job change, inheritance) that affect the plan. Add dependents and external accounts for a complete picture. See [Financial Planning](/platform/wealth/financial-planning). ### 4. Generate a financial plan The financial plan synthesizes goals, risk profile, and financial situation into an investment strategy. It maps each goal to a recommended approach and timeline. See [Financial Planning](/platform/wealth/financial-planning). ### 5. Create an Investment Policy Statement The IPS formalizes the investment rules: target asset allocation, rebalancing policy, risk constraints, and investment objectives. It serves as the governing document for the portfolio. See [Portfolios](/platform/wealth/portfolios). ### 6. Build and manage portfolios Create a portfolio governed by the IPS. Set up auto-invest for recurring contributions, enable DRIP for dividend reinvestment, and trigger rebalancing when allocations drift. See [Portfolios](/platform/wealth/portfolios). ### 7. Monitor and optimize Review insights and recommendations, track performance, run tax optimization analysis, and generate reports. See [Insights & Recommendations](/platform/wealth/insights). ## Wealth vs Self-Directed | Capability | Self-Directed | Wealth Management | |-----------|---------------|-------------------| | Account creation | `trading` type | `individual` type | | KYC & compliance | Same | Same | | Wallets & funding | Same | Same | | Manual order placement | Yes | Yes (but typically automated) | | Investor profile | Not used | Required | | Risk assessment | Not used | Required for portfolio construction | | Financial planning | Not used | Available | | Managed portfolios | Not used | Core feature | | Auto-invest & DRIP | Not used | Available | | Insights & reports | Not used | Available | You can support both models simultaneously — some users may want self-directed trading while others prefer a managed experience. ## Investor Profile Capture the investor's full financial picture — demographics, employment, tax, insurance, and preferences. The **Investor Profile** is a comprehensive representation of the investor's financial situation. It drives risk assessment, financial planning, and portfolio construction. ## Profile sections The profile is modular. Each section can be updated independently as you collect data from your user: | Section | What it captures | |---------|-----------------| | **Demographics** | Marital status, number of dependents | | **Employment** | Status, employer, occupation, annual income, income stability | | **Tax** | Filing status, federal tax bracket, state tax rate | | **Insurance** | Life, disability, health, long-term care coverage | | **Estate Planning** | Will status, trust, power of attorney, beneficiary designations | | **Cash Flow** | Monthly expenses, monthly savings, emergency fund months | | **Investment Preferences** | Experience level, time horizon, liquidity needs, ESG preference | | **Partner** | Spouse/partner employment, income, and retirement details | ## Creating and updating a profile Use `PUT` to create or update the profile. Only include the sections you want to set — omitted sections are not changed: ```bash # Set employment and tax sections curl -X PUT "$BASE_URL/wealth/accounts/$ACCOUNT_ID/profile" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "employment": { "status": "employed", "employer": "Acme Corp", "occupation": "Software Engineer", "annual_income": "185000.00", "income_stability": "stable" }, "tax": { "filing_status": "married_filing_jointly", "federal_tax_bracket": "24", "state_tax_rate": "0" } }' ``` ## Reading the profile ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/profile" \ -H "Authorization: Basic $AUTH" ``` ## Profile snapshots Snapshots capture a point-in-time record of the profile. Useful for compliance, auditing, and tracking changes over time. ```bash # List snapshots curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/profile/snapshots" \ -H "Authorization: Basic $AUTH" # Get a specific snapshot curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/profile/snapshots/$SNAPSHOT_ID" \ -H "Authorization: Basic $AUTH" ``` ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `PUT` | `/wealth/accounts/{id}/profile` | Create or update profile | | `GET` | `/wealth/accounts/{id}/profile` | Get current profile | | `GET` | `/wealth/accounts/{id}/profile/snapshots` | List profile snapshots | | `GET` | `/wealth/accounts/{id}/profile/snapshots/{snapshot_id}` | Get snapshot detail | ## Risk Assessment Questionnaire-based risk tolerance scoring that drives portfolio allocation. A **Risk Assessment** captures the investor's risk tolerance through a structured questionnaire. The result is a risk score and recommended asset allocation that drives portfolio construction. ## How it works 1. Present the risk questionnaire to your user 2. Submit their responses to the Bluum API 3. Bluum computes a risk score and recommended allocation 4. The allocation is used when creating portfolios and IPS ## Questionnaire structure The API uses a `questionnaire_id` to identify which questionnaire version to score against, and a `responses` array of question/answer pairs. ### Questions to present | Question | `question_id` | Answer options (`answer_id`) | |----------|---------------|------------------------------| | What is your primary investment goal? | `investment_goal` | `preservation`, `income`, `balanced`, `growth`, `aggressive_growth` | | How many years until you need this money? | `time_horizon` | `5_years`, `10_years`, `20_years`, `30_years` | | How would you describe your risk tolerance? | `risk_tolerance` | `conservative`, `moderate`, `aggressive` | | If your portfolio dropped 20%, what would you do? | `reaction_to_loss` | `sell_immediately`, `wait_and_see`, `hold_and_wait`, `buy_more` | | Do you need regular income from investments? | `income_needs` | `none`, `some`, `significant` | | How would you describe your investment knowledge? | `investment_knowledge` | `beginner`, `intermediate`, `advanced` | ## Submitting an assessment ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/risk-assessments" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "questionnaire_id": "default-risk-v1", "responses": [ { "question_id": "investment_goal", "answer_id": "growth" }, { "question_id": "time_horizon", "answer_id": "20_years" }, { "question_id": "risk_tolerance", "answer_id": "moderate" }, { "question_id": "reaction_to_loss", "answer_id": "hold_and_wait" }, { "question_id": "income_needs", "answer_id": "none" }, { "question_id": "investment_knowledge", "answer_id": "intermediate" } ] }' ``` ## Response ```json { "id": "ra_f1e2d3c4b5a69870", "risk_score": 65, "risk_category": "moderate_growth", "recommended_equity_allocation": 70, "recommended_fixed_income_allocation": 25, "recommended_alternatives_allocation": 5, "created_at": "2025-06-15T11:00:00Z" } ``` ## Risk categories | Category | Score range | Typical equity allocation | |----------|-----------|--------------------------| | `conservative` | 0–25 | 20–30% | | `moderate_conservative` | 26–40 | 30–45% | | `moderate` | 41–55 | 45–60% | | `moderate_growth` | 56–70 | 60–75% | | `aggressive_growth` | 71–100 | 75–90% | ## Multiple assessments Investors can retake the assessment over time. Use `current` to get the most recent: ```bash # Current (most recent) assessment curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/risk-assessments/current" \ -H "Authorization: Basic $AUTH" # Assessment history summary curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/risk-assessments/summary" \ -H "Authorization: Basic $AUTH" # All assessments curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/risk-assessments" \ -H "Authorization: Basic $AUTH" ``` ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/wealth/accounts/{id}/risk-assessments` | Submit a risk assessment | | `GET` | `/wealth/accounts/{id}/risk-assessments/current` | Get latest assessment | | `GET` | `/wealth/accounts/{id}/risk-assessments/summary` | Get assessment summary | | `GET` | `/wealth/accounts/{id}/risk-assessments` | List all assessments | | `GET` | `/wealth/accounts/{id}/risk-assessments/{assessment_id}` | Get specific assessment | ## Financial Planning Goals, life events, dependents, external accounts, and financial plan generation. Financial planning ties together the investor's objectives, personal circumstances, and existing finances into an actionable investment strategy. ## Goals Goals are the investor's financial objectives — each with a target amount, timeline, and priority. ### Creating a goal ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/goals" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "name": "Retirement", "type": "retirement", "target_amount": "2000000.00", "target_date": "2050-01-01", "priority": "high", "current_savings": "150000.00", "monthly_contribution": "2000.00" }' ``` ### Goal types | Type | Description | |------|-------------| | `retirement` | Retirement savings target | | `education` | Education funding (college, graduate school) | | `home_purchase` | Home down payment or purchase | | `emergency_fund` | Emergency savings buffer | | `major_purchase` | Large planned expense | | `wealth_building` | General wealth accumulation | | `custom` | User-defined goal | ### Managing goals ```bash # List all goals curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/goals" \ -H "Authorization: Basic $AUTH" # Update a goal curl -X PUT "$BASE_URL/wealth/accounts/$ACCOUNT_ID/goals/$GOAL_ID" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "monthly_contribution": "2500.00" }' # Delete a goal curl -X DELETE "$BASE_URL/wealth/accounts/$ACCOUNT_ID/goals/$GOAL_ID" \ -H "Authorization: Basic $AUTH" ``` ## Life events Life events record significant changes that affect the financial plan — marriage, job change, inheritance, birth of a child, etc. ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/life-events" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "type": "marriage", "date": "2025-09-15", "description": "Getting married, combining finances", "financial_impact": "positive" }' ``` ## Dependents Track family members who affect financial planning (children, elderly parents): ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/dependents" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "first_name": "Emma", "relationship": "child", "date_of_birth": "2020-03-15" }' ``` ## External accounts Record investment and savings accounts held outside Bluum for a complete financial picture: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/external-accounts" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "institution": "Fidelity", "account_type": "401k", "balance": "250000.00", "description": "Employer 401(k)" }' ``` ## Generating a financial plan Once goals, profile, and risk assessment are in place, generate a financial plan: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/financial-plan" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "include_goals": true, "include_tax_optimization": true, "planning_horizon_years": 25 }' ``` ### Plan summary ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/financial-plan/summary" \ -H "Authorization: Basic $AUTH" ``` The summary provides a high-level view of projected outcomes, goal attainability, and recommended adjustments. ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/wealth/accounts/{id}/goals` | Create a goal | | `GET` | `/wealth/accounts/{id}/goals` | List goals | | `PUT` | `/wealth/accounts/{id}/goals/{goal_id}` | Update a goal | | `DELETE` | `/wealth/accounts/{id}/goals/{goal_id}` | Delete a goal | | `POST` | `/wealth/accounts/{id}/life-events` | Record a life event | | `GET` | `/wealth/accounts/{id}/life-events` | List life events | | `POST` | `/wealth/accounts/{id}/dependents` | Add a dependent | | `GET` | `/wealth/accounts/{id}/dependents` | List dependents | | `POST` | `/wealth/accounts/{id}/external-accounts` | Add an external account | | `GET` | `/wealth/accounts/{id}/external-accounts` | List external accounts | | `POST` | `/wealth/accounts/{id}/financial-plan` | Generate a financial plan | | `GET` | `/wealth/accounts/{id}/financial-plan/summary` | Get plan summary | ## Portfolio Management Managed portfolios, IPS, rebalancing, auto-invest, and DRIP. A **Portfolio** is a managed collection of holdings governed by an Investment Policy Statement (IPS). Bluum handles portfolio construction, rebalancing, recurring investments, and dividend reinvestment. ## Investment Policy Statement (IPS) The IPS defines the rules that govern the portfolio: - **Risk profile** — Risk tolerance, optional risk score, volatility tolerance - **Time horizon** — Investment duration in years, optional category - **Investment objectives** — Primary and secondary goals, target annual return - **Target allocation** — Target percentages with min/max bands per asset class - **Constraints** — Liquidity requirements, tax considerations, rebalancing policy ### Creating an IPS ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/investment-policy" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "risk_profile": { "risk_tolerance": "moderate", "risk_score": 5, "volatility_tolerance": "medium" }, "time_horizon": { "years": 20, "category": "long_term" }, "investment_objectives": { "primary": "capital_appreciation", "secondary": ["income_generation"], "target_annual_return": "7.00" }, "target_allocation": { "equities": { "target_percent": "50.00", "min_percent": "40.00", "max_percent": "60.00" }, "fixed_income": { "target_percent": "25.00", "min_percent": "20.00", "max_percent": "30.00" }, "alternatives": { "target_percent": "5.00", "min_percent": "0.00", "max_percent": "10.00" } }, "constraints": { "liquidity_requirements": { "minimum_cash_percent": "5.00", "emergency_fund_months": 6 }, "tax_considerations": { "tax_loss_harvesting": true, "tax_bracket": "24", "prefer_tax_advantaged": true }, "rebalancing_policy": { "frequency": "quarterly", "threshold_percent": "5.00", "tax_aware": true } } }' ``` ### Validating an IPS Check that the IPS is internally consistent before creating a portfolio: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/investment-policy/validate" \ -H "Authorization: Basic $AUTH" ``` ## Creating a portfolio ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "name": "Growth Portfolio", "initial_investment": "10000.00" }' ``` ## Portfolio data ### Summary High-level portfolio metrics: ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/summary" \ -H "Authorization: Basic $AUTH" ``` ### Holdings Individual positions within the portfolio: ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/holdings" \ -H "Authorization: Basic $AUTH" ``` ### Performance Returns, benchmarks, and time-weighted performance: ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/performance" \ -H "Authorization: Basic $AUTH" ``` ## Rebalancing When asset allocations drift beyond the IPS threshold, rebalancing brings them back to target. ### Check rebalancing status ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/rebalancing" \ -H "Authorization: Basic $AUTH" ``` ### Trigger rebalancing ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/rebalancing" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "reason": "quarterly_review" }' ``` ## Auto-invest Set up recurring investments into a portfolio: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/auto-invest" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "portfolio_id": "'"$PORTFOLIO_ID"'", "amount": "2000.00", "frequency": "monthly", "day_of_month": 1, "funding_source": "wallet" }' ``` ### Managing schedules ```bash # List schedules curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/auto-invest" \ -H "Authorization: Basic $AUTH" # Pause a schedule curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/auto-invest/$SCHEDULE_ID/pause" \ -H "Authorization: Basic $AUTH" # Resume a schedule curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/auto-invest/$SCHEDULE_ID/resume" \ -H "Authorization: Basic $AUTH" ``` ## DRIP (Dividend Reinvestment) Automatically reinvest dividends back into the portfolio: ```bash curl -X PUT "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/drip" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "enabled": true }' ``` ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/wealth/accounts/{id}/investment-policy` | Create IPS | | `GET` | `/wealth/accounts/{id}/investment-policy` | Get IPS | | `POST` | `/wealth/accounts/{id}/investment-policy/validate` | Validate IPS | | `POST` | `/wealth/accounts/{id}/portfolios` | Create a portfolio | | `GET` | `/wealth/accounts/{id}/portfolios/{pid}/summary` | Portfolio summary | | `GET` | `/wealth/accounts/{id}/portfolios/{pid}/holdings` | Portfolio holdings | | `GET` | `/wealth/accounts/{id}/portfolios/{pid}/performance` | Portfolio performance | | `GET` | `/wealth/accounts/{id}/portfolios/{pid}/rebalancing` | Rebalancing analysis | | `POST` | `/wealth/accounts/{id}/portfolios/{pid}/rebalancing` | Trigger rebalance | | `POST` | `/wealth/accounts/{id}/auto-invest` | Create auto-invest schedule | | `PUT` | `/wealth/accounts/{id}/portfolios/{pid}/drip` | Configure DRIP | ## Insights & Recommendations Portfolio insights, tax optimization, AI assistant, and reporting. The insights layer provides actionable intelligence on top of the wealth management stack — monitoring portfolio health, identifying optimization opportunities, and generating reports. ## Insights Insights are system-generated observations about the investor's portfolio and financial situation: ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/insights" \ -H "Authorization: Basic $AUTH" ``` Insight types include allocation drift alerts, performance outliers, contribution reminders, and risk exposure changes. ## Recommendations Actionable suggestions based on the investor's goals, risk profile, and current portfolio: ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/recommendations" \ -H "Authorization: Basic $AUTH" ``` ## Tax optimization Identify tax-loss harvesting opportunities and other tax-efficient strategies: ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/tax-optimization" \ -H "Authorization: Basic $AUTH" ``` ## AI assistant Interactive chat interface for investment questions and guidance: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/assistant/chat" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "message": "Should I increase my monthly contribution given my retirement goal?" }' ``` ## Reports ### Account statements ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/statements?period=2025-Q1" \ -H "Authorization: Basic $AUTH" ``` ### Profit & Loss report ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/reports/pnl?start=2025-01-01&end=2025-06-30" \ -H "Authorization: Basic $AUTH" ``` ### Benchmark comparison ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/reports/benchmark?benchmark=SPY" \ -H "Authorization: Basic $AUTH" ``` ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/wealth/accounts/{id}/insights` | Get portfolio insights | | `GET` | `/wealth/accounts/{id}/recommendations` | Get recommendations | | `GET` | `/wealth/accounts/{id}/tax-optimization` | Tax optimization analysis | | `POST` | `/wealth/accounts/{id}/assistant/chat` | AI assistant chat | | `GET` | `/wealth/accounts/{id}/statements` | Account statements | | `GET` | `/wealth/accounts/{id}/reports/pnl` | P&L report | | `GET` | `/wealth/accounts/{id}/reports/benchmark` | Benchmark comparison | ## Webhooks Real-time event notifications for account, trading, and transfer activity. Webhooks deliver real-time notifications to your server when events occur in Bluum — orders filling, deposits completing, KYC decisions, and more. Use webhooks instead of polling to build responsive integrations. ## How webhooks work ``` Event occurs in Bluum → Bluum sends POST to your endpoint → Your server responds 2xx │ (retry on failure) ``` 1. Register a webhook endpoint URL and subscribe to event types 2. When an event occurs, Bluum sends a `POST` request with the event payload 3. Your server processes the event and responds with a `2xx` status code 4. If delivery fails, Bluum retries with exponential backoff ## Event types Events follow the naming convention `{domain}.{action}`: | Event | Description | |-------|-------------| | `account.created` | New account created | | `account.updated` | Account details changed | | `order.accepted` | Order accepted for execution | | `order.filled` | Order fully filled | | `order.partially_filled` | Order partially filled | | `order.canceled` | Order canceled | | `order.rejected` | Order rejected | | `transfer.deposit.initiated` | Deposit initiated | | `transfer.deposit.completed` | Deposit completed, funds available | | `transfer.deposit.failed` | Deposit failed | | `transfer.withdrawal.initiated` | Withdrawal initiated | | `transfer.withdrawal.completed` | Withdrawal completed | | `transfer.withdrawal.failed` | Withdrawal failed | | `document.approved` | KYC document approved | | `document.rejected` | KYC document rejected | ### Wildcards Subscribe to all events in a domain with `*`: - `order.*` — All order events - `transfer.*` — All transfer events ### List available event types ```bash curl -X GET "$BASE_URL/webhooks/event-types" \ -H "Authorization: Basic $AUTH" ``` ## Registering a webhook ```bash curl -X POST "$BASE_URL/webhooks" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "name": "Production webhook", "url": "https://your-app.com/webhooks/bluum", "eventTypeNames": ["order.*", "transfer.*"] }' ``` ## Managing webhooks ```bash # List webhooks curl -X GET "$BASE_URL/webhooks" \ -H "Authorization: Basic $AUTH" # Update a webhook curl -X PATCH "$BASE_URL/webhooks/$WEBHOOK_ID" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "eventTypeNames": ["order.*", "transfer.*", "account.*"] }' # Delete a webhook curl -X DELETE "$BASE_URL/webhooks/$WEBHOOK_ID" \ -H "Authorization: Basic $AUTH" ``` ## Webhook payload format ```json { "event": "order.filled", "timestamp": "2025-06-15T14:30:15Z", "data": { "id": "ord_x9y8z7a6b5c4d3e2", "account_id": "3d0b0e65-35d3-4dcd-8df7-10286ebb4b4b", "symbol": "AAPL", "side": "buy", "status": "filled", "filled_qty": "10", "average_price": "178.50" } } ``` ## Retry behavior If your endpoint doesn't respond with `2xx` within 30 seconds, Bluum retries with exponential backoff: | Attempt | Delay | |---------|-------| | 1st retry | ~1 minute | | 2nd retry | ~5 minutes | | 3rd retry | ~30 minutes | | 4th retry | ~2 hours | | 5th retry | ~8 hours | After all retries are exhausted, the event is sent to a dead letter queue. Contact support to replay failed events. ## Best practices - **Respond quickly** — Return `2xx` within 30 seconds. Process the event asynchronously if your logic takes longer. - **Handle duplicates** — The same event may be delivered more than once. Use the event ID for deduplication. - **Verify signatures** — Validate the webhook signature header to ensure the request came from Bluum. - **Use HTTPS** — Webhook endpoints must use HTTPS in production. - **Monitor failures** — Set up alerts if your endpoint starts returning errors. ## Testing webhooks In sandbox, webhooks fire exactly like production. Use these tools for local development: - [webhook.site](https://webhook.site) — Inspect payloads without a server - [ngrok](https://ngrok.com) — Expose your local server to the internet ## Key endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/webhooks/event-types` | List available event types | | `POST` | `/webhooks` | Register a webhook | | `GET` | `/webhooks` | List webhooks | | `PATCH` | `/webhooks/{webhook_id}` | Update a webhook | | `DELETE` | `/webhooks/{webhook_id}` | Delete a webhook | ## Error Handling Error response format, error codes, rate limiting, and retry strategies. All Bluum API errors follow a consistent format with structured error codes for programmatic handling. ## Error response format ```json { "code": "BLUM-400-001", "message": "Invalid quantity. Must be a positive number." } ``` | Field | Description | |-------|-------------| | `code` | Structured error code: `BLUM-{HTTP_STATUS}-{SEQUENCE}` | | `message` | Human-readable description of the error | ## Error code categories | Prefix | HTTP Status | Category | |--------|------------|----------| | `BLUM-400-*` | 400 Bad Request | Validation failures, malformed requests | | `BLUM-401-*` | 401 Unauthorized | Authentication errors | | `BLUM-403-*` | 403 Forbidden | Authorization/permission errors | | `BLUM-404-*` | 404 Not Found | Resource doesn't exist or not accessible | | `BLUM-409-*` | 409 Conflict | Duplicate resource or state conflict | | `BLUM-422-*` | 422 Unprocessable | Business rule violation | | `BLUM-429-*` | 429 Too Many Requests | Rate limit exceeded | | `BLUM-500-*` | 500 Internal Error | Server-side error | ## Common errors ### Authentication (401) | Code | Message | Resolution | |------|---------|------------| | `BLUM-401-001` | Missing authorization | Include `Authorization: Basic ` header | | `BLUM-401-002` | Malformed credentials | Verify Base64 encoding of `API_KEY:API_SECRET` | | `BLUM-401-003` | Invalid credentials | Check key and secret are correct and not revoked | ### Validation (400) | Code | Message | Resolution | |------|---------|------------| | `BLUM-400-001` | Generic validation error | Check the message for specific field issues | | `BLUM-400-002` | Missing required field | Include all required fields in the request | | `BLUM-400-003` | Invalid field format | Match expected types and formats (dates as ISO 8601, etc.) | ### Not Found (404) | Code | Message | Resolution | |------|---------|------------| | `BLUM-404-001` | Resource not found | Verify the ID exists and belongs to your tenant | ### Rate Limiting (429) | Code | Message | Resolution | |------|---------|------------| | `BLUM-429-001` | Rate limit exceeded | Implement backoff; check `Retry-After` header | ## Rate limits | Environment | Limit | |-------------|-------| | Sandbox | 10 requests/second per key pair | | Production | 25 requests/second per key pair | ## Retry strategy Implement exponential backoff with jitter for retryable errors (`429`, `500`, `502`, `503`, `504`): ```javascript async function requestWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, options); if (response.status === 429) { const retryAfter = parseInt(response.headers.get('Retry-After') || '1'); const delay = retryAfter * 1000 * Math.pow(2, attempt) + Math.random() * 1000; await new Promise(resolve => setTimeout(resolve, delay)); continue; } if (response.status >= 500 && attempt < maxRetries - 1) { const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000; await new Promise(resolve => setTimeout(resolve, delay)); continue; } return response; } throw new Error('Max retries exceeded'); } ``` **Do not retry** `400`, `401`, `403`, `404`, or `422` errors — these indicate client-side issues that won't resolve on retry. ## HTTP status codes | Code | Meaning | Retryable | |------|---------|-----------| | `200` | Success (GET, PUT, DELETE) | N/A | | `201` | Created (POST) | N/A | | `400` | Bad request | No | | `401` | Unauthorized | No (fix credentials) | | `403` | Forbidden | No (check permissions) | | `404` | Not found | No | | `409` | Conflict | No (resolve conflict) | | `422` | Unprocessable | No (fix business logic) | | `429` | Rate limited | Yes (with backoff) | | `500` | Server error | Yes (with backoff) | See [Error Code Reference](/resources/error-codes) for the complete catalog. ## Idempotency Prevent duplicate operations with idempotency keys. Idempotency ensures that retrying a request doesn't create duplicate side effects. This is critical for financial operations like deposits and withdrawals where a duplicate could move money twice. ## How it works 1. Generate a unique `Idempotency-Key` for each distinct operation 2. Include it in the request header 3. If you retry the same request with the same key, Bluum returns the original response instead of processing it again ```bash curl -X POST "$BASE_URL/accounts/$ACCOUNT_ID/deposits" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: dep-abc123-unique-id" \ -d '{ "amount": "5000.00", "currency": "USD", "method": "ach", "plaid_options": { "item_id": "item_abc123", "account_id": "acc_1234567890" } }' ``` ## When to use idempotency keys | Operation | Required | Why | |-----------|----------|-----| | Deposits | Yes | Prevent duplicate fund transfers | | Withdrawals | Yes | Prevent duplicate disbursements | | Orders | Recommended | Prevent duplicate trades (use `client_order_id` as alternative) | | Account creation | Optional | Account uniqueness is enforced by other means | | Read operations (GET) | Not needed | GETs are naturally idempotent | ## Generating keys Use any unique string. Common approaches: ```javascript // UUID const key = crypto.randomUUID(); // dep-550e8400-e29b-41d4-a716-446655440000 // Prefixed with operation type const key = `dep-${crypto.randomUUID()}`; // Derived from business logic (user + operation + timestamp) const key = `dep-${userId}-${Date.now()}`; ``` ## Behavior | Scenario | Result | |----------|--------| | First request with key | Processed normally, response stored | | Retry with same key, same body | Original response returned (no reprocessing) | | Retry with same key, different body | Error — key is already associated with a different request | | Different key, same body | Processed as a new request (potential duplicate) | ## Key expiration Idempotency keys expire after 24 hours. After expiration, the same key can be reused for a new request. ## Best practices - Generate a new key for each distinct operation — don't reuse keys across different deposits or withdrawals - Store the key alongside your local transaction record for debugging - Use the key consistently across retries of the same operation - Include the operation type in the key prefix for readability (`dep-`, `wdr-`) ## Account Onboarding Step-by-step guide to creating and activating investment accounts. This guide covers the full account onboarding flow — collecting user information, submitting the account, and handling edge cases. ## Overview Account creation requires four data groups: 1. **Contact** — Email, phone, mailing address 2. **Identity** — Legal name, date of birth, tax ID, citizenship 3. **Disclosures** — Regulatory disclosures (control person, political exposure, etc.) 4. **Agreements** — Account and customer agreements with timestamps and IP address ## Collecting data in your UI You don't need to collect all data on a single screen. A common pattern: ``` Screen 1: Name, email, phone Screen 2: Address Screen 3: Date of birth, SSN, citizenship, funding source Screen 4: Disclosures (yes/no checkboxes) Screen 5: Agreement review and signature ``` Submit the account once all data is collected. ## Creating the account ```bash curl -X POST "$BASE_URL/accounts" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "account_type": "trading", "contact": { "email_address": "user@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" } ] }' ``` ## Handling validation errors Common validation errors and how to handle them: | Error | Cause | Fix | |-------|-------|-----| | `BLUM-400-002` Missing field | Required field omitted | Check your request against the [Accounts](/platform/accounts) schema | | `BLUM-400-003` Invalid format | Wrong data format | Phone: E.164 (`+1...`), dates: `YYYY-MM-DD`, country: ISO alpha-2 | | `BLUM-409-001` Duplicate | Account with same email exists | Check for existing account before creating | ## After account creation Once the account is `ACTIVE`: 1. **Upload KYC documents** — See [KYC Verification](/guides/self-directed/kyc-verification) 2. **Link a bank account** — See [Bank Linking](/guides/self-directed/bank-linking) 3. **Fund the account** — See [Deposits & Withdrawals](/guides/self-directed/deposits-withdrawals) ## Choosing account type | If you're building... | Use `account_type` | |-----------------------|-------------------| | Trading app, invest tab, stock gifting | `trading` | | Robo-advisor, financial planner, managed portfolios | `individual` | See [Accounts](/platform/accounts) for full details on account types and lifecycle. ## KYC Document Verification Upload identity documents and track verification status. After creating an account, the user must complete KYC verification by uploading identity documents. This guide covers the upload flow, status tracking, and handling rejections. ## Upload flow ### 1. Upload a document ```bash curl -X POST "$BASE_URL/documents/accounts/$ACCOUNT_ID/upload" \ -H "Authorization: Basic $AUTH" \ -F "document_type=id_verification" \ -F "file=@/path/to/id-document.jpg" ``` **Accepted formats:** JPEG, PNG, PDF (max 10MB) **Document types:** - `id_verification` — Government-issued photo ID (required) - `proof_of_address` — Utility bill, bank statement (if requested) - `tax_document` — W-9 or equivalent (if requested) ### 2. Track verification status **Option A: Poll** ```bash curl -X GET "$BASE_URL/documents/$DOCUMENT_ID" \ -H "Authorization: Basic $AUTH" ``` **Option B: Webhooks (recommended)** Subscribe to `document.approved` and `document.rejected` events: ```bash curl -X POST "$BASE_URL/webhooks" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/webhooks/bluum", "events": ["document.approved", "document.rejected"] }' ``` ### 3. Handle the result | Status | What to do | |--------|-----------| | `approved` | Proceed to bank linking and funding | | `rejected` | Show the rejection reason and prompt re-upload | ## Handling rejections When a document is rejected, the response includes a reason: ```json { "document_id": "doc_a1b2c3d4e5f6g7h8", "upload_status": "rejected", "rejection_reason": "Document is expired" } ``` Display the reason to your user and allow them to upload a replacement. There is no limit on upload attempts. **Common rejection reasons:** - Document is blurry or illegible - Document is expired - Name doesn't match account identity - Document type not recognized ## Listing documents ```bash # All documents for an account curl -X GET "$BASE_URL/documents/accounts/$ACCOUNT_ID/upload" \ -H "Authorization: Basic $AUTH" # Filter by status curl -X GET "$BASE_URL/documents/accounts/$ACCOUNT_ID/upload?status=approved" \ -H "Authorization: Basic $AUTH" ``` ## Sandbox behavior In sandbox, all document uploads are auto-approved within seconds. To test rejection flows, use specific test values documented in your dashboard. ## Next steps Once KYC is approved: - [Link a bank account](/guides/self-directed/bank-linking) - [Make a deposit](/guides/self-directed/deposits-withdrawals) ## Link a Bank Account Connect a bank account via Plaid Link for ACH deposits and withdrawals. Bank linking uses Plaid to securely connect the user's bank account for ACH transfers. This is a frontend + backend flow involving Plaid Link. ## Flow overview ``` Your Frontend Your Backend Bluum API │ │ │ │ User clicks "Link Bank" │ │ │───────────────────────────────►│ │ │ │── POST link-token ────────►│ │ │◄── { link_token } ─────────│ │◄── Open Plaid Link ───────────│ │ │ │ │ │ User selects bank & logs in │ │ │ │ │ │── public_token ───────────────►│ │ │ │── POST connect ───────────►│ │ │◄── { bank details } ───────│ │◄── "Bank linked!" ────────────│ │ ``` ## Step 1: Create a Link token Your backend requests a Link token from Bluum: ```bash 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 }' ``` Response: ```json { "status": "success", "data": { "link_token": "link-sandbox-abc123def456", "hosted_link_url": null } } ``` ## Step 2: Launch Plaid Link in your frontend Use the Plaid Link SDK with the token: ```javascript const { open } = usePlaidLink({ token: linkToken, // from step 1 onSuccess: (publicToken, metadata) => { // Send publicToken to your backend exchangeToken(publicToken); }, onExit: (err, metadata) => { // User closed Plaid Link } }); ``` ### Hosted Link alternative Set `enable_hosted_link: true` to get a Plaid-hosted URL instead. Redirect the user to `hosted_link_url` — no Plaid SDK needed. Useful for mobile apps or simpler integrations. ## Step 3: Exchange the public token After the user completes Plaid Link, send the `public_token` to Bluum: ```bash 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" }' ``` Response: ```json { "status": "success", "data": { "item": { "itemId": "item_abc123", "institutionName": "Chase Bank", "accounts": [ { "accountId": "acc_1234567890", "accountName": "Checking Account", "accountType": "depository", "mask": "0000" } ] } } } ``` Store `itemId` and `accountId` — you'll need them for deposits and withdrawals. ## Managing funding sources ```bash # List linked funding sources curl -X GET "$BASE_URL/accounts/$ACCOUNT_ID/funding-sources" \ -H "Authorization: Basic $AUTH" # Get a specific funding source curl -X GET "$BASE_URL/accounts/$ACCOUNT_ID/funding-sources/$FUNDING_SOURCE_ID" \ -H "Authorization: Basic $AUTH" ``` ## Multiple bank accounts Users can link multiple bank accounts. Each `connect` call adds a new funding source. When making deposits or withdrawals, specify which bank account to use via `plaid_options.item_id` and `plaid_options.account_id`. ## Next steps - [Make a deposit](/guides/self-directed/deposits-withdrawals) - [Place an order](/guides/self-directed/placing-orders) ## Deposits & Withdrawals Fund accounts via ACH or manual bank transfer, and withdraw to linked bank accounts. This guide covers all funding methods, status tracking, and edge cases for moving money in and out of investment wallets. ## Deposit methods ### ACH via Plaid Pull funds from a linked bank account. Requires a [linked bank account](/guides/self-directed/bank-linking). ```bash 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 funding", "plaid_options": { "item_id": "item_abc123", "account_id": "acc_1234567890" } }' ``` ### Manual bank transfer The user sends a bank transfer using instructions provided in the response: ```bash 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": "manual_bank_transfer", "description": "Wire transfer funding" }' ``` The response includes bank details and a reference code: ```json { "method_details": { "referenceCode": "BLUUM-ABC123XY", "bankDetails": { "bankName": "Choice Financial Group", "accountName": "Bluum Finance, Inc.", "routingNumber": "091311229", "accountNumber": "202534766488", "instructions": "Include reference code \"BLUUM-ABC123XY\" in your transfer memo." } }, "expires_at": "2025-06-22T10:45:00.000Z" } ``` Display the bank details and reference code to your user. The deposit expires at `expires_at` if funds are not received. ## Tracking deposit status ```bash curl -X GET "$BASE_URL/accounts/$ACCOUNT_ID/deposits/$DEPOSIT_ID" \ -H "Authorization: Basic $AUTH" ``` | Status | Meaning | Next | |--------|---------|------| | `pending` | Created, not yet initiated | Wait or cancel | | `processing` | ACH initiated with bank | Wait | | `received` | Funds received, settling | Wait | | `completed` | Funds in wallet, ready to trade | Trade | | `failed` | Transfer failed | Check `failure_reason`, retry | Use webhooks (`transfer.deposit.*`) for real-time status updates instead of polling. ## Withdrawals Withdraw funds to a linked bank account: ```bash 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", "plaid_options": { "item_id": "item_abc123", "account_id": "acc_1234567890" } }' ``` ### Withdrawal statuses | Status | Meaning | |--------|---------| | `pending` | Created, can be canceled | | `processing` | ACH initiated | | `submitted` | Submitted to bank | | `completed` | Funds delivered | | `failed` | Transfer failed | ### Canceling a withdrawal Only `pending` withdrawals can be canceled: ```bash curl -X POST "$BASE_URL/accounts/$ACCOUNT_ID/withdrawals/$WITHDRAWAL_ID/cancel" \ -H "Authorization: Basic $AUTH" ``` ## Idempotency Always include `Idempotency-Key` headers on deposit and withdrawal requests. This prevents duplicate transfers if a request is retried due to network issues or timeouts. ```bash -H "Idempotency-Key: dep-unique-operation-id" ``` See [Idempotency](/platform/idempotency) for best practices. ## Error handling | Error | Cause | Resolution | |-------|-------|------------| | `BLUM-400-*` | Invalid amount or missing fields | Check request body | | `BLUM-422-*` | Insufficient funds (withdrawal) | Check wallet balance | | `BLUM-409-*` | Duplicate idempotency key with different body | Use a new key for different operations | ## Placing Orders Place market, limit, stop, and trailing stop orders with code examples. This guide covers every order type, common patterns, and how to track order execution. ## Market orders Execute immediately at the current market price. ### Buy by quantity ```bash 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" }' ``` ### Buy by dollar amount (fractional shares) Invest exactly $1,000 in GOOGL — Bluum calculates the fractional quantity: ```bash 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" }' ``` `notional` is only available for `market` orders on `fractionable` assets. ## Limit orders Execute only at the specified price or better: ```bash curl -X POST "$BASE_URL/trading/accounts/$ACCOUNT_ID/orders" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "symbol": "MSFT", "side": "buy", "type": "limit", "time_in_force": "gtc", "qty": "5", "limit_price": "350.00", "client_order_id": "my-limit-001" }' ``` ## Stop orders Trigger a market order when the price reaches the stop level: ```bash # Stop-loss: sell if AAPL drops to $170 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": "stop", "time_in_force": "gtc", "qty": "10", "stop_price": "170.00" }' ``` ## Trailing stop orders The stop price follows the market price by a fixed offset: ```bash # Sell if AAPL drops 5% from its peak 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": "trailing_stop", "time_in_force": "gtc", "qty": "10", "trail_percent": "5.0" }' ``` ## Tracking order status ```bash # Get a specific order curl -X GET "$BASE_URL/trading/accounts/$ACCOUNT_ID/orders/$ORDER_ID" \ -H "Authorization: Basic $AUTH" # List all orders curl -X GET "$BASE_URL/trading/accounts/$ACCOUNT_ID/orders" \ -H "Authorization: Basic $AUTH" ``` ### Status progression ``` accepted → filled │ │ │ partially_filled │ canceled / rejected ``` Use webhooks (`order.*`) for real-time notifications: ```bash curl -X POST "$BASE_URL/webhooks" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/webhooks/bluum", "events": ["order.*"] }' ``` ## Order type comparison | Type | Use case | Price guarantee | Fill guarantee | |------|----------|----------------|----------------| | `market` | Quick execution | No | High (during market hours) | | `limit` | Price control | Yes (or better) | No (may not fill) | | `stop` | Loss protection | No | No (triggers at stop price) | | `trailing_stop` | Dynamic stop | No | No (adjusts with market) | ## Common errors | Error | Cause | Resolution | |-------|-------|------------| | Insufficient funds | Wallet balance too low | Deposit more funds | | Market closed | Order placed outside trading hours | Use `gtc` or wait for market open | | Invalid symbol | Asset not found or not tradable | Verify symbol with `/assets/search` | | Invalid quantity | Zero, negative, or exceeds position size (for sells) | Check quantity and position | ## Tracking Positions Query positions, monitor P&L, and understand how holdings update. After orders fill, positions appear in the account. This guide covers how to query, interpret, and display position data. ## Listing positions ```bash curl -X GET "$BASE_URL/trading/accounts/$ACCOUNT_ID/positions" \ -H "Authorization: Basic $AUTH" ``` ```json [ { "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": "182.30", "market_value": "1823.00", "unrealized_pl": "38.00", "unrealized_pl_percent": "0.0213", "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" }, { "id": "pos_b2c3d4e5-f6a7-8901-bcde-f12345678901", "account_id": "3d0b0e65-35d3-4dcd-8df7-10286ebb4b4b", "symbol": "GOOGL", "asset_id": "asset_8e7d6c5b-4a32-10fe-dcba-098765432100", "currency": "USD", "quantity": "5.42", "average_cost_basis": "184.50", "total_cost_basis": "999.99", "current_price": "186.10", "market_value": "1008.66", "unrealized_pl": "8.67", "unrealized_pl_percent": "0.0087", "price_source": "ALPACA", "price_confidence": "REAL_TIME", "price_timestamp": "2025-06-15T14:30:00Z", "last_transaction_at": "2025-06-15T13:45:00Z", "created_at": "2025-06-15T13:45:00Z", "updated_at": "2025-06-15T14:30:00Z" } ] ``` ## Position detail ```bash curl -X GET "$BASE_URL/trading/accounts/$ACCOUNT_ID/positions/$POSITION_ID" \ -H "Authorization: Basic $AUTH" ``` ## Understanding P&L fields | Field | Calculation | Use for | |-------|------------|---------| | `total_cost_basis` | `quantity * average_cost_basis` | What the user paid | | `market_value` | `quantity * current_price` | What it's worth now | | `unrealized_pl` | `market_value - total_cost_basis` | Dollar gain/loss | | `unrealized_pl_percent` | `unrealized_pl / total_cost_basis` | Percentage gain/loss | **Unrealized** means the gain/loss is on paper — the user hasn't sold yet. Once they sell, it becomes **realized** P&L. ## Displaying positions in your UI Common patterns for showing positions to users: ### Portfolio summary - Total market value (sum of all `market_value`) - Total unrealized P&L (sum of all `unrealized_pl`) - Overall percentage return ### Per-position row - Symbol and name - Quantity held - Current price - Market value - Unrealized P&L (with color: green for positive, red for negative) - Percentage change ### Real-time updates Positions update as market prices change. To keep your UI current: - **Polling** — Query positions periodically (e.g., every 30 seconds during market hours) - **Webhooks** — Subscribe to `order.filled` events to know when new positions are created or existing ones change ## When positions update | Event | Effect | |-------|--------| | Buy order fills | New position created, or existing position increased | | Sell order fills | Position decreased or removed (if fully sold) | | Price change | `current_price`, `market_value`, `unrealized_pl` updated | | Stock split | `quantity` and `average_cost_basis` adjusted proportionally | ## Empty positions When a user sells their entire holding in an asset, the position is removed from the list. A `GET /positions` response only includes assets the user currently holds. ## Investor Onboarding for Wealth Create a wealth management account with a complete investor profile. Wealth management onboarding extends the standard account creation with a comprehensive investor profile. This guide covers the full flow from account creation to a complete financial picture. ## Overview ``` Create Account (individual) → KYC → Investor Profile → Dependents & External Accounts ``` ## Step 1: Create an individual account Use `account_type: "individual"` for wealth management: ```bash curl -X POST "$BASE_URL/accounts" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "account_type": "individual", "contact": { ... }, "identity": { ... }, "disclosures": { ... }, "agreements": [ ... ] }' ``` See [Account Onboarding](/guides/self-directed/account-onboarding) for the full account creation payload. The only difference is `account_type`. ## Step 2: Complete KYC Same as self-directed — upload identity documents and wait for approval. See [KYC Verification](/guides/self-directed/kyc-verification). ## Step 3: Build the investor profile The profile is modular — update sections independently as you collect data: ### Demographics & employment ```bash curl -X PUT "$BASE_URL/wealth/accounts/$ACCOUNT_ID/profile" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "demographics": { "marital_status": "married", "number_of_dependents": 2 }, "employment": { "status": "employed", "employer": "Acme Corp", "occupation": "Software Engineer", "annual_income": "185000.00", "income_stability": "stable" } }' ``` ### Tax and cash flow ```bash curl -X PUT "$BASE_URL/wealth/accounts/$ACCOUNT_ID/profile" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "tax": { "filing_status": "married_filing_jointly", "federal_tax_bracket": "24", "state_tax_rate": "0" }, "cash_flow": { "monthly_expenses": "8500.00", "monthly_savings": "3500.00", "emergency_fund_months": 6 } }' ``` ### Investment preferences ```bash curl -X PUT "$BASE_URL/wealth/accounts/$ACCOUNT_ID/profile" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "investment_preferences": { "investment_experience": "intermediate", "investment_horizon": "long_term", "liquidity_needs": "low", "esg_preference": true } }' ``` ### Optional sections Add as needed for comprehensive planning: - **Insurance** — Life, disability, health, long-term care coverage - **Estate planning** — Will, trust, power of attorney, beneficiary designations - **Partner** — Spouse employment, income, and retirement details ## Step 4: Add dependents ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/dependents" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "first_name": "Emma", "relationship": "child", "date_of_birth": "2020-03-15" }' ``` ## Step 5: Record external accounts Capture investments held outside Bluum for complete financial planning: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/external-accounts" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "institution": "Fidelity", "account_type": "401k", "balance": "250000.00", "description": "Employer 401(k)" }' ``` ## Progressive onboarding You don't need to collect everything upfront. A common pattern: | Screen | Data collected | |--------|---------------| | 1 | Account creation (contact, identity, disclosures, agreements) | | 2 | Employment and income | | 3 | Investment preferences and experience | | 4 | Goals (retirement, education, etc.) | | 5 | Risk assessment questionnaire | Additional profile sections (tax, insurance, estate, dependents, external accounts) can be collected later as the user engages more deeply. ## Next steps - [Risk Assessment & Financial Planning](/guides/wealth/risk-and-planning) - [Setting Up Portfolios](/guides/wealth/portfolio-setup) ## Risk Assessment & Financial Planning Determine risk tolerance, define goals, and generate a financial plan. This guide covers the middle part of the wealth management flow: assessing risk, defining financial goals, and generating a plan that ties everything together. ## Step 1: Risk assessment ### Present the questionnaire Build a UI that collects the investor's answers to these questions: | Question | Field | Options | |----------|-------|---------| | What is your primary investment goal? | `investment_goal` | `preservation`, `income`, `balanced`, `growth`, `aggressive_growth` | | How many years until you need this money? | `time_horizon_years` | Number | | How would you describe your risk tolerance? | `risk_tolerance` | `conservative`, `moderate`, `aggressive` | | If your portfolio dropped 20%, what would you do? | `reaction_to_loss` | `sell_immediately`, `wait_and_see`, `hold_and_wait`, `buy_more` | | Do you need regular income from investments? | `income_needs` | `none`, `some`, `significant` | | How would you describe your investment knowledge? | `investment_knowledge` | `beginner`, `intermediate`, `advanced` | | Have you experienced a major market decline? | `experienced_major_decline` | Boolean | | What did you do during that decline? | `action_during_decline` | `sold_everything`, `sold_some`, `held_positions`, `bought_more` | | What's the maximum portfolio loss you can accept? | `largest_acceptable_loss_percent` | Number (e.g., `25` for 25%) | ### Submit the assessment ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/risk-assessments" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "questionnaire_id": "default-risk-v1", "responses": [ { "question_id": "investment_goal", "answer_id": "growth" }, { "question_id": "time_horizon", "answer_id": "20_years" }, { "question_id": "risk_tolerance", "answer_id": "moderate" }, { "question_id": "reaction_to_loss", "answer_id": "hold_and_wait" }, { "question_id": "income_needs", "answer_id": "none" }, { "question_id": "investment_knowledge", "answer_id": "intermediate" } ] }' ``` ### Interpret the result ```json { "risk_score": 65, "risk_category": "moderate_growth", "recommended_equity_allocation": 70, "recommended_fixed_income_allocation": 25, "recommended_alternatives_allocation": 5 } ``` Display the risk category and recommended allocation to the user. Allow them to retake the assessment if they disagree. ## Step 2: Define goals Create financial goals that anchor the plan: ```bash # Retirement goal curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/goals" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "name": "Retirement", "type": "retirement", "target_amount": "2000000.00", "target_date": "2050-01-01", "priority": "high", "current_savings": "150000.00", "monthly_contribution": "2000.00" }' # Education goal curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/goals" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "name": "College Fund", "type": "education", "target_amount": "250000.00", "target_date": "2040-09-01", "priority": "medium", "current_savings": "25000.00", "monthly_contribution": "500.00" }' ``` ### Record life events Life events provide context for plan adjustments: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/life-events" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "type": "marriage", "date": "2025-09-15", "financial_impact": "positive" }' ``` ## Step 3: Generate the financial plan ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/financial-plan" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "include_goals": true, "include_tax_optimization": true, "planning_horizon_years": 25 }' ``` ### View the plan summary ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/financial-plan/summary" \ -H "Authorization: Basic $AUTH" ``` The summary shows projected outcomes for each goal, recommended adjustments, and overall financial health. ## Presenting results to users A good wealth management UI shows: 1. **Risk profile** — Score, category, and recommended allocation (pie chart) 2. **Goals dashboard** — Each goal with progress bar (current savings vs target) 3. **Plan summary** — Projected outcomes, confidence levels, recommended actions 4. **Action items** — "Increase monthly contribution by $500 to stay on track for retirement" ## Next steps - [Setting Up Portfolios](/guides/wealth/portfolio-setup) — Create an IPS and managed portfolio ## Setting Up Portfolios Create an IPS, build a managed portfolio, configure auto-invest and rebalancing. This guide covers portfolio construction — from the Investment Policy Statement through to automated investing. ## Step 1: Create an Investment Policy Statement The IPS defines the rules that govern the portfolio. It's informed by the risk assessment and financial plan. ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/investment-policy" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "risk_profile": { "risk_tolerance": "moderate", "risk_score": 5, "volatility_tolerance": "medium" }, "time_horizon": { "years": 20, "category": "long_term" }, "investment_objectives": { "primary": "capital_appreciation", "secondary": ["income_generation"], "target_annual_return": "7.00" }, "target_allocation": { "equities": { "target_percent": "50.00", "min_percent": "40.00", "max_percent": "60.00" }, "fixed_income": { "target_percent": "25.00", "min_percent": "20.00", "max_percent": "30.00" }, "alternatives": { "target_percent": "5.00", "min_percent": "0.00", "max_percent": "10.00" } }, "constraints": { "liquidity_requirements": { "minimum_cash_percent": "5.00", "emergency_fund_months": 6 }, "tax_considerations": { "tax_loss_harvesting": true, "tax_bracket": "24", "prefer_tax_advantaged": true }, "rebalancing_policy": { "frequency": "quarterly", "threshold_percent": "5.00", "tax_aware": true } } }' ``` ### Validate before creating the portfolio ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/investment-policy/validate" \ -H "Authorization: Basic $AUTH" ``` Validation checks: - Target allocations sum to 100% - Min/max bands are consistent - Risk tolerance aligns with allocation - Rebalancing policy is valid ## Step 2: Create the portfolio ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "name": "Growth Portfolio", "initial_investment": "10000.00" }' ``` The portfolio is constructed according to the IPS asset allocation targets. ## Step 3: Set up auto-invest Configure recurring investments: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/auto-invest" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "portfolio_id": "pf_abc123", "amount": "2000.00", "frequency": "monthly", "day_of_month": 1, "funding_source": "wallet" }' ``` Ensure the wallet has sufficient funds on the scheduled date, or set up recurring deposits to match. ## Step 4: Enable dividend reinvestment ```bash curl -X PUT "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/drip" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "enabled": true }' ``` ## Monitoring the portfolio ### Summary ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/summary" \ -H "Authorization: Basic $AUTH" ``` ### Holdings ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/holdings" \ -H "Authorization: Basic $AUTH" ``` ### Performance ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/performance" \ -H "Authorization: Basic $AUTH" ``` ## Rebalancing Check if the portfolio has drifted beyond the IPS threshold: ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/rebalancing" \ -H "Authorization: Basic $AUTH" ``` Trigger a rebalance: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/rebalancing" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "reason": "quarterly_review" }' ``` ## Managing auto-invest schedules ```bash # Pause curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/auto-invest/$SCHEDULE_ID/pause" \ -H "Authorization: Basic $AUTH" # Resume curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/auto-invest/$SCHEDULE_ID/resume" \ -H "Authorization: Basic $AUTH" # Update amount curl -X PUT "$BASE_URL/wealth/accounts/$ACCOUNT_ID/auto-invest/$SCHEDULE_ID" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "amount": "3000.00" }' ``` ## Next steps - [Monitoring & Insights](/guides/wealth/monitoring) — Track performance and get recommendations ## Monitoring & Insights Track portfolio performance, review insights, optimize taxes, and generate reports. Once portfolios are running, the monitoring layer provides ongoing intelligence — performance tracking, actionable insights, tax optimization opportunities, and formal reports. ## Portfolio performance ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/portfolios/$PORTFOLIO_ID/performance" \ -H "Authorization: Basic $AUTH" ``` Display key metrics: - Time-weighted return - Dollar gain/loss - Benchmark comparison - Allocation drift from IPS targets ## Insights System-generated observations about the investor's portfolio: ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/insights" \ -H "Authorization: Basic $AUTH" ``` Insight types: - **Allocation drift** — Portfolio has drifted beyond IPS threshold - **Performance outlier** — A holding is significantly under/overperforming - **Contribution reminder** — Monthly auto-invest approaching, wallet balance low - **Goal progress** — On track / behind schedule for a goal - **Risk exposure** — Concentration risk in a sector or asset ## Recommendations Actionable suggestions based on the investor's situation: ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/recommendations" \ -H "Authorization: Basic $AUTH" ``` ## Tax optimization Identify tax-loss harvesting and other tax-efficient strategies: ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/tax-optimization" \ -H "Authorization: Basic $AUTH" ``` Show unrealized losses that could be harvested, wash sale considerations, and estimated tax savings. ## AI assistant Interactive investment Q&A: ```bash curl -X POST "$BASE_URL/wealth/accounts/$ACCOUNT_ID/assistant/chat" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "message": "Should I increase my retirement contribution?" }' ``` ## Reports ### Account statements ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/statements?period=2025-Q1" \ -H "Authorization: Basic $AUTH" ``` ### Profit & Loss ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/reports/pnl?start=2025-01-01&end=2025-06-30" \ -H "Authorization: Basic $AUTH" ``` ### Benchmark comparison ```bash curl -X GET "$BASE_URL/wealth/accounts/$ACCOUNT_ID/reports/benchmark?benchmark=SPY" \ -H "Authorization: Basic $AUTH" ``` ## Building a monitoring dashboard A good monitoring UI includes: | Section | Data source | |---------|------------| | Portfolio value chart | `/portfolios/{id}/performance` | | Holdings table | `/portfolios/{id}/holdings` | | Insights feed | `/insights` | | Goal progress bars | `/goals` | | Rebalancing status | `/portfolios/{id}/rebalancing` | | Tax optimization alerts | `/tax-optimization` | Refresh performance and holdings data periodically. Insights and recommendations can be fetched less frequently (e.g., on page load). ## Setting Up Webhooks Register, test, and manage webhook endpoints for real-time event notifications. This guide walks through setting up webhooks from scratch — registration, local testing, event handling, and production best practices. ## Step 1: Create a webhook endpoint Build an HTTP endpoint that accepts POST requests: ```javascript // Express.js example app.post('/webhooks/bluum', (req, res) => { const event = req.body; console.log(`Received event: ${event.event}`, event.data); // Process the event asynchronously processEvent(event).catch(console.error); // Respond immediately with 200 res.status(200).json({ received: true }); }); ``` ```python # Flask example @app.route('/webhooks/bluum', methods=['POST']) def handle_webhook(): event = request.get_json() print(f"Received event: {event['event']}", event['data']) # Process asynchronously process_event.delay(event) return jsonify(received=True), 200 ``` ## Step 2: Test locally Use ngrok to expose your local server: ```bash ngrok http 3000 # Forwarding: https://abc123.ngrok.io → http://localhost:3000 ``` Or use webhook.site for quick inspection without a server. ## Step 3: Register with Bluum ```bash curl -X POST "$BASE_URL/webhooks" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "name": "Development webhook", "url": "https://abc123.ngrok.io/webhooks/bluum", "eventTypeNames": ["order.*", "transfer.*"] }' ``` ## Step 4: Trigger test events Create an order or deposit in sandbox to generate events: ```bash # This will trigger order.accepted and eventually order.filled 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": "1" }' ``` Check your endpoint logs to verify events are being received. ## Event handling patterns ### Idempotent processing The same event may be delivered more than once. Use the event ID for deduplication: ```javascript const processedEvents = new Set(); // Use a database in production app.post('/webhooks/bluum', (req, res) => { const { event, data } = req.body; const eventId = data.id; if (processedEvents.has(eventId)) { return res.status(200).json({ received: true }); // Already processed } processedEvents.add(eventId); handleEvent(event, data); res.status(200).json({ received: true }); }); ``` ### Event routing ```javascript function handleEvent(eventName, data) { switch (eventName) { case 'order.filled': notifyUser(data.account_id, `Order filled: ${data.symbol}`); break; case 'transfer.deposit.completed': enableTrading(data.account_id); break; case 'transfer.deposit.failed': alertUser(data.account_id, 'Deposit failed'); break; case 'document.approved': proceedWithOnboarding(data.account_id); break; } } ``` ## Production checklist - [ ] Endpoint uses HTTPS - [ ] Responds with `2xx` within 30 seconds - [ ] Processes events asynchronously (don't block the response) - [ ] Handles duplicate events idempotently - [ ] Verifies webhook signatures - [ ] Monitors for delivery failures - [ ] Logs all received events for debugging - [ ] Update the webhook URL from ngrok/dev to your production domain ## Updating for production ```bash curl -X PATCH "$BASE_URL/webhooks/$WEBHOOK_ID" \ -H "Authorization: Basic $AUTH" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/webhooks/bluum", "description": "Production webhook" }' ``` ## Working with Market Data Quotes, historical bars, snapshots, market status, and asset discovery. This guide covers how to use Bluum's market data APIs to build asset search, price charts, portfolio valuations, and market status indicators. ## Asset discovery ### Search by name or symbol ```bash curl -X GET "$BASE_URL/assets/search?q=tesla" \ -H "Authorization: Basic $AUTH" ``` Returns matching assets with symbol, name, exchange, and tradability. ### List with filters ```bash # All tradable US equities curl -X GET "$BASE_URL/assets/list?asset_class=us_equity&tradable=true" \ -H "Authorization: Basic $AUTH" ``` ### Get asset details ```bash curl -X GET "$BASE_URL/assets/TSLA" \ -H "Authorization: Basic $AUTH" ``` ## Real-time quotes Get the latest bid/ask/last price: ```bash curl -X GET "$BASE_URL/market-data/assets/AAPL/quote" \ -H "Authorization: Basic $AUTH" ``` Use quotes to: - Display current prices in your UI - Calculate portfolio market value - Show pre-trade price estimates ## Historical bars (OHLCV) Get open, high, low, close, and volume data: ```bash curl -X GET "$BASE_URL/market-data/assets/AAPL/bars?timeframe=1Day&start=2025-01-01&end=2025-06-15" \ -H "Authorization: Basic $AUTH" ``` ### Timeframes | Value | Description | |-------|-------------| | `1Min` | 1-minute bars (intraday) | | `5Min` | 5-minute bars (intraday) | | `15Min` | 15-minute bars (intraday) | | `1Hour` | Hourly bars | | `1Day` | Daily bars | | `1Week` | Weekly bars | | `1Month` | Monthly bars | ## Chart data Get chart-ready data with a simplified API: ```bash # 1 month of daily data curl -X GET "$BASE_URL/assets/chart?symbol=AAPL&timeframe=1Day&period=1M" \ -H "Authorization: Basic $AUTH" # 1 year of weekly data curl -X GET "$BASE_URL/assets/chart?symbol=AAPL&timeframe=1Week&period=1Y" \ -H "Authorization: Basic $AUTH" ``` ## Market snapshots Get a complete picture for an asset — quote, latest bar, and previous close: ```bash curl -X GET "$BASE_URL/market-data/assets/AAPL/snapshot" \ -H "Authorization: Basic $AUTH" ``` Snapshots are useful for building asset detail pages that show current price, daily change, and trading volume in a single request. ## Market status ### Check if a market is open ```bash curl -X GET "$BASE_URL/market-data/markets/XNYS/status" \ -H "Authorization: Basic $AUTH" ``` Use this to: - Show "Market Open" / "Market Closed" indicators - Disable order placement outside market hours (for `day` orders) - Display countdown to market open/close ### Trading calendar ```bash curl -X GET "$BASE_URL/market-data/markets/XNYS/calendar?start=2025-06-01&end=2025-06-30" \ -H "Authorization: Basic $AUTH" ``` Returns open/close times and holidays for the specified period. ### Market codes | Code | Market | |------|--------| | `XNYS` | New York Stock Exchange (NYSE) | | `XNAS` | NASDAQ | ## Building a stock detail page A typical asset detail page combines multiple market data calls: ```javascript const [quote, bars, snapshot] = await Promise.all([ fetch(`${baseUrl}/market-data/assets/${symbol}/quote`, { headers }), fetch(`${baseUrl}/market-data/assets/${symbol}/bars?timeframe=1Day&start=${oneYearAgo}&end=${today}`, { headers }), fetch(`${baseUrl}/market-data/assets/${symbol}/snapshot`, { headers }) ]); ``` Display: current price, daily change, price chart, volume, and 52-week range. ## Testing in Sandbox Test scenarios, simulated behaviors, and how to validate your integration. The sandbox simulates the full Bluum environment — accounts, trading, funding, and webhooks. This guide covers how to test each part of your integration systematically. ## Test scenarios by domain ### Account creation | Scenario | How to test | |----------|------------| | Successful creation | Submit valid account data → verify `ACTIVE` status | | Validation failure | Omit required fields → verify error response | | Duplicate account | Create two accounts with same email → verify `409` conflict | ### KYC verification | Scenario | How to test | |----------|------------| | Approval | Upload any valid image → auto-approved in sandbox | | Rejection | Use test rejection values (see dashboard) | | Missing document | Attempt operations before KYC → verify error | ### Deposits | Scenario | How to test | |----------|------------| | ACH success | Create deposit with valid Plaid options → transitions to `completed` | | Manual transfer | Create manual deposit → verify bank details in response | | Insufficient funds | Create withdrawal exceeding balance → verify `422` error | | Idempotency | Submit same deposit twice with same key → verify no duplicate | | Idempotency conflict | Submit different body with same key → verify error | ### Trading | Scenario | How to test | |----------|------------| | Market buy | Place market order during hours → verify `filled` status | | Limit order | Place limit below market → remains `accepted` | | Fractional buy | Use `notional` for dollar-based purchase | | Insufficient funds | Place order exceeding wallet balance | | Invalid symbol | Use non-existent symbol → verify error | ### Webhooks | Scenario | How to test | |----------|------------| | Event delivery | Register webhook, place order → verify event received | | Duplicate delivery | Process same event twice → verify idempotent handling | | Failure retry | Return 500 from webhook → verify Bluum retries | ## Sandbox-specific behaviors ### Timing - Market orders fill instantly (no execution delay) - ACH deposits complete in seconds (not days) - KYC documents auto-approve immediately ### Data reset - Sandbox data resets nightly at midnight UTC - All accounts, orders, and deposits are cleared - Webhook registrations persist across resets ### Plaid sandbox - Use Plaid sandbox credentials (provided in your dashboard) - Test bank: "First Platypus Bank" with credentials `user_good` / `pass_good` ## Integration test checklist Run through this checklist before going live: - [ ] Account creation with all required fields - [ ] Account creation with missing fields (error handling) - [ ] KYC document upload and status check - [ ] Plaid Link token creation - [ ] Plaid public token exchange - [ ] ACH deposit with idempotency key - [ ] Manual bank transfer deposit - [ ] Deposit status tracking (polling or webhook) - [ ] Market buy order - [ ] Limit buy order - [ ] Position query after fill - [ ] Sell order - [ ] Withdrawal with idempotency key - [ ] Withdrawal cancellation - [ ] Webhook registration and event receipt - [ ] Error handling for all 4xx/5xx responses - [ ] Rate limit handling (429 with backoff) ## Automated testing tips ```javascript // Use environment variables for easy switching const config = { baseUrl: process.env.BLUUM_BASE_URL || 'https://test-service.bluumfinance.com/v1', apiKey: process.env.BLUUM_API_KEY, apiSecret: process.env.BLUUM_API_SECRET }; // Generate unique idempotency keys per test run const idempotencyKey = `test-${Date.now()}-${Math.random().toString(36).slice(2)}`; // Clean up: note that sandbox resets nightly, but you may want to // cancel pending orders and withdrawals between test runs ``` ## Next steps When all tests pass, follow the [Going Live](/get-started/go-live) checklist to move to production. ## API Reference Complete endpoint documentation with schemas, parameters, and examples. This section contains the auto-generated reference for every Bluum API endpoint — request parameters, response schemas, and interactive examples. ## Base URLs | Environment | URL | |-------------|-----| | **Sandbox** | `https://test-service.bluumfinance.com/v1` | | **Production** | `https://service.bluumfinance.com/v1` | ## Authentication HTTP Basic Auth with your API Key (username) and API Secret (password). See [Authentication](/platform/authentication) for details. ## Endpoint groups | Group | Description | |-------|-------------| | **Accounts** | Create, read, and manage investment accounts | | **Documents** | Upload and track KYC verification documents | | **Trading** | Place and manage orders | | **Positions** | View account holdings and P&L | | **Assets** | Search and list tradable instruments | | **Funding Sources** | Link bank accounts via Plaid | | **Transfers** | Deposits and withdrawals | | **Market Data** | Quotes, bars, snapshots, market status | | **Webhooks** | Event notification management | | **Wealth Management** | Profiles, risk, goals, portfolios, insights | ## Conventions - **Decimal values** are strings to preserve precision (e.g., `"178.50"`) - **Dates** use ISO 8601 format (`2025-06-15T14:30:00Z`) - **IDs** are UUID v4 format - **Errors** follow the `BLUM-{STATUS}-{SEQ}` pattern — see [Error Handling](/platform/errors) - **Rate limits** — Sandbox: 10 req/s, Production: 25 req/s ## Interactive playground Use the playground on each endpoint page to make live requests against the sandbox. Enter your API Key and Secret in the authentication fields. ## Additional resources - [Postman Collection](/resources/postman) — Pre-built requests for every endpoint - [SDKs](/resources/sdks) — Client libraries and code generation - [Error Code Reference](/resources/error-codes) — Complete error catalog ## Changelog Latest API updates, new features, and deprecations. All notable changes to the Bluum Finance API are documented here. Entries are ordered from newest to oldest. --- ## 2026-03-16 — Documentation redesign **New** - Redesigned documentation with dedicated sections: Get Started, Platform, Guides, API Reference, Changelog, and Resources - Two quick start guides: Self-Directed Investing and Wealth Management - Platform concept pages covering all core objects (Accounts, Wallets, Trading, Positions, Assets, Webhooks, etc.) - Wealth Management documentation (Investor Profile, Risk Assessment, Financial Planning, Portfolios, Insights) - Step-by-step integration guides for every workflow - Error code reference and glossary --- ## 2025-06-15 — Initial API release **New** - Account creation and management (trading and individual account types) - KYC document upload and verification - Bank account linking via Plaid - ACH deposits and withdrawals - Manual bank transfer deposits - Trading: market, limit, stop, trailing stop orders - Fractional and notional order support - Position tracking with P&L - Asset search and listing - Market data: quotes, bars, snapshots, market status, calendar - Webhook event notifications - Wealth Management: investor profiles, risk assessments, goals, financial plans, portfolios, auto-invest, DRIP, insights, reports - Sandbox environment with simulated trading and funding - HTTP Basic Authentication with API Key:Secret - Idempotency support for financial operations ## SDKs & Libraries Official SDKs and client libraries for the Bluum Finance API. Official Bluum SDKs are in development. In the meantime, the API is accessible via standard HTTP from any language. ## Using the API directly The Bluum API uses REST conventions with JSON payloads. Any HTTP client works: | Language | Recommended client | |----------|--------------------| | JavaScript/Node.js | `fetch` (built-in), `axios` | | Python | `requests`, `httpx` | | Ruby | `net/http`, `faraday` | | Go | `net/http` | | Java | `HttpClient` (Java 11+), `OkHttp` | | PHP | `Guzzle`, `cURL` | ## Authentication helper All requests use HTTP Basic Auth. Here's a minimal helper: ```javascript Node.js class BluumClient { constructor(apiKey, apiSecret, baseUrl = 'https://test-service.bluumfinance.com/v1') { this.baseUrl = baseUrl; this.auth = 'Basic ' + Buffer.from(`${apiKey}:${apiSecret}`).toString('base64'); } async request(method, path, body) { const response = await fetch(`${this.baseUrl}${path}`, { method, headers: { 'Authorization': this.auth, 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined }); if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`); return response.json(); } getAccounts() { return this.request('GET', '/accounts'); } createAccount(data) { return this.request('POST', '/accounts', data); } getAssets(params) { return this.request('GET', `/assets/list?${new URLSearchParams(params)}`); } placeOrder(accountId, order) { return this.request('POST', `/trading/accounts/${accountId}/orders`, order); } } ``` ```python Python import requests import base64 class BluumClient: def __init__(self, api_key, api_secret, base_url='https://test-service.bluumfinance.com/v1'): self.base_url = base_url credentials = base64.b64encode(f'{api_key}:{api_secret}'.encode()).decode() self.headers = {'Authorization': f'Basic {credentials}', 'Content-Type': 'application/json'} def _request(self, method, path, json=None): response = requests.request(method, f'{self.base_url}{path}', headers=self.headers, json=json) response.raise_for_status() return response.json() def get_accounts(self): return self._request('GET', '/accounts') def create_account(self, data): return self._request('POST', '/accounts', json=data) def get_assets(self, **params): return self._request('GET', f'/assets/list?{"&".join(f"{k}={v}" for k,v in params.items())}') def place_order(self, account_id, order): return self._request('POST', f'/trading/accounts/{account_id}/orders', json=order) ``` ## OpenAPI specification The full OpenAPI 3.0 spec is available for code generation: - **Bundled spec:** Available at `/api-reference/openapi.yaml` in the docs - **Tools:** Use [openapi-generator](https://openapi-generator.tech/) or [swagger-codegen](https://swagger.io/tools/swagger-codegen/) to generate typed clients in any language ## Coming soon - Official Node.js SDK (`@bluum-finance/sdk`) - Official Python SDK (`bluum-finance`) - Webhook signature verification libraries Contact [developer support](mailto:ope@bluumfinance.com) if you need SDK assistance. ## Postman Collection Pre-built API requests for every Bluum endpoint. The Bluum Postman collection provides ready-to-run requests for every API endpoint, with pre-configured authentication and example payloads. ## Quick setup Open the [Bluum Postman Collection](https://documenter.getpostman.com/view/40898431/2sBXVo97hh) and click **Run in Postman** to import it. Create a Postman environment with these variables: | Variable | Value | |----------|-------| | `baseUrl` | `https://test-service.bluumfinance.com/v1` | | `apiKey` | Your sandbox API key | | `apiSecret` | Your sandbox API secret | Open the **Assets > List Assets** request and click **Send**. You should see a list of tradable assets. ## Collection contents The collection is organized by API domain: | Folder | Requests | |--------|----------| | **Accounts** | Create, get, list accounts; get wallets and transactions | | **Documents** | Upload KYC documents, check status, list documents | | **Trading** | Place orders (market, limit, stop), get order status, list orders | | **Positions** | List positions, get position details | | **Assets** | Search, list, get details, chart data | | **Funding Sources** | Create Plaid link token, exchange token, list sources | | **Transfers** | Create/list/cancel deposits and withdrawals | | **Market Data** | Quotes, bars, snapshots, market status, calendar | | **Webhooks** | Register, list, update, delete webhooks; list event types | | **Wealth - Profile** | Create/update/get investor profile, snapshots | | **Wealth - Risk** | Submit risk assessment, get current/summary | | **Wealth - Goals** | Create, list, update, delete goals | | **Wealth - Planning** | Generate financial plan, get summary | | **Wealth - Portfolio** | Create portfolio, get summary/holdings/performance, rebalance | | **Wealth - Auto-Invest** | Create, pause, resume auto-invest schedules | | **Wealth - Insights** | Get insights, recommendations, tax optimization | | **Wealth - Reports** | Statements, P&L, benchmark reports | ## Collection variables The collection uses variables for resource IDs. After creating a resource (e.g., an account), update the corresponding variable so subsequent requests reference it: | Variable | Set after | |----------|-----------| | `account_id` | Creating an account | | `document_id` | Uploading a document | | `order_id` | Placing an order | | `deposit_id` | Creating a deposit | | `withdrawal_id` | Creating a withdrawal | | `webhook_id` | Registering a webhook | | `portfolio_id` | Creating a portfolio | ## Switching environments To switch from sandbox to production: 1. Create a new Postman environment 2. Set `baseUrl` to `https://service.bluumfinance.com/v1` 3. Use your production API key and secret 4. Select the production environment before sending requests ## AI-Assisted Development Use AI coding assistants effectively with Bluum's documentation and API specifications. The Bluum documentation is optimized for AI-assisted development through comprehensive resources that provide contextually-aware help with your integration. ## Documentation resources ### llms.txt file The `llms.txt` file is an industry standard that helps AI tools index content more efficiently, similar to how a sitemap helps search engines. It provides a structured overview of all available documentation pages. Structured index of all Bluum documentation pages ### Complete documentation compilation The `llms-full.txt` file combines the entire documentation site into a single file as context for AI tools. You can paste a single URL as context for AI tools for more relevant and accurate responses. Full documentation content in a single file ## API specification Our OpenAPI specification provides detailed API endpoints, request/response schemas, and error messages to ensure accurate implementation. This is particularly valuable for understanding exact API requirements and handling error cases properly. OpenAPI 3.0 spec with all endpoints and schemas ## Best practices When working with AI coding assistants: 1. **Reference the llms-full.txt** for comprehensive context about Bluum APIs and integration patterns 2. **Use the OpenAPI spec** for precise API implementation details 3. **Include specific error handling** based on documented error responses — see [Error Codes](/resources/error-codes) 4. **Follow authentication patterns** outlined in the [Authentication](/platform/authentication) guide > Both documentation files are always up to date and require zero maintenance. ## Error Code Reference Complete catalog of Bluum API error codes with troubleshooting guidance. All Bluum errors follow the pattern `BLUM-{HTTP_STATUS}-{SEQUENCE}`. This page provides the complete catalog. ## 400 — Bad Request Validation failures and malformed requests. | Code | Message | Resolution | |------|---------|------------| | `BLUM-400-001` | Generic validation error | Check the error message for specific field details | | `BLUM-400-002` | Missing required field | Include all required fields in your request | | `BLUM-400-003` | Invalid field format | Match expected types: dates as ISO 8601, phone as E.164, country as ISO alpha-2 | | `BLUM-400-004` | Invalid enum value | Use one of the documented enum values | | `BLUM-400-005` | Invalid amount | Amount must be a positive decimal string (e.g., `"100.00"`) | | `BLUM-400-006` | Invalid quantity | Quantity must be a positive number | | `BLUM-400-007` | Invalid date range | Start date must be before end date | | `BLUM-400-008` | Request body too large | Reduce payload size (file uploads: max 10MB) | ## 401 — Unauthorized Authentication failures. | Code | Message | Resolution | |------|---------|------------| | `BLUM-401-001` | Missing authorization header | Include `Authorization: Basic ` | | `BLUM-401-002` | Malformed authorization header | Format: `Basic ` | | `BLUM-401-003` | Invalid API key or secret | Verify credentials are correct and not revoked | | `BLUM-401-004` | API key inactive | Contact support to activate your API key | | `BLUM-401-005` | API key expired | Generate new credentials in the dashboard | ## 403 — Forbidden Authorization and permission errors. | Code | Message | Resolution | |------|---------|------------| | `BLUM-403-001` | Insufficient permissions | Your API key doesn't have access to this resource | | `BLUM-403-002` | Account not approved | Complete KYC verification before trading | | `BLUM-403-003` | Production access required | Request production access from support | | `BLUM-403-004` | Operation not allowed | The current account state doesn't permit this operation | ## 404 — Not Found Resource doesn't exist or isn't accessible. | Code | Message | Resolution | |------|---------|------------| | `BLUM-404-001` | Resource not found | Verify the ID exists and belongs to your tenant | | `BLUM-404-002` | Endpoint not found | Check the URL path against the API reference | | `BLUM-404-003` | Asset not found | Verify the symbol or asset ID | ## 409 — Conflict Duplicate resources or state conflicts. | Code | Message | Resolution | |------|---------|------------| | `BLUM-409-001` | Duplicate resource | Resource with these attributes already exists | | `BLUM-409-002` | Idempotency conflict | Same idempotency key used with different request body | | `BLUM-409-003` | State conflict | Resource is in a state that prevents this operation | ## 422 — Unprocessable Entity Business rule violations. | Code | Message | Resolution | |------|---------|------------| | `BLUM-422-001` | Insufficient funds | Wallet balance too low for this operation | | `BLUM-422-002` | Market closed | Place order during market hours or use `gtc` | | `BLUM-422-003` | Asset not tradable | This asset is not available for trading | | `BLUM-422-004` | Position insufficient | Not enough shares to sell | | `BLUM-422-005` | Withdrawal limit exceeded | Exceeds available withdrawal balance | ## 429 — Too Many Requests Rate limit exceeded. | Code | Message | Resolution | |------|---------|------------| | `BLUM-429-001` | Rate limit exceeded | Wait and retry with exponential backoff; check `Retry-After` header | ## 500 — Internal Server Error Server-side errors. These are retryable. | Code | Message | Resolution | |------|---------|------------| | `BLUM-500-001` | Internal error | Retry with exponential backoff; contact support if persistent | | `BLUM-500-017` | External service error | Upstream service temporarily unavailable; retry | ## Handling errors programmatically ```javascript async function handleBluumResponse(response) { if (response.ok) return response.json(); const error = await response.json(); const [, status] = error.code.match(/BLUM-(\d+)-/) || []; switch (status) { case '400': // Fix request and don't retry case '401': // Fix credentials case '403': // Fix permissions case '404': // Fix resource ID case '422': // Fix business logic throw new Error(`Client error: ${error.code} - ${error.message}`); case '429': // Retry with backoff const retryAfter = response.headers.get('Retry-After') || 1; await sleep(retryAfter * 1000); return retry(); case '500': // Retry with backoff await sleep(1000); return retry(); } } ``` ## Glossary Key terms and concepts used throughout the Bluum Finance API. | Term | Definition | |------|-----------| | **Account** | An investment account for one end user. Holds identity, contact, disclosures, and links to wallets, orders, and positions. | | **ACH** | Automated Clearing House — electronic bank transfer network used for deposits and withdrawals. | | **Asset** | A tradable financial instrument (stock, ETF) available through Bluum. | | **Auto-Invest** | A recurring schedule that automatically invests a fixed amount into a portfolio on a set frequency. | | **BYO Wallet** | Bring Your Own wallet — the partner manages the balance externally and provides it via an endpoint. | | **Client Order ID** | A partner-assigned identifier for an order, used for reconciliation. | | **Cost Basis** | The total purchase price of a position (`quantity * average entry price`). | | **Custodian** | A regulated financial institution that holds securities on behalf of investors. Bluum routes to custodians automatically. | | **DRIP** | Dividend Reinvestment Plan — automatically reinvests dividends back into the portfolio. | | **Deposit** | A money movement from a bank account into an investment wallet. | | **Financial Plan** | A generated strategy that maps the investor's goals, risk profile, and finances into an actionable investment approach. | | **Fractional Shares** | Owning less than one full share of a stock or ETF. Enabled via notional orders. | | **Funding Source** | A linked bank account (via Plaid) used for ACH transfers. | | **Goal** | A financial objective (retirement, education, home purchase) with a target amount and timeline. | | **Hold** | A temporary reservation on wallet funds during order execution. Released if the order is canceled, captured if filled. | | **Hosted Wallet** | Default wallet type where Bluum manages the cash balance directly. | | **Idempotency Key** | A unique request identifier that prevents duplicate financial operations on retry. | | **Individual Account** | Account type for wealth management — includes investor profile, risk assessment, and portfolio management. | | **Investor Profile** | Comprehensive financial picture: demographics, employment, tax, insurance, estate, cash flow, and preferences. | | **IPS** | Investment Policy Statement — the governing document for a managed portfolio. Defines objectives, constraints, allocation targets, and rebalancing rules. | | **KYC** | Know Your Customer — identity verification process required before trading. | | **Life Event** | A significant personal event (marriage, job change, inheritance) that affects the financial plan. | | **Limit Order** | An order that executes only at the specified price or better. | | **Market Data** | Real-time and historical pricing, quotes, bars, and market status information. | | **Market Order** | An order that executes immediately at the current market price. | | **Notional Order** | An order specified in dollar amount rather than share quantity. Results in fractional shares. | | **Omnibus Wallet** | Wallet type with an immutable double-entry ledger for full audit trails. | | **Plaid** | Third-party service for secure bank account linking and ACH transfers. | | **Portfolio** | A managed collection of holdings governed by an IPS. Supports rebalancing, auto-invest, and DRIP. | | **Position** | A holding in a specific asset — quantity, cost basis, current value, and P&L. | | **Rebalancing** | Adjusting portfolio holdings to return to the target asset allocation defined in the IPS. | | **Risk Assessment** | A questionnaire-based evaluation that determines the investor's risk tolerance and recommended allocation. | | **Sandbox** | Development environment with simulated trading and funding. Data resets nightly. | | **Snapshot** | A point-in-time record of an investor profile, used for compliance and auditing. | | **Stop Order** | An order that triggers a market order when the price reaches a specified level. Used for loss protection. | | **Tenant** | The top-level entity representing a partner company. All resources (accounts, orders, etc.) are scoped to a tenant. | | **Time in Force** | How long an order remains active: `day` (current session), `gtc` (until filled/canceled), `ioc` (immediate or cancel), `fok` (fill or kill). | | **Trading Account** | Account type for self-directed investing — users decide what to buy and sell. | | **Trailing Stop** | A stop order where the stop price follows the market price by a fixed offset. | | **Wallet** | The cash balance container for an investment account. Receives deposits, funds trades, disburses withdrawals. | | **Webhook** | A registered URL that receives HTTP POST notifications when events occur (order fills, deposit completions, etc.). | | **Withdrawal** | A money movement from an investment wallet to a bank account. | ## Getting Help Support channels, escalation paths, and how to report issues. ## Support channels | Channel | Contact | Best for | |---------|---------|----------| | **Email** | `support@bluum.finance` | Credentials, access, compliance, general questions | | **Developer support** | `ope@bluumfinance.com` | Integration help, technical questions, API issues | | **Emergency trading desk** | `+1 (415) 555-0112` | Urgent production issues during market hours (24/5) | ## When contacting support Include the following for faster resolution: - **Your company name** and dashboard account email - **Environment** (sandbox or production) - **Request ID** (`X-Request-Id` header from the API response) - **Error code** (e.g., `BLUM-400-001`) - **Timestamp** of the issue - **Steps to reproduce** (API calls made, parameters used) ## Response times | Severity | Description | Target response | |----------|-------------|----------------| | **Critical** | Production trading or funding blocked | 1 hour (during market hours) | | **High** | Feature degraded, workaround available | 4 hours | | **Medium** | Non-blocking issue or question | 1 business day | | **Low** | Feature request, documentation improvement | 3 business days | ## Self-service resources Before contacting support, check these resources: - [Error Code Reference](/resources/error-codes) — Troubleshooting guidance for every error code - [Sandbox Environment](/get-started/sandbox) — Sandbox-specific behaviors and test data - [API Reference](/api-reference/introduction) — Endpoint documentation with schemas and examples - [System Status](https://status.bluumfinance.com) — Check for ongoing incidents ## Requesting production access Email `support@bluum.finance` with: 1. Company name and dashboard email 2. Integration model (Self-Directed or Wealth Management) 3. Confirmation of sandbox testing completion See [Going Live](/get-started/go-live) for the full production readiness checklist. ## API Endpoints Summary - `GET /accounts` — List all user accounts - `POST /accounts` — Create a new investment account - `GET /accounts/{account_id}` — Retrieve a specific account - `GET /accounts/{account_id}/wallets` — List all wallets for an account - `GET /accounts/{account_id}/transactions` — List transactions for an account - `GET /documents/accounts/{account_id}/upload` — List documents for an account - `POST /documents/accounts/{account_id}/upload` — Upload a document for an account - `GET /documents/{document_id}` — Retrieve a specific document - `DELETE /documents/{document_id}` — Delete a document - `GET /trading/accounts/{account_id}/orders` — List orders for an account - `POST /trading/accounts/{account_id}/orders` — Place a new investment order - `GET /trading/orders/{order_id}` — Retrieve a specific trade order - `GET /trading/accounts/{account_id}/positions` — List positions for an account - `GET /trading/accounts/{account_id}/positions/{position_id}` — Retrieve a specific position - `GET /assets/search` — Search for assets by ticker, name, or partial match - `GET /assets/list` — List all assets with optional filtering - `GET /assets/chart` — Get historical chart/bar data for an asset - `GET /assets/{symbol}` — Get a single asset by symbol/ticker - `GET /accounts/{account_id}/funding-sources` — List funding sources for an account - `DELETE /accounts/{account_id}/funding-sources/{id}` — Disconnect a funding source - `POST /accounts/{account_id}/funding-sources/plaid/link-token` — Create a Plaid Link token - `POST /accounts/{account_id}/funding-sources/plaid/connect` — Connect a bank account via Plaid - `GET /webhooks/event-types` — Get all available event types - `GET /webhooks` — List all webhooks - `POST /webhooks` — Create a new webhook - `PATCH /webhooks/{webhook_id}` — Update a webhook - `DELETE /webhooks/{webhook_id}` — Delete a webhook - `GET /accounts/{account_id}/deposits` — List deposits for an account - `POST /accounts/{account_id}/deposits` — Initiate a new deposit - `GET /accounts/{account_id}/deposits/{deposit_id}` — Get deposit details - `POST /accounts/{account_id}/deposits/{deposit_id}/cancel` — Cancel a pending deposit - `GET /accounts/{account_id}/withdrawals` — List withdrawals for an account - `POST /accounts/{account_id}/withdrawals` — Initiate a new withdrawal - `GET /accounts/{account_id}/withdrawals/{withdrawal_id}` — Get withdrawal details - `POST /accounts/{account_id}/withdrawals/{withdrawal_id}/cancel` — Cancel a pending withdrawal - `GET /market-data/markets` — List available markets - `GET /market-data/markets/{code}` — Get market details - `GET /market-data/markets/{code}/status` — Get live market status - `GET /market-data/markets/{code}/calendar` — Get market calendar - `GET /market-data/assets/search` — Search assets across markets - `GET /market-data/assets/{symbol}` — Get asset profile with latest quote - `GET /market-data/assets/{symbol}/quote` — Get real-time quote - `GET /market-data/assets/{symbol}/bars` — Get historical OHLCV bars - `GET /market-data/assets/{symbol}/snapshot` — Get full asset snapshot - `GET /wealth/accounts/{account_id}/profile` — Get investor profile - `PUT /wealth/accounts/{account_id}/profile` — Update investor profile - `GET /wealth/accounts/{account_id}/profile/snapshots` — List profile snapshots - `POST /wealth/accounts/{account_id}/profile/snapshots` — Create profile snapshot - `GET /wealth/accounts/{account_id}/profile/snapshots/{snapshot_id}` — Get profile snapshot detail - `GET /wealth/accounts/{account_id}/dependents` — List dependents - `POST /wealth/accounts/{account_id}/dependents` — Create a dependent - `GET /wealth/accounts/{account_id}/dependents/{dependent_id}` — Get dependent details - `PUT /wealth/accounts/{account_id}/dependents/{dependent_id}` — Update dependent - `DELETE /wealth/accounts/{account_id}/dependents/{dependent_id}` — Delete dependent - `GET /wealth/accounts/{account_id}/risk-assessments` — List risk assessments - `POST /wealth/accounts/{account_id}/risk-assessments` — Submit risk assessment - `GET /wealth/accounts/{account_id}/risk-assessments/current` — Get current risk assessment - `GET /wealth/accounts/{account_id}/risk-assessments/summary` — Get risk assessment summary - `GET /wealth/accounts/{account_id}/risk-assessments/{assessment_id}` — Get risk assessment details - `GET /wealth/accounts/{account_id}/goals` — List goals - `POST /wealth/accounts/{account_id}/goals` — Create a goal - `GET /wealth/accounts/{account_id}/goals/{goal_id}` — Get a goal - `PUT /wealth/accounts/{account_id}/goals/{goal_id}` — Update a goal - `DELETE /wealth/accounts/{account_id}/goals/{goal_id}` — Delete a goal - `GET /wealth/accounts/{account_id}/life-events` — List life events - `POST /wealth/accounts/{account_id}/life-events` — Create a life event - `GET /wealth/accounts/{account_id}/life-events/{event_id}` — Get a life event - `PUT /wealth/accounts/{account_id}/life-events/{event_id}` — Update a life event - `DELETE /wealth/accounts/{account_id}/life-events/{event_id}` — Delete a life event - `GET /wealth/accounts/{account_id}/external-accounts` — List external accounts - `POST /wealth/accounts/{account_id}/external-accounts` — Create an external account - `GET /wealth/accounts/{account_id}/external-accounts/{external_account_id}` — Get an external account - `PUT /wealth/accounts/{account_id}/external-accounts/{external_account_id}` — Update an external account - `DELETE /wealth/accounts/{account_id}/external-accounts/{external_account_id}` — Delete an external account - `GET /wealth/accounts/{account_id}/financial-plan` — Get financial plan - `PUT /wealth/accounts/{account_id}/financial-plan` — Create or update financial plan - `GET /wealth/accounts/{account_id}/financial-plan/summary` — Get financial health summary - `GET /wealth/accounts/{account_id}/investment-policy` — Get investment policy statement - `PUT /wealth/accounts/{account_id}/investment-policy` — Create or update investment policy statement - `POST /wealth/accounts/{account_id}/investment-policy/validate` — Validate portfolio against IPS - `GET /wealth/accounts/{account_id}/portfolios` — List portfolios for an account - `GET /wealth/accounts/{account_id}/portfolios/{portfolio_id}/summary` — Get portfolio summary - `GET /wealth/accounts/{account_id}/portfolios/{portfolio_id}/holdings` — Get portfolio holdings - `GET /wealth/accounts/{account_id}/portfolios/{portfolio_id}/performance` — Get portfolio performance - `GET /wealth/accounts/{account_id}/portfolios/{portfolio_id}/rebalancing` — Get rebalancing analysis - `POST /wealth/accounts/{account_id}/portfolios/{portfolio_id}/rebalancing` — Execute portfolio rebalance - `GET /wealth/accounts/{account_id}/auto-invest` — List auto-invest schedules - `POST /wealth/accounts/{account_id}/auto-invest` — Create auto-invest schedule - `GET /wealth/accounts/{account_id}/auto-invest/{schedule_id}` — Get auto-invest schedule details - `PATCH /wealth/accounts/{account_id}/auto-invest/{schedule_id}` — Update auto-invest schedule - `DELETE /wealth/accounts/{account_id}/auto-invest/{schedule_id}` — Delete auto-invest schedule - `POST /wealth/accounts/{account_id}/auto-invest/{schedule_id}/pause` — Pause auto-invest schedule - `POST /wealth/accounts/{account_id}/auto-invest/{schedule_id}/resume` — Resume auto-invest schedule - `GET /wealth/accounts/{account_id}/portfolios/{portfolio_id}/drip` — Get DRIP configuration - `PUT /wealth/accounts/{account_id}/portfolios/{portfolio_id}/drip` — Configure dividend reinvestment (DRIP) - `GET /wealth/accounts/{account_id}/insights` — Get personalized insights - `GET /wealth/accounts/{account_id}/recommendations` — Get investment recommendations - `GET /wealth/accounts/{account_id}/tax-optimization` — Get tax optimization suggestions - `POST /wealth/accounts/{account_id}/assistant/chat` — AI assistant chat - `GET /wealth/accounts/{account_id}/statements` — Get account statement - `GET /wealth/accounts/{account_id}/reports/pnl` — Get P&L report - `GET /wealth/accounts/{account_id}/reports/benchmark` — Get benchmark comparison report