Files
fin-tracker/openapi/openapi.json
T
Dmitry 3fc7a954b9 feat(api): FastAPI — auth, RFC 7807 и роуты фазы 1
Префикс /api/v1, operationId = "<tag>_<name>", чтобы Dart-клиент получил методы
вроде accountsList, а не list_accounts_api_v1_accounts_get. Ошибки —
application/problem+json. Auth: access-JWT на час + refresh на 30 дней, который
хранится хэшем и ротируется, логин ограничен по частоте в памяти.

Деньги в JSON — всегда позиционные строки (api/schemas/common.py): float в
проводе потерял бы копейки, которые NUMERIC(24,10) бережёт.

Тестовый harness поднимает свой Postgres через pg_ctl (pytest-postgresql),
мигрирует его один раз на сессию и усекает таблицы после каждого теста.
2026-09-18 13:43:49 +03:00

2964 lines
70 KiB
JSON

{
"components": {
"schemas": {
"AccountBalance": {
"properties": {
"account_id": {
"title": "Account Id",
"type": "integer"
},
"balance": {
"description": "decimal as string",
"title": "Balance",
"type": "string"
},
"balance_rub": {
"anyOf": [
{
"description": "decimal as string",
"type": "string"
},
{
"type": "null"
}
],
"title": "Balance Rub"
},
"currency": {
"title": "Currency",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"role": {
"$ref": "#/components/schemas/AccountRole"
}
},
"required": [
"account_id",
"name",
"currency",
"role",
"balance",
"balance_rub"
],
"title": "AccountBalance",
"type": "object"
},
"AccountKind": {
"enum": [
"zm_cash",
"zm_card",
"zm_checking",
"zm_deposit",
"zm_loan",
"zm_emoney",
"zm_debt",
"broker",
"manual_asset"
],
"title": "AccountKind",
"type": "string"
},
"AccountOut": {
"properties": {
"archived": {
"title": "Archived",
"type": "boolean"
},
"balance": {
"anyOf": [
{
"description": "decimal as string",
"type": "string"
},
{
"type": "null"
}
],
"title": "Balance"
},
"balance_as_of": {
"anyOf": [
{
"format": "date-time",
"type": "string"
},
{
"type": "null"
}
],
"title": "Balance As Of"
},
"broker": {
"anyOf": [
{
"$ref": "#/components/schemas/Broker"
},
{
"type": "null"
}
]
},
"credit_limit": {
"anyOf": [
{
"description": "decimal as string",
"type": "string"
},
{
"type": "null"
}
],
"title": "Credit Limit"
},
"currency": {
"title": "Currency",
"type": "string"
},
"id": {
"title": "Id",
"type": "integer"
},
"include_in_net_worth": {
"title": "Include In Net Worth",
"type": "boolean"
},
"kind": {
"$ref": "#/components/schemas/AccountKind"
},
"mirror_of_account_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Mirror Of Account Id"
},
"name": {
"title": "Name",
"type": "string"
},
"opened_at": {
"anyOf": [
{
"format": "date",
"type": "string"
},
{
"type": "null"
}
],
"title": "Opened At"
},
"primary_event_source": {
"anyOf": [
{
"$ref": "#/components/schemas/EventSource"
},
{
"type": "null"
}
]
},
"role": {
"$ref": "#/components/schemas/AccountRole"
},
"source": {
"title": "Source",
"type": "string"
},
"source_id": {
"title": "Source Id",
"type": "string"
},
"start_balance": {
"anyOf": [
{
"description": "decimal as string",
"type": "string"
},
{
"type": "null"
}
],
"title": "Start Balance"
}
},
"required": [
"id",
"kind",
"source",
"source_id",
"broker",
"name",
"currency",
"role",
"include_in_net_worth",
"mirror_of_account_id",
"primary_event_source",
"archived",
"opened_at",
"balance",
"balance_as_of",
"start_balance",
"credit_limit"
],
"title": "AccountOut",
"type": "object"
},
"AccountPatch": {
"additionalProperties": false,
"description": "Only the fields the user owns: everything else comes from the source on each sync.\n\nUnset fields are left alone; an explicit `null` clears the column.",
"properties": {
"include_in_net_worth": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Include In Net Worth"
},
"mirror_of_account_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Mirror Of Account Id"
},
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Name"
},
"primary_event_source": {
"anyOf": [
{
"$ref": "#/components/schemas/EventSource"
},
{
"type": "null"
}
]
},
"role": {
"anyOf": [
{
"$ref": "#/components/schemas/AccountRole"
},
{
"type": "null"
}
]
}
},
"title": "AccountPatch",
"type": "object"
},
"AccountRole": {
"enum": [
"liquid",
"savings",
"investment",
"debt"
],
"title": "AccountRole",
"type": "string"
},
"Broker": {
"enum": [
"tinvest",
"sber",
"vtb",
"other"
],
"title": "Broker",
"type": "string"
},
"CashFlowMonth": {
"properties": {
"baseline_rub": {
"description": "decimal as string",
"title": "Baseline Rub",
"type": "string"
},
"expense_rub": {
"description": "decimal as string",
"title": "Expense Rub",
"type": "string"
},
"income_rub": {
"description": "decimal as string",
"title": "Income Rub",
"type": "string"
},
"month": {
"format": "date",
"title": "Month",
"type": "string"
},
"one_off_rub": {
"description": "decimal as string",
"title": "One Off Rub",
"type": "string"
},
"savings_rate": {
"anyOf": [
{
"description": "decimal as string",
"type": "string"
},
{
"type": "null"
}
],
"title": "Savings Rate"
},
"savings_transfer_rub": {
"description": "decimal as string",
"title": "Savings Transfer Rub",
"type": "string"
},
"txn_count": {
"title": "Txn Count",
"type": "integer"
}
},
"required": [
"month",
"income_rub",
"expense_rub",
"baseline_rub",
"one_off_rub",
"savings_transfer_rub",
"savings_rate",
"txn_count"
],
"title": "CashFlowMonth",
"type": "object"
},
"CategoryOut": {
"description": "Flat list; the client builds the tree from `parent_id` (ZenMoney nests one level).",
"properties": {
"archived": {
"title": "Archived",
"type": "boolean"
},
"color": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Color"
},
"icon": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Icon"
},
"id": {
"title": "Id",
"type": "integer"
},
"name": {
"title": "Name",
"type": "string"
},
"parent_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Parent Id"
},
"show_income": {
"title": "Show Income",
"type": "boolean"
},
"show_outcome": {
"title": "Show Outcome",
"type": "boolean"
}
},
"required": [
"id",
"parent_id",
"name",
"icon",
"color",
"show_income",
"show_outcome",
"archived"
],
"title": "CategoryOut",
"type": "object"
},
"DataQualityRow": {
"properties": {
"check_name": {
"title": "Check Name",
"type": "string"
},
"computed_at": {
"format": "date-time",
"title": "Computed At",
"type": "string"
},
"count": {
"title": "Count",
"type": "integer"
},
"detail": {
"title": "Detail",
"type": "string"
},
"id": {
"title": "Id",
"type": "integer"
},
"ref": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"title": "Ref"
},
"severity": {
"title": "Severity",
"type": "string"
}
},
"required": [
"id",
"check_name",
"severity",
"detail",
"count",
"ref",
"computed_at"
],
"title": "DataQualityRow",
"type": "object"
},
"EventSource": {
"description": "Which feed is the truth for an account's ledger; others become `shadow` events.",
"enum": [
"tinvest_api",
"report_sber",
"report_vtb",
"manual"
],
"title": "EventSource",
"type": "string"
},
"FlowType": {
"enum": [
"income",
"expense",
"internal_transfer",
"savings_transfer",
"broker_external_flow",
"deleted",
"other"
],
"title": "FlowType",
"type": "string"
},
"Health": {
"properties": {
"database": {
"title": "Database",
"type": "string"
},
"status": {
"title": "Status",
"type": "string"
},
"version": {
"title": "Version",
"type": "string"
}
},
"required": [
"status",
"version",
"database"
],
"title": "Health",
"type": "object"
},
"JobStatus": {
"enum": [
"queued",
"running",
"done",
"error"
],
"title": "JobStatus",
"type": "string"
},
"LoginRequest": {
"properties": {
"email": {
"format": "email",
"title": "Email",
"type": "string"
},
"password": {
"maxLength": 1024,
"minLength": 1,
"title": "Password",
"type": "string"
}
},
"required": [
"email",
"password"
],
"title": "LoginRequest",
"type": "object"
},
"NetWorthBreakdown": {
"properties": {
"accounts": {
"items": {
"$ref": "#/components/schemas/AccountBalance"
},
"title": "Accounts",
"type": "array"
},
"by_currency": {
"additionalProperties": {
"type": "string"
},
"title": "By Currency",
"type": "object"
},
"d": {
"anyOf": [
{
"format": "date",
"type": "string"
},
{
"type": "null"
}
],
"title": "D"
},
"debt_rub": {
"description": "decimal as string",
"title": "Debt Rub",
"type": "string"
},
"investment_rub": {
"description": "decimal as string",
"title": "Investment Rub",
"type": "string"
},
"liquid_rub": {
"description": "decimal as string",
"title": "Liquid Rub",
"type": "string"
},
"missing_fx_count": {
"title": "Missing Fx Count",
"type": "integer"
},
"savings_rub": {
"description": "decimal as string",
"title": "Savings Rub",
"type": "string"
},
"total_rub": {
"description": "decimal as string",
"title": "Total Rub",
"type": "string"
}
},
"required": [
"d",
"total_rub",
"liquid_rub",
"savings_rub",
"investment_rub",
"debt_rub",
"by_currency",
"missing_fx_count",
"accounts"
],
"title": "NetWorthBreakdown",
"type": "object"
},
"NetWorthDay": {
"properties": {
"by_currency": {
"additionalProperties": {
"type": "string"
},
"title": "By Currency",
"type": "object"
},
"d": {
"format": "date",
"title": "D",
"type": "string"
},
"debt_rub": {
"description": "decimal as string",
"title": "Debt Rub",
"type": "string"
},
"investment_rub": {
"description": "decimal as string",
"title": "Investment Rub",
"type": "string"
},
"liquid_rub": {
"description": "decimal as string",
"title": "Liquid Rub",
"type": "string"
},
"missing_fx_count": {
"title": "Missing Fx Count",
"type": "integer"
},
"savings_rub": {
"description": "decimal as string",
"title": "Savings Rub",
"type": "string"
},
"total_rub": {
"description": "decimal as string",
"title": "Total Rub",
"type": "string"
}
},
"required": [
"d",
"total_rub",
"liquid_rub",
"savings_rub",
"investment_rub",
"debt_rub",
"by_currency",
"missing_fx_count"
],
"title": "NetWorthDay",
"type": "object"
},
"Problem": {
"description": "RFC 7807 error body (application/problem+json)",
"properties": {
"detail": {
"type": "string"
},
"errors": {
"items": {},
"type": "array"
},
"status": {
"type": "integer"
},
"title": {
"type": "string"
}
},
"required": [
"status",
"title"
],
"title": "Problem",
"type": "object"
},
"RefreshLogOut": {
"properties": {
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Error"
},
"finished_at": {
"anyOf": [
{
"format": "date-time",
"type": "string"
},
{
"type": "null"
}
],
"title": "Finished At"
},
"id": {
"title": "Id",
"type": "integer"
},
"started_at": {
"format": "date-time",
"title": "Started At",
"type": "string"
},
"trigger": {
"title": "Trigger",
"type": "string"
}
},
"required": [
"id",
"started_at",
"finished_at",
"trigger",
"error"
],
"title": "RefreshLogOut",
"type": "object"
},
"RefreshRequest": {
"properties": {
"refresh_token": {
"minLength": 1,
"title": "Refresh Token",
"type": "string"
}
},
"required": [
"refresh_token"
],
"title": "RefreshRequest",
"type": "object"
},
"RuleCreate": {
"additionalProperties": false,
"properties": {
"enabled": {
"default": true,
"title": "Enabled",
"type": "boolean"
},
"kind": {
"$ref": "#/components/schemas/RuleKind"
},
"match_type": {
"$ref": "#/components/schemas/RuleMatchType"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Note"
},
"pattern": {
"maxLength": 512,
"minLength": 1,
"title": "Pattern",
"type": "string"
},
"priority": {
"default": 100,
"title": "Priority",
"type": "integer"
},
"value": {
"anyOf": [
{
"maxLength": 512,
"type": "string"
},
{
"type": "null"
}
],
"title": "Value"
}
},
"required": [
"kind",
"match_type",
"pattern"
],
"title": "RuleCreate",
"type": "object"
},
"RuleKind": {
"enum": [
"savings",
"one_off",
"category",
"payee",
"broker_target",
"ignore"
],
"title": "RuleKind",
"type": "string"
},
"RuleMatchType": {
"enum": [
"id",
"payee",
"comment",
"category",
"mcc",
"account"
],
"title": "RuleMatchType",
"type": "string"
},
"RuleOut": {
"properties": {
"enabled": {
"title": "Enabled",
"type": "boolean"
},
"id": {
"title": "Id",
"type": "integer"
},
"kind": {
"$ref": "#/components/schemas/RuleKind"
},
"last_matched_at": {
"anyOf": [
{
"format": "date-time",
"type": "string"
},
{
"type": "null"
}
],
"title": "Last Matched At"
},
"match_count": {
"title": "Match Count",
"type": "integer"
},
"match_type": {
"$ref": "#/components/schemas/RuleMatchType"
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Note"
},
"pattern": {
"title": "Pattern",
"type": "string"
},
"priority": {
"title": "Priority",
"type": "integer"
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Value"
}
},
"required": [
"id",
"kind",
"match_type",
"pattern",
"value",
"note",
"enabled",
"priority",
"last_matched_at",
"match_count"
],
"title": "RuleOut",
"type": "object"
},
"RulePatch": {
"additionalProperties": false,
"properties": {
"enabled": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Enabled"
},
"kind": {
"anyOf": [
{
"$ref": "#/components/schemas/RuleKind"
},
{
"type": "null"
}
]
},
"match_type": {
"anyOf": [
{
"$ref": "#/components/schemas/RuleMatchType"
},
{
"type": "null"
}
]
},
"note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Note"
},
"pattern": {
"anyOf": [
{
"maxLength": 512,
"minLength": 1,
"type": "string"
},
{
"type": "null"
}
],
"title": "Pattern"
},
"priority": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Priority"
},
"value": {
"anyOf": [
{
"maxLength": 512,
"type": "string"
},
{
"type": "null"
}
],
"title": "Value"
}
},
"title": "RulePatch",
"type": "object"
},
"RunStatus": {
"enum": [
"running",
"ok",
"error"
],
"title": "RunStatus",
"type": "string"
},
"RunwayOut": {
"properties": {
"as_of": {
"format": "date",
"title": "As Of",
"type": "string"
},
"avg_baseline_3m_rub": {
"description": "decimal as string",
"title": "Avg Baseline 3M Rub",
"type": "string"
},
"liquid_reserve_rub": {
"description": "decimal as string",
"title": "Liquid Reserve Rub",
"type": "string"
},
"runway_months": {
"anyOf": [
{
"description": "decimal as string",
"type": "string"
},
{
"type": "null"
}
],
"title": "Runway Months"
}
},
"required": [
"as_of",
"liquid_reserve_rub",
"avg_baseline_3m_rub",
"runway_months"
],
"title": "RunwayOut",
"type": "object"
},
"SourceStatus": {
"properties": {
"cursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Cursor"
},
"last_run_at": {
"anyOf": [
{
"format": "date-time",
"type": "string"
},
{
"type": "null"
}
],
"title": "Last Run At"
},
"last_run_status": {
"anyOf": [
{
"$ref": "#/components/schemas/RunStatus"
},
{
"type": "null"
}
]
},
"last_success_at": {
"anyOf": [
{
"format": "date-time",
"type": "string"
},
{
"type": "null"
}
],
"title": "Last Success At"
},
"queued": {
"title": "Queued",
"type": "boolean"
},
"source": {
"title": "Source",
"type": "string"
}
},
"required": [
"source",
"cursor",
"last_run_at",
"last_success_at",
"last_run_status",
"queued"
],
"title": "SourceStatus",
"type": "object"
},
"SpendingRow": {
"properties": {
"amount_rub": {
"description": "decimal as string",
"title": "Amount Rub",
"type": "string"
},
"category_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Category Id"
},
"category_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Category Name"
},
"month": {
"format": "date",
"title": "Month",
"type": "string"
},
"root_category_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Root Category Id"
},
"root_category_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Root Category Name"
},
"txn_count": {
"title": "Txn Count",
"type": "integer"
}
},
"required": [
"month",
"category_id",
"category_name",
"root_category_id",
"root_category_name",
"amount_rub",
"txn_count"
],
"title": "SpendingRow",
"type": "object"
},
"SyncJobOut": {
"properties": {
"id": {
"format": "uuid",
"title": "Id",
"type": "string"
},
"requested_at": {
"format": "date-time",
"title": "Requested At",
"type": "string"
},
"source": {
"title": "Source",
"type": "string"
},
"status": {
"$ref": "#/components/schemas/JobStatus"
}
},
"required": [
"id",
"source",
"status",
"requested_at"
],
"title": "SyncJobOut",
"type": "object"
},
"SyncRunOut": {
"properties": {
"counts": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"title": "Counts"
},
"cursor_after": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Cursor After"
},
"cursor_before": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Cursor Before"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Error"
},
"finished_at": {
"anyOf": [
{
"format": "date-time",
"type": "string"
},
{
"type": "null"
}
],
"title": "Finished At"
},
"id": {
"format": "uuid",
"title": "Id",
"type": "string"
},
"source": {
"title": "Source",
"type": "string"
},
"started_at": {
"format": "date-time",
"title": "Started At",
"type": "string"
},
"status": {
"$ref": "#/components/schemas/RunStatus"
},
"triggered_by": {
"title": "Triggered By",
"type": "string"
},
"warnings": {
"anyOf": [
{
"items": {},
"type": "array"
},
{
"type": "null"
}
],
"title": "Warnings"
}
},
"required": [
"id",
"source",
"status",
"triggered_by",
"started_at",
"finished_at",
"cursor_before",
"cursor_after",
"counts",
"warnings",
"error"
],
"title": "SyncRunOut",
"type": "object"
},
"TokenPair": {
"properties": {
"access_token": {
"title": "Access Token",
"type": "string"
},
"expires_in": {
"title": "Expires In",
"type": "integer"
},
"refresh_token": {
"title": "Refresh Token",
"type": "string"
},
"token_type": {
"default": "bearer",
"title": "Token Type",
"type": "string"
}
},
"required": [
"access_token",
"refresh_token",
"expires_in"
],
"title": "TokenPair",
"type": "object"
},
"TransactionOut": {
"properties": {
"category_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Category Id"
},
"comment": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Comment"
},
"date": {
"format": "date",
"title": "Date",
"type": "string"
},
"deleted": {
"title": "Deleted",
"type": "boolean"
},
"flow_type": {
"$ref": "#/components/schemas/FlowType"
},
"hold": {
"title": "Hold",
"type": "boolean"
},
"id": {
"title": "Id",
"type": "integer"
},
"income": {
"description": "decimal as string",
"title": "Income",
"type": "string"
},
"income_account_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Income Account Id"
},
"income_currency": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Income Currency"
},
"income_rub": {
"anyOf": [
{
"description": "decimal as string",
"type": "string"
},
{
"type": "null"
}
],
"title": "Income Rub"
},
"is_one_off": {
"title": "Is One Off",
"type": "boolean"
},
"mcc": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Mcc"
},
"outcome": {
"description": "decimal as string",
"title": "Outcome",
"type": "string"
},
"outcome_account_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Outcome Account Id"
},
"outcome_currency": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Outcome Currency"
},
"outcome_rub": {
"anyOf": [
{
"description": "decimal as string",
"type": "string"
},
{
"type": "null"
}
],
"title": "Outcome Rub"
},
"payee": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Payee"
},
"payee_canonical": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Payee Canonical"
},
"source_id": {
"title": "Source Id",
"type": "string"
},
"tags": {
"items": {
"type": "integer"
},
"title": "Tags",
"type": "array"
},
"trip_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Trip Id"
},
"ts": {
"format": "date-time",
"title": "Ts",
"type": "string"
}
},
"required": [
"id",
"source_id",
"ts",
"date",
"income",
"income_currency",
"income_account_id",
"income_rub",
"outcome",
"outcome_currency",
"outcome_account_id",
"outcome_rub",
"payee",
"payee_canonical",
"comment",
"mcc",
"hold",
"deleted",
"flow_type",
"category_id",
"is_one_off",
"trip_id",
"tags"
],
"title": "TransactionOut",
"type": "object"
},
"TransactionPage": {
"properties": {
"items": {
"items": {
"$ref": "#/components/schemas/TransactionOut"
},
"title": "Items",
"type": "array"
},
"page": {
"title": "Page",
"type": "integer"
},
"page_size": {
"title": "Page Size",
"type": "integer"
},
"total": {
"title": "Total",
"type": "integer"
}
},
"required": [
"items",
"total",
"page",
"page_size"
],
"title": "TransactionPage",
"type": "object"
},
"UserOut": {
"properties": {
"email": {
"title": "Email",
"type": "string"
},
"id": {
"title": "Id",
"type": "integer"
}
},
"required": [
"id",
"email"
],
"title": "UserOut",
"type": "object"
}
},
"securitySchemes": {
"HTTPBearer": {
"scheme": "bearer",
"type": "http"
}
}
},
"info": {
"title": "fin-tracker",
"version": "0.1.0"
},
"openapi": "3.1.0",
"paths": {
"/api/v1/accounts": {
"get": {
"description": "Every account, archived ones included — the client decides what to show.",
"operationId": "accounts_list",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/AccountOut"
},
"title": "Response Accounts List",
"type": "array"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "List",
"tags": [
"accounts"
]
}
},
"/api/v1/accounts/{account_id}": {
"patch": {
"description": "Update the user-owned fields. Everything else is overwritten by the next sync.",
"operationId": "accounts_patch",
"parameters": [
{
"in": "path",
"name": "account_id",
"required": true,
"schema": {
"title": "Account Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AccountPatch"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AccountOut"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Patch",
"tags": [
"accounts"
]
}
},
"/api/v1/auth/login": {
"post": {
"operationId": "auth_login",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TokenPair"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"summary": "Login",
"tags": [
"auth"
]
}
},
"/api/v1/auth/logout": {
"post": {
"operationId": "auth_logout",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RefreshRequest"
}
}
},
"required": true
},
"responses": {
"204": {
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"summary": "Logout",
"tags": [
"auth"
]
}
},
"/api/v1/auth/me": {
"get": {
"operationId": "auth_me",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserOut"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Me",
"tags": [
"auth"
]
}
},
"/api/v1/auth/refresh": {
"post": {
"operationId": "auth_refresh",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RefreshRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TokenPair"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"summary": "Refresh",
"tags": [
"auth"
]
}
},
"/api/v1/cashflow/monthly": {
"get": {
"description": "The last `months` months, oldest first.",
"operationId": "cashflow_monthly",
"parameters": [
{
"in": "query",
"name": "months",
"required": false,
"schema": {
"default": 12,
"maximum": 120,
"minimum": 1,
"title": "Months",
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/CashFlowMonth"
},
"title": "Response Cashflow Monthly",
"type": "array"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Monthly",
"tags": [
"cashflow"
]
}
},
"/api/v1/categories": {
"get": {
"description": "Flat list of the ZenMoney tag tree; the client nests it by `parent_id`.",
"operationId": "categories_list",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/CategoryOut"
},
"title": "Response Categories List",
"type": "array"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "List",
"tags": [
"categories"
]
}
},
"/api/v1/data-quality": {
"get": {
"description": "Findings of the latest refresh, most serious first.",
"operationId": "metrics_data_quality",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/DataQualityRow"
},
"title": "Response Metrics Data Quality",
"type": "array"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Data Quality",
"tags": [
"metrics"
]
}
},
"/api/v1/health": {
"get": {
"operationId": "health_check",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Health"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"summary": "Check",
"tags": [
"health"
]
}
},
"/api/v1/metrics/refresh": {
"post": {
"description": "Rebuild every metric_* table inline (seconds at personal volumes).",
"operationId": "metrics_refresh",
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RefreshLogOut"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Refresh",
"tags": [
"metrics"
]
}
},
"/api/v1/metrics/status": {
"get": {
"description": "When the metric tables were last rebuilt, and whether it failed.",
"operationId": "metrics_status",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/RefreshLogOut"
},
{
"type": "null"
}
],
"title": "Response Metrics Status"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Status",
"tags": [
"metrics"
]
}
},
"/api/v1/networth/breakdown": {
"get": {
"description": "The latest day's buckets, plus every account's current balance native and in RUB.",
"operationId": "networth_breakdown",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NetWorthBreakdown"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Breakdown",
"tags": [
"networth"
]
}
},
"/api/v1/networth/series": {
"get": {
"description": "Daily net worth; defaults to the last 365 days.",
"operationId": "networth_series",
"parameters": [
{
"in": "query",
"name": "from",
"required": false,
"schema": {
"anyOf": [
{
"format": "date",
"type": "string"
},
{
"type": "null"
}
],
"title": "From"
}
},
{
"in": "query",
"name": "to",
"required": false,
"schema": {
"anyOf": [
{
"format": "date",
"type": "string"
},
{
"type": "null"
}
],
"title": "To"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/NetWorthDay"
},
"title": "Response Networth Series",
"type": "array"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Series",
"tags": [
"networth"
]
}
},
"/api/v1/rules": {
"get": {
"operationId": "rules_list",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/RuleOut"
},
"title": "Response Rules List",
"type": "array"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "List",
"tags": [
"rules"
]
},
"post": {
"operationId": "rules_create",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RuleCreate"
}
}
},
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RuleOut"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Create",
"tags": [
"rules"
]
}
},
"/api/v1/rules/apply": {
"post": {
"description": "Re-run the whole metric refresh so edited rules take effect everywhere at once.",
"operationId": "rules_apply",
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RefreshLogOut"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Apply",
"tags": [
"rules"
]
}
},
"/api/v1/rules/stale": {
"get": {
"description": "Enabled rules that matched nothing in the latest refresh: the payee was renamed,\nor the transaction was re-categorised in ZenMoney.",
"operationId": "rules_stale",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/RuleOut"
},
"title": "Response Rules Stale",
"type": "array"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Stale",
"tags": [
"rules"
]
}
},
"/api/v1/rules/{rule_id}": {
"delete": {
"operationId": "rules_delete",
"parameters": [
{
"in": "path",
"name": "rule_id",
"required": true,
"schema": {
"title": "Rule Id",
"type": "integer"
}
}
],
"responses": {
"204": {
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Delete",
"tags": [
"rules"
]
},
"patch": {
"operationId": "rules_patch",
"parameters": [
{
"in": "path",
"name": "rule_id",
"required": true,
"schema": {
"title": "Rule Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RulePatch"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RuleOut"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Patch",
"tags": [
"rules"
]
}
},
"/api/v1/runway": {
"get": {
"description": "How many months the liquid reserve covers; null before the first refresh.",
"operationId": "cashflow_runway",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/RunwayOut"
},
{
"type": "null"
}
],
"title": "Response Cashflow Runway"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Runway",
"tags": [
"cashflow"
]
}
},
"/api/v1/spending/categories": {
"get": {
"description": "Expenses of one month by category, largest first.",
"operationId": "cashflow_spending",
"parameters": [
{
"description": "YYYY-MM; defaults to the latest month",
"in": "query",
"name": "month",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "YYYY-MM; defaults to the latest month",
"title": "Month"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/SpendingRow"
},
"title": "Response Cashflow Spending",
"type": "array"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Spending",
"tags": [
"cashflow"
]
}
},
"/api/v1/sync/runs": {
"get": {
"operationId": "sync_runs",
"parameters": [
{
"in": "query",
"name": "source",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Source"
}
},
{
"in": "query",
"name": "limit",
"required": false,
"schema": {
"default": 20,
"maximum": 200,
"minimum": 1,
"title": "Limit",
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/SyncRunOut"
},
"title": "Response Sync Runs",
"type": "array"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Runs",
"tags": [
"sync"
]
}
},
"/api/v1/sync/status": {
"get": {
"operationId": "sync_status",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/SourceStatus"
},
"title": "Response Sync Status",
"type": "array"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Status",
"tags": [
"sync"
]
}
},
"/api/v1/sync/{source}": {
"post": {
"operationId": "sync_trigger",
"parameters": [
{
"in": "path",
"name": "source",
"required": true,
"schema": {
"title": "Source",
"type": "string"
}
}
],
"responses": {
"202": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SyncJobOut"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "Trigger",
"tags": [
"sync"
]
}
},
"/api/v1/transactions": {
"get": {
"description": "One page of transactions, newest first, with RUB amounts at each own date's rate.",
"operationId": "transactions_list",
"parameters": [
{
"in": "query",
"name": "from",
"required": false,
"schema": {
"anyOf": [
{
"format": "date",
"type": "string"
},
{
"type": "null"
}
],
"title": "From"
}
},
{
"in": "query",
"name": "to",
"required": false,
"schema": {
"anyOf": [
{
"format": "date",
"type": "string"
},
{
"type": "null"
}
],
"title": "To"
}
},
{
"in": "query",
"name": "account_id",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Account Id"
}
},
{
"in": "query",
"name": "category_id",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Category Id"
}
},
{
"in": "query",
"name": "flow_type",
"required": false,
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/FlowType"
},
{
"type": "null"
}
],
"title": "Flow Type"
}
},
{
"description": "substring of payee or comment",
"in": "query",
"name": "q",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "substring of payee or comment",
"title": "Q"
}
},
{
"in": "query",
"name": "include_deleted",
"required": false,
"schema": {
"default": false,
"title": "Include Deleted",
"type": "boolean"
}
},
{
"in": "query",
"name": "page",
"required": false,
"schema": {
"default": 1,
"minimum": 1,
"title": "Page",
"type": "integer"
}
},
{
"in": "query",
"name": "page_size",
"required": false,
"schema": {
"default": 50,
"maximum": 500,
"minimum": 1,
"title": "Page Size",
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TransactionPage"
}
}
},
"description": "Successful Response"
},
"default": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem"
}
}
},
"description": "Error"
}
},
"security": [
{
"HTTPBearer": []
}
],
"summary": "List",
"tags": [
"transactions"
]
}
}
},
"servers": [
{
"url": "/"
}
]
}