View as Markdown

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

idstring

Stable identifier for the benchmark. A UUID — treat it as opaque.

client_idstring or null

The client the benchmark belongs to, or null for a firm-wide benchmark.

namestring

Display name shown in the product, at most 50 characters.

kindstring

Benchmark kind: index, custom, blended, constant or peer_group.

currency_codestring or null

ISO 4217 currency the series is expressed in, when fixed.

target_kindstring or null

What the benchmark itself is set on: lens, tag or asset, with target_id. A legacy benchmark set on an aggregated portfolio reads as the lens the portfolio became. null when there is none. Read-only.

target_idstring or null

Identifier of the target, interpreted per target_kind.

componentsarray of object

The weighted legs in force on as_of. Each leg has: kind (asset, fixed_rate, asset_and_fixed_rate, market_index for a leg on a market index, or unknown for a stored leg in no known shape), asset_id (the asset the leg follows; for a market_index leg the asset the index follows), market_index_id (the market index, market_index legs only), rate (decimal string, the fixed annual rate of a rate leg) and weight (decimal string). An id is null when the leg names an asset or index you have no record of.

bindingsarray of object

Where 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 (lens for an aggregation or the lens an aggregated portfolio became, tag for a class, asset) and target_id (null when the node has no v1 record; both null on 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, null open) and removed_at (deleted bindings, shown with include_expired=true only). Read-only.

valid_fromISO 8601 date or null

First day the benchmark applies. null means no start.

valid_toISO 8601 date or null

Last day the benchmark applies (inclusive). null means open-ended. A removed benchmark ends the day before its removal.

removed_attimestamp or null

When the benchmark was removed (DELETE, or on the legacy platform). null while it stands.

originstring

v1 for a benchmark made on the platform, legacy for one from the legacy platform.

as_ofISO 8601 date

The day a single read or write answer shows the benchmark at. A list carries it on the page instead.


GET/v1/benchmarks

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 date

The day to read at: a benchmark is listed when valid_from and valid_to cover it, with the components in force that day. Defaults to today.

include_expiredboolean

Also list ended and removed benchmarks, and deleted bindings. Defaults to false.

client_idstring

Only this client's benchmarks.

limitinteger

Page size. Defaults to 100, capped at 1 000.

cursorstring

Opaque cursor from the previous page's next_cursor. Omit for the first page.

Request
GET/v1/benchmarks
curl -G https://api.aleta.ai/v1/benchmarks \
  -H "Authorization: Bearer {access_token}" \
  -d limit=1
Response
{
  "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"
}

POST/v1/benchmarks

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

namestring

Display name, at most 50 characters. Required.

componentsarray of object

Weighted legs, in the shape of the data model: kind (asset, fixed_rate, asset_and_fixed_rate or market_index), asset_id (asset legs), market_index_id (market_index legs), rate (rate legs) and weight. Decimals may be strings or numbers. A market_index leg 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_id may be left out. An index you don't have, or one that follows none of your assets, is named in the 400. Required, at least one.

client_idstring

The client the benchmark belongs to; needs write access to it (403 without it, 400 for a client you cannot read). Left out, the benchmark is firm-wide, which only a firm admin may create.

kindstring

Benchmark kind, one of the kinds in the data model. Defaults to custom.

currency_codestring

Fix the series currency, a three-letter code. Optional.

Request
POST/v1/benchmarks
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" }
    ]
  }'
Response
{
  "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"
}

GET/v1/benchmarks/{id}

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 date

The day whose components to show. Defaults to today.

include_expiredboolean

Also show deleted bindings. Defaults to false.

Request
GET/v1/benchmarks/{id}
curl -G https://api.aleta.ai/v1/benchmarks/019f918c-aa09-7430-849d-4020662552ab \
  -H "Authorization: Bearer {access_token}" \
  -d as_of=2025-06-30
Response
{
  "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"
}

PATCH/v1/benchmarks/{id}

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

namestring

New display name, at most 50 characters.

kindstring

New benchmark kind.

client_idstring or null

New client, or null to make the benchmark firm-wide.

currency_codestring or null

New series currency, or null to clear it.

componentsarray of object

Full replacement set of legs, in the create shape.

effective_fromISO 8601 date

The day new components start to hold. Needs components.

valid_toISO 8601 date or null

The benchmark's last day (inclusive): how a benchmark in use is ended. null reopens it.

reasonstring

Why, for the change list.

Request
PATCH/v1/benchmarks/{id}
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/v1/benchmarks/{id}

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.

Request
DELETE/v1/benchmarks/{id}
curl -X DELETE https://api.aleta.ai/v1/benchmarks/019f934d-fff2-736e-bd71-e319254d91be \
  -H "Authorization: Bearer {access_token}"
Response
{
  "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"
  }
}

GET/v1/benchmarks/{id}/returns

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 date

Inclusive start of the range. Optional. from is accepted as an alias.

to_dateISO 8601 date

Inclusive end of the range. Defaults to as_of. to is accepted as an alias.

as_ofISO 8601 date

The day the read is made at, shown on the page. Defaults to today.

limitinteger

Page size. Defaults to 100, capped at 1 000.

cursorstring

Opaque cursor from the previous page's next_cursor. Omit for the first page.

Return fields

as_of_dateISO 8601 date

The date of the observation.

rebalancing_methodobject or null

The series' rebalancing method (daily, monthly with day, yearly with month and day); null for the benchmark's own series.

currency_codestring

Currency the return is expressed in.

return_daydecimal string

Daily return.

return_mtddecimal string

Month-to-date return, rounded to 10 decimals.

return_ytddecimal string

Return since the start of the accounting year, rounded to 10 decimals.

Request
GET/v1/benchmarks/{id}/returns
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
Response
{
  "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"
}
  • Performance — the lens series to compare against.
  • Assets — the ids components[].asset_id names.