Quickstart
Create a token, find your household, and read your first page of transactions in about five minutes.
This guide makes three read requests and one optional write. You need a Penny account that belongs to a household with an active subscription.
Create a token
In the app, open Settings → API Tokens and create a token. Read only access is enough until the last step. The full token is shown once, so copy it before you close the screen, then store it in an environment variable:
export PENNY_API_TOKEN="penny_pat_YOUR_TOKEN"Find your household
Almost everything in the API belongs to a household, and its ID is part of the path. List the households you can access:
curl https://api.penny.finance/v1/households \
-H "Authorization: Bearer $PENNY_API_TOKEN"{
"households": [
{
"id": "Hh4vK8pQ2sLm",
"name": "Chen Household",
"currency": "USD",
"timezone": "America/Los_Angeles",
"role": "owner",
"access_status": "available"
}
]
}Some fields are omitted here. Keep the id for the next requests:
export PENNY_HOUSEHOLD_ID="Hh4vK8pQ2sLm"List transactions
Request the household's transactions from the start of a month. The response is ordered newest first:
curl "https://api.penny.finance/v1/households/$PENNY_HOUSEHOLD_ID/transactions?start_date=2026-08-01" \
-H "Authorization: Bearer $PENNY_API_TOKEN"{
"transactions": [
{
"id": "Tx3fJ9mS6vBa",
"name": "Morning coffee",
"date": "2026-08-14",
"amount": { "amount": "-6.50", "currency": "USD" },
"pending": false,
"account": { "id": "Ac7kP2mN9qRs", "name": "Everyday Checking" },
"merchant": { "id": "Mr9dL3xT6bQa", "name": "Blue Bottle Coffee" },
"household_finance_category": {
"id": "Ct5nR8wY1jFu",
"name": "Coffee & Dining"
},
"tags": []
}
],
"daily_sums": [
{ "date": "2026-08-14", "amount": { "amount": "-6.50", "currency": "USD" } }
],
"next_cursor": "eyJkYXRlIjogIjIwMjYtMDgtMTQiLCAiZmgiOiAiOTUyMTFhYjAxMDVmYzA0MSJ9",
"has_more": true
}Each object carries more fields than shown; the endpoint reference describes all of them. Two conventions apply everywhere in the API: amounts are decimal strings, and expenses are negative while income is positive.
Read every page
When has_more is true, pass next_cursor back as cursor with the same filters.
This loop collects every transaction since the start of the month:
const headers = { Authorization: `Bearer ${process.env.PENNY_API_TOKEN}` }
const url = new URL(
`/v1/households/${process.env.PENNY_HOUSEHOLD_ID}/transactions`,
"https://api.penny.finance"
)
url.searchParams.set("start_date", "2026-08-01")
const transactions = []
while (true) {
const response = await fetch(url, { headers })
const page = await response.json()
if (!response.ok) throw new Error(`${response.status} ${page.code}`)
transactions.push(...page.transactions)
if (!page.has_more) break
url.searchParams.set("cursor", page.next_cursor)
}Every paged list works the same way. See pagination for page sizes and the lists that return everything in one page by default.
Make a change
Writes need a token with Read and write access, and a household role of member or owner. Create a tag, then add it to the transaction from the previous step:
curl -X POST "https://api.penny.finance/v1/households/$PENNY_HOUSEHOLD_ID/tags" \
-H "Authorization: Bearer $PENNY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "Work meals" }'{
"id": "Tg8qN1cK5rWd",
"name": "Work meals",
"color": null,
"transaction_count": 0
}curl -X POST "https://api.penny.finance/v1/households/$PENNY_HOUSEHOLD_ID/transactions/bulk-add-tags" \
-H "Authorization: Bearer $PENNY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "transaction_ids": ["Tx3fJ9mS6vBa"], "tag_ids": ["Tg8qN1cK5rWd"] }'The response returns each transaction that changed, with the new tag in its tags.
With a read-only token, both requests fail with 403 read_only_token.
These requests change your real data. The API has no sandbox.
Next steps
- Overview: authentication, household scope, pagination, and the money and date conventions.
- Errors: the error format and every code the API returns.
- Download the OpenAPI schema to import into an API tool or generate a client.
Last updated on