Whisper Money

Search the documentation

Search pages and sections.

Balances

A balance is a snapshot of what an account held at the end of one day, in the account currency's minor units. A bank-connected account gets its balances from the bank sync; you record those of a manual account yourself. A credit card has no balance: it has a credit limit, and what is in use of it comes from its transactions.

The example requests read your personal token from $WHISPER_MONEY_TOKEN: run export WHISPER_MONEY_TOKEN=wm_api_your_token_here before pasting them.

List an account's balances

GET /api/v1/accounts/{account}/balances

The account's balance snapshots, the most recent day first.

Scope: read. A personal token needs api:read; an OAuth token needs api:use.

Parameters

Name In Type Required Description
account path string (uuid) Yes The account's id.
limit query integer No How many objects to return, between 1 and 100. Defaults to 25.
starting_after query string (uuid) No An object id: return the page that follows it. Pass the last id of the page you have.
ending_before query string (uuid) No An object id: return the page that precedes it. Pass the first id of the page you have. Cannot be combined with starting_after.
space query string (uuid) No The id of the space to work in (see GET /spaces). Defaults to your personal space. On writes it may also be sent in the JSON body.

Response

200

Field Type Description
data array of AccountBalance
data[].id string (uuid) The snapshot's id.
data[].account_id string (uuid) The id of the account the snapshot belongs to.
data[].balance_date string (date) The day the balance is for. An account has at most one snapshot per day.
data[].balance integer The balance, in the account currency's minor units.
data[].invested_amount integer or null How much of the balance is money put in rather than gains, in minor units, or null. Set on investment accounts.
data[].derived boolean Whether the snapshot was worked out backwards from a bank-connected account's transactions, rather than reported by the bank or recorded by you. A balance recorded through the API is never derived.
has_more boolean Whether more objects exist past this page, in the direction being walked.

Example request

curl https://whisper.money/api/v1/accounts/0199a3f2-6c1e-7b4d-9a8e-2f5c1d3b4a60/balances \
  -H "Authorization: Bearer $WHISPER_MONEY_TOKEN"

Example response

{
    "data": [
        {
            "id": "0199a3f6-2b4c-7d5e-9f60-718293a4b5c6",
            "account_id": "0199a3f4-1d2e-7c3b-8a4f-5e6d7c8b9a01",
            "balance_date": "2026-01-31",
            "balance": 250000,
            "invested_amount": null,
            "derived": true
        }
    ],
    "has_more": true
}

Errors

Status When
400 A malformed request: an unknown pagination cursor (invalid_cursor) or an invalid Idempotency-Key (invalid_idempotency_key).
401 No bearer token, or one that is unknown, expired or revoked.
403 The token lacks the scope (insufficient_scope), the account has no Pro plan (plan_required, with a subscribe_url) or it is a shared demo account (shared_account).
404 No object with that id exists, or it belongs to someone else.
422 The request failed validation.
429 The rate limit is exhausted. Wait the seconds in Retry-After.

Record a balance

POST /api/v1/accounts/{account}/balances

Records what a manual account held on a day, replacing the snapshot already recorded for that day. Answers 201 when it adds a snapshot and 200 when it replaces one. A bank-connected account is refused, since the next sync would overwrite the figure, and so is a credit card, which has no balance; both answer 422 with the error under account.

Scope: write. A personal token needs api:write (a read & write token); an OAuth token needs api:use.

Parameters

Name In Type Required Description
account path string (uuid) Yes The account's id.
space query string (uuid) No The id of the space to work in (see GET /spaces). Defaults to your personal space. On writes it may also be sent in the JSON body.
Idempotency-Key header string No Any unique string up to 255 characters. Retrying with the same key within 24 hours returns the first response instead of repeating the request.

Request body

Field Type Required Description
balance integer Yes The balance, in the account currency's minor units.
balance_date string (date) No The day the balance is for. Defaults to today.
invested_amount integer or null No How much of the balance is money put in rather than gains, in minor units, for investment accounts. Left out, the one already recorded for that day stays.

Response

201

Field Type Description
data AccountBalance
data.id string (uuid) The snapshot's id.
data.account_id string (uuid) The id of the account the snapshot belongs to.
data.balance_date string (date) The day the balance is for. An account has at most one snapshot per day.
data.balance integer The balance, in the account currency's minor units.
data.invested_amount integer or null How much of the balance is money put in rather than gains, in minor units, or null. Set on investment accounts.
data.derived boolean Whether the snapshot was worked out backwards from a bank-connected account's transactions, rather than reported by the bank or recorded by you. A balance recorded through the API is never derived.

Example request

curl -X POST https://whisper.money/api/v1/accounts/0199a3f2-6c1e-7b4d-9a8e-2f5c1d3b4a60/balances \
  -H "Authorization: Bearer $WHISPER_MONEY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "balance": 250000,
    "balance_date": "2026-01-31"
}'

Example response

{
    "data": {
        "id": "0199a3f6-2b4c-7d5e-9f60-718293a4b5c6",
        "account_id": "0199a3f4-1d2e-7c3b-8a4f-5e6d7c8b9a01",
        "balance_date": "2026-01-31",
        "balance": 250000,
        "invested_amount": null,
        "derived": true
    }
}

Errors

Status When
400 A malformed request: an unknown pagination cursor (invalid_cursor) or an invalid Idempotency-Key (invalid_idempotency_key).
401 No bearer token, or one that is unknown, expired or revoked.
403 The token lacks the scope (insufficient_scope), the account has no Pro plan (plan_required, with a subscribe_url) or it is a shared demo account (shared_account).
404 No object with that id exists, or it belongs to someone else.
409 A request with the same Idempotency-Key is still being processed.
422 The request failed validation.
429 The rate limit is exhausted. Wait the seconds in Retry-After.