Financial Planning

Finance Planner API

v0.13.1OpenAPI 3.1.0

OIDC-authenticated backend for the Finance Planner web app: financial accounts, balance recordings, net-worth forecasting items, and per-user account statement document uploads.

Base URL: https://finance.jalexw.ca40 endpoints /api/openapi.json

Authentication

  • SessionCookiecookie sv_finance_session

    Encrypted session cookie issued by the OIDC login flow (GET /api/auth/login).

auth

get/api/auth/loginpublic

Start the OIDC login flow (authorization code + PKCE) at the identity provider

Parameters

  • returnToquery · optional
    string

    Same-site path to return to after login (default /home)

Responses

  • 302Redirect to the identity provider
get/api/auth/callbackpublic

OAuth2 redirect URI: exchanges the authorization code (with client secret) and establishes a session

Responses

  • 302Redirect back into the application
  • 400Missing/expired login transaction or invalid response

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

get/api/auth/logoutpublic

Clear the session and redirect through the identity provider's logout, when available

Responses

  • 302Redirect to the identity provider logout or home
get/api/auth/me SessionCookie

Return the authenticated user's session info

Responses

  • 200The authenticated user

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

accounts

get/api/accounts SessionCookie

List the authenticated user's financial accounts

Responses

  • 200The user's financial accounts

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

post/api/accounts SessionCookie

Create a financial account

Request body

application/json

Responses

  • 201The created account

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 409An account with that ID already exists

    application/json

get/api/accounts/{accountId} SessionCookie

Get one financial account

Parameters

  • accountIdpath · required
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

Responses

put/api/accounts/{accountId} SessionCookie

Update a financial account's type/label

Parameters

  • accountIdpath · required
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

Request body

application/json

Responses

delete/api/accounts/{accountId} SessionCookie

Delete a financial account (its balance recordings, statements, and parsed transactions are removed too)

Parameters

  • accountIdpath · required
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

Responses

  • 204Account deleted
  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Account not found

    application/json

balances

get/api/accounts/{accountId}/balances SessionCookie

List manually recorded balances for an account

Parameters

  • accountIdpath · required
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

Responses

post/api/accounts/{accountId}/balances SessionCookie

Record the account's current balance

Parameters

  • accountIdpath · required
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

Request body

application/json

Responses

get/api/accounts/{accountId}/balances/latest SessionCookie

Get the most recently recorded balance for an account

Parameters

  • accountIdpath · required
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

Responses

  • 200The latest balance (null when none recorded yet)

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Account not found

    application/json

transactions

get/api/accounts/{accountId}/transactions SessionCookie

List the account's transactions (manually entered and AI-identified)

Parameters

  • accountIdpath · required
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

Responses

post/api/accounts/{accountId}/transactions SessionCookie

Manually record a transaction against the account

Creates a transaction with source='manual', distinguishing it from rows identified by AI statement parsing.

Parameters

  • accountIdpath · required
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

Request body

application/json

Responses

delete/api/accounts/{accountId}/transactions/{transactionId} SessionCookie

Delete one transaction (manually entered or AI-identified)

Parameters

  • accountIdpath · required
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

  • transactionIdpath · required
    string

    uuid

Responses

  • 204Transaction deleted
  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Account or transaction not found

    application/json

get/api/documents/{documentId}/transactions SessionCookie

List the transactions identified inside one statement document

Parameters

  • documentIdpath · required
    string

    uuid

Responses

  • 200Transactions parsed from this document, in statement order

    application/json

    Array of:

    • idstringrequired

      uuid

    • accountIdstringrequired
    • documentIdstring | nullrequired

      uuid

    • descriptionstring | nullrequired
    • amountnumberrequired
    • transactionAtintegerrequired
    • sourcestringrequired

      "manual" | "ai"

    • sourcePageinteger | nullrequired

      greater than 0

    • sourceTextstring | nullrequired
    • tagsobject[]required
      • idstringrequired

        uuid

      • labelstringrequired
      • scopestringrequired

        "global" | "user"

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Document not found

    application/json

tags

put/api/accounts/{accountId}/transactions/{transactionId}/tags SessionCookie

Replace the set of tags assigned to a transaction

Assigns exactly the given tags (global or the user's custom tags) to the transaction, removing any previously assigned tags not in the list.

Parameters

  • accountIdpath · required
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

  • transactionIdpath · required
    string

    uuid

Request body

Responses

get/api/tags SessionCookie

List the tags available for the user's transactions

Returns the global tags (common spending categories shared by every user) together with the user's own custom tags, ordered by label.

Responses

  • 200Available tags, ordered by label

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

post/api/tags SessionCookie

Create a custom tag

Creates a tag visible only to the authenticated user. Labels are unique case-insensitively across the global tags and the user's own.

Request body

application/json

Responses

  • 201The created tag

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 409A tag with that label already exists

    application/json

delete/api/tags/{tagId} SessionCookie

Delete one of the user's custom tags

Deletes a custom tag and removes it from every transaction it was assigned to. Global tags cannot be deleted.

Parameters

  • tagIdpath · required
    string

    uuid

Responses

  • 204Tag deleted
  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Global tags cannot be deleted (or the user is not authorized)

    application/json

  • 404Tag not found

    application/json

analytics

get/api/analytics/spending SessionCookie

Break 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.

Parameters

  • accountIdquery · optional
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

  • fromquery · optional
    integer | null

    min 0

  • toquery · optional
    integer | null

    min 0

Responses

  • 200Spending over the window, by category, largest first

    application/json

  • 400The window ends before it starts

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Account not found

    application/json

get/api/analytics/income SessionCookie

Break 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.

Parameters

  • accountIdquery · optional
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

  • fromquery · optional
    integer | null

    min 0

  • toquery · optional
    integer | null

    min 0

Responses

  • 200Income over the window, by category, largest first

    application/json

  • 400The window ends before it starts

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Account not found

    application/json

budgets

get/api/budgets SessionCookie

List 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.

Responses

  • 200The user's budgets, oldest first

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

post/api/budgets SessionCookie

Create a budget

The user's first budget automatically becomes their default budget.

Request body

application/json

Responses

  • 201The created budget

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 409A budget with that name already exists

    application/json

put/api/budgets/{budgetId} SessionCookie

Rename a budget

Parameters

  • budgetIdpath · required
    string

    uuid

Request body

application/json

Responses

  • 200The renamed budget

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Budget not found

    application/json

  • 409A budget with that name already exists

    application/json

delete/api/budgets/{budgetId} SessionCookie

Delete a budget (its forecasting items are removed too)

Deleting the default budget promotes the user's oldest remaining budget to be the new default.

Parameters

  • budgetIdpath · required
    string

    uuid

Responses

  • 204Budget deleted
  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Budget not found

    application/json

put/api/budgets/{budgetId}/default SessionCookie

Make 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.

Parameters

  • budgetIdpath · required
    string

    uuid

Responses

  • 200The new default budget

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Budget not found

    application/json

forecasting

get/api/budgets/{budgetId}/forecasting-items SessionCookie

List the budget's anticipated financial changes

Parameters

  • budgetIdpath · required
    string

    uuid

Responses

post/api/budgets/{budgetId}/forecasting-items SessionCookie

Add an anticipated financial change to the budget

Parameters

  • budgetIdpath · required
    string

    uuid

Request body

Responses

delete/api/budgets/{budgetId}/forecasting-items/{uuid} SessionCookie

Remove an anticipated financial change from the budget

Parameters

  • budgetIdpath · required
    string

    uuid

  • uuidpath · required
    string

    uuid

Responses

  • 204Forecasting item deleted
  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Budget or forecasting item not found

    application/json

networth

get/api/networth SessionCookie

Calculate 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.

Parameters

  • atquery · optional
    integer | null

    min 0

Responses

  • 200Net worth on that date, and the balances behind it

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

get/api/networth/history SessionCookie

Reconstruct 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.

Parameters

  • fromquery · optional
    integer | null

    min 0

  • toquery · optional
    integer | null

    min 0

  • pointsquery · optional
    integer

    min 2 · max 1000

  • accountsquery · optional
    string

Responses

  • 200Net worth over the window

    application/json

  • 400The window ends before it starts

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Account not found

    application/json

documents

get/api/documents SessionCookie

List the user's uploaded statement documents

Parameters

  • accountIdquery · optional
    string

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

Responses

  • 200Uploaded statement documents, newest first

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

post/api/documents SessionCookie

Upload 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.

Request body

multipart/form-data

  • filestringrequired

    binary

  • accountIdstringoptional

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

Responses

  • 201The uploaded document

    application/json

  • 400Missing file, unsupported type, or unknown account

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 413File exceeds the upload size limit

    application/json

get/api/documents/{documentId} SessionCookie

Get a statement document's metadata

Parameters

  • documentIdpath · required
    string

    uuid

Responses

delete/api/documents/{documentId} SessionCookie

Delete a statement document (metadata and stored blob)

Parameters

  • documentIdpath · required
    string

    uuid

Responses

  • 204Document deleted
  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Document not found

    application/json

get/api/documents/{documentId}/content SessionCookie

Download a statement document's content

Parameters

  • documentIdpath · required
    string

    uuid

Responses

  • 200The document content

    application/octet-stream

    string

    binary

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Document (or its stored content) not found

    application/json

post/api/documents/{documentId}/parse SessionCookie

Parse 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.

Parameters

  • documentIdpath · required
    string

    uuid

Responses

  • 200Parsing finished; transactions were written to the account

    application/json

  • 401Not authenticated (no valid session cookie)

    application/json

  • 403Authenticated user is not in AUTHORIZED_OIDC_SUBS_WHITELIST

    application/json

  • 404Document (or its stored content) not found

    application/json

  • 409Document is not linked to a financial account

    application/json

  • 413Statement is too large for AI parsing

    application/json

  • 415Unsupported content type for AI parsing

    application/json

  • 502The AI could not parse the statement

    application/json

  • 503AI parsing is not configured on this deployment

    application/json

meta

get/api/healthzpublic

Liveness probe

Responses

  • 200Service is up

    application/json

get/api/openapi.jsonpublic

OpenAPI 3.1 description of this API

Responses

  • 200The OpenAPI document

Schemas

AccountReference

  • typestringrequired

    "chequing" | "credit" | "personal-loan"

  • labelstringrequired

    min length 1 · max length 32

  • idstringrequired

    min length 1 · max length 32 · pattern ^[a-z0-9-]+$

AnticipatedFinancialChange

  • uuidstringrequired

    uuid

  • amountnumberrequired

    min 0

  • startintegerrequired

    min 0

  • typestringrequired

    "income" | "expense"

  • descriptionstringoptional
  • frequencyintegeroptional

    min 0

  • finishintegeroptional

    min 0

BalanceRecording

  • balancenumberrequired
  • timestampnumberrequired

Budget

  • idstringrequired

    uuid

  • namestringrequired

    min length 1 · max length 120

  • isDefaultbooleanrequired

CreateTagRequest

  • labelstringrequired

    min length 1 · max length 50

CreateTransactionRequest

  • descriptionstring | nullrequired

    min length 1 · max length 500

  • amountnumberrequired
  • transactionAtintegerrequired

    min 0

  • tagIdsstring[]optional

    max items 20

ErrorResponse

  • errorstringrequired

Health

  • okbooleanrequired

    true

IncomeBreakdown

  • accountIdstring | nullrequired
  • fromintegerrequired
  • tointegerrequired
  • totalIncomenumberrequired
  • totalSpendingnumberrequired
  • transactionCountintegerrequired

    min 0

  • categoriesobject[]required
    • tagIdstring | nullrequired

      uuid

    • labelstringrequired
    • amountnumberrequired
    • transactionCountintegerrequired

      min 0

LatestBalance

  • balancenumber | nullrequired
  • timestampnumber | nullrequired

NetWorthHistory

  • frominteger | nullrequired
  • tointegerrequired
  • pointsobject[]required
    • timenumberrequired
    • pricenumberrequired

NetWorthSnapshot

  • atintegerrequired
  • netWorthnumberrequired
  • accountsobject[]required
    • accountIdstringrequired

      min length 1 · max length 32 · pattern ^[a-z0-9-]+$

    • labelstringrequired

      min length 1 · max length 32

    • typestringrequired

      "chequing" | "credit" | "personal-loan"

    • balancenumberrequired
    • netWorthContributionnumberrequired

RecordBalanceRequest

  • balancenumberrequired
  • timestampintegeroptional

    min 0

SessionInfo

  • userobjectrequired
    • userIdstringrequired

      uuid

    • substringrequired

      min length 1

    • issstringrequired

      min length 1

    • emailstringoptional
    • namestringoptional
    • picturestringoptional

SetTransactionTagsRequest

  • tagIdsstring[]required

    max items 20

SpendingBreakdown

  • accountIdstring | nullrequired
  • fromintegerrequired
  • tointegerrequired
  • totalSpendingnumberrequired
  • totalIncomenumberrequired
  • transactionCountintegerrequired

    min 0

  • categoriesobject[]required
    • tagIdstring | nullrequired

      uuid

    • labelstringrequired
    • amountnumberrequired
    • transactionCountintegerrequired

      min 0

StatementDocument

  • idstringrequired

    uuid

  • accountIdstring | nullrequired
  • filenamestringrequired
  • contentTypestringrequired
  • sizeBytesintegerrequired

    min 0

  • statusstringrequired

    "uploaded" | "parsing" | "parsed" | "failed"

  • uploadedAtintegerrequired

    min 0

  • openingBalancenumber | nullrequired
  • closingBalancenumber | nullrequired
  • periodStartAtinteger | nullrequired
  • periodEndAtinteger | nullrequired
  • parsedWithVersionstring | nullrequired

StatementParseResult

  • transactionCountintegerrequired

    min 0

StatementTransaction

  • idstringrequired

    uuid

  • accountIdstringrequired
  • documentIdstring | nullrequired

    uuid

  • descriptionstring | nullrequired
  • amountnumberrequired
  • transactionAtintegerrequired
  • sourcestringrequired

    "manual" | "ai"

  • sourcePageinteger | nullrequired

    greater than 0

  • sourceTextstring | nullrequired
  • tagsobject[]required
    • idstringrequired

      uuid

    • labelstringrequired
    • scopestringrequired

      "global" | "user"

Tag

  • idstringrequired

    uuid

  • labelstringrequired
  • scopestringrequired

    "global" | "user"

UpsertBudgetRequest

  • namestringrequired

    min length 1 · max length 120