Benchmarks
Benchmarks are the reference series lens returns are measured against — an index, a rate, or a custom composite built from weighted components. Define the benchmark once, then read its daily return series alongside performance. Reading requires the reporting:read scope; every mutation requires reporting:write.
A benchmark belongs to a client, or to the whole firm when it has none. You see the benchmarks of the clients you can read and the firm's own; you write a benchmark only with write access to its client, and a firm-wide one only as a firm admin. Benchmarks from the legacy platform are listed too, read-only until your move to v1 is done.
Data model
idstringStable identifier for the benchmark. A UUID — treat it as opaque.
client_idstring or nullThe client the benchmark belongs to, or
nullfor a firm-wide benchmark.namestringDisplay name shown in the product, at most 50 characters.
kindstringBenchmark kind:
index,custom,blended,constantorpeer_group.currency_codestring or nullISO 4217 currency the series is expressed in, when fixed.
target_kindstring or nullWhat the benchmark itself is set on:
lens,tagorasset, withtarget_id. A legacy benchmark set on an aggregated portfolio reads as the lens the portfolio became.nullwhen there is none. Read-only.target_idstring or nullIdentifier of the target, interpreted per
target_kind.componentsarray of objectThe weighted legs in force on
as_of. Each leg has:kind(asset,fixed_rate,asset_and_fixed_rate,market_indexfor a leg on a market index, orunknownfor a stored leg in no known shape),asset_id(the asset the leg follows; for amarket_indexleg the asset the index follows),market_index_id(the market index,market_indexlegs only),rate(decimal string, the fixed annual rate of a rate leg) andweight(decimal string). An id isnullwhen the leg names an asset or index you have no record of.bindingsarray of objectWhere the benchmark is used: the legacy platform's bindings of it on nodes of clients you can read. Each binding has
id,client_id,target_kind(lensfor an aggregation or the lens an aggregated portfolio became,tagfor a class,asset) andtarget_id(nullwhen the node has no v1 record; bothnullon a legacy sector node, which has no v1 resource),currency_code,rebalancing_method({"kind": "daily"},{"kind": "monthly", "day": 1}or{"kind": "yearly", "month": 6, "day": 30}),valid_from,valid_to(inclusive,nullopen) andremoved_at(deleted bindings, shown withinclude_expired=trueonly). Read-only.valid_fromISO 8601 date or nullFirst day the benchmark applies.
nullmeans no start.valid_toISO 8601 date or nullLast day the benchmark applies (inclusive).
nullmeans open-ended. A removed benchmark ends the day before its removal.removed_attimestamp or nullWhen the benchmark was removed (DELETE, or on the legacy platform).
nullwhile it stands.originstringv1for a benchmark made on the platform,legacyfor one from the legacy platform.as_ofISO 8601 dateThe day a single read or write answer shows the benchmark at. A list carries it on the page instead.
List benchmarks
Returns the benchmarks in force on as_of that you can see, oldest first, in the standard v1 list envelope. An unknown query parameter is a 400.
Query parameters
as_ofISO 8601 dateThe day to read at: a benchmark is listed when
valid_fromandvalid_tocover it, with the components in force that day. Defaults to today.include_expiredbooleanAlso list ended and removed benchmarks, and deleted bindings. Defaults to
false.client_idstringOnly this client's benchmarks.
limitintegerPage size. Defaults to 100, capped at 1 000.
cursorstringOpaque cursor from the previous page's
next_cursor. Omit for the first page.
curl -G https://api.aleta.ai/v1/benchmarks \
-H "Authorization: Bearer {access_token}" \
-d limit=1
{
"data": [
{
"id": "019f918c-aa09-7430-849d-4020662552ab",
"client_id": "019dff81-0000-70de-bd7a-80bf3662b001",
"name": "Global 60/40",
"kind": "custom",
"currency_code": "DKK",
"target_kind": null,
"target_id": null,
"components": [
{
"kind": "asset",
"asset_id": "019dff81-7aaa-70de-bd7a-80bf3662baff",
"market_index_id": null,
"rate": null,
"weight": "0.6"
},
{
"kind": "fixed_rate",
"asset_id": null,
"market_index_id": null,
"rate": "0.02",
"weight": "0.4"
}
],
"bindings": [
{
"id": "019f9200-1111-7430-849d-4020662552ab",
"client_id": "019dff81-0000-70de-bd7a-80bf3662b001",
"target_kind": "lens",
"target_id": "019f9201-2222-7430-849d-4020662552ab",
"currency_code": "DKK",
"rebalancing_method": { "kind": "monthly", "day": 1 },
"valid_from": "2024-01-01",
"valid_to": null,
"removed_at": null
}
],
"valid_from": "2023-11-14",
"valid_to": null,
"removed_at": null,
"origin": "legacy"
}
],
"next_cursor": "MjAyNi0wMS0wMSAwMDowMDowMCswMHwwMTlmOTE4Yy1hYTA5LTc0MzAtODQ5ZC00MDIwNjYyNTUyYWI",
"limit": 1,
"as_of": "2026-10-18"
}
Create a benchmark
Creates a benchmark. name and components are required. Returns 201 with the created benchmark. Takes ?dry_run=true and Idempotency-Key; the create is logged as a change.
The legacy platform's rules apply, and every rule the body breaks is named in one 400 problem: at least one component, the weights sum to 1 (within 0.0001), at most one fixed_rate component, and a name of at most 50 characters. Each weight must be above 0, each rate within ±10, and each asset_id an asset you have. An unknown body field is a 400.
Body parameters
namestringDisplay name, at most 50 characters. Required.
componentsarray of objectWeighted legs, in the shape of the data model:
kind(asset,fixed_rate,asset_and_fixed_rateormarket_index),asset_id(asset legs),market_index_id(market_indexlegs),rate(rate legs) andweight. Decimals may be strings or numbers. Amarket_indexleg names an index from market indices (one Aleta turned on for you, or your own) and is computed on the asset the index follows;asset_idmay be left out. An index you don't have, or one that follows none of your assets, is named in the400. Required, at least one.client_idstringThe client the benchmark belongs to; needs write access to it (
403without it,400for a client you cannot read). Left out, the benchmark is firm-wide, which only a firm admin may create.kindstringBenchmark kind, one of the kinds in the data model. Defaults to
custom.currency_codestringFix the series currency, a three-letter code. Optional.
curl -X POST https://api.aleta.ai/v1/benchmarks \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: {unique_key}" \
-d '{
"name": "BM",
"currency_code": "DKK",
"components": [
{ "kind": "asset", "asset_id": "019dff81-7aaa-70de-bd7a-80bf3662baff", "weight": "0.8" },
{ "kind": "fixed_rate", "rate": "0.02", "weight": "0.2" }
]
}'
{
"id": "019f918c-aa09-7430-849d-4020662552ab",
"client_id": null,
"name": "BM",
"kind": "custom",
"currency_code": "DKK",
"target_kind": null,
"target_id": null,
"components": [
{
"kind": "asset",
"asset_id": "019dff81-7aaa-70de-bd7a-80bf3662baff",
"market_index_id": null,
"rate": null,
"weight": "0.8"
},
{
"kind": "fixed_rate",
"asset_id": null,
"market_index_id": null,
"rate": "0.02",
"weight": "0.2"
}
],
"bindings": [],
"valid_from": null,
"valid_to": null,
"removed_at": null,
"origin": "v1",
"as_of": "2026-10-18"
}
Retrieve a benchmark
Fetch a single benchmark by id, with the components in force on as_of and the date it was read at. A removed or ended benchmark still answers. Returns a 404 problem when the id is no benchmark you can see. An unknown query parameter is a 400.
Query parameters
as_ofISO 8601 dateThe day whose components to show. Defaults to today.
include_expiredbooleanAlso show deleted bindings. Defaults to
false.
curl -G https://api.aleta.ai/v1/benchmarks/019f918c-aa09-7430-849d-4020662552ab \
-H "Authorization: Bearer {access_token}" \
-d as_of=2025-06-30
{
"id": "019f918c-aa09-7430-849d-4020662552ab",
"client_id": null,
"name": "BM",
"kind": "custom",
"currency_code": "DKK",
"target_kind": null,
"target_id": null,
"components": [
{
"kind": "fixed_rate",
"asset_id": null,
"market_index_id": null,
"rate": "0.02",
"weight": "1"
}
],
"bindings": [],
"valid_from": null,
"valid_to": null,
"removed_at": null,
"origin": "v1",
"as_of": "2025-06-30"
}
Update a benchmark
Partially updates a benchmark and returns 200 with it, as read today. client_id, currency_code and valid_to can be cleared by passing null explicitly; components is replaced as a whole when present and passes the create rules. Takes ?dry_run=true and Idempotency-Key. An unknown body field is a 400.
Without effective_from, new components correct the benchmark: they hold for its whole history. With effective_from they change it from that day: the components before the day stay, the new ones hold from it, and the benchmark keeps both versions (read them with as_of). The stored returns are not recalculated: /returns keeps the figures already calculated for past days. Each update is logged as a change with reason.
The update needs write access to the benchmark's client, and to the new one when client_id moves it. A benchmark that is bound to nodes keeps its client (409 conflict), a removed benchmark cannot be changed (409 conflict), and a benchmark from the legacy platform answers 409 legacy_managed until your move to v1 is done. A day outside the benchmark's versions is a 422 effective_from_outside_versions; where your tenant has no dated components, effective_from is a 422 dated_versions_unavailable.
Body parameters
namestringNew display name, at most 50 characters.
kindstringNew benchmark kind.
client_idstring or nullNew client, or
nullto make the benchmark firm-wide.currency_codestring or nullNew series currency, or
nullto clear it.componentsarray of objectFull replacement set of legs, in the create shape.
effective_fromISO 8601 dateThe day new
componentsstart to hold. Needscomponents.valid_toISO 8601 date or nullThe benchmark's last day (inclusive): how a benchmark in use is ended.
nullreopens it.reasonstringWhy, for the change list.
curl -X PATCH https://api.aleta.ai/v1/benchmarks/019f918c-aa09-7430-849d-4020662552ab \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"components": [{ "kind": "fixed_rate", "rate": "0.03", "weight": "1" }],
"effective_from": "2026-01-01",
"reason": "new mandate"
}'
Delete a benchmark
Removes a benchmark: it gets removed_at, leaves the lists (shown with include_expired=true) and still answers by id, and its return series stays. Returns 204; a repeat on a removed benchmark is 204 too. Takes ?dry_run=true and Idempotency-Key, needs write access to the benchmark's client, and is logged as a change.
A benchmark that is still bound to a lens, tag or asset answers 409 in_use with next_action pointing to PATCH /v1/benchmarks/{id} with valid_to: end it instead. A lens's consolidation binding (market value or target allocation) names no benchmark, so it never blocks a delete. A benchmark from the legacy platform answers 409 legacy_managed until your move to v1 is done. 404 when the id is no benchmark you can see.
curl -X DELETE https://api.aleta.ai/v1/benchmarks/019f934d-fff2-736e-bd71-e319254d91be \
-H "Authorization: Bearer {access_token}"
{
"type": "https://docs.aleta.ai/developers/api/v1/errors#in_use",
"title": "Conflict",
"status": 409,
"code": "in_use",
"detail": "benchmark 019f934d-fff2-736e-bd71-e319254d91be is still bound to a lens, tag or asset; end it instead.",
"next_action": {
"next": "end_date",
"method": "PATCH",
"path": "/v1/benchmarks/019f934d-fff2-736e-bd71-e319254d91be",
"field": "valid_to"
}
}
Retrieve benchmark returns
Returns the benchmark's daily return series over a date range in the standard v1 list envelope: one row per day, rebalancing method and currency, ordered by date. A benchmark bound with several rebalancing methods has a series for each. return_mtd and return_ytd compound return_day within the month and within the year; the year is the client's accounting year (calendar year when it has none). As in the legacy platform, the month restarts on a row dated the 1st and the year on a row dated the year's first day; a series without that row compounds on across the boundary. A benchmark without a series answers 200 with no rows; an id that is no benchmark you can see is a 404. A malformed date, from_date after to_date or an unknown query parameter is a 400.
The series is read from the analytics store, so a recalculation shows after a short delay.
Query parameters
from_dateISO 8601 dateInclusive start of the range. Optional.
fromis accepted as an alias.to_dateISO 8601 dateInclusive end of the range. Defaults to
as_of.tois accepted as an alias.as_ofISO 8601 dateThe day the read is made at, shown on the page. Defaults to today.
limitintegerPage size. Defaults to 100, capped at 1 000.
cursorstringOpaque cursor from the previous page's
next_cursor. Omit for the first page.
Return fields
as_of_dateISO 8601 dateThe date of the observation.
rebalancing_methodobject or nullThe series' rebalancing method (
daily,monthlywithday,yearlywithmonthandday);nullfor the benchmark's own series.currency_codestringCurrency the return is expressed in.
return_daydecimal stringDaily return.
return_mtddecimal stringMonth-to-date return, rounded to 10 decimals.
return_ytddecimal stringReturn since the start of the accounting year, rounded to 10 decimals.
curl -G https://api.aleta.ai/v1/benchmarks/019f918c-aa09-7430-849d-4020662552ab/returns \
-H "Authorization: Bearer {access_token}" \
-d from_date=2026-01-01 \
-d to_date=2026-06-30 \
-d limit=1
{
"data": [
{
"as_of_date": "2026-01-02",
"rebalancing_method": { "kind": "monthly", "day": 1 },
"currency_code": "DKK",
"return_day": "0.0012",
"return_mtd": "0.0012",
"return_ytd": "0.0012"
}
],
"next_cursor": "MjAyNi0wMS0wMnwx",
"limit": 1,
"as_of": "2026-10-18"
}
Related
- Performance — the lens series to compare against.
- Assets — the ids
components[].asset_idnames.