Kubera Data API v3



Read and write your Kubera portfolio programmatically — authenticate with an API key/secret, list and export portfolios, create and update assets and debts, manage cash flow entries, archive items, and generate recap reports.

  • Base URL: https://api.kubera.com        
  • Path prefix: /api/v3/data        
  • Content type: application/json         on every request
  • Auth: HMAC-SHA256 signed headers (see Authentication)

On this page

Getting started

Profile

Portfolio endpoints

Sheet endpoints

Section endpoints

Item endpoints

Cash flow endpoints

Fund schedule endpoints

File endpoints

Recap report endpoints

Reference


Endpoint index

Every endpoint requires the three authentication headers. All paths are relative to https://api.kubera.com       .

# Group Purpose Method Path Body Returns
1 Profile Get profile GET /api/v3/data/profile   — Account holder details
2
Look up a ticker GET /api/v3/data/ticker?name={q}   — ticker[]  
3 Portfolios List portfolios GET /api/v3/data/portfolio   — portfolio[]  
4
Get portfolio data GET /api/v3/data/portfolio/{portfolioId}   — Portfolio with assets, debts, totals
5
Create portfolio POST /api/v3/data/portfolio   Yes New portfolio
6
Update portfolio POST /api/v3/data/portfolio/{portfolioId}   Yes Updated portfolio
7
Delete portfolio DELETE /api/v3/data/portfolio/{portfolioId}   — Success envelope. Permanent
8
Portfolio CAGR GET /api/v3/data/portfolio/{portfolioId}/cagr   — CAGR per window + benchmarks
9
Net worth history GET /api/v3/data/portfolio/{portfolioId}/history   — Daily net worth series
10
Top movers GET /api/v3/data/portfolio/{portfolioId}/topMovers   — Biggest movers by day and month
11 Sheets Create sheet POST /api/v3/data/portfolio/{portfolioId}/sheet   Yes New sheet + seeded section
12
Update sheet POST /api/v3/data/sheet/{sheetId}   Yes Updated sheet
13
Archive sheet POST /api/v3/data/sheet/{sheetId}/archive   — Success envelope
14 Sections Create section POST /api/v3/data/sheet/{sheetId}/section   Yes New section
15
Update section POST /api/v3/data/section/{sectionId}   Yes Updated section
16
Archive section POST /api/v3/data/section/{sectionId}/archive   — Success envelope
17 Items Create item POST /api/v3/data/item   Yes New item ids
18
Update item POST /api/v3/data/item/{itemId}   Yes Success envelope (+ demotedPrimaryResidences  )
19
Archive item POST /api/v3/data/item/{itemId}/archive   — Empty data  
20
Item value history GET /api/v3/data/item/{itemId}/history   — Value series for one item
21 Cash flow Get cash flow GET /api/v3/data/item/{itemId}/cashFlow   — cashFlow[]  
22
Insert/update cash flow POST /api/v3/data/item/{itemId}/cashFlow   Yes Written entry + previous values
23
Delete cash flow entry DELETE /api/v3/data/item/{itemId}/cashFlow/{date}   — Deleted entry. Permanent
24 Fund schedule Get fund schedule GET /api/v3/data/item/{itemId}/fundSchedule   — fundSchedule[]  
25
Insert/update fund schedule POST /api/v3/data/item/{itemId}/fundSchedule   Yes Created/updated entry
26
Delete fund schedule entry DELETE /api/v3/data/item/{itemId}/fundSchedule/{type}/{date}   — Deleted entry. Permanent
27 Recap Request recap report GET /api/v3/data/portfolio/{portfolioId}/recap/report   — 202   + reportId  
28
Poll recap report GET /api/v3/data/portfolio/{portfolioId}/recap/report/{reportId}   — Status, then results
29 Files Get file GET /api/v3/data/file/{documentId}   — File details + signed download link

{itemId}       is an asset id or a debt id — the same endpoints serve both.


Authentication

Generate your keys

Generate your API key and secret from Kubera Settings > API.

  • IP restrictions are optional but highly recommended.
  • Keep your API key and secret confidential at all times.
  • In case of accidental disclosure, delete the compromised keys.
  • When creating a key, choose the appropriate permission level. The default setting is “Read portfolio data”.
  • Remove any API keys that are no longer in use.

Required headers

Every request must include these three headers, plus Content-Type: application/json        .

Header Value
x-api-token   Your API key
x-timestamp   Current time in seconds (Unix epoch)
x-signature   HMAC-SHA256 signature (see below)

Signature generation

Build the signing string by concatenating five values in this exact order, with no separators:

{apiKey}{timestamp}{HTTP_METHOD}{requestPath}{body}
Part Notes
apiKey   Same value as the x-api-token   header
timestamp   Unix epoch in seconds, same value as the x-timestamp   header
HTTP_METHOD   Uppercase — GET  , POST   or DELETE  
requestPath   Path only, starting with /api/v3/...  . No host. Leave out the query string (signing the path with its query string is also accepted)
body   The request body serialized with compact encoding (no spaces between keys/values). For GET  , always the empty string. For POST   and DELETE   requests that send no body (archive, delete), sign {}  

Sign that string with HMAC-SHA256 using your API secret. The hex digest is your x-signature        .

The body you sign and the body you send must be byte-identical. If your HTTP client re-serializes the body (adding spaces or reordering keys), the signature will not match. Serialize once, sign that string, send that same string.

x-timestamp   must be no more than 300 seconds old, or the request is rejected with Invalid timestamp  . A read-only API key can only make GET   requests; any other method is rejected.

JavaScript

const crypto = require('crypto-js');

const apiKey = 'Your API Key';
const secret = 'Your API Secret';
const timestamp = Math.floor(Date.now() / 1000);        // x-timestamp
const bodyData = JSON.stringify(request.body);          // compact encoding
const data = `${apiKey}${timestamp}POST${request.path}${bodyData}`;
const signature = crypto.HmacSHA256(data, secret).toString(crypto.enc.Hex); // x-signature

Python

import time
import math
import hashlib
import hmac
import json

api_key = "Your API Key"
secret = "Your API Secret"
item_id = "Item ID"
request_body = {"value": 400}

timestamp = str(math.floor(time.time()))                # x-timestamp
request_type = "POST"
# GET signs ""; any other method signs the JSON body, or "{}" when there is none
body_data = "" if request_type == "GET" else json.dumps(request_body or {}, separators=(',', ':'))
request_path = f"/api/v3/data/item/{item_id}"

data = f"{api_key}{timestamp}{request_type}{request_path}{body_data}"
signature = hmac.new(
    secret.encode('utf-8'),
    data.encode('utf-8'),
    hashlib.sha256
).hexdigest()                                           # x-signature

Worked signature examples

Both examples use this sample key and secret:

apiKey = a20d0129-c121-433c-90e3-97068458584d
secret = s-397a324204a34921a72c9ec7a41c22f2

Case 1 — GET (no body)

timestamp = 1726554618
body      = (empty string)
path      = /api/v3/data/portfolio

signing string:
a20d0129-c121-433c-90e3-97068458584d1726554618GET/api/v3/data/portfolio

signature:
8a24943d24d6def02b38fd6522a4a899883243f30e9f3bda715ddbb6345e0abf

Case 2 — POST (with body)

timestamp = 1726554715
body      = {"value":400}
path      = /api/v3/data/item/27f59377-44e8-4a94-ac3b-f1e1c5080a48

signing string:
a20d0129-c121-433c-90e3-97068458584d1726554715POST/api/v3/data/item/27f59377-44e8-4a94-ac3b-f1e1c5080a48{"value":400}

signature:
584977d5822c3bf697ce929cc86ef4ac41331edba4f3d3c55d0d67361b76542e

Use these to verify your signing implementation before making live calls — if you reproduce both digests exactly, your signing is correct.


Response format

Every response uses the same envelope:

{
  "data": ...,
  "errorCode": 0
}
Field Description
data   The payload. Shape depends on the endpoint; {}   when the endpoint returns nothing
errorCode   0   on success

Conventions

Convention Detail
Ids UUID strings. Portfolio, item, section and sheet ids are all opaque — pass them back exactly as received
Dates YYYY-MM-DD   strings
Timestamps x-timestamp   is in seconds. Recap createdAt   / updatedAt   are in milliseconds
Monetary fields Item-level amounts are returned as a Value object — { "amount": number, "currency": string }   — not a bare number. The portfolio totals in Get portfolio data are plain numbers in the portfolio currency
Currency ISO codes, e.g. USD  
Item ids An asset id and a debt id are interchangeable wherever {itemId}   appears

API limits

There are different types of limits, all of which are subject to change at any time.

Limit Threshold
Rate 600 requests per minute
Kubera Essential 5000 requests per day (UTC)
Kubera Black 25000 requests per day (UTC)
Kubera for Business 25000 requests per day (UTC)

Endpoints added for parity with Kubera's AI tools — Profile, ticker lookup, the portfolio create/update/delete and analytics endpoints, and everything under Sheets, Sections and Fund schedule — together with Create item, Update item, Item value history, Delete cash flow entry and Get file — are counted against a separate allowance of the same size, both per minute and per day, rather than sharing the one above.

List portfolios, Get portfolio data, Archive item, Get / Insert cash flow and Request recap report use the original allowance. Polling a recap report is not counted, but is refused once the limit has been reached.

A request over a limit gets HTTP 429   with errorCode   1028  .


Get profile

GET /api/v3/data/profile      

Returns the account holder's own details. Useful for confirming which account an API key belongs to, and for reading the time zone Kubera uses when it rolls up daily values.

curl --location --request GET 'https://api.kubera.com/api/v3/data/profile' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Response

{
  "data": {
    "name": "Ada Lovelace",
    "utcOffset": -330,
    "tz": "Asia/Kolkata",
    "dateOfBirth": ""
  },
  "errorCode": 0
}

utcOffset       is in minutes. dateOfBirth       is an empty string when it has not been set.


Look up a ticker

GET /api/v3/data/ticker      

Resolves a name or symbol to Kubera's tickers. Call this before sending a ticker       to Create item — the same symbol exists on several exchanges and as different instrument types, so pick the match whose type       / subType       , market       / code       and currency       fit the holding you mean.

curl --location --request GET 'https://api.kubera.com/api/v3/data/ticker?name=apple' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Query parameters

Parameter Type Required Description
name   string Yes Name or symbol to search for

Response

{
  "data": [
    {
      "id": 4321,
      "ticker": "AAPL",
      "name": "Apple Inc",
      "type": "stock",
      "subType": "Common Stock",
      "code": "AAPL",
      "market": "NASDAQ",
      "currency": "USD",
      "countryCode": "US"
    }
  ],
  "errorCode": 0
}

Exact matches come first, and at most 10 matches are returned. An empty array simply means nothing matched. Each entry is a Ticker object; pass its ticker       value back when creating an item.


List portfolios

GET /api/v3/data/portfolio        

Fetch the list of portfolios. Start here — every other endpoint needs a portfolioId         or an itemId         that originates from this call.

curl --location --request GET 'https://api.kubera.com/api/v3/data/portfolio' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Response

{
  "data": [
    {
      "id": "6eb1ac79-2ae1-49e6-aada-3a5fb4fdce55",
      "name": "Mike",
      "currency": "USD"
    }
  ],
  "errorCode": 0
}

data         is an array of Portfolio objects.


Get portfolio data

GET /api/v3/data/portfolio/{portfolioId}        

Fetch a particular portfolio’s full data.

Path parameters

Parameter Required Description
portfolioId   Yes From the List portfolios response
curl --location --request GET 'https://api.kubera.com/api/v3/data/portfolio/<PORTFOLIO_ID>' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Response

{
  "data": {
    "id": "Portfolio id",
    "name": "Portfolio name",
    "ticker": "USD",
    "tickerId": 150,
    "timestamp": "Time",
    "asset": [],
    "debt": [],
    "document": [],
    "insurance": [],
    "structure": [
      {
        "id": "Sheet id",
        "name": "Investments",
        "category": "asset",
        "sortKey": "a",
        "updateFrequency": 1,
        "sections": [
          { "id": "Section id", "name": "Brokerage", "sortKey": "a", "itemCount": 4 }
        ]
      }
    ],
    "assetTotal": 250000,
    "investable": 180000,
    "debtTotal": 50000,
    "netWorth": 200000,
    "allocationByAssetClass": {
      "Stock": 52.4,
      "Cash": 19.6,
      "Real Estate": 28
    },
    "portfolioChart": {
      "granularity": "Monthly",
      "points": [{ "date": "2026-10-01", "value": 196500 }]
    }
  },
  "errorCode": 0
}
Field Type Description
asset   array Asset objects
debt   array Debt objects — same shape as assets
insurance   array Insurance objects — same shape as assets
document   array Document objects
ticker  , tickerId   string, number Portfolio currency code and its ticker id
tsStartDate   number Portfolio start date, epoch seconds. Present only when a custom start date is set
structure   array Every sheet and its sections, including empty ones: { id, name, category, sortKey, updateFrequency, sections: [{ id, name, sortKey, itemCount }] }  
assetTotal  , investable  , debtTotal  , netWorth   number Plain numbers in the portfolio currency. investable   is the investable-asset total; netWorth   = assetTotal   − debtTotal  
allocationByAssetClass   object Percentage of assetTotal   per asset class, keyed by class name (Stock  , Private Equity  , Alternative  , …)
cashFlow   array Every cash flow entry in the portfolio, with its item, sheet and section. Present only when there are entries
cagr   object CAGR figures for the portfolio — see Portfolio CAGR
portfolioChart   object Net worth series: { granularity: "Monthly" | "Weekly" | "Daily", points: [{ date, value }] }  
ownership   object Present only when the portfolio uses ownership tags: owners[]   (name  , type   person   | trust  , isDefault  , assets  , debts  , net  ), trust members[]   and the tag assignments[]   at portfolio, sheet, section or item level
nestedPortfolios   array Linked portfolios, each in this same shape, plus linkOwnership   — the share held, as a fraction (1   = 100%)

Additional fields may be present — parse defensively and ignore unknown keys.

The id         values inside asset         and debt         are the {itemId}         used by the item and cash flow endpoints.


Create portfolio

POST /api/v3/data/portfolio      

Creates an empty portfolio, pre-seeded with Kubera's default sheets and sections (Investments, Real Estate, Others, Credit Cards, Loans, and so on). Call Get portfolio data on the new portfolio to see what it was given before adding sheets of your own.

curl --location --request POST 'https://api.kubera.com/api/v3/data/portfolio' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>' \
--data '{
  "name": "Family Trust",
  "currency": "USD"
}'

Body properties

Property Type Required Description
name   string Yes Portfolio name
currency   string No Reporting currency, e.g. USD  . Defaults to the currency inferred for the account. Ignored for Kubera for Business users — those portfolios always take the organisation's currency, so read currency   back off the response rather than assuming the value you sent

Response

{
  "data": {
    "id": "6eb1ac79-2ae1-49e6-aada-3a5fb4fdce55",
    "name": "Family Trust",
    "currency": "USD"
  },
  "errorCode": 0
}

Errors — 400     for an invalid currency; 500 Maximum portfolio limit reached     when the plan caps portfolio count.


Update portfolio

POST /api/v3/data/portfolio/{portfolioId}      

Renames a portfolio and/or changes its reporting currency. This is the portfolio itself — to change an asset or debt inside it use Update item.

Changing currency       re-denominates the entire portfolio and makes Kubera recompute returns and rebuild its charts, which takes a while to settle. Send it only when the reporting currency really is changing. It is not how you record an item held in another currency — set the currency on the item instead.

curl --location --request POST 'https://api.kubera.com/api/v3/data/portfolio/<PORTFOLIO_ID>' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>' \
--data '{
  "name": "Family Trust (2026)"
}'

Body properties

Property Type Required Description
name   string No New portfolio name
currency   string No New reporting currency. Must be a fiat currency

At least one of the two is required.

Response

{
  "data": {
    "id": "6eb1ac79-2ae1-49e6-aada-3a5fb4fdce55",
    "name": "Family Trust (2026)",
    "currency": "USD"
  },
  "errorCode": 0
}

Errors — 400 Nothing to update     when both fields are absent; 400     for an invalid or non-fiat currency.


Delete portfolio

DELETE /api/v3/data/portfolio/{portfolioId}      

This is permanent. It deletes the portfolio and everything in it — every sheet, section, asset, debt, insurance policy, document, cash flow entry, and the complete value history of all of them. Nothing is archived and nothing is recoverable afterwards. To remove only part of a portfolio, archive the individual sheets, sections or items instead — archiving preserves their history so past net worth stays correct.

curl --location --request DELETE 'https://api.kubera.com/api/v3/data/portfolio/<PORTFOLIO_ID>' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Path parameters

Parameter Type Description
portfolioId   string Portfolio to delete permanently

Response

{
  "data": {
    "success": true,
    "message": "Portfolio deleted successfully"
  },
  "errorCode": 0
}

Errors — 400 Cannot delete the only portfolio in the account    .


Portfolio CAGR

GET /api/v3/data/portfolio/{portfolioId}/cagr      

Compound annual growth rate for the portfolio, over each of Kubera's standard windows, for both net worth and investable assets — plus the same-period return of a set of market benchmarks to compare against.

curl --location --request GET 'https://api.kubera.com/api/v3/data/portfolio/<PORTFOLIO_ID>/cagr' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Path parameters

Parameter Type Description
portfolioId   string Portfolio to report on

Response

{
  "data": {
    "portfolioId": "6eb1ac79-2ae1-49e6-aada-3a5fb4fdce55",
    "portfolioName": "Personal",
    "portfolioCurrency": "USD",
    "portfolioCurrencyTickerId": 171,
    "portfolioStartDate": "2021-03-14",
    "isCustomStartDate": false,
    "cagrPercentages": {
      "ytd_networth": 7.42,
      "ytd_investable": 9.11,
      "qtd_networth": 2.03,
      "qtd_investable": 2.58,
      "yearly_networth": 11.87,
      "yearly_investable": 14.02,
      "alltime_networth": 9.65,
      "alltime_investable": 12.30
    },
    "cagrOldValues": {
      "ytd_networth": { "oldValue": 1250000, "date": "2026-01-01" },
      "alltime_networth": { "oldValue": 480000, "date": "2021-03-14" }
    },
    "market": {
      "GSPC.INDX": 10.44,
      "IXIC.INDX": 13.91,
      "BTC.CC": 22.07
    }
  },
  "errorCode": 0
}

Field notes

Field Description
cagrPercentages   CAGR as a percentage for each window. Eight keys: ytd  , qtd  , yearly   and alltime  , each as _networth   and _investable  . Present only once the portfolio has data for today
cagrOldValues   The starting value each window was measured from, as { oldValue, date }  . Same eight keys. Always present
market   Benchmark returns over the same span as alltime  , keyed by index symbol (GSPC.INDX  , BTC.CC  , …). null   when the portfolio has no start date. A benchmark that cannot be priced is omitted, so treat every key as optional
portfolioStartDate   Where the all-time window begins — YYYY-MM-DD  , or null   if the portfolio has no history yet

Portfolio net worth history

GET /api/v3/data/portfolio/{portfolioId}/history      

The portfolio's net worth over time, one data point per day, oldest first. This is the series behind the net worth chart in the app.

curl --location --request GET 'https://api.kubera.com/api/v3/data/portfolio/<PORTFOLIO_ID>/history' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Path parameters

Parameter Type Description
portfolioId   string Portfolio to report on

Response

{
  "data": {
    "portfolioId": "6eb1ac79-2ae1-49e6-aada-3a5fb4fdce55",
    "portfolioName": "Personal",
    "portfolioCurrency": "USD",
    "portfolioCurrencyTickerId": 171,
    "portfolioStartDate": "2021-03-14",
    "isCustomStartDate": false,
    "portfolioDataPoints": [
      {
        "date": "2026-09-26",
        "value": 1342500,
        "investibleTotal": 910400,
        "assetTotal": 1520000,
        "debtTotal": 177500
      }
    ]
  },
  "errorCode": 0
}

value       is net worth — assetTotal       minus debtTotal       — in the portfolio currency. portfolioDataPoints       is an empty array when the portfolio has no history yet.


Top movers

GET /api/v3/data/portfolio/{portfolioId}/topMovers      

The items whose value moved most over the last day and the last month, assets and debts ranked separately, largest change first.

curl --location --request GET 'https://api.kubera.com/api/v3/data/portfolio/<PORTFOLIO_ID>/topMovers' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Path parameters

Parameter Type Description
portfolioId   string Portfolio to report on

Response

{
  "data": {
    "change": {
      "day": {
        "baseDate": "2026-09-27",
        "currentDate": "2026-09-28",
        "asset": [
          {
            "name": "Apple",
            "value": 24100,
            "currentValue": 24850,
            "archived": 0
          }
        ],
        "assetChangeTotal": 750,
        "assetTotal": 1520000,
        "debt": [],
        "debtChangeTotal": 0,
        "debtTotal": 177500
      },
      "month": { "...": "same shape, compared against a month ago" },
      "week": null,
      "year": null,
      "lastYearEnd": null
    },
    "currency": "USD",
    "portfolioId": "6eb1ac79-2ae1-49e6-aada-3a5fb4fdce55",
    "portfolioName": "Personal",
    "tickerId": 171,
    "tsCreated": 1790000000
  },
  "errorCode": 0
}

Only day       and month       are populated. The week       , year       and lastYearEnd       keys are always present but unset on this endpoint. A window is also unset when the portfolio is younger than it.

Within a window, value       is the item's value on baseDate       and currentValue       is its value today, both in the portfolio currency; archived       is 1       for an item archived since. tsCreated       is in seconds.


Create sheet

POST /api/v3/data/portfolio/{portfolioId}/sheet      

A sheet is a top-level tab that holds sections; sections hold the items. A sheet belongs to exactly one category and can only hold items of that category, so choose it deliberately.

A new sheet arrives with one empty section already in it (“Section 1”), so you can add items straight away. Use Create section only when you want additional sections.

Insurance       cannot be created here. A portfolio is given its single Insurance sheet when the portfolio itself is created, and there is no way to add another. Update sheet still accepts Insurance       , for that existing sheet.

curl --location --request POST 'https://api.kubera.com/api/v3/data/portfolio/<PORTFOLIO_ID>/sheet' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>' \
--data '{
  "name": "Private Equity",
  "category": "Asset",
  "updateFrequency": 3
}'

Body properties

Property Type Required Description
name   string Yes Sheet name
category   string No Asset   (default) or Debt  . Case-insensitive
updateFrequency   number No How often Kubera reminds you to refresh manually-valued items on this sheet. 0   never (default), 1   weekly, 6   fortnightly, 2   monthly, 3   quarterly, 4   half-yearly, 5   yearly

Response

{
  "data": {
    "id": "5c2f0e21-7a44-4c0e-9a1e-2b6f0d3c8a19",
    "name": "Private Equity",
    "category": "Asset",
    "portfolioId": "6eb1ac79-2ae1-49e6-aada-3a5fb4fdce55",
    "sortKey": "3",
    "updateFrequency": 3,
    "tsArchive": 0,
    "sections": [
      {
        "id": "9fcbee08-3524-470f-9352-ba99f0f6cf37",
        "name": "Section 1",
        "sheetId": "5c2f0e21-7a44-4c0e-9a1e-2b6f0d3c8a19",
        "sortKey": "0",
        "expanded": 1,
        "tsArchive": 0
      }
    ]
  },
  "errorCode": 0
}

sortKey       is assigned automatically, appending the sheet after the existing sheets in its category. See the Sheet object.

Errors — 400 "category" must be one of [Asset, Debt]    .


Update sheet

POST /api/v3/data/sheet/{sheetId}      

Renames a sheet, reorders it, or changes its category or reminder cadence. Send only the fields you want changed; anything omitted keeps its current value.

Changing category       moves the whole sheet — and every item on it — between the asset, debt and insurance sides of the portfolio. That flips whether those items count towards net worth as owned or owed. A sheet cannot be moved to a different portfolio.

curl --location --request POST 'https://api.kubera.com/api/v3/data/sheet/<SHEET_ID>' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>' \
--data '{
  "name": "Private Markets",
  "sortKey": "1"
}'

Body properties

Property Type Required Description
name   string No New sheet name
category   string No Asset  , Debt   or Insurance  
updateFrequency   number No Same values as Create sheet
sortKey   string No Numeric string positioning the sheet among the sheets of its category. "0"   is first

At least one field is required.

Response

{
  "data": {
    "id": "5c2f0e21-7a44-4c0e-9a1e-2b6f0d3c8a19",
    "name": "Private Markets",
    "category": "Asset",
    "portfolioId": "6eb1ac79-2ae1-49e6-aada-3a5fb4fdce55",
    "sortKey": "1",
    "updateFrequency": 3,
    "tsArchive": 0
  },
  "errorCode": 0
}

The response is the updated sheet without its sections       .

Errors — 400 Nothing to update    ; 400 Invalid sheetId/Archived Sheet    .


Archive sheet

POST /api/v3/data/sheet/{sheetId}/archive      

Archives a sheet and everything under it — all of its sections and all of their items. This is exactly what the app's delete-sheet action does: the sheet stops appearing and stops counting towards net worth, while past values are preserved so historical net worth stays correct.

A sheet can only be un-archived from the Kubera app, not through this API.

curl --location --request POST 'https://api.kubera.com/api/v3/data/sheet/<SHEET_ID>/archive' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Path parameters

Parameter Type Description
sheetId   string Sheet to archive

Response

{
  "data": {
    "success": true,
    "message": "Sheet archived successfully"
  },
  "errorCode": 0
}

Errors — 400 Invalid sheetId/Archived Sheet    , for an unknown sheet or one already archived.


Create section

POST /api/v3/data/sheet/{sheetId}/section      

A section is the group that actually holds assets and debts, so a sheet needs at least one before anything can go in it. The section inherits its sheet's category.

Once it exists, put items in it by passing the sheet and section names to Create item's sheetName       / sectionName       . You only need this endpoint to create a section up front — Create item also makes one on demand when its sheetName       matches a sheet and its sectionName       matches nothing in it.

curl --location --request POST 'https://api.kubera.com/api/v3/data/sheet/<SHEET_ID>/section' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>' \
--data '{
  "name": "Venture Funds",
  "expanded": 1
}'

Body properties

Property Type Required Description
name   string Yes Section name
expanded   number No Whether the section renders expanded in the app: 1   (default) or 0  . Cosmetic only

Response

{
  "data": {
    "id": "9fcbee08-3524-470f-9352-ba99f0f6cf37",
    "name": "Venture Funds",
    "sheetId": "5c2f0e21-7a44-4c0e-9a1e-2b6f0d3c8a19",
    "sortKey": "2",
    "expanded": 1,
    "columnSortKey": "",
    "columnSortOrder": "",
    "tsArchive": 0
  },
  "errorCode": 0
}

See the Section object.

Errors — 400 Invalid sheetId/Archived Sheet    .


Update section

POST /api/v3/data/section/{sectionId}      

Renames a section, reorders it, collapses or expands it, or moves it to another sheet in the same portfolio — its items move with it. Send only the fields you want changed.

Moving a section to a sheet of a different category changes whether its items count as owned or owed. A sheet in a different portfolio is rejected.

curl --location --request POST 'https://api.kubera.com/api/v3/data/section/<SECTION_ID>' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>' \
--data '{
  "name": "Venture Capital",
  "expanded": 0
}'

Body properties

Property Type Required Description
name   string No New section name
sortKey   string No Numeric string positioning the section within its sheet
expanded   number No 1   or 0  
sheetId   string No Move the section into this sheet. Must be in the same portfolio

At least one field is required.

Response

{
  "data": {
    "id": "9fcbee08-3524-470f-9352-ba99f0f6cf37",
    "name": "Venture Capital",
    "sheetId": "5c2f0e21-7a44-4c0e-9a1e-2b6f0d3c8a19",
    "sortKey": "2",
    "expanded": 0,
    "columnSortKey": "",
    "columnSortOrder": "",
    "tsArchive": 0
  },
  "errorCode": 0
}

Errors — 400 Nothing to update    ; 400 Cannot move a section to a sheet in another portfolio    ; 400 Invalid sectionId/Archived Section    .


Archive section

POST /api/v3/data/section/{sectionId}/archive      

Archives a section and every item in it, preserving past values the same way Archive sheet does. To archive a single item instead, use Archive item.

Un-archiving is only possible in the Kubera app.

curl --location --request POST 'https://api.kubera.com/api/v3/data/section/<SECTION_ID>/archive' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Path parameters

Parameter Type Description
sectionId   string Section to archive

Response

{
  "data": {
    "success": true,
    "message": "Section archived successfully"
  },
  "errorCode": 0
}

Errors — 400 Invalid sectionId/Archived Section    .


Create item

POST /api/v3/data/item        

Create a new manual asset or debt in a portfolio. By default the item is added to the portfolio’s first Asset section (or first Debt section when isDebt   is true  ).

Use sheetName   / sectionName   (case-insensitive) to choose where it goes:

  • Both match an existing section — the item goes there.
  • A sheetName   that matches no sheet — that sheet is created (a Debt sheet when isDebt   is true  ), and the item goes in its first section. sectionName   is ignored.
  • An existing sheet with a sectionName   it does not have — that section is created in the sheet.
  • sectionName   alone that matches nothing — the default section is used.

Ticker-backed item — value         is the quantity held:

curl --location --request POST 'https://api.kubera.com/api/v3/data/item' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>' \
--data '{
  "portfolioId": "<PORTFOLIO_ID>",
  "name": "Apple",
  "ticker": "AAPL",
  "value": 10,
  "cost": 1500
}'

Cash / manually-valued item — pass currency         instead of ticker         (or neither, to use the portfolio currency):

curl --location --request POST 'https://api.kubera.com/api/v3/data/item' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>' \
--data '{
  "portfolioId": "<PORTFOLIO_ID>",
  "name": "Savings",
  "currency": "USD",
  "value": 5000
}'

value         is overloaded — read this before sending

Item type What value   means
ticker   is set — stock, ETF, fund, crypto, precious metal The quantity held (shares/units), not a currency amount. Kubera derives market value as quantity × price
No ticker   — cash or manually-valued item The monetary amount in the item’s currency
rate   is set — unlisted shares, units or other quantity × price item The quantity. Kubera derives the value as value   × rate  

cost         is always a monetary amount (a total, not a per-unit price).

Sending {"ticker": "AAPL", "value": 1500}         creates 1,500 shares of Apple, not $1,500 of Apple.

Body properties

Property Type Required Description
portfolioId   string Yes Portfolio to add the item to
name   string Yes Item name
value   number Yes Quantity (ticker items) or amount (cash items)
ticker   string No Ticker symbol, e.g. AAPL  , BTC  . When set, value   is the quantity
currency   string No Currency code for a cash item, e.g. USD  . When ticker   is also set, ticker   wins; currency   is used only if the ticker cannot be found. Defaults to the portfolio currency
cost   number No Cost basis as a monetary amount
description   string No —
note   string No Text for the item’s Notes panel
isDebt   boolean No true   to create a debt. Defaults to false   (asset)
sheetName   string No Sheet to add the item to. Created if no sheet has this name — see above
sectionName   string No Section to add the item to, inside sheetName  . Created in that sheet if it has no section with this name
costCurrency   string No Currency for cost  . Fiat only; falls back to the item's currency, then the portfolio's
ownership   number No Percentage of the item the user owns — the app's “Portfolio Share”. 0–999, defaults to 100  . Values above 100 are allowed, for a geared position
cmtdCap   number No Committed capital, as a monetary amount. Never negative. Pairs with the fund schedule
parentId   string No Create the item as a holding inside this existing item — e.g. a stock inside a brokerage account. See the note below
symbol   string No Deprecated alias for ticker  , used only when ticker   is absent
rate   number No Unit price, ≥ 0. Makes the item a quantity × price item, with value   as the quantity. Cannot be combined with ticker   or currency  
rateCurrency   string No Fiat currency of rate  . Only with rate  . Defaults to the portfolio currency
assetClass   string No Assets only. Case-insensitive, one of: Investment, Stock, Bond, Fund, Crypto, Precious Metal, Real Estate, Derivative, Vehicle, Cash, Miscellaneous, Private Equity, Private Debt, Commodity, Venture, Art & Collectibles, Alternative
investable   string No Assets only. investable_easy_convert  , investable_cash   or non_investable  
adjValue   number No Value Adjustment — an amount subtracted from the item’s value after ownership is applied (e.g. carried interest). A negative amount adds
adjCurrency   string No Fiat currency of adjValue  . Only with adjValue  . Defaults to the item’s currency
adjNotes   string No Note for the adjustment, up to 4096 characters. Saved only together with adjValue  

portfolioShare       and share       are accepted as aliases for ownership       , in that order of precedence. Prefer ownership       .

cmtdCap       and ownership       apply to top-level items only. Setting either on a holding is rejected: committed capital rolls up into the parent item, and a holding's ownership is read from its parent — so set them on the parent instead.

parentId       turns a plain parent into a manual account whose value becomes the sum of its holdings. It rejects connected accounts, items that are already holdings, a parent in another portfolio, and ticker-backed plain items. sheetName       , sectionName       and isDebt       are ignored when it is set, since the holding inherits its parent's placement.

Response

{
  "data": {
    "success": true,
    "itemId": "cust-abc",
    "portfolioId": "6eb1ac79-2ae1-49e6-aada-3a5fb4fdce55",
    "sectionId": "9fcbee08-3524-470f-9352-ba99f0f6cf37"
  },
  "errorCode": 0
}

The response also carries parentId   when the item was created as a holding, and createdSheet   or createdSection   ({ id, name }  ) when the call created a sheet or section for it.


Update item

POST /api/v3/data/item/{itemId}        

Update a particular asset or debt.

Path parameters

Parameter Required Description
itemId   Yes Asset id or debt id from the Get portfolio data response

Update the current value

curl --location --request POST 'https://api.kubera.com/api/v3/data/item/<ASSET_ID/DEBT_ID>' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>' \
--data '{
  "value": 400
}'

Update a historical value — include date        :

curl --location --request POST 'https://api.kubera.com/api/v3/data/item/<ASSET_ID/DEBT_ID>' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>' \
--data '{
  "value": 350,
  "date": "2026-01-01"
}'

Body properties

Property Type Description
name   string —
description   string —
value   number Follows the same quantity-vs-amount rule as Create item
cost   number Monetary amount
date   string YYYY-MM-DD  . Writes value  , cost   and rate   at that date instead of today, using that date’s exchange rate. Every other field always changes the current item
costCurrency   string Currency for cost  . Fiat only; falls back to the item's currency, then the portfolio's
ownership   number Percentage of the item the user owns — the app's “Portfolio Share”. 0–999, defaults to 100  . Values above 100 are allowed, for a geared position
cmtdCap   number Committed capital, as a monetary amount. Never negative. Pairs with the fund schedule
note   string Replaces the item’s note
appendNote   boolean true   to add note   after the existing note instead of replacing it
ticker   string Change the value ticker of a manually managed item, e.g. AAPL  , BTC  , EUR  . Not allowed on a connected item, on an account with holdings, or together with a past date  
rate   number Unit price, ≥ 0. Only on an existing quantity × price item
rateCurrency   string Fiat currency of rate  . Only with rate  
assetClass   string Assets only. Same values as Create item
investable   string Assets only. investable_easy_convert  , investable_cash  , non_investable  , or primary_residence   for a home. Marking a home as the primary residence demotes any other. On an account with holdings, applies to every holding
adjValue   number | null Value Adjustment amount, as in Create item. 0   or null   removes it. Not allowed on an account with holdings
adjCurrency   string Fiat currency of adjValue  . Only with adjValue  
adjNotes   string Note for the adjustment. Sent alone, it keeps the current amount and currency

portfolioShare       and share       are accepted as aliases for ownership       , in that order of precedence. Prefer ownership       .

cmtdCap       and ownership       apply to top-level items only. Setting either on a holding is rejected: committed capital rolls up into the parent item, and a holding's ownership is read from its parent — so set them on the parent instead.

Response

{
  "data": {
    "success": true,
    "message": "Portfolio item updated successfully"
  },
  "errorCode": 0
}

When setting investable   to primary_residence   demoted another home, the response also lists it in demotedPrimaryResidences   (an array of item names).


Archive item

POST /api/v3/data/item/{itemId}/archive        

Archive a single manual asset or debt. No request body is required.

Path parameters

Parameter Required Description
itemId   Yes Asset id or debt id from the Get portfolio data response

Behavior depends on the item

Item Result
A holding (belongs to a parent account) Its value is zeroed. The parent and sibling holdings are untouched
A top-level account The item is archived. Archiving a manual account also archives its holdings
A connected/linked account (bank, brokerage, crypto, etc.) Rejected. Only manually-added items can be archived
curl --location --request POST 'https://api.kubera.com/api/v3/data/item/<ASSET_ID/DEBT_ID>/archive' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Response

{
  "data": {},
  "errorCode": 0
}

Get item value history

GET /api/v3/data/item/{itemId}/history      

The recorded value of a single item over time, oldest first. For a ticker-backed holding this is its market value; for a manually-valued item it is the values you have entered.

curl --location --request GET 'https://api.kubera.com/api/v3/data/item/<ASSET_ID>/history' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Path parameters

Parameter Type Description
itemId   string Item whose history to fetch

Response

{
  "data": {
    "itemId": "cust-abc",
    "itemName": "Apple",
    "itemSymbol": "AAPL",
    "itemCurrency": "USD",
    "itemCurrencyTickerId": 150,
    "itemDataPoints": [
      { "date": "2026-09-26", "value": 24100, "quantity": 100 },
      { "date": "2026-09-27", "value": 24850, "quantity": 100 }
    ]
  },
  "errorCode": 0
}

value       is the value in the portfolio’s currency — itemCurrency   and itemCurrencyTickerId   are the portfolio currency. quantity       is the raw stored figure — the number of units for a ticker-backed holding — and is null       where none was recorded on or before that date. itemSymbol       is an empty string for an item with no ticker.


Get cash flow entries

GET /api/v3/data/item/{itemId}/cashFlow        

Fetch the list of cash flow entries for an item.

curl --location --request GET 'https://api.kubera.com/api/v3/data/item/<ASSET_ID>/cashFlow' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Response

{
  "data": [
    {
      "cashIn": 300,
      "cashOut": null,
      "currency": "USD",
      "date": "2025-02-02",
      "note": "Initial investment"
    }
  ],
  "errorCode": 0
}

data         is an array of cash flow objects.


Insert/update cash flow entry

POST /api/v3/data/item/{itemId}/cashFlow        

Add a cash flow entry to an asset, or update the existing entry for that date. Cash flow can only be recorded on assets, not debts or insurance.

date   is the entry’s key: posting a date that already has an entry updates it. The amounts you send replace the stored ones rather than being added to them, and fields you leave out keep their stored values. On a new entry, a side you leave out is stored empty. Amounts are converted at the exchange rate for the entry’s date. The item’s first entry switches its IRR to the cash flow method.

curl --location --request POST 'https://api.kubera.com/api/v3/data/item/<ASSET_ID>/cashFlow' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>' \
--data '{
  "cashIn": 100.0,
  "cashOut": 200.0,
  "date": "2025-04-02",
  "currency": "USD",
  "note": "3rd investment"
}'

Body properties

Property Type Description
cashIn   number Money in
cashOut   number Money out
date   string Required. YYYY-MM-DD  
currency   string Required. A fiat currency code, or USDC  
note   string Free-text note

Response

{
  "data": {
    "created": false,
    "date": "2025-04-02",
    "cashIn": 100,
    "cashOut": 200,
    "note": "3rd investment",
    "previous": { "cashIn": 50, "cashOut": null, "note": "" }
  },
  "errorCode": 0
}

created   is true   for a new entry. previous   appears only on an update and holds the values it replaced.


Delete cash flow entry

DELETE /api/v3/data/item/{itemId}/cashFlow/{date}      

This is permanent. It removes the single cash flow entry the item holds on that date, and cannot be undone.

The entry is addressed by date rather than by a row id: an item holds at most one entry per date — the same way Insert/update cash flow entry addresses one — and Get cash flow entries returns no id to quote back.

One entry per call; there is no bulk form. To clear an item's whole cash flow, delete each entry in turn, or do it in the app.

Kubera recomputes the item's IRR from the entries that remain, so deleting one changes its reported return. Deleting the last entry leaves the item with no cash-flow-based return — it is not switched back to a cost-basis IRR. This matches what the app itself does.

curl --location --request DELETE 'https://api.kubera.com/api/v3/data/item/<ASSET_ID>/cashFlow/2025-04-02' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Path parameters

Parameter Type Description
itemId   string Item the entry belongs to
date   string YYYY-MM-DD  , as returned by Get cash flow entries

Response

{
  "data": {
    "success": true,
    "deleted": true,
    "date": "2025-04-02",
    "cashIn": 100,
    "cashOut": 200,
    "currency": "USD",
    "note": "3rd investment",
    "message": "Cash flow entry deleted successfully"
  },
  "errorCode": 0
}

The response echoes the entry that was removed, so you can report what went.

Errors — 400 No cash flow entry on <date> for this item    . The message also names the dates that do have entries, so a caller can correct itself.


Get fund schedule

GET /api/v3/data/item/{itemId}/fundSchedule      

Returns an item's dated capital calls and distributions — typically for a private equity or other capital-commitment holding, alongside the item's committed capital (cmtdCap       ).

These entries are a schedule, not money that has moved. Kubera does not use them to compute IRR, returns, net worth or the item's value; nothing here changes a reported number. Actual money in and out lives in cash flow, and that is what drives IRR. An item may have both, and they will not agree if a call was scheduled but not yet paid.

curl --location --request GET 'https://api.kubera.com/api/v3/data/item/<ASSET_ID>/fundSchedule' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Path parameters

Parameter Type Description
itemId   string The item

Response

{
  "data": {
    "fundSchedule": [
      {
        "type": "capitalcall",
        "date": "2026-01-15",
        "value": 50000,
        "currency": "USD",
        "note": "Call 3 of 8"
      },
      {
        "type": "distribution",
        "date": "2026-06-30",
        "value": 12000,
        "currency": "USD",
        "note": ""
      }
    ]
  },
  "errorCode": 0
}

An empty array simply means no entries have been recorded. Each entry is a Fund schedule object.


Insert/update fund schedule entry

POST /api/v3/data/item/{itemId}/fundSchedule      

Adds a capital call or distribution to an item, or updates the existing entry of that type on that date.

The key is type       and date       together: an item may hold one capital call and one distribution on the same date, but not two of the same type. Writing a distribution therefore never disturbs a capital call on that date. When an entry of the same type already exists on that date it is updated in place — value       is replaced, not added to — and fields you leave out keep their stored values.

curl --location --request POST 'https://api.kubera.com/api/v3/data/item/<ASSET_ID>/fundSchedule' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>' \
--data '{
  "type": "capitalcall",
  "date": "2026-01-15",
  "currency": "USD",
  "value": 50000,
  "note": "Call 3 of 8"
}'

Body properties

Property Type Required Description
type   string Yes capitalcall   or distribution  
date   string Yes YYYY-MM-DD  . May be future- or past-dated
currency   string Yes Currency of value  
value   number No Positive for both types — the direction comes from type  , not the sign
note   string No Short note, e.g. Call 3 of 8  

Response

{
  "data": {
    "success": true,
    "created": false,
    "type": "capitalcall",
    "date": "2026-01-15",
    "value": 50000,
    "currency": "USD",
    "note": "Call 3 of 8",
    "previous": {
      "value": 45000,
      "note": "Call 3 of 8 (provisional)"
    }
  },
  "errorCode": 0
}

created       is true       when a new entry was inserted and false       when an existing one was updated. previous       appears only on an update, carrying the values that were overwritten.

Errors — 400 "type" must be one of [capitalcall, distribution]    ; 400 Invalid currency: <x>    .


Delete fund schedule entry

DELETE /api/v3/data/item/{itemId}/fundSchedule/{type}/{date}      

This is permanent. It removes the single entry of that type on that date. Because the same date may hold one of each type, the type decides which of the two goes — passing the wrong one removes the wrong entry.

One entry per call; there is no bulk form.

No computed figure changes as a result. Any cash flow recorded for the same event is separate and is not removed by this — use Delete cash flow entry for that.

curl --location --request DELETE 'https://api.kubera.com/api/v3/data/item/<ASSET_ID>/fundSchedule/capitalcall/2026-01-15' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Path parameters

Parameter Type Description
itemId   string Item the entry belongs to
type   string capitalcall   or distribution  
date   string YYYY-MM-DD  , as returned by Get fund schedule

Response

{
  "data": {
    "success": true,
    "deleted": true,
    "type": "capitalcall",
    "date": "2026-01-15",
    "value": 50000,
    "currency": "USD",
    "note": "Call 3 of 8",
    "message": "Fund schedule entry deleted successfully"
  },
  "errorCode": 0
}

Errors — 400 No <type> entry on <date> for this item    . The message also names the entries that do exist, as type date     pairs.


Get file

GET /api/v3/data/file/{documentId}  

Get one uploaded document’s details and a short-lived download link. The file contents are not returned in the response — download them from downloadUrl  .

curl --location --request GET 'https://api.kubera.com/api/v3/data/file/<DOCUMENT_ID>' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Path parameters

Parameter Required Description
documentId   Yes A document id   from the document   array of Get portfolio data

Response

{
  "data": {
    "file": {
      "id": "<DOCUMENT_ID>",
      "name": "K-1 2025.pdf",
      "fileType": "application/pdf",
      "size": 182044
    },
    "downloadUrl": "https://...",
    "downloadUrlExpiresAt": "2026-11-05T10:05:00.000Z"
  },
  "errorCode": 0
}

downloadUrl   expires 5 minutes after the call (downloadUrlExpiresAt  , ISO 8601). Request a new one when it lapses. The link downloads the file as an attachment.

Errors — 400 File not found: <id>. It may have been deleted, or the id is a folder rather than a file  .


Recap reports

A recap report can span years of history across every holding in a portfolio, so it is too slow to compute inside a single HTTP request. The API is therefore request-then-poll:

Step Endpoint Returns
1. Submit GET /api/v3/data/portfolio/{portfolioId}/recap/report   202   with a reportId  
2. Poll GET /api/v3/data/portfolio/{portfolioId}/recap/report/{reportId}   200   with status, and the results once complete

Only one recap computation per user runs at a time.

Request recap report

GET /api/v3/data/portfolio/{portfolioId}/recap/report        

Queues the computation and returns the id of the report to poll for.

curl --location --request GET \
'https://api.kubera.com/api/v3/data/portfolio/<PORTFOLIO_ID>/recap/report?reports=networth,asset_classes&timeRanges=monthly&reportType=totals&dataPoints=12&currency=USD' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Query parameters

Parameter Required Description
reports   Yes Comma-separated report IDs. Case-insensitive
timeRanges   Yes Comma-separated time ranges. Case-insensitive
reportType   Yes totals   or percentageAllocation  
dataPoints   Yes Integer ≥ 1. Caps the report to the most recent N points per series. quarterly   and today   are derived from the raw series, so the number of points actually returned for those ranges can differ
currency   No ISO currency code, e.g. USD  . Every value in the report is converted to it. Defaults to the portfolio’s own currency. An unknown code returns 400  

Note: the query string is not part of the signing string — sign the path only. See Signature generation.

Response — 202 Accepted        

{
  "data": {
    "reportId": "7c1f0a3d9b6e42f58ad0c4e91b73d2a6f5c8e1097b4d6a3f2e8c5b91d7a0463f",
    "status": "pending",
    "pollIntervalMs": 2000
  },
  "errorCode": 0
}

The response also carries a Retry-After: 2         header. status         is pending         for a newly queued report, or processing         / completed         when the same request was already requested and is still cached.

Response — another report is already running

If a different recap request is already in flight, the request returns 202         without queuing anything:

{
  "data": {
    "status": "in_progress",
    "message": "A recap computation is already in progress for this user. Please retry shortly.",
    "retryAfterMs": 2000
  },
  "errorCode": 0
}

There is no reportId         in this response — retry the request after the suggested interval. These retries do not consume your API quota.

Agent note: branch on the presence of reportId        , not on status        . No reportId         means nothing was queued and you must request it again.

Poll recap report

GET /api/v3/data/portfolio/{portfolioId}/recap/report/{reportId}        

Fetch the state — and, once finished, the contents — of a report you requested.

curl --location --request GET \
'https://api.kubera.com/api/v3/data/portfolio/<PORTFOLIO_ID>/recap/report/<REPORT_ID>' \
--header 'Content-Type: application/json' \
--header 'x-api-token: <API_KEY>' \
--header 'x-timestamp: <TIMESTAMP>' \
--header 'x-signature: <SIGNATURE>'

Statuses

Status Meaning Next action
pending   Queued, not started yet Keep polling
processing   Being computed Keep polling
completed   Finished — params   and results   are present Read results  
failed   The computation failed. The response carries an error   message Submit again

Response — still running

{
  "data": {
    "reportId": "7c1f0a3d9b6e42f58ad0c4e91b73d2a6f5c8e1097b4d6a3f2e8c5b91d7a0463f",
    "status": "processing",
    "createdAt": 1756612800000,
    "updatedAt": 1756612812000
  },
  "errorCode": 0
}

createdAt         and updatedAt         are Unix timestamps in milliseconds.

Response — completed

{
  "data": {
    "reportId": "7c1f0a3d9b6e42f58ad0c4e91b73d2a6f5c8e1097b4d6a3f2e8c5b91d7a0463f",
    "status": "completed",
    "createdAt": 1756612800000,
    "updatedAt": 1756612830000,
    "params": {
      "portfolioId": "6eb1ac79-2ae1-49e6-aada-3a5fb4fdce55",
      "currency": "USD",
      "reportType": "totals",
      "reports": ["asset_classes"],
      "timeRanges": ["weekly"],
      "dataPoints": 2
    },
    "results": [
      {
        "timeRange": "weekly",
        "report": "asset_classes",
        "currency": "USD",
        "valueType": "number",
        "rows": [
          {
            "label": "Stocks",
            "category": "asset",
            "dataPoints": [
              { "date": "2026-08-22", "value": 4050112.88 },
              { "date": "2026-08-29", "value": 4126069.16 }
            ],
            "children": [
              {
                "label": "Apple Inc",
                "category": "asset",
                "itemId": "27f59377-44e8-4a94-ac3b-f1e1c5080a48",
                "sectionId": "0f0b3a2e-6d51-4e42-9f0e-6d51c1a2b3c4",
                "portfolioId": "6eb1ac79-2ae1-49e6-aada-3a5fb4fdce55",
                "isArchived": false,
                "dataPoints": [
                  { "date": "2026-08-22", "value": 878220.45 },
                  { "date": "2026-08-29", "value": 892401.00 }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  "errorCode": 0
}

Reading results        

results         is flat: one entry per report × time range, so its length equals reports         × timeRanges        . A combination that produced no data still appears, with "rows": []        .

Each entry is a Recap Series object; each row is a Recap Row object.

Report IDs

Valid values for the reports         query parameter.

Report ID What it shows
networth   Total net worth over time
sheets_and_sections   Breakdown by your portfolio sheets and sections
asset_classes   Assets by class (stocks, real estate, crypto, …)
investable   Investable assets
investable_without_cash   Investable assets excluding cash
investable_by_sheets_and_sections   Investable assets within each sheet and section
investable_without_cash_by_sheets_and_sections   Same, excluding cash
cash_on_hand   Cash holdings over time
assets_and_currency   Fiat assets by currency
stocks_and_geography   Stocks by geography
stocks_and_sector   Stocks by sector
stocks_and_marketcap   Stocks by market cap tier
crypto   Crypto holdings
brokerages   Holdings by brokerage / institution
taxable_assets   Assets by tax treatment (taxable, tax-deferred, tax-free)

Time ranges

Valid values for the timeRanges         query parameter: today        , daily        , weekly        , monthly        , quarterly        , yearly        .

Each range determines which historical snapshot represents a period — weekly keeps the Saturday point, monthly the last point of the calendar month, yearly the last point of the year.

Report types

Valid values for the reportType         query parameter.

reportType   Result
totals   valueType: "number"   — each value   is an amount in currency  
percentageAllocation   valueType: "percentage"   — each value   is a rounded share of the total, with the unrounded figure in preciseValue  

Object reference

Primitives first, then the composite objects that use them.

Value object

Field Description
amount   Numeric amount
currency   Currency code

Rate object

Field Description
currency   Currency
price   Price

Connection object

Field Description
accountId   Account id
aggregator   Aggregator name
id   Connection id
lastUpdated   When the connection last synced, ISO 8601, or null  
providerName   Institution name
providerId   Institution id at the aggregator
accountName  , accountMask   Account name and masked number at the institution
container   Account type reported by the aggregator
chain  , walletAddress   For a connected crypto wallet
status   Connected   or Connection Error  
statusReason   Only on an error: reconnect required  , login expiring soon  , connection being removed   or account no longer found at provider  

Document object

Field Description
fileType   MIME type
id   Document id
name   Name
size   Size in bytes
category   Document category, or null  
lastModified   ISO 8601, or null  
folder   Folder name, or null  
attachedTo   The item the document is attached to — { id, name, archived }   — or null  

Portfolio object

Returned by List portfolios.

Field Description
id   Portfolio id — use as {portfolioId}  
name   Portfolio name
currency   Portfolio currency

Asset / Debt / Insurance object

The three share one shape; category         tells them apart.

Field Description
id   Asset / debt / insurance id — use as {itemId}  
name   Name
category   Type — asset   / debt   / insurance  
subType   More detailed item type
description   Item description
note   Item note
value   Value object. Net of ownership and of any Value Adjustment
cost   Cost object
quantity   Quantity
rate   Rate object
ownership   Share owned, as a fraction (1   = 100%). A holding reports its parent’s
irr   IRR
irrType   Assets only: costbasis  , cashflow   or holding  
valueAdjustment   { amount, currency }   — the Value Adjustment, already subtracted from value  . Present only when set
cashIn   Value object
cashOut   Value object
committedCapital   Value object
unfunded   Value object
investable   Investable type — non_investable   / investable_easy_convert   / investable_cash  
ticker   Ticker symbol
tickerId   Ticker id
tickerSector   Sector name
tickerSubType   Asset class
isin   ISIN
exchange   Exchange name
accountNumber   Account number
holdingsCount   Number of holdings
parent   Parent account object
sectionId   Containing section id
sectionName   Section name
sheetId   Containing sheet id
sheetName   Sheet name
costBasisForTax   Value object
taxStatus   taxable   / tax-deferred   / tax-exempt  . On every asset
taxRate   Tax percentage. On every asset
taxOnUnrealizedGain   Value object
connection   Connection object
hidden   1   if the item is hidden, else 0  
cash   1   on an investable-cash item
liquidity   Assets: high  , low   or unknown  
assetClass  , type   Asset class and instrument type, lowercase
sector  , marketCap  , geography   Lowercase sector; Large Cap   / Mid Cap   / Small Cap   / Unknown   for stocks, else null  ; { country, region }  
isManual   true   for a manually added item
availableCredit   Debt: { kind: "availableCredit", amount, currency }  . Asset: { kind: "unvested", quantity }  
linkedDebt  , equity   Real estate and vehicles with linked debts: { ids, amount, currency }   and { amount, currency }  , in the portfolio currency
cruiseControl   The item’s enabled Cruise Control rules. Not on insurance
owners   [{ name, type, isDefault, percentage }]  , when the portfolio uses ownership tags

Example — holding

A holding belongs to a parent account and carries a parent         object.

{
  "id": "9f4445b3-0b25-49fb-b1d9-4387e081639d_8E4L9XLl6MudjEpwPAAgivmdZRdBPJuvMPlPb",
  "name": "Nflx Feb 01'18 $355 Call",
  "sectionId": "9fcbee08-3524-470f-9352-ba99f0f6cf37",
  "sectionName": "Chase - Plaid IRA - 5555",
  "sheetId": "627a1796-f28e-4646-a796-d983e8576d46",
  "sheetName": "Sheet 6",
  "category": "asset",
  "value": { "amount": 110, "currency": "USD" },
  "ticker": "USD",
  "tickerId": 150,
  "tickerSubType": null,
  "tickerSector": "Other",
  "quantity": 110,
  "irr": 1099900,
  "investable": "investable_easy_convert",
  "ownership": 1,
  "description": null,
  "note": null,
  "isin": null,
  "subType": "derivative",
  "holdingsCount": 0,
  "cost": { "amount": 0.01, "currency": "USD" },
  "costBasisForTax": { "amount": 0.01, "currency": "USD" },
  "taxRate": 30,
  "taxStatus": "taxable",
  "taxOnUnrealizedGain": { "amount": 32.997, "currency": "USD" },
  "connection": {
    "aggregator": "plaid",
    "providerName": "Chase",
    "lastUpdated": "2024-08-12T08:52:41.000Z",
    "id": "9DorR9zEmNsqA6xvyM1JtmolBWNWNjfRzBpv4",
    "accountId": "P6LzkBlD1Xhn3B649KLvcxE14Jm37Vuo47Wzk"
  },
  "parent": {
    "id": "9f4445b3-0b25-49fb-b1d9-4387e081639d",
    "name": "Chase - Plaid IRA - 5555"
  }
}

Example — account

A top-level account has no parent         and reports holdingsCount         > 0.

{
  "id": "9f4445b3-0b25-49fb-b1d9-4387e081639d",
  "name": "Chase - Plaid IRA - 5555",
  "sectionId": "9fcbee08-3524-470f-9352-ba99f0f6cf37",
  "sectionName": "Chase - Plaid IRA - 5555",
  "sheetId": "627a1796-f28e-4646-a796-d983e8576d46",
  "sheetName": "Sheet 6",
  "category": "asset",
  "value": { "amount": 249.20000000000002, "currency": "USD" },
  "ticker": "USD",
  "tickerId": 150,
  "tickerSubType": null,
  "tickerSector": "Other",
  "quantity": 249.20000000000002,
  "irr": 522.8442889277682,
  "investable": "investable_easy_convert",
  "ownership": 1,
  "description": null,
  "note": null,
  "isin": null,
  "subType": "investment",
  "holdingsCount": 2,
  "cost": { "amount": 40.01, "currency": "USD" },
  "costBasisForTax": { "amount": 40.01, "currency": "USD" },
  "taxRate": 30,
  "taxStatus": "taxable",
  "taxOnUnrealizedGain": { "amount": 62.757000000000005, "currency": "USD" },
  "connection": {
    "aggregator": "plaid",
    "providerName": "Chase",
    "lastUpdated": null,
    "id": "9DorR9zEmNsqA6xvyM1JtmolBWNWNjfRzBpv4",
    "accountId": "P6LzkBlD1Xhn3B649KLvcxE14Jm37Vuo47Wzk"
  }
}

Cash flow object

Field Type Description
cashIn   number | null Money in
cashOut   number | null Money out
currency   string Currency code
date   string YYYY-MM-DD  
note   string Free-text note

Ticker object

Field Type Description
id   number Kubera's internal ticker id
ticker   string The short symbol. This is the value to send as ticker   when creating an item
name   string Full instrument name
type   string stock  , fund  , derivative  , bond  , crypto  , fiat   or index  
subType   string Finer classification, e.g. Common Stock  
code   string Exchange code for the instrument
market   string Exchange or market, e.g. NASDAQ  
currency   string Currency the instrument is priced in
countryCode   string ISO country code

Sheet object

Field Type Description
id   string Sheet id
name   string Sheet name
category   string Asset  , Debt   or Insurance  . Decides which side of net worth the sheet's items count on
portfolioId   string Portfolio the sheet belongs to
sortKey   string Numeric string ordering the sheet within its category
updateFrequency   number Reminder cadence for refreshing manual values. See Create sheet
tsArchive   number Epoch seconds when the sheet was archived; 0   when it is active
irr   string Internal rate of return, when computed
sections   array Section objects. Returned by Create sheet, not by Update sheet

Section object

Field Type Description
id   string Section id
name   string Section name
sheetId   string Sheet the section sits on
sortKey   string Numeric string ordering the section within its sheet
expanded   number 1   if it renders expanded in the app, 0   if collapsed. Cosmetic only
columnSortKey   string Column the section is sorted by in the app
columnSortOrder   string Sort direction for that column
tsArchive   number Epoch seconds when archived; 0   when active

Fund schedule object

A scheduled capital call or distribution. These do not affect IRR, returns or net worth — see Get fund schedule.

Field Type Description
type   string capitalcall   or distribution  
date   string YYYY-MM-DD  . May be in the future
value   number Always positive — direction comes from type  
currency   string Currency of value  
note   string Free-text note; empty string when unset

Recap Series object

One entry in results        .

{
  "timeRange": "weekly",
  "report": "asset_classes",
  "currency": "USD",
  "valueType": "number",
  "rows": []
}
Field Description
timeRange   One of the requested time ranges
report   One of the requested report IDs
currency   Always present
valueType   number   or percentage   — see report types
rows   Array of Recap Row objects

Recap Row object

Field Present on Description
label   All rows Display name of the group or holding
dataPoints   All rows The row’s time series, ordered oldest → newest. Each point is { "date": "YYYY-MM-DD", "value": number }  
children   Group rows Nested rows. Omitted on leaf rows — never an empty array
category   Most rows asset   or debt  
itemId   Leaf rows The asset/debt id — use it with the item endpoints
sectionId  , sheetId   Where applicable Ids of the containing section and sheet
portfolioId   Leaf rows Owning portfolio — differs from the requested one for linked portfolios
isArchived   Leaf rows Present and true   when the item is archived
isLinkedPortfolio   Leaf rows Present and true   when the row comes from a linked portfolio

To distinguish a group row from a leaf row, test for the presence of children         — leaf rows omit the key entirely.


Unofficial SDKs

These are not maintained or verified by the Kubera team. Use at your own risk.