Configuration API
Everything a report needs before it can be rendered: identity, reporting period, strategies, fees, disclosure text, and branding. One canonical endpoint (/config) covers all of it; the narrower per-section endpoints below remain available for clients that want a smaller payload.
All paths are relative to https://api.renesis.fi/lp-report-studio.
GET /api/funds
List the reports the caller can read. Admins see everything; other callers see reports they own or are listed on.Response
These are raw storage records, not the normalized shape. The display name is
report_name here, but identity.name in /config. Rows come back newest-updated first, capped at 200.POST /api/funds
Create a report.Request body
string
required
The Renesis portfolio that supplies NAV, balances, and transactions. Also accepted as
identity.portfolio_id.string
Display name. Also accepted as
identity.name.owner_email is always taken from the credential, never from the body, so a caller cannot create a report owned by someone else.
Response
201 with the freshly created configuration, in the same shape GET /config returns. A missing portfolio_id returns 400; a database rejection also returns 400 with the underlying message.
GET /api/funds/{report_id}/config
Read the unified configuration.The unified configuration shape
Empty blocks are omitted. Only
identity, strategies, layout, and sections are guaranteed present; a fresh report returns a much smaller object than the example above. Code defensively against missing keys rather than assuming the full shape.adjustments, recap, pdf_visibility, statement_captions, statement_strategies, and layout_grid. They drive statement layout and manual adjustments. Unless you are replicating the Report Workspace editor, read them and write them back unchanged.
Fields that no longer exist
These were removed from the configuration surface. They are neither returned nor writable, and sending them does nothing:fees.custodian_pct/custodian_fee,fees.supervisory_pct/supervisory_fee. Collected historically but never read by any computation.fees.hurdle_rate_pct. Only the LP Portal share accounting consumes it, and it reads the stored column directly.- The whole
visibilityblock (show_fee_breakdown,show_fund_nav_chart, and the rest). Those toggles gate LP Portal responses, not the statement, and are managed there. fund.size,fund.number_of_holdings,fund.unit_pricing,fund.unit_price.
Field notes
reporting.currencydrives FX. Rates are pulled live and resolved atperiod_end. OnlyEUR,CHF,JPY,BTC, andETHhave FX coverage, plusUSDwhich is the base. Any other code, includingGBP, silently resolves to a rate of 1.0, producing a statement that prints the wrong currency symbol on unconverted USD values. Validate this field on your side.reporting.aum_basis:current_eomaccrues month K’s management fee on month K’s closing AUM.prior_eomaccrues it on month K-1’s closing AUM, the convention most institutional mandates specify. The two produce different fee numbers.reporting.crypto_decimals: native precision for crypto pockets. Values below2are clamped to2; there is no upper bound, though the editor only offers 2, 4, and 6.strategies[].mgmt_pct/perf_pct: omitting the key (ornull) inherits the fund-level default.0is an explicit zero. The two are not the same, and0does not suppress the strategy’s fee block: the statement still renders Management, Performance, and Total rows, showing zeros.strategies[].denominationis the strategy’s native currency. It drives whether NAV and fees are displayed in native units (BTC, ETH) or in the reporting currency. Any unrecognized code is treated as non-fiat.strategies[].share_token_contractspins a strategy to its on-chain vault by contract address, so renaming the strategy or holding a symbol-less token does not break the match.strategies[].fx_to_reportingis legacy and ignored; live FX is used.strategies[].position_group_id,position_group_nav_usd,strategy_typeandmandateappear on strategies backed by a position group. The strategy’s NAV, returns and allocation slice then derive from the group’s legs rather than from a single share token. Read, modify in place, and send back what you received: the array replaces wholesale, so dropping these fields detaches the strategy from its group.strategy_mode:single,vault, ormanual. Any other string is treated as not-single without error.layout:statement(default),compact(single page), orinstitutional(four pages). An unrecognized value silently falls back tostatement.branding.logo_urlis the routable URL to use in an<img>. Do not sendbranding.logo_path, see the read-only notes below.
PUT /api/funds/{report_id}/config
Partial update. Only the keys present in the body are written; everything else is left alone. Nested objects (fund, contact, fees, content, reporting) deep-merge, so a one-field patch does not clobber its siblings.
GET.
strategies and sharing.email_addresses are arrays and replace wholesale. To remove one entry, send the array without it.
Validation and silent failures
This is the part most likely to burn an integration. The write path is permissive by design and the frontend editor, not the API, enforces most of what looks like a rule. Unknown keys are silently dropped, not rejected. A misspelled field, or one of the removed fields, writes nothing and returns200 with the unchanged config. Do not rely on a 400 to catch typos; diff the echoed response against what you sent.
The single exception is sharing.invite_codes and sharing.redeemed_invite_codes: sending either returns 400 with unknown lp_reports columns in patch.
Long text is truncated silently. Prose is clamped at the write boundary and the request still returns 200:
Check the echoed config if you need to detect truncation. Note that text written through the legacy
POST /branding is not clamped, and over-budget values stored that way will fail every later render.
Dates are not validated. period_start and period_end are accepted in any format and any order. A malformed or reversed pair does not error: period.months comes back empty, and monthly[], fees_summary.by_month_*, and nav_series are empty while recap is null. Headline NAV and FX still resolve at period_end.
Renaming strategies. Editing names in place, keeping the array the same length, carries contract pins across and remaps anything keyed by strategy name (adjustments.per_strategy, statement_captions.by_strategy, statement_strategies). Removing and re-adding a strategy in the same request bypasses that remap and orphans those references silently. Rename in place.
Read-only and effectively-managed blocks
Accepted aliases
Legacy names are normalized on write. The response always uses the canonical name.
That is the complete list. There are no aliases for the removed custodian and supervisory fields.
DELETE /api/funds/{report_id}
Delete a report. Cascades to its investors and capital events.Strategy auto-detection
For funds holding on-chain vault positions, the studio can propose the strategy list instead of having you type it.GET /api/funds/{report_id}/detect-vaults
Preview only, writes nothing. Walks every account linked to the report’s portfolio and returns the vault share tokens it finds.Only vaults whose reconciliation protocol is Enzyme are proposed today. Other share tokens stay plain holdings and will not appear here.
inception, benchmark, and fee_terms are always empty on detection; fill them yourself.
POST /api/funds/{report_id}/strategies/from-vaults
Replacesstrategies with the detected list and sets strategy_mode to vault. Detected strategies come back with mgmt_pct and perf_pct at 0 and fx_to_reporting at 1.0, so set the real fee terms through PUT /config before rendering. Fiat-denominated strategies are ordered first.
Body: none.
detect-vaults to preview, confirm with the user, strategies/from-vaults to apply, then PUT /config to set fees and benchmarks.
Logo
POST /api/funds/{report_id}/logo
file
required
multipart/form-data file part. PNG or JPEG only.400 with no file, empty filename, unsupported format, or not a valid image. A 500 with image validation unavailable means the server-side image library is missing; retry or report it.
GET /assets/logo/{report_id}
Returns the uploaded image. Read access is sufficient.404 when no logo has been uploaded.
Templates
A template is a reusable bundle of branding, layout, and fee defaults that is not tied to one report. Templates are personal to the caller; there is no shared or organization-level scope, and theadmin role does not bypass ownership here.
POST /api/templates
string
default:"Untitled template"
Display name for the template.
object
A configuration in the canonical shape. Per-report fields are stripped server-side.
string
Snapshot an existing report’s configuration instead of supplying a payload. The caller must be able to read that report.
400; sending both is not an error, payload wins and source_report_id is ignored. Returns 201 with the created template.
PUT with neither name nor payload returns 400 with nothing to update.
POST /api/templates/{template_id}/apply
string
required
The target report.
applied is the subset that actually got written, useful for showing the user what changed.
Narrower alternatives
These predate/config and stay supported. Use them when you want a smaller payload; use /config for anything new.
PUT /api/funds/{report_id}/fees
Body: any subset ofmaintenance_fee, performance_fee (whole-number percentages), and hwm (boolean, the high-water-mark toggle). custodian_fee and supervisory_fee are silently ignored. Values are coerced with a float conversion, so a non-numeric value returns 500, not 400. Returns { ok, fees_expenses }.
GET /api/funds/{report_id}/branding
Returns the full rendering-side configuration document, withlogo_url attached when a logo exists. Read access is sufficient.
POST /api/funds/{report_id}/branding
Body: any subset ofstrategies, strategy_mode, vault_decomposition, scope, reporting_ccy, fx_source, share_class, aum_basis, period, period_start, period_end, plus text_blocks. Keys outside that list are ignored. text_blocks deep-merges; every other key replaces. Returns { ok, branding }.

