57 endpoints over HTTP, on your own instance, authenticated with a key you mint yourself. Your finances are yours: read them, write them, move them elsewhere.
Getting started
Every endpoint lives under /api/v1 on the instance you run. Responses are JSON, amounts are in euros, and a month parameter is always YYYY-MM, defaulting to the current month in the server’s timezone.
Create a key in the app under Settings, then API keys. It is shown once, because only its SHA-256 hash is stored: the database never holds a usable secret. Send it as a bearer token, or as x-api-key.
Authorization: Bearer dough_xxxxxxxx
A key carries read, or read and write. A read key can see everything and change nothing. A missing or revoked key returns 401; a key without the scope an endpoint needs returns 403. Revoking a key in settings stops it working on its next request.
On an instance without the settings page, a key can still be minted on the machine that owns the database:
An app can ask a person to sign in to their own instance instead of asking them for a key. Every Dough is its own authorization server, so there is nothing to register with anybody: the app reads /.well-known/oauth-authorization-server from whatever host it was pointed at, registers itself at /api/oauth/register, and sends the person to /oauth/authorize to approve it.
The flow is authorization code with PKCE, which is required: S256 only, no client secrets, no implicit grant. Codes are single use and expire in two minutes; reusing one revokes every token it produced. Access tokens last an hour, refresh tokens rotate on use, and a rotated refresh token cannot be used again.
The token that comes back authenticates every endpoint below exactly as an API key does, and carries the same read and write scopes. Revoke it from the app or from Settings.
Overview
The two calls that answer how things stand, without walking every account.
GET/api/v1/summaryread
Total balance, and the month's income, budgeted, activity and Ready to Assign. The first call an assistant reaches for.
Query parameters
monthYYYY-MMDefaults to the current month in the server's timezone.
GET/api/v1/net-worthread
Net worth by kind (checking, savings, investments, debts) plus the saved snapshot history.
GET/api/v1/ai-statusread
Each AI service (Dougie, receipts, categorizing, summaries) with its provider and model, its state (ok, failing with the last error, or off) and the share of this month's AI allowance used, null when unlimited.
Dashboard
Everything the dashboard shows, in one call: the daily budget and the reason behind it, what is left today, the obligations ahead, the charts and the month's figures. Computed by the same module the web dashboard reads, so two clients looking at one instance cannot disagree.
GET/api/v1/dashboardread
The whole dashboard model: daily budget with its breakdown and notice, today's spending and what is left, upcoming bills and income, spending flow against pace, categories, streak, trends, cash flow, recent transactions and net worth.
Accounts
Balances as the app sees them. budget_excluded marks accounts left out of the spendable-balance figure, so a client can show the same number the UI does.
GET/api/v1/accountsread
Every open account with its balance.
Query parameters
include_closed1Also return closed accounts.
POST/api/v1/accounts/createwrite
Add a manual account.
Body
namestringrequiredWhat the account is called.
typestringchecking, savings, otherAsset or otherDebt. Defaults to checking.
balancenumberSeeds both balance and cleared balance.
on_budgetbooleanDefaults to true.
POST/api/v1/accounts/updatewrite
Edit an account. Only the fields sent change. Setting balance records a reconciliation transaction, so history keeps adding up.
Body
idstringrequiredAccount id.
namestring
typestring
balancenumber
on_budgetboolean
closedboolean
sort_ordernumber
POST/api/v1/accounts/deletewrite
Manual accounts are removed. Synced accounts are closed instead, because their history is not ours to drop.
Body
idstringrequiredAccount id.
POST/api/v1/accounts/reorderwrite
Set the order accounts are listed in.
Body
orderstring[]requiredAccount ids, in the order wanted.
Budget links
Which budget category a bill, subscription, savings goal, debt or investment is linked to. Linking is set from the item's side, as the web's editors do.
GET/api/v1/budget-linksread
The category an item is linked to, or null.
Query parameters
typestringrequiredbill, subscription, savings_goal, debt_account or investment_account.
idstringrequiredThe item's id.
POST/api/v1/budget-linkswrite
Link an item to a category, or unlink it.
Body
typestringrequiredAs above.
idstringrequiredThe item's id.
category_idnumber | nullrequiredThe category, or null to unlink.
Transactions
The ledger. Writes here are local mode only: in YNAB mode transactions belong to YNAB and must be edited there.
GET/api/v1/payeesread
The payees this household uses, most used first, each with the category it is usually filed under, the account it is usually paid from, what it last cost and its last description. A client can complete an entry from this without asking again, and it keeps working offline. The category is stated only when the history agrees, so an inconsistently filed payee gets none rather than a wrong guess.
Query parameters
limitnumber1 to 1000, default 300.
GET/api/v1/payees/merge-suggestionsread
Payees that look like one merchant written several ways, as groups of { into, from }, proposed by the quick AI model from the 300 most used payees. Asking merges nothing.
POST/api/v1/payees/mergewrite
Rename every transaction under the names in from to into. A rename is a merge of one name. Local mode only.
Body
fromstring[]requiredThe payee names to replace.
intostringrequiredThe name they all take.
GET/api/v1/transactionsread
Transactions newest first.
Query parameters
monthYYYY-MMDefaults to the current month in the server's timezone.
account_idstringOne account.
categorystringExact category name.
qstringSearch payee, category, memo, account name, or the amount when a number (12,50 or 12.50).
limitnumber1 to 500, default 50.
POST/api/v1/transactions/createwritelocal mode only
Add a transaction and apply its balance effect. Meant for rows the bank import has not seen yet, most of all pending card holds.
Body
account_idstringrequiredWhich account it hits.
amountnumberrequiredThe absolute value; inflow decides the direction.
inflowbooleanDefaults to false, money out.
dateYYYY-MM-DDDefaults to today.
payee_namestring
memostring
categorystringCategory name.
clearedstring
transfer_account_idstringWith category "Internal transfer", creates the counterpart leg.
POST/api/v1/transactions/updatewritelocal mode only
Patch one transaction. Only the fields sent change.
Body
transaction_idstringrequiredThe id the read endpoints return.
amountnumberAbsolute value; inflow true stores it positive.
inflowboolean
payee_namestring
memostring
account_idstring
dateYYYY-MM-DD
categorystring
transfer_account_idstringFixes a misrouted transfer and maintains the opposite leg.
POST/api/v1/transactions/deletewritelocal mode only
Remove a transaction, split siblings included, and reverse its balance effect.
Body
transaction_idstringrequired
Budget
Envelope budgeting: what each category has been given, spent and has left. The same maths as the budget page, carry-forward included.
GET/api/v1/badgesread
The navigation's notice dots: budget_overspent (categories overspent this month), transactions_unread (1 when an expense was added by hand since this key's user last looked) and chat_unread (messages from others since then).
POST/api/v1/badgeswrite
Clear a dot when its screen is opened. The budget dot clears only when nothing is overspent.
Body
seenstringrequiredtransactions or chat.
GET/api/v1/budgetread
The month's income, total budgeted, Ready to Assign, age of money, every active category's budgeted, activity and available, and the hidden categories.
Query parameters
monthYYYY-MMDefaults to the current month in the server's timezone.
POST/api/v1/budget/assignwrite
Set, not add, one category's budgeted amount for a month.
Body
monthYYYY-MMDefaults to the current month in the server's timezone.
category_idnumberEither this or category_name.
category_namestring
budgetednumberrequiredThe amount the category ends up with.
POST/api/v1/budget/unassignwrite
Clear over-assigning: take back unspent money from this month's assignments until Ready to Assign is zero, first what sits beyond a target, then categories without a target, then targeted ones. Nothing already spent is undone.
Body
monthstringYYYY-MM, default this month.
GET/api/v1/budget/auto-assignread
Preview funding targets from Ready to Assign. Without mode, the total each mode would assign; with mode, the full per-category plan. Writes nothing.
Query parameters
monthYYYY-MMDefaults to the current month in the server's timezone.
modestringunderfunded, last_assigned, last_spent, or smart (overspending first, then targets, most-used categories first).
POST/api/v1/budget/auto-assignwrite
Apply a plan. Capped so it never overbudgets.
Body
monthYYYY-MMDefaults to the current month in the server's timezone.
modestringrequiredunderfunded, last_assigned, last_spent, or smart (overspending first, then targets, most-used categories first).
POST/api/v1/budget/movewrite
Move assigned money from one category to another within a month.
Body
monthYYYY-MMDefaults to the current month in the server's timezone.
from_category_idnumberrequired
to_category_idnumberrequired
amountnumberrequired
POST/api/v1/budget/snoozewrite
Snooze a category for a month, so its target stops asking to be funded.
Body
category_idnumberrequired
monthYYYY-MMDefaults to the current month in the server's timezone.
POST/api/v1/budget/unsnoozewrite
Remove a category's snooze for a month.
Body
category_idnumberrequired
monthYYYY-MMDefaults to the current month in the server's timezone.
Categories
The envelopes themselves, and the targets that decide what auto-assign funds.
GET/api/v1/categories/suggestread
The categories a picker should offer first for an entry: guess is the single best pick (a payee and amount always filed the same way, else the AI's), ranked the categories this payee or description is most often filed under.
Query parameters
payeestringThe payee as typed.
memostringThe description, which often tells a generic payee apart.
amountnumberThe amount, for the payee and amount history.
POST/api/v1/categories/createwrite
Add a category.
Body
namestringrequired
group_namestringThe group it sits under.
colorstring
POST/api/v1/categories/updatewrite
Edit a category, or link it to a subscription, bill, debt account, savings goal or investment account.
Body
idnumberrequiredPlus the fields to change.
POST/api/v1/categories/deletewrite
Delete a category. One with transactions needs reassign_to, and its monthly budgets merge into that target.
Body
idnumberrequired
reassign_tonumberAnother category id.
POST/api/v1/categories/targetwrite
Set or clear a category's budget target.
Body
category_idnumberrequired
monthly_amountnumberRequired unless clearing.
cadencestringdaily, weekly, monthly, yearly or by_date.
target_dateYYYY-MM-DDWith cadence by_date.
due_daynumberWith cadence monthly: the day of the month it is due by, 1-31. null clears it.
snooze_until_monthYYYY-MM
clearbooleantrue removes the target.
Bills
Recurring obligations with a due day, monthly or yearly.
GET/api/v1/billsread
Every bill with amount, due day and cadence.
POST/api/v1/bills/createwrite
Add a recurring bill.
Body
namestringrequired
amountnumberrequired
due_daynumberrequired1 to 31.
categorystring
cadencestringmonthly or yearly.
interval_monthsnumberEvery how many months: 1 monthly, 12 yearly.
due_monthnumber1 to 12, the month it next falls in when the interval is over one.
POST/api/v1/bills/updatewrite
Edit a bill, or mark this month's instance paid.
Body
idnumberrequired
mark_paidbooleanMarks the current month paid.
paid_amountnumberWhat was actually paid.
is_priorityboolean
is_activeboolean
POST/api/v1/bills/deletewrite
Delete a bill with its status, history and patterns.
Body
idnumberrequired
Subscriptions
The smaller recurring charges, kept apart from bills so they can be counted on their own.
GET/api/v1/subscriptionsread
Every subscription with amount, due day, how many months apart it falls (interval_months, due_month), whether it falls this month (due_this_month) and whether this month's charge is paid.
Query parameters
monthYYYY-MMDefaults to the current month in the server's timezone.
POST/api/v1/subscriptions/createwrite
Add a subscription.
Body
namestringrequired
amountnumberrequired
due_daynumberrequired1 to 31.
interval_monthsnumberEvery how many months: 1 monthly (default), 12 yearly.
due_monthnumber1 to 12, the month it next falls in when the interval is over one.
brand_colorstring
brand_logostring
POST/api/v1/subscriptions/updatewrite
Edit a subscription, or mark this month's charge paid. Only the fields sent change.
Body
idnumberrequired
namestring
amountnumber
due_daynumber1 to 31.
interval_monthsnumberEvery how many months: 1 monthly, 12 yearly.
due_monthnumber1 to 12, the month it next falls in when the interval is over one.
brand_colorstring
brand_logostring
is_prioritybooleanA must-pay subscription.
is_activeboolean
mark_paidbooleanThis month's charge paid or not.
POST/api/v1/subscriptions/deletewrite
Delete a subscription.
Body
idnumberrequired
Income
What is expected to arrive, and whether it has. upcoming is true for an active source not yet received whose day is still ahead.
GET/api/v1/incomeread
Income sources with the month's received and upcoming status.
Query parameters
monthYYYY-MMDefaults to the current month in the server's timezone.
POST/api/v1/income/createwrite
Add an income source.
Body
namestringrequired
amountnumberrequired
expected_daynumberrequiredDay of month it usually lands.
is_recurringboolean
target_account_idstringWhere it arrives.
POST/api/v1/income/updatewrite
Edit an income source, or mark it received this month.
Body
idnumberrequired
mark_receivedboolean
POST/api/v1/income/deletewrite
Delete an income source.
Body
idnumberrequired
Savings goals
Saved is the linked category's available balance, assigned minus spent and carried forward, never the lifetime sum of assignments.
GET/api/v1/savings-goalsread
Active goals with target and derived saved amount.
Query parameters
monthYYYY-MMDefaults to the current month in the server's timezone.
POST/api/v1/savings-goals/createwrite
Add a goal, optionally linked to a budget category.
Body
namestringrequired
target_amountnumberrequired
ynab_category_idstringLink to a category by id.
ynab_category_namestringOr by name.
target_dateYYYY-MM-DD
descriptionstring
POST/api/v1/savings-goals/updatewrite
Edit a goal. Only the fields sent change.
Body
idnumberrequired
include_in_calculationsboolean
is_activeboolean
POST/api/v1/savings-goals/deletewrite
Delete a goal and clear its budget link.
Body
idnumberrequired
Debts
Open otherDebt accounts with the interest rate, minimum payment and due day kept beside them.
GET/api/v1/debtsread
Every open debt with its override fields, the payoff plans for an extra monthly payment (extra=), and months_to_debt_free_at_minimums: when the minimums alone clear everything, or null when they never do.
POST/api/v1/debts/updatewrite
Set a debt's override fields.
Body
ynab_account_idstringrequiredWhich debt.
interest_ratenumberAnnual percentage.
minimum_paymentnumber
due_daynumber
original_amountnumber
notesstring
is_priorityboolean
POST/api/v1/debts/reorderwrite
Set the display order.
Body
orderstring[]requiredAccount ids, in the order wanted.
Investments
otherAsset accounts with what goes in monthly and what return is assumed.
GET/api/v1/investmentsread
Every investment account with its overrides.
POST/api/v1/investments/updatewrite
Edit an override, or re-value the account.
Body
ynab_account_idstringrequiredWhich account.
monthly_contributionnumber
expected_returnnumberAnnual percentage.
valuenumberRe-value the account to this.
addednumberMoney put in alongside the re-valuation.
tickerstring
notesstring
POST/api/v1/investments/reorderwrite
Set the display order.
Body
orderstring[]requiredAccount ids, in the order wanted.
Account
The household's data to take away, and deleting the account the key belongs to.
GET/api/v1/account/exportwrite
The household's data as one SQLite file, the format Dough runs on, so it opens anywhere and can start a self-hosted instance. Left out: API keys, OAuth tokens, the saved bank and AI keys and the members' passwords. The export in the app's Settings keeps the saved keys and passwords.
POST/api/v1/account/deletewrite
Delete the key owner's account. Their chats, keys and tokens go; what they added to the household passes to the next member. The last member's deletion removes the whole household. Answers { deleted: "left-household" | "erased-household" }, or 403 for a wrong password.
Body
passwordstringrequiredThe account's password, as confirmation.
Errors
Errors are JSON with an error string. 400 means the body was wrong, or the write is not available in the instance’s current mode. 401 means the key is missing, wrong or revoked. 403 means the key lacks the scope. 404 means the row is not there. A client should degrade per endpoint rather than per app: a read key is perfectly useful, and so is an instance in YNAB mode where transaction writes are refused.
MCP server
The Dough MCP server is a thin client of this API: point it at your instance with an API key and an assistant can answer questions about your finances. It exposes each endpoint here as a tool.