View as Markdown

API reference · v2

v2 covers the bulk of Aleta's legacy API: clients, users, legal entities, accounts, depositories, reporting entities, portfolios, transactions, performance, limits, and webhook signing keys. In early 2026 the asset family of endpoints was relabelled to instrument and moved to a new v3 prefix; custodian integration (auth flows and authorities) also lives on v3 only. v2 and v3 share the same backend on the same host.


Base URL

Every path on v2 is relative to https://platform.aleta.io. Versioning is by URL prefix only — there is no version header.

Base URL

https://platform.aleta.io

Call a v2 endpoint

GET/api/v2/clients
curl https://platform.aleta.io/api/v2/clients \
  -H "Authorization: Bearer {access_token}"

Authentication

v2 uses OAuth 2.0 with the JWT-Bearer profile (RFC 7523) — there is no interactive login. Every integration is a machine-to-machine consumer authenticated by an asymmetric key pair.

The flow:

  1. Generate an ECDSA P-256 key pair. Aleta recommends Azure Key Vault so the private key is never readable.
  2. Send the public key in JWK format to Aleta. You receive a kid, an iss, and a sub.
  3. Sign a short-lived assertion JWT (audience https://auth.aleta.io, expiry around 60 seconds) with the private key.
  4. Exchange the assertion at https://auth.aleta.io/oauth/token with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer.
  5. Use the returned access_token as a bearer on every v2 request until it expires.

The same access tokens work on v3 and v1 — a single integration can call all three tracks without rotating credentials.

Token exchange

POST/oauth/token
curl -X POST https://auth.aleta.io/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
  --data-urlencode "assertion={signed_jwt}"

Authenticated request

GET/api/v2/clients
curl https://platform.aleta.io/api/v2/clients \
  -H "Authorization: Bearer {access_token}"

JSON:API conventions

v2 follows the JSON:API specification. Every request and response uses the application/vnd.api+json media type, every resource is wrapped in a top-level data envelope with type, id, attributes, and relationships, and every error response uses the JSON:API errors array.

Attribute names are camelCase. Resource types are kebab-case singular (e.g. legal-entity, reporting-entity).

The error envelope on v2 and v3 is not RFC 7807 — that's v1. Migrating off the JSON:API errors envelope is one of the v1 differences.

Resource shape

{
  "data": {
    "type": "client",
    "id": "6710efbc34673a0a646a45de",
    "attributes": {
      "name": "Andersen Family Office"
    },
    "relationships": {
      "legalEntities": { "data": [...] },
      "reportingEntities": { "data": [...] }
    }
  }
}

Error shape

{
  "errors": [
    {
      "status": "404",
      "code": "client_not_found",
      "title": "Client not found",
      "detail": "No client matched the supplied id."
    }
  ]
}

Pagination

v2 and v3 list endpoints return every matching row in a single response. There is no cursor parameter on these tracks — the platform applies a server-side row cap on large responses, so constrain queries with the documented filter parameters (e.g. accountId plus fromDate/toDate on transactions) to keep responses bounded.

Cursor pagination lands on v1 — every list endpoint there takes a cursor and a limit and returns the next cursor in the response.


Rate limits

Every request is counted against a per-tenant budget keyed off the access token's sub claim. The default budget is 1,000 requests per minute with a 300-call burst, isolated per tenant. Over-budget responses are 429 Too Many Requests with a Retry-After: <seconds> header carrying the wait window before the next token replenishes.

The same budget applies on v1, v2, and v3 — one access token, one shared rate-limit bucket.


Idempotency

v2 and v3 do not accept an Idempotency-Key request header. Retry-safety on POST is per-endpoint:

  • Historical-upload endpoints keyed on a natural identifier — (account, date) for account-holdings, (depository, instrument, date) for instrument-holdings — overwrite the existing row on re-post, so retrying is safe.
  • DELETE is naturally idempotent: deleting an already-deleted resource returns 404, not 200, so client-side retry loops stay simple.
  • Other POST and PATCH endpoints are not retry-safe by default. If a request times out, fetch the resulting resource before retrying to avoid duplicates.

How v2 and v3 split

v3 is a partial rename, not a parallel rewrite. The only thing that changed in early 2026:

  • The asset family was renamed to instrument and moved to a new v3 prefix.
  • The lot-level holding endpoint was renamed at the same time.
  • Time-series families were consolidated under instrument sub-paths and direct series addressing.

Everything else — clients, legal entities, reporting entities, custodians, transactions, performance, limits, depositories, users, accounts — stayed on v2 and continues to be served from there. If an endpoint isn't on v3, it's here.

What moved to v3

/v2/asset*           →  /v3/instrument*
/v2/asset-holdings   →  /v3/instrument-holdings

Resource groups

The reference is organised around Aleta's domain model rather than generic CRUD slices. Start with Clients — every other resource is scoped to a client.

  • Name
    Clients
    Description

    Clients own the data. Inside a client, the legal entity structure captures ownership; the reporting entity structure captures how the client wants to view it. Users are the humans with scoped access through role assignments.

  • Name
    Data
    Description

    Custodian holdings, transactions, and performance — what most integrators read out. Holdings (consolidated and lot-level) live on v3; the v2 surface here covers the raw custodian snapshot, the transaction ledger, performance returns, and historical-data uploads.

  • Name
    Platform
    Description

    Investment-policy limits and webhook signing keys.


What's different on v1

v1 is a clean rewrite, not an incremental bump. Highlights for anyone planning to migrate.

  • Name
    One canonical surface
    Description

    Replaces both legacy URL prefixes with a single host (api.aleta.io). No more split-prefix mental model.

  • Name
    RFC 7807 errors
    Description

    Every error is a problem-details payload with a uniform shape. The JSON:API errors envelope used here is preserved on v2 and v3 for compatibility but does not appear on v1.

  • Name
    Per-tenant isolation
    Description

    Rate limits and concurrency budgets are scoped to the calling tenant. One integration can no longer impact another's traffic.

  • Name
    Cursor pagination and ETags
    Description

    Cursor pagination on every list endpoint, conditional reads on every read-heavy endpoint.

  • Name
    Same access tokens
    Description

    v1 accepts the same access tokens issued by auth.aleta.io that v2 and v3 already accept; existing API users authenticate against v1 without rotating keys.


Stability

v2 is frozen. No new endpoints, no new optional fields, no new behaviour — only security fixes. New capabilities land on v1 instead.