Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.ivory.finance/llms.txt

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

Authentication

All portfolio endpoints require a valid JWT Bearer token. The tenant_id is extracted from the token’s sub claim — each portfolio is isolated to the calling tenant.
X-API-Key: <your-api-key>
Authorization: Bearer <jwt>

Portfolios

Create portfolio

POST /v1/portfolios Creates a new portfolio aggregate. After creation, the read-model snapshot is seeded automatically.
FieldRequiredDefaultDescription
nameYesPortfolio display name
portfolio_typeYeslong_only | long_short | balanced | fixed_income | alternatives | multi_asset | fund_of_funds
currencyNoUSDISO 4217 currency code
inception_dateNotodayISO date string
benchmark_tickerNoBenchmark ticker, e.g. SPY
rebalance_thresholdNo0.05Drift % that triggers a rebalance recommendation (0.05 = 5%)
metadataNo{}Arbitrary key/value metadata
curl -X POST https://api.ivory.finance/v1/portfolios \
  -H "X-API-Key: $IVORY_API_KEY" \
  -H "Authorization: Bearer $IVORY_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Global Equity Alpha",
    "portfolio_type": "long_only",
    "currency": "USD",
    "benchmark_ticker": "SPY",
    "rebalance_threshold": 0.05
  }'
{
  "portfolio_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "name": "Global Equity Alpha",
  "portfolio_type": "long_only",
  "currency": "USD",
  "tenant_id": "user_abc123",
  "created": true
}

List portfolios

GET /v1/portfolios Returns all portfolios for the tenant from the CQRS read model, ordered by AUM descending.
ParameterTypeDefaultDescription
limitinteger50Max results (1–200)
offsetinteger0Pagination offset
curl "https://api.ivory.finance/v1/portfolios?limit=20" \
  -H "X-API-Key: $IVORY_API_KEY" \
  -H "Authorization: Bearer $IVORY_JWT"
{
  "portfolios": [
    {
      "portfolio_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "name": "Global Equity Alpha",
      "portfolio_type": "long_only",
      "currency": "USD",
      "aum_usd_mm": 142.5,
      "position_count": 48,
      "benchmark_ticker": "SPY",
      "ytd_return": 0.0821,
      "active_return_ytd": 0.0214,
      "var_95_1d": 0.0142,
      "sharpe_ratio": 1.84,
      "max_drift_pct": 2.3,
      "rebalance_needed": false,
      "macro_regime": "risk_on",
      "projected_at": "2026-03-26T08:00:00Z"
    }
  ],
  "total": 3,
  "limit": 20,
  "offset": 0
}

Get portfolio

GET /v1/portfolios/{portfolio_id} Returns write-model detail plus the latest risk decomposition snapshot and position count.
curl "https://api.ivory.finance/v1/portfolios/f47ac10b-58cc-4372-a567-0e02b2c3d479" \
  -H "X-API-Key: $IVORY_API_KEY" \
  -H "Authorization: Bearer $IVORY_JWT"
{
  "portfolio": {
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "name": "Global Equity Alpha",
    "portfolio_type": "long_only",
    "currency": "USD",
    "aum_usd_mm": 142.5,
    "rebalance_threshold": 0.05,
    "benchmark_ticker": "SPY",
    "inception_date": "2024-01-02",
    "is_active": true,
    "created_at": "2024-01-02T10:00:00Z"
  },
  "position_count": 48,
  "risk": {
    "var_95_1d": 0.0142,
    "var_99_1d": 0.0201,
    "cvar_95_1d": 0.0178,
    "hhi": 0.0312,
    "top5_weight": 0.284,
    "top10_weight": 0.481,
    "portfolio_vol_30d": 0.0652,
    "sharpe_ratio": 1.84,
    "tracking_error": 0.0041,
    "information_ratio": 1.12,
    "macro_regime": "risk_on",
    "nav_impact_pct": -0.82,
    "computed_at": "2026-03-26T07:55:00Z"
  }
}

Update portfolio

PATCH /v1/portfolios/{portfolio_id} Send only the fields you want to change.
FieldDescription
namePortfolio display name
rebalance_thresholdNew drift threshold (e.g. 0.08)
benchmark_tickerNew benchmark ticker
is_activefalse to archive
metadataReplace metadata object
curl -X PATCH "https://api.ivory.finance/v1/portfolios/f47ac10b-58cc-4372-a567-0e02b2c3d479" \
  -H "X-API-Key: $IVORY_API_KEY" \
  -H "Authorization: Bearer $IVORY_JWT" \
  -H "Content-Type: application/json" \
  -d '{ "rebalance_threshold": 0.08, "benchmark_ticker": "IVV" }'

Positions

Import positions (CSV / Excel)

POST /v1/portfolios/{portfolio_id}/positions/import Upload a CSV or Excel file of positions. The file is parsed, entity-resolved, and upserted. PortfolioRiskAgent and RebalanceAgent are fired asynchronously after a successful import. Expected columns (case-insensitive, extras ignored):
ColumnDescription
tickerEquity ticker symbol
security_nameFull security name
asset_classequity | fixed_income | cash | alternatives | derivatives | real_estate | commodity | crypto
quantityNumber of shares / units
avg_cost_priceAverage cost per unit
current_priceCurrent market price
sectorSector slug (e.g. technology)
geographyISO-2 country code (e.g. US)
isinISIN (used for entity resolution if ticker absent)
cusipCUSIP
curl -X POST \
  "https://api.ivory.finance/v1/portfolios/f47ac10b-58cc-4372-a567-0e02b2c3d479/positions/import" \
  -H "X-API-Key: $IVORY_API_KEY" \
  -H "Authorization: Bearer $IVORY_JWT" \
  -F "file=@positions.csv"
{
  "portfolio_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "rows_imported": 48,
  "rows_with_errors": 1,
  "errors": [
    { "row": 22, "error": "Unknown asset_class 'structured_note'" }
  ],
  "agents_fired": ["PortfolioRiskAgent", "RebalanceAgent"]
}

List positions

GET /v1/portfolios/{portfolio_id}/positions
ParameterTypeDescription
asset_classstringFilter by asset class
sectorstringFilter by sector slug
limitintegerMax results (1–500, default 100)
offsetintegerPagination offset
curl "https://api.ivory.finance/v1/portfolios/f47ac10b/positions?asset_class=equity&limit=50" \
  -H "X-API-Key: $IVORY_API_KEY" \
  -H "Authorization: Bearer $IVORY_JWT"

Risk & Analytics

Get risk decomposition

GET /v1/portfolios/{portfolio_id}/risk Returns the latest PortfolioRiskAgent output. Pass ?refresh=true to recompute synchronously before returning.
ParameterTypeDefaultDescription
refreshbooleanfalseRecompute VaR/CVaR before returning
curl "https://api.ivory.finance/v1/portfolios/f47ac10b/risk?refresh=true" \
  -H "X-API-Key: $IVORY_API_KEY" \
  -H "Authorization: Bearer $IVORY_JWT"
{
  "portfolio_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "risk": {
    "var_95_1d": 0.0142,
    "var_99_1d": 0.0201,
    "cvar_95_1d": 0.0178,
    "hhi": 0.0312,
    "top5_weight": 0.284,
    "top10_weight": 0.481,
    "portfolio_vol_30d": 0.0652,
    "portfolio_vol_90d": 0.1128,
    "tracking_error": 0.0041,
    "information_ratio": 1.12,
    "sharpe_ratio": 1.84,
    "macro_regime": "risk_on",
    "wacc_delta_bps": 25.0,
    "nav_impact_pct": -0.82,
    "computed_at": "2026-03-26T08:00:00Z"
  }
}

Get performance attribution

GET /v1/portfolios/{portfolio_id}/attribution Brinson-Fachler decomposition by sector. Breaks down active return into allocation effect, selection effect, and interaction effect.
ParameterTypeDefaultDescription
periodstringytdytd | 1y | q1_2025 etc.
refreshbooleanfalseRecompute before returning
curl "https://api.ivory.finance/v1/portfolios/f47ac10b/attribution?period=ytd" \
  -H "X-API-Key: $IVORY_API_KEY" \
  -H "Authorization: Bearer $IVORY_JWT"
{
  "portfolio_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "period": "ytd",
  "total_active_return": 0.02140,
  "attribution": [
    {
      "sector_slug": "technology",
      "portfolio_weight": 0.31,
      "benchmark_weight": 0.22,
      "portfolio_return": 0.148,
      "benchmark_return": 0.112,
      "allocation_effect": 0.00403,
      "selection_effect": 0.00792,
      "interaction_effect": 0.00324,
      "total_effect": 0.01519
    },
    {
      "sector_slug": "financials",
      "portfolio_weight": 0.14,
      "benchmark_weight": 0.18,
      "allocation_effect": -0.00210,
      "selection_effect": 0.00115,
      "interaction_effect": -0.00084,
      "total_effect": -0.00179
    }
  ]
}

Get factor exposures

GET /v1/portfolios/{portfolio_id}/factors Factor regression loadings (market, size, value, momentum, quality, low_volatility) with t-stats and R².
ParameterTypeDefaultDescription
refreshbooleanfalseRecompute before returning
curl "https://api.ivory.finance/v1/portfolios/f47ac10b/factors" \
  -H "X-API-Key: $IVORY_API_KEY" \
  -H "Authorization: Bearer $IVORY_JWT"
{
  "portfolio_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "factors": [
    { "factor_name": "market",       "loading": 1.08,  "t_stat": 7.2, "r_squared": 0.82, "window_days": 252 },
    { "factor_name": "momentum",     "loading": 0.42,  "t_stat": 2.8, "r_squared": 0.31, "window_days": 252 },
    { "factor_name": "quality",      "loading": 0.28,  "t_stat": 1.9, "r_squared": 0.24, "window_days": 252 },
    { "factor_name": "size",         "loading": -0.12, "t_stat": -0.8,"r_squared": 0.09, "window_days": 252 },
    { "factor_name": "value",        "loading": -0.08, "t_stat": -0.5,"r_squared": 0.06, "window_days": 252 },
    { "factor_name": "low_volatility","loading": 0.15, "t_stat": 1.0, "r_squared": 0.12, "window_days": 252 }
  ]
}

Trigger rebalance

POST /v1/portfolios/{portfolio_id}/rebalance Manually trigger RebalanceAgent. If actual position weights have drifted beyond rebalance_threshold (or force: true), publishes an event to the pm:rebalance:recommended stream.
FieldTypeDefaultDescription
forcebooleanfalseRecommend rebalance regardless of drift threshold
curl -X POST "https://api.ivory.finance/v1/portfolios/f47ac10b/rebalance" \
  -H "X-API-Key: $IVORY_API_KEY" \
  -H "Authorization: Bearer $IVORY_JWT" \
  -H "Content-Type: application/json" \
  -d '{ "force": false }'
{
  "portfolio_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "status": "rebalance_agent_triggered",
  "stream": "pm:rebalance:recommended"
}

Target allocations

Set allocation

POST /v1/portfolios/{portfolio_id}/allocations Set or update a target allocation weight for a portfolio bucket. The RebalanceAgent compares actual weights against these targets to determine drift.
FieldRequiredDescription
allocation_typeYessector | asset_class | geography | security
bucketYesThe bucket identifier (e.g. technology, equity, US, AAPL)
target_weightYesTarget portfolio weight (0.0–1.0)
min_weightNoMinimum allowed weight (default 0.0)
max_weightNoMaximum allowed weight (default 1.0)
curl -X POST "https://api.ivory.finance/v1/portfolios/f47ac10b/allocations" \
  -H "X-API-Key: $IVORY_API_KEY" \
  -H "Authorization: Bearer $IVORY_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "allocation_type": "sector",
    "bucket": "technology",
    "target_weight": 0.25,
    "min_weight": 0.15,
    "max_weight": 0.35
  }'