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:
- Use semantic
asset_slotswhen you know the asset's role. - 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. |
Recommended Integration Flow
- Start with front, performance overview, portfolio analysis, and back pages.
- Send a small payload with two or three instruments.
- Validate against
/docsor/openapi.json. - Add more time series history and holdings.
- Add optional pages such as expert commentary, proposed changes, scenarios, and mandate pages.
- Add
report_info.styleorreport_info.theme_pack_idafter the base payload validates. - Generate
/portfolio-reportfirst to inspect structured output. - Generate
/portfolio-report/pdfonce the JSON output looks right.