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:

  1. Start a report run.
  2. Poll until the run reaches a terminal status.
  3. 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:

  1. Get a Bearer access token. See Generating an Authentication Token.
  2. Confirm the API key has reporting access on the organization.
  3. Find the organization id you want to report on.
  4. Install curl to run the request examples.
  5. Install jq if you want to run the end-to-end shell example.

Replace these placeholders throughout the guide:

PlaceholderDescription
ORG_IDYour Bitwave organization id.
ACCESS_TOKENA valid Bearer access token. Tokens are valid for 3600 seconds.
REPORT_RUN_IDThe 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

FieldValue
MethodPOST
URLhttps://api.bitwave.io/v2/orgs/{ORG_ID}/report-runs
AuthorizationAuthorization: Bearer ACCESS_TOKEN
Content-Typeapplication/json
ReturnsA 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.

KeyRequiredValueDescription
endDateYesYYYY-MM-DDBalance "as of" the end of this date, in the org's local time zone.
groupByYes0 or 10 = one row per asset; 1 = group rows by wallet.
currencyNoFiat code, e.g. USDReporting currency. Defaults to the org's base currency.
walletIdsNoJSON array string, e.g. ["wal_abc"]Restrict to specific wallets.
subsidiaryIdsNoJSON array stringRestrict to specific subsidiaries.
asOfTimestampSecNoUnix secondsStrike the balance at a precise instant (intraday NAV) instead of end-of-day.
includeIgnoredNotrue/falseInclude transactions marked as ignored. Defaults to false.
excludeNftNotrue/falseExclude NFT balances.
skipPricingNotrue/falseSkip pricing entirely (no fiat values).
skipPricingSpamNotrue/falseSkip pricing for spam-classified tokens.
titleNostringCustom 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

{
  "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

FieldValue
MethodGET
URLhttps://api.bitwave.io/v2/orgs/{ORG_ID}/report-runs/{reportRunId}/status
AuthorizationAuthorization: Bearer ACCESS_TOKEN
ReturnsA 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" }
StatusTerminalMeaning
newNoRun accepted, not yet started.
runningNoReport is generating.
succeededYesComplete — fetch the result.
failedYesGeneration failed.
timed-outYesGeneration 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

FieldValue
MethodGET
URLhttps://api.bitwave.io/v2/orgs/{ORG_ID}/report-runs/{reportRunId}
AuthorizationAuthorization: Bearer ACCESS_TOKEN
ReturnsA 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

FieldTypeDescription
reportTypestringThe report type, e.g. balance-report.
reportRunIdstringThe run id. This echoes the path parameter.
titlestringHuman-readable report title.
subTitle1, subTitle2string (optional)Optional subtitles, when present.
columnsstring[]Ordered column headers. Each row's cells aligns to this order.
rowsReportRow[]The report rows. See ReportRow fields.
filterOptionsobject[]Available filter options; each is { label, key, options?, boolean? }. Empty for the Balance Report.
downloadLinkobject (optional){ "href", "method" } pointing at the CSV download endpoint.

ReportRow fields

FieldTypeDescription
cellsstring[]Row values, positionally aligned with columns.
expandablebooleanWhether the row has nested drill-down rows.
rowsReportRow[]Nested rows. Present when expandable is true; empty otherwise.
headerTextstring (optional)Section header label, when the row is a header.
isTotalRowboolean (optional)true for total or summary rows.
additionalRowsTokenstring (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

FieldValue
MethodGET
URLhttps://api.bitwave.io/v2/orgs/{ORG_ID}/report-runs/{reportRunId}/download
AuthorizationAuthorization: Bearer ACCESS_TOKEN
ReturnsA 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.csv

A 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 parameterRequiredDefaultMaximumDescription
typeYesNoneN/AReport type to list. Use balance-report.
limitNo50200Number of runs to return.
pageTokenNoNoneN/AToken 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.csv

Successful output includes polling messages like this and creates balance-report.csv:

status: new
status: running
status: succeeded

Error handling

Handle these common response states when you integrate with the Balance Report API:

ScenarioWhat it meansWhat to do
400 when starting a runOne or more inputs failed validation.Inspect the response body for every invalid field, fix the request body, and retry.
Other non-2xx statusThe 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 statusReport generation failed.Stop polling and inspect any returned error details.
timed-out statusReport generation exceeded its time budget.Stop polling and start a new run if you still need the report.
Fetch before succeededThe report result is not ready.Continue polling until the status is succeeded.
Expired tokenThe access token is no longer valid.Request a new token and retry the API call.

Best practices

  • Use string input values: Send every inputs.value as 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, and timed-out as 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 reportRunId so you can poll, fetch, download, or look up the run later.

Next steps