Skip to main content

Rendering

Three endpoints: render and store a statement, render a throwaway preview with unsaved edits applied, and download a stored render.
The renderer emits a self-contained HTML document. The logo is embedded as base64 and there are no external requests, so the browser’s print-to-PDF produces the finished statement. Ask for format: "pdf" and the server does that print step for you, returning a PDF instead. See PDF output.
Rendering requires write access, so the caller must be the report owner (or an admin). Recipients with read access can still download an existing render.

POST /api/funds/{report_id}/render

Render the report and store the output.

Request body

All fields optional. Anything omitted falls back to the period saved on the report’s configuration.
string
Display label shown in the header, for example Q1 2026.
string
YYYY-MM-DD. Drives the chart x-axis and the fee accrual window.
string
YYYY-MM-DD. The as-of stamp and the FX snapshot date.
string
default:"html"
html or pdf. See PDF output. Any other value returns 400.

Response

Rendering is synchronous and typically takes one to three seconds. Fetch download_url to get the document. The report is always rendered at portfolio scope. If a manager runs several LP mandates, each mandate gets its own portfolio and its own report; there is no per-investor parameter here. For per-investor figures, see the LP Portal.

Errors

/render has no 422 path. Every render-time validation failure surfaces here as a 500 with a generic message. To find out what actually went wrong, replay the same request against /render-preview with an empty config_overrides, which returns the real message with a 422.
The failures that behave this way: 404 is returned for an unknown report_id.

Layout warnings

compact_overflow and recap_overflow are advisory. When set, the document was still produced, but part of it will clip when printed. Show a banner rather than blocking. compact_overflow is only ever computed for reports whose layout is compact. On the default statement layout and on institutional it is always null, so most integrations will never see it populated.
The fix is switching that report to the institutional layout, which gives adjustments their own page. recap_overflow fires when the recap section exceeds its single-page budget and carries the same fields (estimated_height_px, budget_height_px, overflow_px, n_strategies, message). It is null when the recap fits, is disabled, has no data, or spans multiple print pages by design.

POST /api/funds/{report_id}/render-preview

Render with unsaved configuration applied, and return the document inline. Nothing is written: no database changes, no stored file. This is what drives live editing, where the client debounces the edit form and posts the dirty patch.

Request body

All optional.
string
Display label, period_start and period_end behave as on /render above.
object
A patch in the same shape PUT /config accepts, applied in memory before rendering.

Responses

422 is the expected outcome while a user is mid-typing. Keep the last good preview on screen and show the message rather than swapping in a broken frame. Because this endpoint reports validation failures properly, it is also the right tool for diagnosing a /render that returns 500. Warnings ride on response headers so they can be read without parsing the document:
  • X-Recap-Overflow-Px: pixels by which the recap page exceeds its budget. Absent when the recap fits, is disabled, or has no data.

GET /output/{filename}

Download a stored render. Returns the HTML document as an attachment.
Filenames follow {report_id}_fund_{random}.html. Access is checked against the report id embedded in the filename, so learning a URL is not enough to read another fund’s statement. Read access is sufficient, which is what lets a recipient download a statement they cannot re-render.

PDF output

Server-side PDF is rolling out. If format: "pdf" answers 503 on your environment, it has not reached it yet; the client-side path below works everywhere in the meantime.
Two ways to get a PDF, and they produce the same document.

Ask the server for it

Add format: "pdf" to the /render body, or post to the dedicated route:
POST /api/funds/{report_id}/render-pdf takes the same body and returns the same response as /render; it exists so a client that only ever wants PDFs has a URL to hardcode instead of remembering a body flag. filename and download_url point at a .pdf, and GET /output/{filename} serves it as application/pdf. The server is not building a different document. It renders the same HTML and prints it with the same browser engine you would use yourself, so the PDF matches the HTML exactly. Conversions are capped and queued server-side, so a burst of PDF requests waits rather than failing. The rendered HTML prints correctly with no further work, which is useful if you are already displaying it or want to control the print settings:
Page size, margins, and page breaks are already set in the document, so no extra print options are needed.