Labels
A label is a free tag: a transaction can carry any number of them next to its single category. Labels are also how a savings goal counts its contributions, and budgets and automation rules can use them. Names are unique within a space.
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 labels
GET /api/v1/labels
Your labels in a space, newest first, the ones that track a savings goal included. As in the app, another member's labels are not listed.
Scope: read. A personal token needs api:read; an OAuth token needs api:use.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
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 Label | |
data[].id |
string (uuid) | The label's id. |
data[].space_id |
string (uuid) or null | The id of the space the label is in. |
data[].name |
string | The label's name, unique within the space. |
data[].color |
string | The label's color. |
data[].source |
"user" | "saving_goal" | Where the label comes from: user for one made by hand, saving_goal for the one a savings goal tracks its contributions with. |
data[].created_at |
string (date-time) or null | When the label was created. |
has_more |
boolean | Whether more objects exist past this page, in the direction being walked. |
Example request
curl https://whisper.money/api/v1/labels \
-H "Authorization: Bearer $WHISPER_MONEY_TOKEN"
Example response
{
"data": [
{
"id": "0199a3f6-3c4d-7e5f-9a6b-7c8d9e0f1a23",
"space_id": "0199a3f2-6c1e-7b4d-9a8e-2f5c1d3b4a60",
"name": "Holidays",
"color": "blue",
"source": "user",
"created_at": "2026-01-15T09:30:00+00:00"
}
],
"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). |
422 |
The request failed validation. |
429 |
The rate limit is exhausted. Wait the seconds in Retry-After. |
Create a label
POST /api/v1/labels
Tag transactions with it through PUT /api/v1/transactions/{id}/labels.
Scope: write. A personal token needs api:write (a read & write token); an OAuth token needs api:use.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
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 |
|---|---|---|---|
name |
string | Yes | The label's name, unique within the space. |
color |
LabelColor | Yes | The label's color. |
Response
201
| Field | Type | Description |
|---|---|---|
data |
Label | |
data.id |
string (uuid) | The label's id. |
data.space_id |
string (uuid) or null | The id of the space the label is in. |
data.name |
string | The label's name, unique within the space. |
data.color |
string | The label's color. |
data.source |
"user" | "saving_goal" | Where the label comes from: user for one made by hand, saving_goal for the one a savings goal tracks its contributions with. |
data.created_at |
string (date-time) or null | When the label was created. |
Example request
curl -X POST https://whisper.money/api/v1/labels \
-H "Authorization: Bearer $WHISPER_MONEY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Holidays",
"color": "amber"
}'
Example response
{
"data": {
"id": "0199a3f6-3c4d-7e5f-9a6b-7c8d9e0f1a23",
"space_id": "0199a3f2-6c1e-7b4d-9a8e-2f5c1d3b4a60",
"name": "Holidays",
"color": "blue",
"source": "user",
"created_at": "2026-01-15T09:30:00+00:00"
}
}
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). |
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. |
Retrieve a label
GET /api/v1/labels/{label}
Scope: read. A personal token needs api:read; an OAuth token needs api:use.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
label |
path | string (uuid) | Yes | The label'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. |
Response
200
| Field | Type | Description |
|---|---|---|
data |
Label | |
data.id |
string (uuid) | The label's id. |
data.space_id |
string (uuid) or null | The id of the space the label is in. |
data.name |
string | The label's name, unique within the space. |
data.color |
string | The label's color. |
data.source |
"user" | "saving_goal" | Where the label comes from: user for one made by hand, saving_goal for the one a savings goal tracks its contributions with. |
data.created_at |
string (date-time) or null | When the label was created. |
Example request
curl https://whisper.money/api/v1/labels/0199a3f2-6c1e-7b4d-9a8e-2f5c1d3b4a60 \
-H "Authorization: Bearer $WHISPER_MONEY_TOKEN"
Example response
{
"data": {
"id": "0199a3f6-3c4d-7e5f-9a6b-7c8d9e0f1a23",
"space_id": "0199a3f2-6c1e-7b4d-9a8e-2f5c1d3b4a60",
"name": "Holidays",
"color": "blue",
"source": "user",
"created_at": "2026-01-15T09:30:00+00:00"
}
}
Errors
| Status | When |
|---|---|
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. |
Update a label
PATCH /api/v1/labels/{label}
Changes only the fields you send. The label of a savings goal (source: saving_goal) carries the goal's name, so renaming it is a 422; its color can still be changed.
Scope: write. A personal token needs api:write (a read & write token); an OAuth token needs api:use.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
label |
path | string (uuid) | Yes | The label'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. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | No | The label's name, unique within the space. |
color |
LabelColor | No | The label's color. |
Response
200
| Field | Type | Description |
|---|---|---|
data |
Label | |
data.id |
string (uuid) | The label's id. |
data.space_id |
string (uuid) or null | The id of the space the label is in. |
data.name |
string | The label's name, unique within the space. |
data.color |
string | The label's color. |
data.source |
"user" | "saving_goal" | Where the label comes from: user for one made by hand, saving_goal for the one a savings goal tracks its contributions with. |
data.created_at |
string (date-time) or null | When the label was created. |
Example request
curl -X PATCH https://whisper.money/api/v1/labels/0199a3f2-6c1e-7b4d-9a8e-2f5c1d3b4a60 \
-H "Authorization: Bearer $WHISPER_MONEY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Holidays"
}'
Example response
{
"data": {
"id": "0199a3f6-3c4d-7e5f-9a6b-7c8d9e0f1a23",
"space_id": "0199a3f2-6c1e-7b4d-9a8e-2f5c1d3b4a60",
"name": "Holidays",
"color": "blue",
"source": "user",
"created_at": "2026-01-15T09:30:00+00:00"
}
}
Errors
| Status | When |
|---|---|
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. |
Delete a label
DELETE /api/v1/labels/{label}
Deletes the label; the transactions that carried it no longer show it. The label of a savings goal (source: saving_goal) is how the goal counts its contributions, so it cannot be deleted on its own: that answers 422 with the error under label.
Scope: write. A personal token needs api:write (a read & write token); an OAuth token needs api:use.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
label |
path | string (uuid) | Yes | The label'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. |
Response
200
| Field | Type | Description |
|---|---|---|
id |
string (uuid) | The deleted label's id. |
deleted |
boolean | Always true. |
Example request
curl -X DELETE https://whisper.money/api/v1/labels/0199a3f2-6c1e-7b4d-9a8e-2f5c1d3b4a60 \
-H "Authorization: Bearer $WHISPER_MONEY_TOKEN"
Example response
{
"id": "0199a3f2-6c1e-7b4d-9a8e-2f5c1d3b4a60",
"deleted": true
}
Errors
| Status | When |
|---|---|
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. |