Claros Input Guide

This guide explains how to build the JSON payload for the Claros report API. Use it as the human-readable companion to the generated OpenAPI docs at /docs, /redoc, and /openapi.json.

Endpoints

Claros exposes report-generation endpoints and tenant theme-pack endpoints:

Endpoint Result
POST /portfolio-report Returns structured report data as JSON.
POST /portfolio-report/pdf Returns the rendered report as a PDF.
GET /theme-packs/manage Opens the hosted theme pack upload and management page.
PUT /theme-packs/{theme_pack_id} Uploads or replaces a tenant theme pack.
GET /theme-packs Lists uploaded tenant theme packs.
GET /theme-packs/{theme_pack_id} Returns one uploaded theme pack manifest summary.
DELETE /theme-packs/{theme_pack_id} Deletes one uploaded tenant theme pack.

The two report-generation endpoints accept the same JSON request body. Report generation and tenant theme-pack API calls require tenant headers:

x-tenant-id: tenant-123
x-api-key: <tenant-api-key>

Validation Responses

Invalid report payloads return HTTP 422. Schema errors, such as an invalid date string or missing mandatory field, use the standard FastAPI error format. Cross-field and page-specific validation errors return a detail array with this shape:

{
  "loc": ["body", "portfolio_info", "wealth_manager"],
  "code": "unknown_wealth_manager",
  "message": "portfolio_info.wealth_manager must match a manager_info.managers[].unique_name.",
  "severity": "error"
}

POST /portfolio-report also returns non-blocking checks in meta.validation_warnings. Warnings flag data that looks unusual, such as weights that do not sum close to 1, but they do not stop report generation.

Payload Shape

Every report-generation request has four top-level blocks. The theme-pack upload endpoint uses a multipart zip upload, and the other theme-pack endpoints do not use this report JSON payload.

{
  "report_info": {},
  "data": {},
  "portfolio_info": {},
  "manager_info": {}
}
Block Purpose
report_info Controls which pages are generated, the reporting period, client name, theme, formatting, and optional styling.
data Contains portfolio returns, NAV, holdings, prices, breakdowns, security data, and optional mandate data.
portfolio_info Identifies the portfolio, currency, benchmark, institution, and assigned wealth manager.
manager_info Provides contact details and manager records used by the report.

The most important idea is this: report_info.report_page_config determines which data fields are required. A report with only front and back pages needs much less data than a report with performance attribution, scenarios, mandate fees, and portfolio breakdowns. The top-level blocks should still be present so validation, page generation, and downstream rendering can use a predictable shape.

Minimal Standard Report

This is a compact example for a normal portfolio report. Real reports normally include longer time series and more instruments.

{
  "report_info": {
    "report_page_config": [
      { "page_type": "Front Page" },
      { "page_type": "Performance Overview Page" },
      {
        "page_type": "Portfolio Analysis Page",
        "breakdowns": ["asset_class", "equity_sector", "instrument"]
      },
      { "page_type": "Back Page" }
    ],
    "number_formatting": {
      "date_format": "%B %d, %Y",
      "percentage_format": "{:.2%}",
      "amount_format": "{:,.2f}"
    },
    "reporting_period": ["2025-07-01", "2025-09-30"],
    "reporting_period_str": "Q3 2025",
    "reporting_date": "2025-10-05",
    "visual_theme": "Water Theme",
    "base_currency": "USD",
    "client_name": "Ms. Emma Clarkson",
    "disclaimer": "<p>This is a demo report generated by Quantcierge.</p>"
  },
  "data": {
    "portfolio_returns": {
      "2025-07-01": 0.0,
      "2025-07-02": 0.004
    },
    "portfolio_nav": {
      "2025-07-01": 1000000.0,
      "2025-07-02": 1004000.0
    },
    "benchmark_returns": {
      "2025-07-01": 0.0,
      "2025-07-02": 0.002
    },
    "portfolio_holdings": {
      "2025-09-30": {
        "NVDA": 120.0,
        "MSFT": 75.0
      }
    },
    "portfolio_weights": {
      "2025-09-30": {
        "NVDA": 0.052,
        "MSFT": 0.031
      }
    },
    "prices": {
      "2025-09-30": {
        "NVDA": 875.0,
        "MSFT": 413.0
      }
    },
    "portfolio_breakdowns": {
      "asset_class": {
        "Equity": 0.7942,
        "Fixed Income": 0.099
      },
      "equity_sector": {
        "Technology": 0.28,
        "Financials": 0.13
      },
      "instrument": {
        "NVDA": 0.052,
        "MSFT": 0.031
      }
    },
    "instrument_identifier_type": "Ticker",
    "security_data": {
      "NVDA": {
        "name": "NVIDIA",
        "instrument_type": "Equity",
        "sector": "Technology",
        "asset_class": "Equity",
        "country": "United States",
        "focus_text": "<ul><li>NVIDIA was a key contributor over the period.</li></ul>",
        "website_url": "https://www.nvidia.com",
        "exchange": "US",
        "currency": "USD"
      },
      "MSFT": {
        "name": "Microsoft",
        "instrument_type": "Equity",
        "sector": "Technology",
        "asset_class": "Equity",
        "country": "United States",
        "website_url": "https://www.microsoft.com",
        "exchange": "US",
        "currency": "USD"
      }
    }
  },
  "portfolio_info": {
    "inception_date": "2023-01-01",
    "wealth_manager": "toby_stetson",
    "portfolio_currency": "USD",
    "benchmark": "MSCI World Index",
    "benchmark_identifier": "MIWO00000PUS",
    "company": "PWM"
  },
  "manager_info": {
    "contact_details": {
      "email": "toby.stetson@pwm.com",
      "website": "https://www.pwm.com"
    },
    "managers": [
      {
        "first_name": "Toby",
        "last_name": "Stetson",
        "full_name": "Toby Stetson",
        "email": "toby.stetson@pwm.com",
        "job_title": "Wealth Manager",
        "unique_name": "toby_stetson"
      }
    ]
  }
}

report_info

report_info controls the report structure, reporting dates, theme, client metadata, and report-wide formatting.

{
  "report_page_config": [
    { "page_type": "Front Page" },
    { "page_type": "Performance Overview Page" },
    { "page_type": "Back Page" }
  ],
  "reporting_period": ["2025-07-01", "2025-09-30"],
  "reporting_period_str": "Q3 2025",
  "reporting_date": "2025-10-05",
  "visual_theme": "Water Theme",
  "base_currency": "USD",
  "client_name": "Ms. Emma Clarkson",
  "disclaimer": "<p>Demo disclaimer.</p>"
}

Important fields:

Field Meaning
report_page_config Ordered list of pages to generate.
number_formatting Optional date, percentage, and amount format strings.
reporting_period Start and end dates covered by the report.
reporting_period_str Optional display label, for example Q3 2025.
reporting_date Date shown as the report date.
visual_theme Built-in visual theme, for example Water Theme.
theme_pack_id Optional uploaded tenant theme pack to apply on top of the selected visual theme.
style Optional font and color overrides.
base_currency Required reporting base currency.
reference_currency Optional secondary currency for mandate money values.
enrich_security_narratives Optional runtime enrichment flag for security narratives when supported.
client_name Client display name.
disclaimer HTML disclaimer shown on the back page.

visual_theme is required even when theme_pack_id is present. The built-in theme supplies fallback visuals and layout assumptions; the theme pack can then override styles and assets for one tenant. Supported built-in values are: Water Theme, Architecture Theme, Botanical Theme, Infrastructure Theme, Topographic Theme, Materials Theme, Light & Shadow Theme, Observatory Theme, Urban Night Theme, Desert Theme, and Gallery Theme.

Page Configuration

Each entry in report_page_config includes a page_type. Some pages require extra configuration. The list order is the report order, so put divider pages where section breaks should appear.

Front, Back, And Performance Pages

[
  { "page_type": "Front Page" },
  { "page_type": "Performance Overview Page" },
  { "page_type": "Performance Period Page" },
  { "page_type": "Back Page" }
]

Front and back pages use report metadata, manager contact details, disclaimers, and theme assets. Performance overview and period pages read the portfolio and benchmark time series from data; they currently support only timeseries charts.

Divider Page

{
  "page_type": "Divider Page",
  "title": "Your Performance Analysis"
}

Use divider pages to introduce a new section. A divider can also include a visual path when you want to override the theme-selected divider image for that page only.

Performance Analysis Page

Use a performance analysis page when the report should explain return drivers, not just show return history. The page currently renders a waterfall graph.

{
  "page_type": "Performance Analysis Page",
  "performance_breakdown_type": "Contribution",
  "performance_breakdown_calculation_type": "Provided",
  "provided_attribution": {
    "components": [
      { "name": "Equity", "value": 0.0568 },
      { "name": "Currencies", "value": 0.0092 }
    ],
    "total_return": 0.0593,
    "benchmark_return": 0.041
  }
}

performance_breakdown_type can be Contribution or Attribution. Use Provided for performance_breakdown_calculation_type and include provided_attribution. calculate_residual_breakdown adds residual analysis when available, and expand_on_breakdown adds detail pages for important components.

If attribution is supplied externally, use Provided and include provided_attribution:

{
  "page_type": "Performance Analysis Page",
  "performance_breakdown_type": "Contribution",
  "performance_breakdown_calculation_type": "Provided",
  "provided_attribution": {
    "components": [
      { "name": "Equity", "value": 0.0568 },
      { "name": "Currencies", "value": 0.0092 }
    ],
    "total_return": 0.0593,
    "benchmark_return": 0.041
  }
}

Component value, total_return, and benchmark_return values are decimal return contributions. For example, 0.0568 means 5.68%.

Portfolio Analysis Page

Use portfolio analysis pages for composition, exposure, holdings, and look-through views. Each requested breakdown must also exist in data.portfolio_breakdowns.

{
  "page_type": "Portfolio Analysis Page",
  "breakdowns": ["asset_class", "equity_sector", "country", "instrument"]
}

Currently rendered breakdown values are:

Value Meaning
asset_class Asset-class composition.
equity_sector Equity sector breakdown.
country Country breakdown.
instrument Top holdings.

You can configure graph types:

{
  "page_type": "Portfolio Analysis Page",
  "breakdowns": ["asset_class", "equity_sector"],
  "graph_types": {
    "asset_class": "donut",
    "equity_sector": "horizontal_bar"
  }
}

The validator accepts donut, pie, bar, horizontal_bar, holdings, and holdings_focus for portfolio analysis pages. Use graph_type when one graph type should apply to all requested breakdowns, and graph_types when each breakdown needs its own presentation.

Expert Commentary Page

Use expert commentary pages for named manager or expert views. Each commentary block is rendered with the matching manager profile.

{
  "page_type": "Expert Commentary Page",
  "expert_commentary": [
    {
      "manager": "mark_edwards",
      "commentary": "<ul><li>This quarter markets continued their strong performance.</li></ul>"
    }
  ]
}

The manager value must match a unique_name in manager_info.managers. commentary accepts simple HTML such as paragraphs and bullet lists.

Proposed Changes Page

Use proposed changes pages to show client-facing portfolio adjustments. The change_type should be one of the same breakdown keys used by portfolio analysis pages.

{
  "page_type": "Proposed Changes Page",
  "proposed_changes": [
    {
      "name": "Defensives",
      "text": "Increase allocation to defensive stocks by 10 percentage points.",
      "change_type": "equity_sector",
      "weight_delta": 0.1,
      "start_weight": 0.1709,
      "end_weight": 0.2709
    }
  ]
}

Weights and deltas are decimal fractions, so 0.1 means 10 percentage points. start_weight + weight_delta should normally equal end_weight.

Scenario Page

Use scenario pages for forward-looking paths. The window and window_frequency define the horizon, while sampling_frequency controls how often observations are generated in the path.

{
  "page_type": "Scenario Page",
  "scenarios": {
    "window": 360,
    "window_frequency": "Months",
    "sampling_frequency": "Months",
    "calculation_method": "Brownian Bridge Sampling",
    "quantiles": [
      {
        "name": "Optimistic",
        "expected_portfolio_annual_cagr": 0.08,
        "expected_portfolio_annual_volatility": 0.2
      },
      {
        "name": "Expected",
        "expected_portfolio_annual_cagr": 0.07,
        "expected_portfolio_annual_volatility": 0.25,
        "expected_drawdown": -0.1,
        "expected_drawdown_window": 3.0
      }
    ]
  }
}

Returns, volatility, and drawdowns are decimal fractions. A drawdown of -0.1 means -10%.

data

data contains the portfolio inputs used by the selected pages. The model expects portfolio_breakdowns and instrument_identifier_type; other time series and holdings fields are used when the selected pages need them.

Field Meaning
portfolio_returns Date-indexed portfolio returns as decimal fractions.
portfolio_nav Date-indexed portfolio net asset values.
benchmark_returns Date-indexed benchmark returns as decimal fractions.
portfolio_holdings Date-indexed instrument quantities.
portfolio_weights Date-indexed instrument weights.
prices Date-indexed instrument prices.
portfolio_breakdowns Precomputed breakdowns by asset class, sector, country, instrument, etc.
instrument_identifier_type Identifier type used for instrument keys, for example Ticker.
security_data Security master data keyed by instrument identifier.
mandate_report Optional mandate-specific report data.

Dates must be ISO format strings: YYYY-MM-DD.

Returns and weights are decimals:

5% return  -> 0.05
10% weight -> 0.10

Use the same instrument keys consistently across portfolio_holdings, portfolio_weights, prices, portfolio_breakdowns.instrument, and security_data. Those keys should match the declared instrument_identifier_type.

Portfolio Breakdowns

portfolio_breakdowns should contain the breakdowns needed by the requested portfolio analysis pages. Keys such as asset_class, equity_sector, or country must match the values requested in report_page_config[].breakdowns. Values are normally decimal portfolio weights.

{
  "portfolio_breakdowns": {
    "asset_class": {
      "Equity": 0.7942,
      "Fixed Income": 0.099
    },
    "equity_sector": {
      "Technology": 0.28,
      "Financials": 0.13
    },
    "country": {
      "United States": 0.1977,
      "United Kingdom": 0.08
    },
    "instrument": {
      "NVDA": 0.052,
      "XT": 0.031
    }
  }
}

Classification breakdown labels should line up with the classification fields in security_data where possible. For example, a country breakdown with United States should generally correspond to securities whose country is also United States.

Security Data

security_data provides metadata used for security names, classifications, issuer links, and optional holding focus text. It is keyed by the same instrument identifier used in holdings, prices, and instrument breakdowns.

{
  "security_data": {
    "NVDA": {
      "name": "NVIDIA",
      "instrument_type": "Equity",
      "sector": "Technology",
      "asset_class": "Equity",
      "country": "United States",
      "focus_text": "<ul><li>NVIDIA was a key contributor over the period.</li></ul>",
      "website_url": "https://www.nvidia.com",
      "exchange": "US",
      "currency": "USD"
    }
  }
}

Use focus_text to provide custom HTML for a holding focus panel. If omitted, the report uses description when available.

For ETFs or funds, lookthrough can be supplied:

{
  "security_data": {
    "XT": {
      "name": "iShares Future Exponential Technologies ETF",
      "instrument_type": "ETF",
      "lookthrough": {
        "NVDA": 0.038,
        "MSFT": 0.042
      },
      "currency": "USD"
    }
  }
}

portfolio_info

portfolio_info identifies the portfolio being reported and connects it to the manager records. Use portfolio_currency for the portfolio's own currency, and report_info.base_currency for the base currency used in the report output.

{
  "inception_date": "2023-01-01",
  "wealth_manager": "toby_stetson",
  "portfolio_currency": "USD",
  "benchmark": "MSCI World Index",
  "benchmark_identifier": "MIWO00000PUS",
  "company": "PWM"
}

wealth_manager should match a unique_name in manager_info.managers. benchmark and benchmark_identifier are optional but should be supplied when the performance pages compare the portfolio to a benchmark.

manager_info

manager_info.contact_details is used on the back page. The managers list is also used by expert commentary and any page that needs a manager display name, job title, email, or profile reference.

{
  "contact_details": {
    "email": "toby.stetson@pwm.com",
    "website": "https://www.pwm.com"
  },
  "managers": [
    {
      "first_name": "Toby",
      "last_name": "Stetson",
      "full_name": "Toby Stetson",
      "email": "toby.stetson@pwm.com",
      "job_title": "Wealth Manager",
      "unique_name": "toby_stetson"
    }
  ]
}

Mandate Reports

Mandate-specific pages use data.mandate_report and must also be requested in report_page_config.

[
  { "page_type": "Mandate Positions Page", "max_rows_per_page": 22 },
  { "page_type": "Mandate Fees Page", "fee_display_mode": "percentage" },
  { "page_type": "Mandate Assumptions Page" }
]

Use the positions page for holdings tables, the fees page for management and performance fee waterfalls, and the assumptions page for appendix-style assumption sections.

{
  "mandate_report": {
    "asset_class_order": ["Equities", "Fixed Income", "Money Market"],
    "position_column_order": [
      "instrument",
      "isin_ticker",
      "market_value_base",
      "weight",
      "return_ytd"
    ],
    "positions": [
      {
        "instrument": "NVIDIA",
        "asset_class": "Equities",
        "isin_ticker": "NVDA",
        "currency": "USD",
        "market_value_base": 105000.0,
        "weight": 0.052,
        "return_ytd": 0.081,
        "return_1m": 0.012
      }
    ]
  }
}

Position fields are optional. The report only shows position columns that have values. position_column_order controls display order for active columns, while asset_class_order or group_order controls the order of position groups. split_positions on the page config allows long groups to be split across pages; otherwise a group is kept together when possible.

Mandate fees require mandate_report.fees. Mandate assumptions require mandate_report.assumptions. Base money fields use report_info.base_currency; reference money fields use report_info.reference_currency when supplied. Return fields are decimal fractions:

Field Meaning
return_ytd Year-to-date return.
return_1m One-month return.
return_3m Three-month return.
return_6m Six-month return.
return_12m Twelve-month return.

Styling

Use report_info.style to override fonts, semantic color roles, and chart item colors for a single request. The snippet below belongs inside report_info.

{
  "style": {
    "typography": {
      "font": "Inter"
    },
    "colors": {
      "text_primary": "#112130",
      "surface_panel": "#F7F6F1",
      "accent_primary": "#8FB532",
      "accent_secondary": "#016B81",
      "status_negative": "#E37337"
    },
    "chart_colors": {
      "donut": {
        "item_1": "#016B81",
        "item_2": "#8FB532",
        "item_3": "#E37337"
      },
      "horizontal_bar": {
        "item_1": "#8FB532"
      }
    }
  }
}

Use semantic color role names such as text_primary, surface_panel, accent_primary, accent_secondary, and status_negative. Hex values may include alpha, for example #11213044. chart_colors is keyed by graph type, then by item keys such as item_1, item_2, and item_3.

The selected font must be available to the renderer, either installed in the runtime or supplied by a theme pack. Built-in themes and assets remain the fallback when no custom style is supplied.

Theme Packs

Customers can upload a tenant-scoped theme pack when fonts, logos, icons, cover images, and default colors should be reused across many reports. A theme pack is a zip file with theme-pack.json at the root. The manifest id must match the theme_pack_id used in the upload URL.

The browser upload page is available at /theme-packs/manage. The page sends the same tenant headers as the API endpoints when it lists, uploads, or deletes packs.

Example zip layout:

theme-pack.json
fonts/ClientSans.ttf
logos/brand-logo.svg
logos/brand-icon.svg
logos/report-logo.svg
images/cover.jpg
images/performance.jpg
images/portfolio-analysis-divider.jpg
images/company-visual.jpg
images/generic-company-1.jpg
icons/focus.svg
icons/link.svg
logos/instrument-logo.svg
experts/mark-edwards.jpg
public/images/performance-visual.png

Example theme-pack.json:

{
  "id": "client-brand",
  "name": "Client Brand",
  "description": "Client fonts, colors, logos, and cover imagery.",
  "style": {
    "typography": {
      "font": "Client Sans"
    },
    "colors": {
      "text_primary": "#1B1B1B",
      "surface_panel": "#F8F7F2",
      "accent_primary": "#005EB8",
      "accent_secondary": "#6A4C93",
      "status_negative": "#C43E2F"
    }
  },
  "asset_slots": {
    "brand_logo": "logos/brand-logo.svg",
    "brand_icon": "logos/brand-icon.svg",
    "report_icon": "logos/report-logo.svg",
    "front_visual": "images/cover.jpg",
    "visuals": {
      "performance-visual": "images/performance.jpg",
      "portfolio-analysis-divider-visual": "images/portfolio-analysis-divider.jpg"
    },
    "icons": {
      "in-focus-icon": "icons/focus.svg",
      "hyperlink_icon": "icons/link.svg"
    },
    "logos": {
      "logos_nvda": "logos/instrument-logo.svg"
    },
    "company_visuals": {
      "visual_nvda": "images/company-visual.jpg",
      "generic-company-1": "images/generic-company-1.jpg"
    },
    "expert_pictures": {
      "mark_edwards": "experts/mark-edwards.jpg"
    }
  },
  "font_paths": ["fonts"]
}

Use asset_slots for normal branding and generated asset families. asset_overrides is still available for advanced path-level replacements, but it should not be needed for common logos, icons, or cover images.

For generated asset families, use extensionless semantic keys such as performance-visual, in-focus-icon, logos_nvda, or mark_edwards. The uploaded theme-pack files can use any supported image extension, including .jpg, .jpeg, .png, .svg, or .webp.

For complete image coverage, theme packs give you two complementary approaches:

  1. Use semantic asset_slots when you know the asset's role.
  2. Use path-based replacements when you need to shadow a generated web path directly.

Within asset_slots, choose the slot family that matches the generated asset:

Slot family Use it for
Top-level slots such as brand_logo, brand_icon, report_icon, front_visual, back_visual, front_logo, front_icon, back_logo, and back_icon Common report-wide branding and front/back-page imagery.
asset_slots.visuals Generated report visuals such as performance-visual, scenario-page-visual, breakdown-asset-class-visual, and divider visuals. A semantic key maps across Water, Architecture, and slotted visual themes.
asset_slots.icons Generated UI and breakdown icons such as in-focus-icon, hyperlink_icon, sector icons, and country icons.
asset_slots.logos Generated instrument/security logos such as logos_nvda.
asset_slots.company_visuals Generated company visuals such as visual_nvda, plus generic placeholders such as generic-company-1 or visual_generic_company_1.
asset_slots.expert_pictures Expert profile pictures keyed by generated expert picture name.

Path-based replacements are escape hatches:

Path-based option Use it for
public/ overlay files Drop-in files that satisfy generated web paths during PDF rendering. For image files, the resolver also checks same-stem alternatives, so public/images/performance-visual.jpeg can satisfy /images/performance-visual.png.
asset_overrides Explicit manifest mappings from generated Claros asset paths to pack-relative files, for example "/images/front-page-visual.png": "images/cover.jpg". Prefer semantic slots when a slot exists.

A tenant can upload multiple theme packs. Each pack has its own id, and each report chooses the pack to use with report_info.theme_pack_id.

Upload or replace a pack:

Invoke-RestMethod `
  -Uri http://127.0.0.1:8001/theme-packs/client-brand `
  -Method Put `
  -Headers @{
    "x-tenant-id" = "tenant-123"
    "x-api-key" = "<tenant-api-key>"
  } `
  -Form @{
    package = Get-Item .\client-brand.zip
  }

Use the pack in a report request:

{
  "report_info": {
    "theme_pack_id": "client-brand"
  }
}

Theme pack styles are defaults. Any report_info.style values sent in the report request override the uploaded pack style for that one report.

Common Mistakes

Mistake Fix
Sending percentages as 5 instead of 0.05. Use decimal fractions for returns and weights.
Requesting a page without its data. Check report_page_config and include the corresponding data block.
Manager IDs do not match. Ensure portfolio_info.wealth_manager and commentary manager values match manager_info.managers[].unique_name.
Missing security_data for a holding. Add security metadata for every instrument shown in holdings or breakdown pages.
Dates are not ISO strings. Use YYYY-MM-DD.
A theme-pack upload fails because an asset is missing. Make sure every path referenced by asset_slots, asset_overrides, and font_paths exists inside the zip.
A theme or icon asset is missing at render time. Use a built-in visual_theme, a semantic theme-pack slot, or a matching public/ overlay file.
theme_pack_id works in upload but not in a report. Confirm the manifest id, upload URL id, and report_info.theme_pack_id are identical lowercase ids.
  1. Start with front, performance overview, portfolio analysis, and back pages.
  2. Send a small payload with two or three instruments.
  3. Validate against /docs or /openapi.json.
  4. Add more time series history and holdings.
  5. Add optional pages such as expert commentary, proposed changes, scenarios, and mandate pages.
  6. Add report_info.style or report_info.theme_pack_id after the base payload validates.
  7. Generate /portfolio-report first to inspect structured output.
  8. Generate /portfolio-report/pdf once the JSON output looks right.