Skip to main content
The Wealthyhood investments screen shows a performance chart plus high-level metrics for the investor’s primary portfolio. Use the Portfolios API endpoints documented here to fetch the price series and portfolio holdings that drive this UI.
All routes require a bearer token with the read:users scope plus the x-user-id header so the backend can resolve the retail account.

Overview

The investments screen displays portfolio performance, holdings breakdown, and interactive charts. It uses 4 endpoints to build the UI.

Endpoints

1. Get User Portfolios

  • Endpoint: GET /portfolios
  • Purpose: Get all portfolios for the user and identify the primary portfolio
  • Response: Array of portfolios with basic holdings information

2. Get Portfolio Holdings

  • Endpoint: GET /portfolios/{portfolioId}
  • Purpose: Get detailed portfolio holdings with asset information
  • Response: Portfolio with holdings array containing ISIN and quantity

3. Prices by Tenor

  • Endpoint: GET /portfolios/{portfolioId}/prices-by-tenor
  • Purpose: Get historical price data for the portfolio chart
  • Response: Time series data organized by tenor (1w, 1m, 3m, 6m, 1y, max)

4. Investment Products

  • Endpoint: GET /investment-products
  • Purpose: Get current ticker/pricing data for all investment products
  • Response: Array of investment products with current ticker data (automatically includes pricing)
The B2B API does not provide portfolio returns data (returnsValues, upByValues) or holdings with return calculations. You’ll need to calculate returns client-side using the price history data if needed.

Data Flow and Component Building

Phase 1: Initial Load

  1. Fetch User Portfolios
    • Call GET /portfolios to retrieve all portfolios for the user
    • Extract the primary portfolio ID (typically the first portfolio)
    • Check if user has any portfolios; if empty, show appropriate empty state
  2. Fetch Portfolio Holdings (parallel or sequential)
    • Call GET /portfolios/{portfolioId} to get detailed holdings
    • Extract holdings array with ISIN and quantity information

Phase 2: Main Data Fetching

Fetch these in parallel:
  1. Prices by Tenor
    • Response includes time series data for multiple tenors:
      • 1w, 1m, 3m, 6m, 1y, max
    • Each tenor contains:
      • data: Array of {timestamp, value, displayIntraday} points
      • value: Portfolio value in the investor’s base currency
      • displayIntraday: Boolean indicating whether to render intraday granularity
  2. Investment Products (optional, can be lazy-loaded)
    • Response: Array of investment products with current ticker data
    • Used to enrich holdings with current prices and asset metadata
    • Ticker data is automatically included (no query parameters needed)

Phase 3: Building UI Components

Component 1: Portfolio Value Display

Data Source: Prices by Tenor
  1. Extract the default timeframe (e.g., "max")
  2. Display:
    • Current portfolio value: Use the last value from Prices by Tenor for the selected timeframe
    • Historical comparison: Calculate difference between first and last value in the series
    • Percentage change: Calculate ((lastValue - firstValue) / firstValue) * 100
User Interaction: When user selects a different timeframe (1W, 1M, 3M, 6M, 1Y, MAX):
  • Update displayed value from Prices by Tenor for that timeframe
  • Update chart data from Prices by Tenor for that timeframe
  • Recalculate percentage change for the selected timeframe

Component 2: Performance Chart

Data Source: Prices by Tenor
  1. Parse the selected timeframe’s time series data
  2. Transform data points:
    • Convert timestamp (milliseconds) to date objects
    • Extract value as the price point
    • Format dates for x-axis labels
  3. Render line chart with:
    • X-axis: Dates
    • Y-axis: Portfolio value
    • Handle displayIntraday flag for intraday vs daily data granularity
User Interaction: When timeframe filter changes:
  • Switch to the corresponding time series from the Prices by Tenor response
  • Re-render chart with new data

Component 3: Holdings Breakdown

Data Source: Portfolio Holdings + Investment Products
  1. Process holdings:
    • Match holdings ISINs with investment products to get current prices
    • Calculate holding value: holding.quantity * asset.currentTicker.price
    • Calculate total portfolio value: Sum of all holding values
    • Calculate allocation percentage per holding: (holdingValue / totalValue) * 100
    • Sort holdings by value (descending) or alphabetically
  2. Group holdings (optional):
    • By Asset Class: Group by asset.assetClassType (if available in investment products)
    • By Asset Type: Group by asset.isStock vs asset.isETF
    • Ungrouped: Show flat list
  3. Build components:
    • Pie Chart: Aggregate allocations by asset class or type
    • Asset List: Display individual holdings with:
      • Asset name (from investment products)
      • Allocation percentage
      • Current value
      • Quantity held

Component 4: Asset Class Filters

Data Source: Portfolio Holdings + Investment Products
  1. Extract unique asset classes/types from holdings (using investment products data)
  2. Create filter buttons/chips for each asset class
  3. When filter selected:
    • Filter holdings by selected asset class
    • Update pie chart and asset list
    • Recalculate totals

Complete Initialization Flow

1. Load Screen

2. Fetch User Portfolios (GET /portfolios)
   ├─→ Empty: Show empty portfolio message (STOP)
   └─→ Has portfolios: Continue to step 3

3. Extract primary portfolioId

4. Parallel Fetch:
   ├─→ Portfolio Holdings (GET /portfolios/{id})
   ├─→ Prices by Tenor (GET /portfolios/{id}/prices-by-tenor)
   └─→ Investment Products (GET /investment-products) [optional]

5. Process Holdings Data:
   ├─→ Match holdings with investment products
   ├─→ Calculate holding values
   ├─→ Calculate allocations
   ├─→ Group by asset class
   └─→ Build pie chart data

6. Process Chart Data:
   ├─→ Parse time series for default timeframe
   └─→ Render chart

7. Render Components:
   ├─→ Portfolio value display
   ├─→ Performance chart
   ├─→ Asset class filters
   ├─→ Pie chart
   └─→ Holdings list

Refresh Flow

When user pulls to refresh or returns to screen:
  1. Invalidate cached data (if using caching)
  2. Re-fetch in parallel:
    • Portfolio Holdings
    • Prices by Tenor
    • Investment Products (if needed)
  3. Update all components with new data

Error Handling

  • If Portfolio Holdings fails: Show error, hide breakdown components
  • If Prices by Tenor fails: Show error, hide chart, keep holdings display
  • If Investment Products fails: Show holdings without enrichment (ISIN only)

Data Dependencies

  • Portfolio Holdings: Required for holdings breakdown
  • Prices by Tenor: Required for chart and value display
  • Investment Products: Optional, enhances holdings with current prices and metadata

Performance Considerations

  1. Cache responses: These endpoints support caching
  2. Parallel fetching: Fetch Holdings, Prices, and Products simultaneously
  3. Lazy load Investment Products: Only fetch if needed for detailed views
  4. Debounce timeframe changes: Avoid rapid chart re-renders
  5. Pagination: If holdings list is long, implement virtual scrolling or pagination

Key Data Transformations

Prices by Tenor Response Structure

{
  "1w": {
    "data": [
      {"timestamp": 1720601400000, "value": 5123.42, "displayIntraday": true},
      {"timestamp": 1720687800000, "value": 5138.11, "displayIntraday": true}
    ]
  },
  "1m": {
    "data": [
      {"timestamp": 1718188800000, "value": 4980.3, "displayIntraday": false},
      {"timestamp": 1720687800000, "value": 5138.11, "displayIntraday": false}
    ]
  },
  "max": {
    "data": [
      {"timestamp": 1646091600000, "value": 2100.54, "displayIntraday": false},
      {"timestamp": 1720687800000, "value": 5138.11, "displayIntraday": false}
    ]
  }
}

Portfolio Holdings Response Structure

{
  "id": "64f0c51e7fb3fc001234abcd",
  "currency": "EUR",
  "holdings": [
    {"isin": "US0378331005", "quantity": 12.5},
    {"isin": "EU0009658145", "quantity": 8}
  ],
  "createdAt": "2024-03-01T10:00:00.000Z",
  "lastUpdated": "2024-07-10T12:00:00.000Z"
}

Investment Products Response Structure

Each investment product contains:
  • commonId: Internal asset identifier
  • isin: ISIN code
  • name: Asset name
  • symbol: Trading symbol
  • currentTicker: Current price information
    • price: Price in base currency
    • pricePerCurrency: Prices in multiple currencies
    • currency: Ticker currency
    • updatedAt: Last update timestamp
  • isStock: Boolean indicating if it’s a stock
  • isETF: Boolean indicating if it’s an ETF
  • assetClassType: Asset class classification (if available)

Example: Complete Fetch Implementation

const BASE = 'https://api.wealthyhood.com';

async function fetchInvestmentsScreen({ token, userId, portfolioId }) {
  const headers = {
    'Authorization': `Bearer ${token}`,
    'x-user-id': userId,
    'Accept': 'application/json'
  };

  // Fetch all data in parallel
  const [holdingsRes, pricesRes, productsRes] = await Promise.all([
    fetch(`${BASE}/portfolios/${portfolioId}`, { headers }),
    fetch(`${BASE}/portfolios/${portfolioId}/prices-by-tenor`, { headers }),
    fetch(`${BASE}/investment-products`, { headers })
  ]);

  if (!holdingsRes.ok) throw new Error(`holdings failed: ${holdingsRes.status}`);
  if (!pricesRes.ok) throw new Error(`prices failed: ${pricesRes.status}`);
  if (!productsRes.ok) throw new Error(`products failed: ${productsRes.status}`);

  const [holdings, pricesByTenor, products] = await Promise.all([
    holdingsRes.json(),
    pricesRes.json(),
    productsRes.json()
  ]);

  // Enrich holdings with product data
  const enrichedHoldings = holdings.holdings.map(holding => {
    const product = products.find(p => p.isin === holding.isin);
    return {
      ...holding,
      product,
      currentValue: product?.currentTicker?.price 
        ? holding.quantity * product.currentTicker.price 
        : null
    };
  });

  // Calculate total portfolio value
  const totalValue = enrichedHoldings.reduce((sum, h) => sum + (h.currentValue || 0), 0);

  // Calculate allocations
  const holdingsWithAllocation = enrichedHoldings.map(h => ({
    ...h,
    allocation: h.currentValue ? (h.currentValue / totalValue) * 100 : 0
  }));

  return {
    portfolio: holdings,
    pricesByTenor,
    holdings: holdingsWithAllocation,
    totalValue
  };
}
Persist the latest prices-by-tenor response to cache so you can render the chart instantly on next load and refresh it in the background. The intraday flag (displayIntraday) tells you when to render finer granularity.

See also

  • API Reference → Portfolios API → GET /portfolios
  • API Reference → Portfolios API → GET /portfolios/{id}
  • API Reference → Portfolios API → GET /portfolios/{id}/prices-by-tenor
  • API Reference → Investment Products API → GET /investment-products