Running the Balance Report via the API
Generate the Balance Report through Bitwave's asynchronous reporting API, poll for completion, and fetch the result as structured rows or a CSV file.
Overview
Generate the Balance Report programmatically through Bitwave's asynchronous reporting API, then fetch the completed report as structured rows or a CSV file.
Reports can take time to compute, so use this required flow:
- Start a report run.
- Poll until the run reaches a terminal status.
- Fetch the result after the run succeeds.
Use the API base URL https://api.bitwave.io for all endpoints.
Prerequisites
Before you run the examples, complete these steps:
- Get a Bearer access token. See Generating an Authentication Token.
- Confirm the API key has reporting access on the organization.
- Find the organization id you want to report on.
- Install
curlto run the request examples. - Install
jqif you want to run the end-to-end shell example.
Replace these placeholders throughout the guide:
| Placeholder | Description |
|---|---|
ORG_ID | Your Bitwave organization id. |
ACCESS_TOKEN | A valid Bearer access token. Tokens are valid for 3600 seconds. |
REPORT_RUN_ID | The reportRunId returned when you start the report run. |
Refresh the access token when it expires.
Request flow
1. Start a report run
POST /v2/orgs/{ORG_ID}/report-runs
Start a Balance Report run and save the returned reportRunId. You will use that id to poll for status and fetch the result.
Endpoint details
| Field | Value |
|---|---|
| Method | POST |
| URL | https://api.bitwave.io/v2/orgs/{ORG_ID}/report-runs |
| Authorization | Authorization: Bearer ACCESS_TOKEN |
| Content-Type | application/json |
| Returns | A JSON object containing reportRunId. |
Request body
{
"reportType": "balance-report",
"inputs": [
{ "key": "endDate", "value": "2026-06-30" },
{ "key": "groupBy", "value": "0" },
{ "key": "currency", "value": "USD" }
]
}inputs is a list of { "key", "value" } pairs. All values are strings, including numbers and booleans, such as "true" and "0". The API rejects unknown keys.
| Key | Required | Value | Description |
|---|---|---|---|
endDate | Yes | YYYY-MM-DD | Balance "as of" the end of this date, in the org's local time zone. |
groupBy | Yes | 0 or 1 | 0 = one row per asset; 1 = group rows by wallet. |
currency | No | Fiat code, e.g. USD | Reporting currency. Defaults to the org's base currency. |
walletIds | No | JSON array string, e.g. ["wal_abc"] | Restrict to specific wallets. |
subsidiaryIds | No | JSON array string | Restrict to specific subsidiaries. |
asOfTimestampSec | No | Unix seconds | Strike the balance at a precise instant (intraday NAV) instead of end-of-day. |
includeIgnored | No | true/false | Include transactions marked as ignored. Defaults to false. |
excludeNft | No | true/false | Exclude NFT balances. |
skipPricing | No | true/false | Skip pricing entirely (no fiat values). |
skipPricingSpam | No | true/false | Skip pricing for spam-classified tokens. |
title | No | string | Custom report title. Auto-generated when omitted. |
Example request
curl -X POST "https://api.bitwave.io/v2/orgs/ORG_ID/report-runs" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reportType": "balance-report",
"inputs": [
{ "key": "endDate", "value": "2026-06-30" },
{ "key": "groupBy", "value": "0" }
]
}'Response 200 OK
200 OK{
"reportRunId": "9f1c0c5e-3b2a-4d2e-8a77-2b6f4d1e9aa1"
}A 2xx response with a reportRunId means the API accepted the run. Save the id for the next step.
2. Poll for status
GET /v2/orgs/{ORG_ID}/report-runs/{reportRunId}/status
Poll this endpoint until the run reaches a terminal status. Wait a few seconds between polls because the API rate-limits each organization to roughly 100 requests per minute.
Avoid tight polling loops.
Endpoint details
| Field | Value |
|---|---|
| Method | GET |
| URL | https://api.bitwave.io/v2/orgs/{ORG_ID}/report-runs/{reportRunId}/status |
| Authorization | Authorization: Bearer ACCESS_TOKEN |
| Returns | A JSON object containing status. |
Example request
curl "https://api.bitwave.io/v2/orgs/ORG_ID/report-runs/REPORT_RUN_ID/status" \
-H "Authorization: Bearer ACCESS_TOKEN"Response
{ "status": "running" }| Status | Terminal | Meaning |
|---|---|---|
new | No | Run accepted, not yet started. |
running | No | Report is generating. |
succeeded | Yes | Complete — fetch the result. |
failed | Yes | Generation failed. |
timed-out | Yes | Generation exceeded its time budget. |
Fetch the result only after the status is succeeded.
3a. Fetch the result as structured rows
GET /v2/orgs/{ORG_ID}/report-runs/{reportRunId}
Fetch the completed report as structured columns and rows.
Endpoint details
| Field | Value |
|---|---|
| Method | GET |
| URL | https://api.bitwave.io/v2/orgs/{ORG_ID}/report-runs/{reportRunId} |
| Authorization | Authorization: Bearer ACCESS_TOKEN |
| Returns | A JSON report result with columns, rows, and optional download metadata. |
Example request
curl "https://api.bitwave.io/v2/orgs/ORG_ID/report-runs/REPORT_RUN_ID" \
-H "Authorization: Bearer ACCESS_TOKEN"Response
{
"reportType": "balance-report",
"reportRunId": "9f1c0c5e-3b2a-4d2e-8a77-2b6f4d1e9aa1",
"title": "Balance Report",
"columns": ["Ticker", "Amount", "Fiat Value", "Wallet", "Wallet Address",
"Token Address", "Token ID", "Name", "Subsidiary"],
"rows": [
{ "cells": ["BTC", "1.5", "60000.00", "Main Wallet", "bc1q…", "", "", "Bitcoin", ""],
"expandable": false, "rows": [] }
],
"filterOptions": [],
"downloadLink": {
"href": "/v2/orgs/ORG_ID/report-runs/REPORT_RUN_ID/download",
"method": "get"
}
}Each row's cells array aligns positionally with columns. Calling this endpoint before the run succeeds returns an error.
Response fields
| Field | Type | Description |
|---|---|---|
reportType | string | The report type, e.g. balance-report. |
reportRunId | string | The run id. This echoes the path parameter. |
title | string | Human-readable report title. |
subTitle1, subTitle2 | string (optional) | Optional subtitles, when present. |
columns | string[] | Ordered column headers. Each row's cells aligns to this order. |
rows | ReportRow[] | The report rows. See ReportRow fields. |
filterOptions | object[] | Available filter options; each is { label, key, options?, boolean? }. Empty for the Balance Report. |
downloadLink | object (optional) | { "href", "method" } pointing at the CSV download endpoint. |
ReportRow fields
| Field | Type | Description |
|---|---|---|
cells | string[] | Row values, positionally aligned with columns. |
expandable | boolean | Whether the row has nested drill-down rows. |
rows | ReportRow[] | Nested rows. Present when expandable is true; empty otherwise. |
headerText | string (optional) | Section header label, when the row is a header. |
isTotalRow | boolean (optional) | true for total or summary rows. |
additionalRowsToken | string (optional) | Pagination token for fetching further nested rows, when present. |
For the Balance Report, the columns are Ticker, Amount, Fiat Value, Wallet, Wallet Address, Token Address, Token ID, Name, and Subsidiary.
3b. Fetch the result as a CSV download
GET /v2/orgs/{ORG_ID}/report-runs/{reportRunId}/download
Download the completed report as a CSV file. The endpoint streams the file with Content-Type: text/csv.
Only succeeded runs can be downloaded.
Endpoint details
| Field | Value |
|---|---|
| Method | GET |
| URL | https://api.bitwave.io/v2/orgs/{ORG_ID}/report-runs/{reportRunId}/download |
| Authorization | Authorization: Bearer ACCESS_TOKEN |
| Returns | A CSV file stream. |
Example request
curl "https://api.bitwave.io/v2/orgs/ORG_ID/report-runs/REPORT_RUN_ID/download" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-o balance-report.csvA successful request creates a balance-report.csv file in your current directory.
Optional: List previous runs
GET /v2/orgs/{ORG_ID}/report-runs?type=balance-report&limit=20
List recent Balance Report runs for the organization, most recent first. Use this endpoint when you need to find a previous run or inspect recent run statuses.
| Query parameter | Required | Default | Maximum | Description |
|---|---|---|---|---|
type | Yes | None | N/A | Report type to list. Use balance-report. |
limit | No | 50 | 200 | Number of runs to return. |
pageToken | No | None | N/A | Token from nextPageToken to fetch older runs. |
Use the nextPageToken from the response as the pageToken query parameter to page through older runs.
Example response
{
"items": [
{
"orgId": "org_abc",
"reportRunId": "9f1c0c5e-3b2a-4d2e-8a77-2b6f4d1e9aa1",
"reportType": "balance-report",
"status": "succeeded",
"startedAtSec": 1782345600,
"completedAtSec": 1782345712
}
],
"nextPageToken": "…"
}Timestamps, including startedAtSec and completedAtSec, are Unix epoch seconds. completedAtSec is absent until the run reaches a terminal status. errors and config may also appear.
End-to-end example
Run this shell example to get a token, start a Balance Report run, poll until completion, and download the CSV result.
# 1. Get a token
ACCESS_TOKEN=$(curl -s -X POST \
"https://api.bitwave.io/v2/oauth/token?grant_type=client_credentials" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=YOUR_API_KEY&client_secret=YOUR_API_SECRET" | jq -r .access_token)
# 2. Start the run
RUN_ID=$(curl -s -X POST "https://api.bitwave.io/v2/orgs/ORG_ID/report-runs" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"reportType":"balance-report","inputs":[{"key":"endDate","value":"2026-06-30"},{"key":"groupBy","value":"0"}]}' \
| jq -r .reportRunId)
# 3. Poll until terminal
while :; do
STATUS=$(curl -s "https://api.bitwave.io/v2/orgs/ORG_ID/report-runs/$RUN_ID/status" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq -r .status)
echo "status: $STATUS"
[ "$STATUS" = "succeeded" ] && break
case "$STATUS" in
failed|timed-out)
echo "run did not succeed"
exit 1
;;
esac
sleep 5
done
# 4. Download the CSV
curl -s "https://api.bitwave.io/v2/orgs/ORG_ID/report-runs/$RUN_ID/download" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-o balance-report.csvSuccessful output includes polling messages like this and creates balance-report.csv:
status: new
status: running
status: succeededError handling
Handle these common response states when you integrate with the Balance Report API:
| Scenario | What it means | What to do |
|---|---|---|
400 when starting a run | One or more inputs failed validation. | Inspect the response body for every invalid field, fix the request body, and retry. |
| Other non-2xx status | The API could not start the run or process the request. | Log the status code and response body, then retry only after you understand the cause. |
failed status | Report generation failed. | Stop polling and inspect any returned error details. |
timed-out status | Report generation exceeded its time budget. | Stop polling and start a new run if you still need the report. |
Fetch before succeeded | The report result is not ready. | Continue polling until the status is succeeded. |
| Expired token | The access token is no longer valid. | Request a new token and retry the API call. |
Best practices
- Use string input values: Send every
inputs.valueas a string, including numbers and booleans. - Poll with a delay: Wait a few seconds between status checks to avoid tight loops and organization-level rate limits.
- Check terminal statuses: Treat
succeeded,failed, andtimed-outas terminal states. - Fetch after success only: Request structured rows or CSV downloads only after the run status is
succeeded. - Refresh tokens: Access tokens are valid for 3600 seconds. Request a new token when the current one expires.
- Save the run id: Store
reportRunIdso you can poll, fetch, download, or look up the run later.
Next steps
- Use Generating an Authentication Token to review token creation and expiration handling.
- Use Balance Report to compare the API output with the in-app Balance Report workflow.
