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/jsonon every request - Auth: HMAC-SHA256 signed headers (see Authentication)
On this page
Getting started
Portfolio endpoints
Item endpoints
Cash flow endpoints
Recap report endpoints
Reference
Endpoint index
Every endpoint requires the three authentication headers. All paths are relative to https://api.kubera.com .
| # | Purpose | Method | Path | Body | Returns |
|---|---|---|---|---|---|
| 1 | List portfolios | GET | /api/v3/data/portfolio |
— | portfolio[] |
| 2 | Get portfolio data | GET | /api/v3/data/portfolio/{portfolioId} |
— | Portfolio with assets, debts, totals |
| 3 | Create item | POST | /api/v3/data/item |
Yes | New item ids |
| 4 | Update item | POST | /api/v3/data/item/{itemId} |
Yes | Success envelope |
| 5 | Archive item | POST | /api/v3/data/item/{itemId}/archive |
— | Empty data |
| 6 | Get cash flow | GET | /api/v3/data/item/{itemId}/cashFlow |
— | cashFlow[] |
| 7 | Insert/update cash flow | POST | /api/v3/data/item/{itemId}/cashFlow |
Yes | Success envelope |
| 8 | Submit recap report | GET | /api/v3/data/portfolio/{portfolioId}/recap/report |
— | 202 + reportId |
| 9 | Poll recap report | GET | /api/v3/data/portfolio/{portfolioId}/recap/report/{reportId} |
— | Status, then results |
{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 or POST |
requestPath |
Path only, starting with /api/v3/.... No host, no query string |
body |
The request body serialized with compact encoding (no spaces between keys/values). Empty string for requests with no body, such as GET |
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.
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
body_data = json.dumps(request_body, separators=(',', ':')) if request_body else ""
request_type = "POST"
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 | Returned as a Value object — { "amount": number, "currency": string } — not a bare number |
| 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 | 6000 requests per day (UTC) |
| Kubera Black | 24000 requests per day (UTC) |
| Kubera for Business | 24000 requests per day (UTC) |
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": "Portfolio currency",
"timestamp": "Time",
"asset": [],
"debt": [],
"document": [],
"insurance": [],
"totalAssets": { "amount": 0, "currency": 0 },
"totalDebts": { "amount": 0, "currency": 0 },
"netWorth": { "amount": 0, "currency": 0 },
"costBasis": 0,
"unrealizedGain": 0,
"allocationByAssetClass": {
"Cash": 0,
"Crypto": 0,
"Stock": 0,
"Fund": 0,
"Derivative": 0,
"Investment": 0
}
},
"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 |
totalAssets, totalDebts, netWorth |
object | Value objects |
costBasis, unrealizedGain |
number | — |
allocationByAssetClass |
object | Allocation per asset class |
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 item
POST /api/v3/data/item
Create a new manual asset or debt in a portfolio. The item is added to the portfolio’s first Asset section (or first Debt section when isDebt is true ), unless sheetName / sectionName match an existing section.
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 |
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. Ignored when ticker is set. Defaults to the portfolio currency |
cost |
number | No | Cost basis as a monetary amount |
description |
string | No | — |
isDebt |
boolean | No | true to create a debt. Defaults to false (asset) |
sheetName |
string | No | Target an existing section by sheet name (use with sectionName) |
sectionName |
string | No | Target an existing section by name |
symbol is accepted as a legacy alias for ticker . Prefer ticker .
Response
{
"data": {
"success": true,
"itemId": "cust-abc",
"portfolioId": "6eb1ac79-2ae1-49e6-aada-3a5fb4fdce55",
"sectionId": "9fcbee08-3524-470f-9352-ba99f0f6cf37"
},
"errorCode": 0
}
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 the value at that historical date instead of today |
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 account is archived |
| 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 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 item, or update the existing entry for that date.
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 | YYYY-MM-DD |
currency |
string | ISO currency code |
note |
string | Free-text note |
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 submit-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.
Submit 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¤cy=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 submitted and is still cached.
Response — another report is already running
If a different recap request is already in flight, the submit 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 submit 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 resubmit.
Poll recap report
GET /api/v3/data/portfolio/{portfolioId}/recap/report/{reportId}
Fetch the state — and, once finished, the contents — of a submitted report.
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 |
lastUpdatedTimestamp |
Last updated timestamp |
providerName |
Institution name |
Document object
| Field | Description |
|---|---|
fileType |
MIME type |
id |
Document id |
name |
Name |
size |
Size in bytes |
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 |
cost |
Cost object |
quantity |
Quantity |
rate |
Rate object |
ownership |
Ownership percentage |
irr |
IRR |
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 |
taxability |
taxable / tax-deferred / tax-free |
taxRate |
Tax percentage |
taxOnUnrealizedGain |
Value object |
connection |
Connection object |
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": 100,
"description": null,
"note": null,
"isin": null,
"subType": "derivative",
"holdingsCount": 0,
"cost": { "amount": 0.01, "currency": "USD" },
"costBasisForTax": { "amount": 0.01, "currency": "USD" },
"taxRate": 30,
"taxability": "taxable",
"taxOnUnrealizedGain": { "amount": 32.997, "currency": "USD" },
"connection": {
"aggregator": "plaid",
"providerName": "Chase",
"lastUpdatedTimestamp": 1723452761,
"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": 100,
"description": null,
"note": null,
"isin": null,
"subType": "investment",
"holdingsCount": 2,
"cost": { "amount": 40.01, "currency": "USD" },
"costBasisForTax": { "amount": 40.01, "currency": "USD" },
"taxRate": 30,
"taxability": "taxable",
"taxOnUnrealizedGain": { "amount": 62.757000000000005, "currency": "USD" },
"connection": {
"aggregator": "plaid",
"providerName": "Chase",
"lastUpdatedTimestamp": 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 |
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.