Portfolios
Portfolios
GET /api/portfolios A portfolio is a basket of assets held at set weights. It is valued every day as an index, which starts at 100 on the portfolio’s first day, its inception date. After that, the index moves with the value of the holdings.
From time to time a portfolio is rebalanced: it gets a new set of target weights, and the index carries on from where it was. Between rebalances the weights drift as prices move.
Every portfolio is valued in US dollars. Each one has a cash holding, USDCASH, which earns nothing. The weights of a basket add up to 1, cash included. Weights are fractions throughout: 0.25 means 25%.
The examples on this page use an illustrative portfolio, bytefolio-crypto. Use GET /api/portfolios for the portfolios you can read.
Routes
Six routes read portfolios. Four describe the portfolio and its aggregate figures. Two show the holdings.
| Route | Returns | Shows the holdings |
|---|---|---|
GET /api/portfolios |
Every portfolio | No |
GET /api/portfolios/:slug |
One portfolio’s settings | No |
GET /api/portfolios/:slug/series |
The daily index, with returns, risk figures and average trend scores | No |
GET /api/portfolios/:slug/eligible-symbols |
The assets the portfolio can hold | No |
GET /api/portfolios/:slug/rebalances |
Each basket of target weights, in order | Yes |
GET /api/portfolios/:slug/constituents |
What the portfolio holds now, and at what weight | Yes |
The aggregate routes never return the weight of a single holding. For that, use Rebalances and Constituents.
:slug is the portfolio’s slug, as listed by GET /api/portfolios. An unknown slug returns 404.
Each route rejects a query parameter it doesn’t list with 400. format is always accepted (see Response Format).
List portfolios
Every portfolio, ordered by sortOrder and then by slug. Archived portfolios are left out unless you ask for them.
Example
curl https://api.bytetree.io/api/portfolios \
-H "Authorization: Bearer YOUR_API_KEY" const res = await fetch("https://api.bytetree.io/api/portfolios", {
headers: { "Authorization": "Bearer YOUR_API_KEY" },
});
const body = await res.json(); import requests
res = requests.get(
"https://api.bytetree.io/api/portfolios",
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
body = res.json() 200 {
"count": 1,
"total": 1,
"limit": 500,
"offset": 0,
"data": [
{
"slug": "bytefolio-crypto",
"label": "ByteFolio — Crypto",
"description": "Bitcoin and Ether, with a cash buffer.",
"universe": "bytefolio",
"assetType": "crypto",
"baseCurrency": "USD",
"inceptionDate": "2025-01-01T00:00:00.000Z",
"rebaseValue": 100,
"cashSymbol": "USDCASH",
"cashYieldMode": "zero",
"benchmarkSymbol": "MSCIWORLD",
"enabled": true,
"sortOrder": 0,
"parentSlug": null,
"source": "manual",
"archivedAt": null
}
]
} Include archived portfolios
curl "https://api.bytetree.io/api/portfolios?includeArchived=true" \
-H "Authorization: Bearer YOUR_API_KEY" Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit |
number |
No | Maximum number of portfolios per page. Default 500, capped at 1000 |
offset |
number |
No | Number of portfolios to skip. Default 0. Pair with limit to paginate |
includeArchived |
string |
No | true to include archived portfolios; false, the default, leaves them out. An empty value is the same as leaving it out; any other value returns 400 |
Response
This route uses the paginated list envelope. Each entry in data is a portfolio object.
Get a portfolio
One portfolio’s settings. The holdings are not included: see Constituents.
Request
curl https://api.bytetree.io/api/portfolios/bytefolio-crypto \
-H "Authorization: Bearer YOUR_API_KEY"
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug |
string |
Yes | The portfolio’s slug |
This route takes no query parameters other than format.
Response
{ "data": { … } }, where data is a portfolio object. An archived portfolio can still be read here.
{
"data": {
"slug": "bytefolio-crypto",
"label": "ByteFolio — Crypto",
"description": "Bitcoin and Ether, with a cash buffer.",
"universe": "bytefolio",
"assetType": "crypto",
"baseCurrency": "USD",
"inceptionDate": "2025-01-01T00:00:00.000Z",
"rebaseValue": 100,
"cashSymbol": "USDCASH",
"cashYieldMode": "zero",
"benchmarkSymbol": "MSCIWORLD",
"enabled": true,
"sortOrder": 0,
"parentSlug": null,
"source": "manual",
"archivedAt": null
}
}
The portfolio object
| Field | Type | Description |
|---|---|---|
slug |
string |
The portfolio’s identifier, used in the paths on this page |
label |
string |
Display name |
description |
string or null |
A short description |
universe |
string |
The universe the portfolio draws its holdings from, or macro for one that can hold anything ByteTree tracks |
assetType |
string or null |
The type of asset the portfolio holds: stock, crypto, etf, index or commodity. null when it is not limited to one type. It decides the eligible symbols |
baseCurrency |
string |
Always "USD" |
inceptionDate |
string |
The first day the portfolio is valued, as an ISO 8601 timestamp at 00:00 UTC |
rebaseValue |
number |
The index level on the inception date: 100 |
cashSymbol |
string |
The symbol of the cash holding: "USDCASH" |
cashYieldMode |
string |
What cash earns: "zero", so cash earns nothing |
benchmarkSymbol |
string or null |
The benchmark the portfolio is set against, for example "MSCIWORLD". No figures relative to it are returned yet |
enabled |
boolean |
true while the portfolio is valued each day |
sortOrder |
number |
Display order, lowest first. The list is sorted by it |
parentSlug |
string or null |
The slug of the portfolio this one is part of, or null when it stands alone |
source |
string |
How the weights are chosen: manual (by hand) or algo (by an algorithm) |
archivedAt |
string or null |
When the portfolio was archived, or null. An archived portfolio is no longer valued each day, but its history can still be read |
Performance series
The portfolio’s index, one row per calendar day from the inception date, oldest first. Each row also carries the index’s returns and risk figures, and the average trend score of the holdings.
Request
# This year's series
curl "https://api.bytetree.io/api/portfolios/bytefolio-crypto/series?from=2026-01-01" \
-H "Authorization: Bearer YOUR_API_KEY"
# Just the index level and the daily return
curl "https://api.bytetree.io/api/portfolios/bytefolio-crypto/series?fields=indexLevel,dailyReturn" \
-H "Authorization: Bearer YOUR_API_KEY"
# As CSV
curl "https://api.bytetree.io/api/portfolios/bytefolio-crypto/series?from=2026-01-01&format=csv" \
-H "Authorization: Bearer YOUR_API_KEY"
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug |
string |
Yes | The portfolio’s slug |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
from |
string |
No | First day to return, YYYY-MM-DD, inclusive |
to |
string |
No | Last day to return, YYYY-MM-DD, inclusive |
fields |
string |
No | Comma-separated names from the row table. date is always included. A name that isn’t in the table is left out of the response, not an error |
limit |
number |
No | Maximum number of rows per page. Default 2000, capped at 10000 |
offset |
number |
No | Number of rows to skip. Default 0 |
Dates and field names are checked as described in Value validation.
Response
The paginated list envelope, with the portfolio’s slug beside it:
| Field | Type | Description |
|---|---|---|
slug |
string |
The portfolio’s slug |
count |
number |
Rows on this page |
total |
number |
Rows in the date range, ignoring limit and offset |
limit |
number |
The limit applied |
offset |
number |
The offset applied |
data |
object[] |
The rows, oldest first |
Series rows
These are the only fields a row can have. fields can narrow them but cannot add others.
| Field | Type | Description |
|---|---|---|
date |
string |
The day, as an ISO 8601 timestamp at 00:00 UTC |
indexLevel |
number |
The index level at the day’s close. It is 100 on the inception date |
dailyReturn |
number or null |
The change in indexLevel from the day before, as a fraction (0.0133 means +1.33%). null on the inception date |
returnUsd1W, returnUsd1M, returnUsd3M, returnUsd6M, returnUsd12M |
number or null |
The index’s return over the last week, month, 3 months, 6 months and 12 months, as a fraction. null until the portfolio has that much history |
returnUsdInception |
number |
The return since inception, as a fraction: indexLevel / 100 − 1 |
avgByteTrendCapr200w |
number or null |
The holdings’ Weekly CAPR ByteTrend score, averaged by weight. Cash is left out |
avgByteTrendLocal200w |
number or null |
The same for the Weekly Local score. See Local and CAPR |
trendDistribution |
object |
The share of the portfolio in each trend regime, by Weekly CAPR, keyed by regime. Cash is in no regime, so the shares add up to 1 − cashWeight at most |
dominantTrend |
string or null |
The regime with the largest share in trendDistribution |
avgVolatility30w |
number or null |
The index’s annualised volatility over the last 30 weeks, as a fraction |
sharpe52w |
number or null |
The index’s annualised Sharpe ratio over the last 52 weeks, with a risk-free rate of 0 |
drawdown52w |
number or null |
The index’s largest fall from a peak over the last 52 weeks, as a negative fraction (-0.49 means −49%) |
constituentCount |
number |
Holdings with a weight above zero, not counting cash |
cashWeight |
number |
The share held in cash, as a fraction |
totalMarketCapUsd |
number or null |
The market caps of the holdings added together, in US dollars. Cash is left out |
dataQuality |
string |
computed when every holding had a price that day. partial when at least one holding’s price was carried forward from an earlier day |
Risk figures and longer returns are null until the portfolio has enough history.
Example
Two days of the series. Numbers are rounded.
{
"slug": "bytefolio-crypto",
"count": 2,
"total": 2,
"limit": 2000,
"offset": 0,
"data": [
{
"date": "2026-10-01T00:00:00.000Z",
"indexLevel": 88.4495,
"dailyReturn": 0.005,
"returnUsd1W": 0.0005,
"returnUsd1M": 0.0556,
"returnUsd3M": 0.3212,
"returnUsd6M": 0.1979,
"returnUsd12M": -0.2737,
"returnUsdInception": -0.1155,
"avgByteTrendCapr200w": 4,
"avgByteTrendLocal200w": 5,
"trendDistribution": { "Neutral": 0.7741 },
"dominantTrend": "Neutral",
"avgVolatility30w": 0.3022,
"sharpe52w": -0.7643,
"drawdown52w": -0.489,
"constituentCount": 2,
"cashWeight": 0.2259,
"totalMarketCapUsd": 1997058787299.06,
"dataQuality": "computed"
},
{
"date": "2026-10-02T00:00:00.000Z",
"indexLevel": 89.6249,
"dailyReturn": 0.0133,
"returnUsd1W": 0.0118,
"returnUsd1M": 0.0845,
"returnUsd3M": 0.3226,
"returnUsd6M": 0.2137,
"returnUsd12M": -0.2775,
"returnUsdInception": -0.1038,
"avgByteTrendCapr200w": 4.6514,
"avgByteTrendLocal200w": 5,
"trendDistribution": { "Leading Trend": 0.5062, "Neutral": 0.2709 },
"dominantTrend": "Leading Trend",
"avgVolatility30w": 0.2997,
"sharpe52w": -0.7565,
"drawdown52w": -0.489,
"constituentCount": 2,
"cashWeight": 0.2229,
"totalMarketCapUsd": 2036087330566.21,
"dataQuality": "computed"
}
]
}
Eligible symbols
The assets the portfolio can hold: every active asset of the portfolio’s assetType, sorted by symbol. When assetType is null, it is every active asset, which is a long list. This route is not paginated.
Request
curl https://api.bytetree.io/api/portfolios/bytefolio-crypto/eligible-symbols \
-H "Authorization: Bearer YOUR_API_KEY"
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug |
string |
Yes | The portfolio’s slug |
This route takes no query parameters other than format.
Response
| Field | Type | Description |
|---|---|---|
count |
number |
Number of assets |
data |
object[] |
One { symbol, name } per asset, sorted by symbol |
{
"count": 5,
"data": [
{ "symbol": "BTCUSD", "name": "Bitcoin USD" },
{ "symbol": "ETHUSD", "name": "Ethereum USD" },
{ "symbol": "HNTUSD", "name": "Helium USD" },
{ "symbol": "HYPEUSD", "name": "Hyperliquid USD" },
{ "symbol": "SKYUSD", "name": "Skycoin USD" }
]
}
Rebalances
Each basket of target weights the portfolio has had, in the order they took effect. The first one, sequence 1, is the basket the portfolio started with.
A rebalance’s weights are set at the close of its effectiveDate, so they first affect the next day’s return. Only rebalances in effect are returned: those dated today (UTC) or earlier. A rebalance dated in the future appears here once its date arrives.
Request
curl https://api.bytetree.io/api/portfolios/bytefolio-crypto/rebalances \
-H "Authorization: Bearer YOUR_API_KEY"
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug |
string |
Yes | The portfolio’s slug |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit |
number |
No | Maximum number of rebalances per page. Default 500, capped at 1000 |
offset |
number |
No | Number of rebalances to skip. Default 0 |
status |
string |
No | active or draft. Every rebalance returned is active, so active changes nothing and draft returns an empty page. An empty value is the same as leaving it out; any other value returns 400 |
Response
The paginated list envelope, with the portfolio’s slug beside it. data holds the rebalances, ordered by sequence.
| Field | Type | Description |
|---|---|---|
portfolioSlug |
string |
The portfolio’s slug |
effectiveDate |
string |
The day the weights take effect, at its close, as an ISO 8601 timestamp at 00:00 UTC |
sequence |
number |
The rebalance’s place in the portfolio’s history: 1, 2, 3 and so on |
constituents |
object[] |
The basket: one { symbol, targetWeight } per holding, cash included. The weights are fractions |
weightSum |
number |
The target weights added up: 1 |
source |
string |
How the weights were chosen: manual or algo |
note |
string or null |
A free-text note |
status |
string |
Always "active" |
appliedAt |
string or null |
When the rebalance was applied to the index series, as an ISO 8601 timestamp. null until then |
{
"slug": "bytefolio-crypto",
"count": 2,
"total": 2,
"limit": 500,
"offset": 0,
"data": [
{
"portfolioSlug": "bytefolio-crypto",
"effectiveDate": "2025-01-01T00:00:00.000Z",
"sequence": 1,
"constituents": [
{ "symbol": "BTCUSD", "targetWeight": 0.5 },
{ "symbol": "ETHUSD", "targetWeight": 0.3 },
{ "symbol": "USDCASH", "targetWeight": 0.2 }
],
"weightSum": 1,
"source": "manual",
"note": "Inception basket",
"status": "active",
"appliedAt": "2026-07-16T09:51:39.002Z"
},
{
"portfolioSlug": "bytefolio-crypto",
"effectiveDate": "2026-07-16T00:00:00.000Z",
"sequence": 2,
"constituents": [
{ "symbol": "BTCUSD", "targetWeight": 0.48 },
{ "symbol": "ETHUSD", "targetWeight": 0.24 },
{ "symbol": "USDCASH", "targetWeight": 0.28 }
],
"weightSum": 1,
"source": "manual",
"note": "Raise cash",
"status": "active",
"appliedAt": "2026-07-16T10:42:29.484Z"
}
]
}
Constituents
What the portfolio holds now: the basket from its latest rebalance, with each holding’s weight today. Cash is included. Holdings are sorted by symbol. This route is not paginated.
Request
curl https://api.bytetree.io/api/portfolios/bytefolio-crypto/constituents \
-H "Authorization: Bearer YOUR_API_KEY"
# Include holdings the portfolio has dropped
curl "https://api.bytetree.io/api/portfolios/bytefolio-crypto/constituents?includeInactive=true" \
-H "Authorization: Bearer YOUR_API_KEY"
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
slug |
string |
Yes | The portfolio’s slug |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
includeInactive |
string |
No | true to include holdings the portfolio has dropped, with active: false; false, the default, leaves them out. An empty value is the same as leaving it out; any other value returns 400 |
Response
| Field | Type | Description |
|---|---|---|
slug |
string |
The portfolio’s slug |
count |
number |
Number of holdings |
data |
object[] |
The holdings, sorted by symbol |
Each holding:
| Field | Type | Description |
|---|---|---|
symbol |
string |
The asset’s symbol, or USDCASH for cash |
currentTargetWeight |
number |
The weight set at the latest rebalance |
currentDriftWeight |
number or null |
The weight on the latest valued day, after prices have moved. null until the portfolio has been valued |
active |
boolean |
true while the portfolio holds it |
A dropped holding keeps the weights it last had while it was held.
{
"slug": "bytefolio-crypto",
"count": 3,
"data": [
{ "symbol": "BTCUSD", "currentTargetWeight": 0.48, "currentDriftWeight": 0.5085, "active": true },
{ "symbol": "ETHUSD", "currentTargetWeight": 0.24, "currentDriftWeight": 0.259, "active": true },
{ "symbol": "USDCASH", "currentTargetWeight": 0.28, "currentDriftWeight": 0.2325, "active": true }
]
}
Errors
Errors use the { "error": "<message>" } envelope.
| Status | When |
|---|---|
400 |
A query parameter the route doesn’t list, or a malformed value for limit, offset, from, to, fields, status, includeArchived or includeInactive. See Value validation |
401 |
Missing or invalid API key |
403 |
The key lacks the api+mcp capability |
404 |
No portfolio has that slug: { "error": "Not found" } |
An unknown query parameter is reported before an unknown slug. A malformed value is checked after the slug, so a bad value on an unknown slug returns 404.