Get started
- 1Activate BMarker Pro.
The report endpoint is available to workspaces with an active Pro plan.
- 2Create an API key.
Open Settings → Report API keys, name the key, and copy its secret.
- 3Send the key with each request.
Store it with your application secrets and use the Bearer header below.
Authorization: Bearer bmk_live_your_keyRequest and output examples
Choose an output type to update the request and preview its response. Each downloadable artifact is generated from this request through the same report pipeline used by the API.
{
"analysis": {
"analysisWindow": null,
"balanceNormalization": "enabled",
"regressionWindow": null,
"rollingWindow": 126
},
"branding": {
"accentColor": "#0c6748",
"logoDataUrl": null,
"name": "Example Capital",
"showBmarker": false,
"websiteUrl": "https://example.com"
},
"format": "html",
"portfolio": {
"benchmarkSymbol": "SPY",
"currency": "USD",
"name": "Long-term portfolio"
},
"pricing": {
"mode": "completed-close"
},
"presentation": {
"balancePrivacy": true,
"shareTransactionNotes": false,
"showHoldings": true,
"showTrades": true
},
"themeOverrides": {
"light": {
"colors": {
"accent": "#147a5c",
"background": "#f7f8f6",
"border": "#d5dcda",
"muted": "#60615f",
"negative": "#c84634",
"positive": "#147a5c",
"surface": "#ffffff",
"text": "#171717"
},
"font": "modern",
"radius": "soft"
}
},
"theme": "light",
"title": "Portfolio performance report",
"transactions": [
{
"date": "2026-01-05",
"fees": "1",
"notes": "Opened the position",
"price": "267.26",
"quantity": "10",
"symbol": "AAPL",
"type": "BUY"
},
{
"date": "2026-03-02",
"fees": "0.5",
"notes": "Trimmed the position",
"price": "264.72",
"quantity": "2",
"symbol": "AAPL",
"type": "SELL"
}
]
}curl --request POST https://bmarker.com/api/v1/reports \
--header "Authorization: Bearer $BMARKER_API_KEY" \
--header "Content-Type: application/json" \
--data @report.json \
--output portfolio-report.htmlChange format and the output filename to create PDF, XML, or JSON.
Request fields
Send UTF-8 JSON with Content-Type: application/json. Prices, quantities, and fees accept JSON numbers or decimal strings. Supply transactions as either structured JSON rows or CSV text inside the JSON request.
Top-level fields
| Field | Required | Description |
|---|---|---|
format | Yes | html, pdf, xml, or json. |
portfolio | Yes | The portfolio name, currency, and benchmark ticker. |
transactions | One input required | An array containing one or more BUY or SELL records. |
transactionsCsv | One input required | CSV text with date, type, symbol, quantity, and price headers. fees and notes are optional. Use this instead of transactions. |
analysis | No | Analysis, regression, rolling, and balance-normalization settings. |
pricing | No | Controls whether the report includes live or completed-close prices. |
presentation | No | Balance privacy, holdings, trades, and transaction-note settings. |
title | No | Up to 140 characters. Defaults to “Portfolio performance report.” |
branding | No | A display name, accent color, and website link. |
theme | No | light or dark. Defaults to light and sets the default HTML and PDF theme. |
themeOverrides | No | Optional light and dark color, font, and corner-radius overrides for matching an embedding site. |
CSV transactions
Put RFC 4180-compatible CSV text in transactionsCsv. Header names are case-insensitive, quoted values may contain commas or line breaks, blank fees default to zero, and blank notes become null. All other report fields stay the same, so CSV input supports every analysis, presentation, branding, and output option available to structured transaction rows.
date,type,symbol,quantity,price,fees,notes
2026-01-05,BUY,AAPL,10,267.26,1,Opened the position
2026-03-02,SELL,AAPL,2,264.72,0.5,Trimmed the positionPortfolio and analysis
| Field | Accepted values |
|---|---|
portfolio.name | 1–100 characters. |
portfolio.currency | A three-letter code. Defaults to USD. |
portfolio.benchmarkSymbol | A ticker up to 20 characters. Defaults to SPY. |
analysis.analysisWindow | null, 21, 63, 126, or 252. Null uses all available history. |
analysis.regressionWindow | null, 21, 63, 126, or 252. Null uses the full analysis span. |
analysis.rollingWindow | 21, 63, 126, or 252. Defaults to 126. |
analysis.balanceNormalization | enabled or disabled. Defaults to enabled. |
Pricing
Set pricing.mode to live or completed-close. The default is live.
| Mode | Behavior |
|---|---|
live | Uses completed daily closes and may add current-session prices when live pricing is enabled for the workspace. Extended-hours prices follow the workspace setting. |
completed-close | Uses the most recent fully completed market session. Intraday and extended-hours price requests are disabled, and the complete report is generated from completed closes. |
Presentation
These fields are passed to the shared report component used by public share pages. The defaults match a new share page.
| Field | Default | Use |
|---|---|---|
presentation.balancePrivacy | true | Shows return, benchmark, excess-return, and drawdown cards. Set to false to show current value, net invested, gain/loss, and portfolio return. |
presentation.showHoldings | false | Adds current holdings and holdings-over-time sections. |
presentation.showTrades | false | Adds the trade-activity section. |
presentation.shareTransactionNotes | false | Includes transaction notes when trade activity is shown. |
Theme
Set the root theme field to light or dark to choose the default style for HTML and PDF reports. A self-contained HTML report can override that choice when embedded by adding ?theme=light or ?theme=dark to its URL.
Use themeOverrides when a report is embedded in another site and its light or dark palette should match the host. Each mode may provide any subset ofcolors ( accent, background, border, muted, negative, positive, surface, and text), plus font ( system, modern, or editorial) and radius (sharp, soft, or round). Unspecified values keep the selected built-in theme.
{
"themeOverrides": {
"light": {
"colors": {
"accent": "#147a5c",
"background": "#f7f8f6",
"border": "#d5dcda",
"muted": "#60615f",
"negative": "#c84634",
"positive": "#147a5c",
"surface": "#ffffff",
"text": "#171717"
},
"font": "modern",
"radius": "soft"
}
}
}Branding
Branding appears in the HTML and PDF header and is included in the XML and JSON report envelope. When branding is omitted, the report uses “Portfolio report” and BMarker green.
| Field | Default | Use |
|---|---|---|
branding.name | Portfolio report | A 1–100 character brand name shown in the report header. |
branding.websiteUrl | null | An HTTP or HTTPS link for the brand name and logo. Credentials in the URL are rejected. |
branding.accentColor | #0c6748 | A six-digit hex color used for report accents and positive values. |
branding.logoDataUrl | null | A base64 PNG, JPEG, or WebP data URL up to 350,000 characters. The header uses the first letter of the brand name when this is null. |
branding.showBmarker | false | Set to true to add the “Powered by BMarker” footer. |
A logo value begins with a media prefix such asdata:image/png;base64,, followed by the image's base64 data. PNG, JPEG, and WebP signatures are checked before the report is generated.
Transaction fields
| Field | Required | Description |
|---|---|---|
date | Yes | A real calendar date in YYYY-MM-DD form. |
type | Yes | BUY or SELL. |
symbol | Yes | A ticker up to 20 characters. |
quantity | Yes | A decimal value greater than zero. |
price | Yes | The execution price, greater than zero, or open or closeto use that ticker's unadjusted daily price on the transaction date. |
fees | No | A decimal value of zero or greater. Defaults to zero. |
notes | No | Up to 500 characters, or null. |
Report data
BMarker loads completed daily closes, applies stock splits, repairs missing close ranges, and runs the same transaction-aware performance engine used by the app. Live-mode reports may also include the current market session. QuantStats supplies the deeper risk and benchmark-relative analytics.
| Field | Contents |
|---|---|
performance | Summary, timeframe returns, holdings, daily series, cumulative growth, excess return, alpha, balance, and holdings history. |
analytics | Calculation parameters, Sharpe, Sortino, CAGR, volatility, drawdowns, capture, correlation, calendar returns, distributions, and rolling series. |
intraday | The live price snapshot and current portfolio valuation, including market session, prior-close growth, quote freshness, and fallback symbols. Its enabled field is false for completed-close reports. |
pricing | The requested pricing mode used to generate the report. |
effectiveWindows | The analysis and regression windows supported by the available observations. |
excludedSymbols | Symbols that could not be analyzed, with reasons and affected transaction row IDs. |
transactions | The normalized transaction rows used by the analysis. |
theme | The requested default theme: light or dark. |
Historical returns and risk metrics have a completed-close timestamp inperformance.summary.asOf. Current valuation comes fromintraday.valuation, with quote details in intraday.snapshot. HTML and PDF show that value and its prior-close growth when pricing.mode is live, intraday.enabled is true, andpresentation.balancePrivacy is false.
Formats
A successful response has status 200 and a filename in theContent-Disposition header, such aslong-term-portfolio-performance.pdf.
| Format | Content-Type | Contents |
|---|---|---|
html | text/html | A self-contained share-page report with embedded styles. |
pdf | application/pdf | A print rendering of the same HTML report. |
xml | application/xml | The complete report envelope as XML. |
json | application/json | The complete report envelope as JSON. |
Errors
Error responses use JSON. Every response has an X-Request-ID header; error bodies repeat it as requestId for support requests.
{
"error": {
"code": "invalid_request",
"message": "transactions.0.date: Use a real calendar date."
},
"requestId": "87a20465-1b42-4d85-9305-55467b783064"
}| Status | Meaning |
|---|---|
400 | The JSON or one of its fields is invalid. |
401 | The API key is missing, malformed, deleted, or unknown. |
403 | The key lacks report permission or the workspace needs an active Pro plan. |
413 | The request body exceeds 2 MiB. |
415 | The Content-Type header must be application/json. |
422 | The transactions could not produce an analyzable portfolio report. The body includes the performance issues. |
429 | The market-data provider is rate limited. |
503 | Market data, analytics, or the report renderer is temporarily unavailable. |
504 | Analytics or PDF rendering timed out. |
API keys
The full key appears once when it is created. Give each integration its own key so its access can be managed independently.
Roll creates a replacement while the current key stays active. Update the integration, confirm a successful request, and delete the previous key. Deletion takes effect immediately.
Manage API keys →Limits
- At least one transaction per report.
- Up to 250 unique transaction symbols per API report.
- 2 MiB maximum request body.
- Up to 12 decimal places in decimal-string inputs.
JSON arrays and embedded CSV share the 2 MiB request allowance. For larger reports, contact contact@bmarker.com.