/api/auth/loginpublicStart the OIDC login flow (authorization code + PKCE) at the identity provider
Parameters
returnToquery · optionalstringSame-site path to return to after login (default /home)
Responses
- 302Redirect to the identity provider
OIDC-authenticated backend for the Finance Planner web app: financial accounts, balance recordings, net-worth forecasting items, and per-user account statement document uploads.
SessionCookiecookie sv_finance_sessionEncrypted session cookie issued by the OIDC login flow (GET /api/auth/login).
/api/auth/loginpublicStart the OIDC login flow (authorization code + PKCE) at the identity provider
returnToquery · optionalSame-site path to return to after login (default /home)
/api/auth/callbackpublicOAuth2 redirect URI: exchanges the authorization code (with client secret) and establishes a session
application/json
application/json
/api/auth/logoutpublicClear the session and redirect through the identity provider's logout, when available
/api/auth/me SessionCookieReturn the authenticated user's session info
application/json
application/json
application/json
/api/accounts SessionCookieList the authenticated user's financial accounts
application/json
application/json
application/json
/api/accounts SessionCookieCreate a financial account
application/json
application/json
application/json
application/json
application/json
/api/accounts/{accountId} SessionCookieGet one financial account
accountIdpath · requiredmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
application/json
application/json
application/json
application/json
/api/accounts/{accountId} SessionCookieUpdate a financial account's type/label
accountIdpath · requiredmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
application/json
application/json
application/json
application/json
application/json
application/json
/api/accounts/{accountId} SessionCookieDelete a financial account (its balance recordings, statements, and parsed transactions are removed too)
accountIdpath · requiredmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
application/json
application/json
application/json
/api/accounts/{accountId}/balances SessionCookieList manually recorded balances for an account
accountIdpath · requiredmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
application/json
application/json
application/json
application/json
/api/accounts/{accountId}/balances SessionCookieRecord the account's current balance
accountIdpath · requiredmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
application/json
application/json
application/json
application/json
application/json
/api/accounts/{accountId}/balances/latest SessionCookieGet the most recently recorded balance for an account
accountIdpath · requiredmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
application/json
application/json
application/json
application/json
/api/accounts/{accountId}/transactions SessionCookieList the account's transactions (manually entered and AI-identified)
accountIdpath · requiredmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
application/json
application/json
application/json
application/json
/api/accounts/{accountId}/transactions SessionCookieManually record a transaction against the account
Creates a transaction with source='manual', distinguishing it from rows identified by AI statement parsing.
accountIdpath · requiredmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
application/json
application/json
application/json
application/json
application/json
/api/accounts/{accountId}/transactions/{transactionId} SessionCookieDelete one transaction (manually entered or AI-identified)
accountIdpath · requiredmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
transactionIdpath · requireduuid
application/json
application/json
application/json
/api/documents/{documentId}/transactions SessionCookieList the transactions identified inside one statement document
documentIdpath · requireduuid
application/json
Array of:
idstringrequireduuid
accountIdstringrequireddocumentIdstring | nullrequireduuid
descriptionstring | nullrequiredamountnumberrequiredtransactionAtintegerrequiredsourcestringrequired"manual" | "ai"
sourcePageinteger | nullrequiredgreater than 0
sourceTextstring | nullrequiredtagsobject[]requiredidstringrequireduuid
labelstringrequiredscopestringrequired"global" | "user"
application/json
application/json
application/json
/api/analytics/spending SessionCookieBreak spending down by category over a time range
Totals the user's money-out transactions between `from` and `to` by the tags assigned to them, for one financial account or across all of them. A transaction carrying several tags has its amount split evenly between them, so the categories always add up to the total spent; transactions with no tags are collected into an "Untagged" category.
accountIdquery · optionalmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
fromquery · optionalmin 0
toquery · optionalmin 0
application/json
application/json
application/json
application/json
application/json
/api/analytics/income SessionCookieBreak income down by category over a time range
Totals the user's money-in transactions between `from` and `to` by the tags assigned to them, for one financial account or across all of them. A transaction carrying several tags has its amount split evenly between them, so the categories always add up to the total received; transactions with no tags are collected into an "Untagged" category.
accountIdquery · optionalmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
fromquery · optionalmin 0
toquery · optionalmin 0
application/json
application/json
application/json
application/json
application/json
/api/budgets SessionCookieList the user's budgets
Budgets are named collections of forecasting items; the one flagged isDefault drives the dashboard's net-worth forecast until another budget is selected. Users start with an automatically created "My Budget", so the list is never empty.
application/json
application/json
application/json
/api/budgets SessionCookieCreate a budget
The user's first budget automatically becomes their default budget.
application/json
application/json
application/json
application/json
application/json
/api/budgets/{budgetId} SessionCookieRename a budget
budgetIdpath · requireduuid
application/json
application/json
application/json
application/json
application/json
application/json
/api/budgets/{budgetId} SessionCookieDelete a budget (its forecasting items are removed too)
Deleting the default budget promotes the user's oldest remaining budget to be the new default.
budgetIdpath · requireduuid
application/json
application/json
application/json
/api/budgets/{budgetId}/default SessionCookieMake a budget the user's default
The default budget is the one the dashboard's net-worth forecast loads with; setting it clears the flag from any other budget.
budgetIdpath · requireduuid
application/json
application/json
application/json
application/json
/api/budgets/{budgetId}/forecasting-items SessionCookieList the budget's anticipated financial changes
budgetIdpath · requireduuid
application/json
application/json
application/json
application/json
/api/budgets/{budgetId}/forecasting-items SessionCookieAdd an anticipated financial change to the budget
budgetIdpath · requireduuid
application/json
application/json
application/json
application/json
application/json
application/json
/api/budgets/{budgetId}/forecasting-items/{uuid} SessionCookieRemove an anticipated financial change from the budget
budgetIdpath · requireduuid
uuidpath · requireduuid
application/json
application/json
application/json
/api/networth SessionCookieCalculate net worth on a given date
Totals every account's balance as it stood on `at` (defaulting to now), and reports the per-account balances it was added up from. Each account is valued from the balance recorded nearest the requested date, with the transactions in between applied to carry that balance forwards — or unwound to carry it backwards, so net worth can be valued before the balance was ever recorded. Balances held (chequing) add to net worth and balances owed (credit cards, personal loans) subtract from it. An account whose balance has never been recorded is anchored at zero at its latest transaction: it counts as zero today, but its transactions still reconstruct how it moved before then.
atquery · optionalmin 0
application/json
application/json
application/json
/api/networth/history SessionCookieReconstruct net worth across a window of time
Values net worth at roughly `points` evenly spaced moments between `from` and `to`, finishing exactly on `to`. The series starts no earlier than the oldest balance or transaction on record, so it can be shorter than the requested window, and is empty when nothing has been recorded yet. Passing `accounts` reconstructs from just those accounts, as if the others did not exist. Each account is valued from the balance recorded nearest the requested date, with the transactions in between applied to carry that balance forwards — or unwound to carry it backwards, so net worth can be valued before the balance was ever recorded. Balances held (chequing) add to net worth and balances owed (credit cards, personal loans) subtract from it. An account whose balance has never been recorded is anchored at zero at its latest transaction: it counts as zero today, but its transactions still reconstruct how it moved before then.
fromquery · optionalmin 0
toquery · optionalmin 0
pointsquery · optionalmin 2 · max 1000
accountsquery · optionalapplication/json
application/json
application/json
application/json
application/json
/api/documents SessionCookieList the user's uploaded statement documents
accountIdquery · optionalmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
application/json
application/json
application/json
/api/documents SessionCookieUpload an account statement document (stored privately in blob storage)
Multipart form upload. Fields: `file` (the statement: PDF, CSV, OFX/QFX, or an image) and optional `accountId` (slug of the financial account the statement belongs to). Uploaded documents will eventually be parsed with AI to identify transactions.
multipart/form-data
filestringrequiredbinary
accountIdstringoptionalmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
application/json
application/json
application/json
application/json
application/json
/api/documents/{documentId} SessionCookieGet a statement document's metadata
documentIdpath · requireduuid
application/json
application/json
application/json
application/json
/api/documents/{documentId} SessionCookieDelete a statement document (metadata and stored blob)
documentIdpath · requireduuid
application/json
application/json
application/json
/api/documents/{documentId}/content SessionCookieDownload a statement document's content
documentIdpath · requireduuid
application/octet-stream
binary
application/json
application/json
application/json
/api/documents/{documentId}/parse SessionCookieParse the statement with AI to identify transactions
Sends the stored statement to Claude, which identifies every transaction row (with the page and verbatim statement line it came from). Identified transactions are written to the account's transaction list with source='ai', replacing any AI-identified rows from a previous parse of the same document. The AI also assigns each transaction any of the user's tags that clearly apply, leaving transactions untagged when no category is obvious. Statement-level details the statement explicitly prints — opening balance, closing balance, and the statement period — are stored on the document when found, and null when not (the AI never estimates them). The app version that ran the parse is recorded on the document as `parsedWithVersion`, so documents whose results predate parser improvements can be found and re-parsed.
documentIdpath · requireduuid
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
/api/healthzpublicLiveness probe
application/json
/api/openapi.jsonpublicOpenAPI 3.1 description of this API
typestringrequired"chequing" | "credit" | "personal-loan"
labelstringrequiredmin length 1 · max length 32
idstringrequiredmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
uuidstringrequireduuid
amountnumberrequiredmin 0
startintegerrequiredmin 0
typestringrequired"income" | "expense"
descriptionstringoptionalfrequencyintegeroptionalmin 0
finishintegeroptionalmin 0
balancenumberrequiredtimestampnumberrequiredidstringrequireduuid
namestringrequiredmin length 1 · max length 120
isDefaultbooleanrequiredlabelstringrequiredmin length 1 · max length 50
descriptionstring | nullrequiredmin length 1 · max length 500
amountnumberrequiredtransactionAtintegerrequiredmin 0
tagIdsstring[]optionalmax items 20
errorstringrequiredokbooleanrequiredtrue
accountIdstring | nullrequiredfromintegerrequiredtointegerrequiredtotalIncomenumberrequiredtotalSpendingnumberrequiredtransactionCountintegerrequiredmin 0
categoriesobject[]requiredtagIdstring | nullrequireduuid
labelstringrequiredamountnumberrequiredtransactionCountintegerrequiredmin 0
balancenumber | nullrequiredtimestampnumber | nullrequiredfrominteger | nullrequiredtointegerrequiredpointsobject[]requiredtimenumberrequiredpricenumberrequiredatintegerrequirednetWorthnumberrequiredaccountsobject[]requiredaccountIdstringrequiredmin length 1 · max length 32 · pattern ^[a-z0-9-]+$
labelstringrequiredmin length 1 · max length 32
typestringrequired"chequing" | "credit" | "personal-loan"
balancenumberrequirednetWorthContributionnumberrequiredbalancenumberrequiredtimestampintegeroptionalmin 0
userobjectrequireduserIdstringrequireduuid
substringrequiredmin length 1
issstringrequiredmin length 1
emailstringoptionalnamestringoptionalpicturestringoptionaltagIdsstring[]requiredmax items 20
accountIdstring | nullrequiredfromintegerrequiredtointegerrequiredtotalSpendingnumberrequiredtotalIncomenumberrequiredtransactionCountintegerrequiredmin 0
categoriesobject[]requiredtagIdstring | nullrequireduuid
labelstringrequiredamountnumberrequiredtransactionCountintegerrequiredmin 0
idstringrequireduuid
accountIdstring | nullrequiredfilenamestringrequiredcontentTypestringrequiredsizeBytesintegerrequiredmin 0
statusstringrequired"uploaded" | "parsing" | "parsed" | "failed"
uploadedAtintegerrequiredmin 0
openingBalancenumber | nullrequiredclosingBalancenumber | nullrequiredperiodStartAtinteger | nullrequiredperiodEndAtinteger | nullrequiredparsedWithVersionstring | nullrequiredtransactionCountintegerrequiredmin 0
idstringrequireduuid
accountIdstringrequireddocumentIdstring | nullrequireduuid
descriptionstring | nullrequiredamountnumberrequiredtransactionAtintegerrequiredsourcestringrequired"manual" | "ai"
sourcePageinteger | nullrequiredgreater than 0
sourceTextstring | nullrequiredtagsobject[]requiredidstringrequireduuid
labelstringrequiredscopestringrequired"global" | "user"
idstringrequireduuid
labelstringrequiredscopestringrequired"global" | "user"
namestringrequiredmin length 1 · max length 120