{"components":{"parameters":{"Cursor":{"description":"Opaque cursor from a previous response's pagination.next_cursor.","in":"query","name":"cursor","required":false,"schema":{"type":"string"}},"EndDate":{"description":"Inclusive end date (YYYY-MM-DD).","in":"query","name":"end_date","required":false,"schema":{"format":"date","type":"string"}},"EndDateRequired":{"description":"Inclusive end date (YYYY-MM-DD).","in":"query","name":"end_date","required":true,"schema":{"format":"date","type":"string"}},"IdempotencyKey":{"description":"A caller-chosen unique key (1-128 chars) that makes the write idempotent. A retry with the SAME key against the SAME operation, resource, and body replays the original response (Idempotency-Replayed: true). The same key with anything different — a changed body, a different endpoint, or a different {id} — is a 409 conflict; a still-in-flight duplicate is 409 with Retry-After. Required on every write. Stored for 24 hours.\n\nThe key is bound to the target, not only to the payload, so reusing one key across two deletes (which carry no body at all) conflicts rather than replaying the first delete's response.","in":"header","name":"Idempotency-Key","required":true,"schema":{"maxLength":128,"minLength":1,"type":"string"}},"PageSize":{"description":"Maximum number of items to return.","in":"query","name":"page_size","required":false,"schema":{"format":"int32","minimum":1,"type":"integer"}},"PathID":{"description":"The resource UUID.","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},"StartDate":{"description":"Inclusive start date (YYYY-MM-DD).","in":"query","name":"start_date","required":false,"schema":{"format":"date","type":"string"}},"StartDateRequired":{"description":"Inclusive start date (YYYY-MM-DD).","in":"query","name":"start_date","required":true,"schema":{"format":"date","type":"string"}}},"responses":{"Problem":{"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}},"description":"An RFC 9457 problem+json error. See the ProblemType enum for the stable type slugs."}},"schemas":{"Account":{"properties":{"available_balance":{"$ref":"#/components/schemas/Money"},"balance":{"$ref":"#/components/schemas/Money"},"connection_id":{"format":"uuid","type":"string"},"created_at":{"format":"date-time","nullable":true,"type":"string"},"currency":{"type":"string"},"id":{"format":"uuid","type":"string"},"institution_logo_url":{"description":"Logo for the account's own institution. Present under the same condition as institution_name, and redacted with it.","type":"string"},"institution_name":{"description":"The bank this ACCOUNT is at. Present only when it differs from its connection's institution — one connection can span several banks — so when it is absent the connection's institution is the account's. Redacted together with the connection's, under connection.institution.","type":"string"},"is_hidden":{"type":"boolean"},"mask":{"nullable":true,"type":"string"},"name":{"type":"string"},"nickname":{"nullable":true,"type":"string"},"provider":{"type":"string"},"subtype":{"nullable":true,"type":"string"},"type":{"type":"string"},"updated_at":{"format":"date-time","nullable":true,"type":"string"}},"required":["id"],"type":"object"},"AccountBalance":{"properties":{"account_id":{"format":"uuid","type":"string"},"as_of":{"format":"date-time","nullable":true,"type":"string"},"available_balance":{"$ref":"#/components/schemas/Money"},"balance":{"$ref":"#/components/schemas/Money"},"name":{"type":"string"}},"required":["account_id"],"type":"object"},"AllocationEntry":{"properties":{"day_change_amount":{"description":"A decimal amount string.","nullable":true,"type":"string"},"day_change_pct":{"description":"A decimal percentage string.","nullable":true,"type":"string"},"label":{"type":"string"},"percentage":{"format":"double","type":"number"},"value":{"description":"A decimal amount string.","type":"string"}},"required":["label"],"type":"object"},"BankConnection":{"properties":{"account_count":{"format":"int32","type":"integer"},"created_at":{"format":"date-time","nullable":true,"type":"string"},"error_code":{"nullable":true,"type":"string"},"error_message":{"nullable":true,"type":"string"},"id":{"format":"uuid","type":"string"},"institution":{"type":"string"},"institution_logo_url":{"type":"string"},"last_synced_at":{"format":"date-time","nullable":true,"type":"string"},"provider":{"description":"The rail this connection runs on — plaid, quiltt, or manual.","type":"string"},"source_aggregator":{"description":"The underlying data aggregator when the provider fronts one, as a lowercase token (finicity, mx, plaid, akoya). Quiltt is a unified layer over several aggregators, so this names the one that actually sourced the connection's data; it is absent when the provider is itself the aggregator. Never redacted — like provider, it names the rail the data came over, not the institution it came from.","type":"string"},"status":{"type":"string"},"updated_at":{"format":"date-time","nullable":true,"type":"string"}},"required":["id"],"type":"object"},"Budget":{"properties":{"created_at":{"format":"date-time","nullable":true,"type":"string"},"enabled_features":{"items":{"type":"string"},"type":"array"},"expected_monthly_income":{"nullable":true,"type":"string"},"id":{"format":"uuid","type":"string"},"income_type":{"nullable":true,"type":"string"},"is_active":{"type":"boolean"},"method_meta":{"$ref":"#/components/schemas/BudgetMethodMeta"},"method_type":{"type":"string"},"name":{"type":"string"},"period_type":{"type":"string"},"target_groups":{"items":{"$ref":"#/components/schemas/TargetGroup"},"type":"array"}},"required":["id"],"type":"object"},"BudgetAnomaly":{"properties":{"amount":{"description":"A decimal amount string.","type":"string"},"covered_by_goal":{"type":"string"},"covered_by_plan_line":{"description":"Name of the goal spending-plan line the absorbed spend was attributed to.","type":"string"},"status":{"type":"string"},"transaction_date":{"format":"date","type":"string"},"transaction_id":{"format":"uuid","type":"string"}},"required":["transaction_id"],"type":"object"},"BudgetItem":{"properties":{"allocated_amount":{"description":"A decimal amount string.","type":"string"},"category_id":{"format":"uuid","type":"string"},"id":{"format":"uuid","type":"string"},"spent_amount":{"description":"A decimal amount string.","type":"string"}},"required":["id"],"type":"object"},"BudgetMethodMeta":{"properties":{"display_name":{"type":"string"},"has_fixed_groups":{"type":"boolean"},"is_name_editable":{"type":"boolean"}},"type":"object"},"BudgetMethodSwitchRequest":{"description":"The POST body for switching the active budget method.","properties":{"confirm":{"description":"Must be true — the switch archives the current budget and creates a new one.","type":"boolean"},"method":{"description":"The budget method to switch to.","enum":["fifty_thirty_twenty","pay_yourself_first","zero_based","envelope","kakeibo","fire","values_based","custom"],"type":"string"}},"required":["method","confirm"],"type":"object"},"BudgetMethodSwitchResult":{"description":"The result of a budget method switch.","properties":{"new_budget_id":{"format":"uuid","type":"string"},"previous_budget_archived":{"type":"boolean"}},"required":["new_budget_id","previous_budget_archived"],"type":"object"},"BudgetProgress":{"properties":{"anomalies":{"items":{"$ref":"#/components/schemas/BudgetAnomaly"},"type":"array"},"anomaly_total":{"description":"A decimal amount string.","type":"string"},"budget_id":{"format":"uuid","type":"string"},"expected_income":{"description":"A decimal amount string.","type":"string"},"groups":{"items":{"$ref":"#/components/schemas/GroupProgress"},"type":"array"},"is_current_period":{"description":"True when the period contains today and figures are still moving; false for frozen past periods.","type":"boolean"},"method_meta":{"$ref":"#/components/schemas/BudgetMethodMeta"},"method_type":{"type":"string"},"period_end":{"description":"Exclusive end of the period these figures cover — the first day NOT included.","format":"date","type":"string"},"period_label":{"description":"Server-formatted display label for the period.","type":"string"},"period_start":{"description":"First day of the period these figures cover (inclusive). Budgets are not always calendar months.","format":"date","type":"string"},"remaining":{"description":"A decimal amount string.","type":"string"},"total_allocated":{"description":"A decimal amount string.","type":"string"},"total_income":{"description":"A decimal amount string.","type":"string"},"total_spent":{"description":"A decimal amount string.","type":"string"}},"required":["budget_id"],"type":"object"},"BulkCategorizeItemResult":{"description":"One row's outcome. not_found and invalid_category are deliberately coarse: a row or category hidden by this connection's exclusions is indistinguishable from one that never existed.","properties":{"status":{"description":"applied — the category was assigned and the row is verified. not_found — no such transaction for this caller. invalid_category — the target category is not assignable by this caller. version_conflict — expected_version did not match; nothing was written. split — the row is a split parent whose slices carry the attribution; refused rather than flattened.","enum":["applied","not_found","invalid_category","version_conflict","split"],"type":"string"},"transaction_id":{"format":"uuid","type":"string"}},"required":["transaction_id","status"],"type":"object"},"BulkCategorizeResult":{"description":"The result of a bulk categorize. This call is PARTIAL by design: a mixed outcome is still 200, and every submitted item explains itself. Retry the items you want to retry with a FRESH Idempotency-Key — replaying the original key returns the original response, corrections included or not.","properties":{"applied_count":{"format":"int32","type":"integer"},"results":{"items":{"$ref":"#/components/schemas/BulkCategorizeItemResult"},"type":"array"}},"required":["applied_count","results"],"type":"object"},"CashFlowForecast":{"properties":{"currency":{"type":"string"},"points":{"items":{"$ref":"#/components/schemas/CashFlowPoint"},"type":"array"},"projected_end_balance":{"description":"A decimal amount string.","type":"string"},"projections":{"items":{"$ref":"#/components/schemas/CashFlowProjection"},"type":"array"},"recurring_items":{"items":{"$ref":"#/components/schemas/CashFlowRecurringItem"},"type":"array"},"starting_balance":{"description":"A decimal amount string.","type":"string"},"starting_balance_available":{"type":"boolean"}},"type":"object"},"CashFlowPoint":{"properties":{"date":{"format":"date","type":"string"},"expenses":{"description":"A decimal amount string.","type":"string"},"income":{"description":"A decimal amount string.","type":"string"},"projected_balance":{"description":"A decimal amount string.","type":"string"}},"required":["date"],"type":"object"},"CashFlowProjection":{"properties":{"expected_expenses":{"description":"A decimal amount string.","type":"string"},"expected_income":{"description":"A decimal amount string.","type":"string"},"net_cash_flow":{"description":"A decimal amount string.","type":"string"},"period_days":{"format":"int32","type":"integer"},"projected_balance":{"description":"A decimal amount string.","type":"string"}},"type":"object"},"CashFlowRecurringItem":{"properties":{"amount":{"description":"A decimal amount string.","type":"string"},"frequency":{"type":"string"},"merchant":{"type":"string"},"next_expected":{"format":"date","type":"string"},"stream_type":{"type":"string"}},"type":"object"},"Category":{"properties":{"color":{"type":"string"},"icon":{"type":"string"},"id":{"format":"uuid","type":"string"},"is_system":{"type":"boolean"},"name":{"type":"string"},"parent_id":{"format":"uuid","nullable":true,"type":"string"},"sort_order":{"format":"int32","type":"integer"},"type_tag":{"type":"string"}},"required":["id"],"type":"object"},"CategoryCreateRequest":{"description":"The POST body for a custom category. parent_id must name a visible top-level category (categories nest one level). Reaching the per-account category limit is 409, not a rate limit — waiting does not help.","properties":{"color":{"description":"A hex color, e.g. \"#FF5733\".","type":"string"},"icon":{"description":"A Material icon name, or \"emoji:\u003cgrapheme\u003e\".","type":"string"},"name":{"maxLength":60,"type":"string"},"parent_id":{"format":"uuid","type":"string"}},"required":["name"],"type":"object"},"CategoryDeleteRequest":{"description":"The OPTIONAL DELETE body. With no body (the ordinary case) the deleted category's transactions become uncategorized; naming a merge target reassigns them to it instead.","properties":{"merge_into_category_id":{"format":"uuid","type":"string"}},"type":"object"},"CategoryDeleteResult":{"description":"The result of deleting a category.","properties":{"deleted":{"type":"boolean"},"transactions_merged":{"description":"Transactions reassigned to merge_into_category_id; 0 when not merging.","format":"int64","type":"integer"}},"required":["deleted","transactions_merged"],"type":"object"},"CategoryUpdateRequest":{"description":"The PATCH body for a category. At least one field is required. An empty string means \"leave unchanged\", so a name or icon cannot be blanked here; removing a PARENT has its own flag. System categories are not editable (409).","properties":{"clear_parent":{"description":"Move the category to the top level.","type":"boolean"},"color":{"type":"string"},"icon":{"type":"string"},"name":{"maxLength":60,"type":"string"},"parent_id":{"format":"uuid","type":"string"}},"type":"object"},"DebtBalance":{"properties":{"balance":{"description":"A decimal amount string.","type":"string"},"liability_id":{"format":"uuid","type":"string"}},"required":["liability_id"],"type":"object"},"DebtPayoffDebtResult":{"properties":{"liability_id":{"format":"uuid","type":"string"},"negative_amortization":{"type":"boolean"},"payoff_date":{"format":"date","type":"string"},"payoff_month":{"format":"int32","type":"integer"},"payoff_order":{"format":"int32","type":"integer"},"total_interest":{"description":"A decimal amount string.","type":"string"},"total_paid":{"description":"A decimal amount string.","type":"string"}},"required":["liability_id"],"type":"object"},"DebtPayoffPlan":{"properties":{"assumptions":{"items":{"type":"string"},"type":"array"},"baseline_total_interest":{"description":"A decimal amount string.","type":"string"},"budget_insufficient":{"type":"boolean"},"currency":{"type":"string"},"debt_free_date":{"format":"date","type":"string"},"debts":{"items":{"$ref":"#/components/schemas/DebtPayoffDebtResult"},"type":"array"},"excluded":{"items":{"$ref":"#/components/schemas/ExcludedLiability"},"type":"array"},"horizon_exceeded":{"type":"boolean"},"interest_saved_vs_minimum":{"description":"A decimal amount string.","type":"string"},"minimum_required_budget":{"description":"A decimal amount string.","type":"string"},"plan_feasible":{"type":"boolean"},"timeline":{"items":{"$ref":"#/components/schemas/DebtPayoffTimelinePoint"},"type":"array"},"total_interest":{"description":"A decimal amount string.","type":"string"},"total_paid":{"description":"A decimal amount string.","type":"string"}},"required":["debts"],"type":"object"},"DebtPayoffPlanRequest":{"description":"The POST body for computing a debt payoff plan.","properties":{"custom_order":{"description":"The explicit payoff order when strategy is custom.","items":{"format":"uuid","type":"string"},"type":"array"},"extra_monthly_payment":{"description":"A decimal amount string.","type":"string"},"liability_ids":{"items":{"format":"uuid","type":"string"},"type":"array"},"strategy":{"enum":["snowball","avalanche","custom"],"type":"string"},"total_monthly_budget":{"description":"A decimal amount string.","type":"string"}},"type":"object"},"DebtPayoffTimelinePoint":{"properties":{"cumulative_interest":{"description":"A decimal amount string.","type":"string"},"debts":{"items":{"$ref":"#/components/schemas/DebtBalance"},"type":"array"},"month":{"description":"The timeline month as YYYY-MM.","type":"string"},"total_balance":{"description":"A decimal amount string.","type":"string"}},"required":["month"],"type":"object"},"DebtSummary":{"properties":{"summaries":{"items":{"$ref":"#/components/schemas/DebtSummaryPerCurrency"},"type":"array"},"total_debt_count":{"format":"int32","type":"integer"}},"required":["summaries"],"type":"object"},"DebtSummaryPerCurrency":{"properties":{"currency":{"type":"string"},"debt_count":{"format":"int32","type":"integer"},"estimated_interest_debt_count":{"format":"int32","type":"integer"},"estimated_monthly_interest":{"description":"A decimal amount string.","type":"string"},"total_debt":{"description":"A decimal amount string.","type":"string"},"total_ytd_interest_paid":{"description":"A decimal amount string.","type":"string"},"ytd_interest_debt_count":{"format":"int32","type":"integer"}},"required":["currency"],"type":"object"},"Envelope":{"description":"The standard response wrapper for every /v1 endpoint.","properties":{"data":{"description":"The response payload. Its shape depends on the endpoint."},"filtered":{"description":"True when row or aggregate filtering is in effect for this response.","type":"boolean"},"pagination":{"allOf":[{"$ref":"#/components/schemas/Page"}],"description":"Cursor pagination state, present on list endpoints.","nullable":true},"redacted_fields":{"description":"Field ids stripped from the payload by connection scope.","items":{"type":"string"},"type":"array"}},"required":["data"],"type":"object"},"ExcludedLiability":{"properties":{"liability_id":{"format":"uuid","type":"string"},"reason":{"type":"string"}},"required":["liability_id"],"type":"object"},"Goal":{"properties":{"account_links":{"items":{"$ref":"#/components/schemas/GoalAccountLink"},"type":"array"},"budget_links":{"items":{"$ref":"#/components/schemas/GoalBudgetLink"},"type":"array"},"completed_at":{"format":"date-time","nullable":true,"type":"string"},"created_at":{"format":"date-time","nullable":true,"type":"string"},"currency":{"type":"string"},"current_amount":{"description":"A decimal amount string.","type":"string"},"description":{"nullable":true,"type":"string"},"funding_source_type":{"type":"string"},"goal_type":{"type":"string"},"id":{"format":"uuid","type":"string"},"name":{"type":"string"},"progress":{"$ref":"#/components/schemas/GoalProgress"},"show_in_digest":{"type":"boolean"},"sort_order":{"format":"int32","type":"integer"},"status":{"type":"string"},"target_amount":{"description":"A decimal amount string.","type":"string"},"target_date":{"format":"date","nullable":true,"type":"string"},"tracking_mode":{"description":"What moves current_amount. budget_driven is recorded contributions; balance_mirror republishes a linked account's live balance; rule_split takes a persisted share of arriving money. A balance_mirror goal REFUSES POST /v1/goals/{id}/contributions with 409 — read this field to know that before you call.","enum":["budget_driven","balance_mirror","rule_split"],"type":"string"},"updated_at":{"format":"date-time","nullable":true,"type":"string"}},"required":["id"],"type":"object"},"GoalAccountLink":{"properties":{"account_id":{"format":"uuid","type":"string"},"account_type":{"type":"string"},"link_role":{"type":"string"}},"required":["account_id"],"type":"object"},"GoalBudgetLink":{"properties":{"budget_item_id":{"format":"uuid","type":"string"},"contribution_amount":{"description":"A decimal amount string.","type":"string"},"is_active":{"type":"boolean"}},"required":["budget_item_id"],"type":"object"},"GoalContribution":{"properties":{"amount":{"description":"A decimal amount string.","type":"string"},"budget_item_id":{"format":"uuid","nullable":true,"type":"string"},"currency":{"type":"string"},"detected_at":{"format":"date-time","nullable":true,"type":"string"},"goal_id":{"format":"uuid","type":"string"},"id":{"format":"uuid","type":"string"},"note":{"nullable":true,"type":"string"},"period_start":{"format":"date","type":"string"},"source":{"type":"string"}},"required":["id"],"type":"object"},"GoalContributionRequest":{"description":"The POST body for recording a manual goal contribution.","properties":{"amount":{"description":"A positive decimal amount string.","type":"string"},"currency_code":{"description":"ISO 4217 code. Defaults to the goal's currency when omitted.","type":"string"},"note":{"type":"string"},"period_start":{"description":"The budget period (YYYY-MM-DD, first of month). Defaults to the current month.","format":"date","type":"string"}},"required":["amount"],"type":"object"},"GoalContributionResult":{"description":"The result of recording a goal contribution.","properties":{"contribution":{"$ref":"#/components/schemas/GoalContribution"},"progress":{"$ref":"#/components/schemas/GoalProgress"}},"type":"object"},"GoalProgress":{"properties":{"current_amount":{"description":"A decimal amount string.","type":"string"},"days_remaining":{"format":"int32","nullable":true,"type":"integer"},"goal_id":{"format":"uuid","type":"string"},"last_contribution_at":{"format":"date-time","nullable":true,"type":"string"},"monthly_contribution_needed":{"description":"A decimal amount string.","nullable":true,"type":"string"},"monthly_shortfall":{"description":"A decimal amount string.","nullable":true,"type":"string"},"progress_pct":{"description":"A decimal percentage string.","type":"string"},"projected_completion_date":{"format":"date","nullable":true,"type":"string"},"remaining_amount":{"description":"A decimal amount string.","type":"string"},"target_amount":{"description":"A decimal amount string.","type":"string"}},"required":["goal_id"],"type":"object"},"GoalSnapshot":{"properties":{"current_amount":{"description":"A decimal amount string.","type":"string"},"goal_id":{"format":"uuid","type":"string"},"id":{"format":"uuid","type":"string"},"progress_pct":{"description":"A decimal percentage string.","type":"string"},"snapshot_date":{"format":"date","type":"string"},"target_amount":{"description":"A decimal amount string.","type":"string"}},"required":["id"],"type":"object"},"GoalUpdateRequest":{"description":"The PATCH body for a goal. All fields are optional but at least one is required. Unknown fields (status, is_shared, sort_order, funding, ...) are rejected with 400 validation.","properties":{"description":{"description":"Send an empty string to clear the description.","type":"string"},"name":{"type":"string"},"target_amount":{"description":"A decimal amount string.","type":"string"},"target_date":{"description":"YYYY-MM-DD, or an empty string to clear the target date.","format":"date","type":"string"}},"type":"object"},"GroupProgress":{"properties":{"allocated":{"description":"A decimal amount string.","type":"string"},"group_id":{"format":"uuid","type":"string"},"group_name":{"type":"string"},"phase_active":{"description":"False when the group is anchored to a different cycle phase than the period served — it received no fresh allocation, but its spend still counts.","type":"boolean"},"remaining":{"description":"A decimal amount string.","type":"string"},"rollover_amount":{"description":"A decimal amount string.","type":"string"},"spent":{"description":"A decimal amount string.","type":"string"},"spent_percentage":{"format":"double","type":"number"},"upcoming_recurring_total":{"description":"A decimal amount string.","type":"string"}},"required":["group_id"],"type":"object"},"Holding":{"properties":{"account_id":{"format":"uuid","nullable":true,"type":"string"},"acquired_at":{"format":"date-time","nullable":true,"type":"string"},"asset_type":{"type":"string"},"cost_basis":{"description":"A decimal amount string.","nullable":true,"type":"string"},"cost_basis_method":{"type":"string"},"created_at":{"format":"date-time","nullable":true,"type":"string"},"currency":{"type":"string"},"current_price":{"description":"A decimal price string.","type":"string"},"current_value":{"$ref":"#/components/schemas/Money"},"daily_change_pct":{"description":"A decimal percentage string.","type":"string"},"data_source":{"type":"string"},"id":{"format":"uuid","type":"string"},"name":{"type":"string"},"notes":{"nullable":true,"type":"string"},"price_source":{"type":"string"},"quantity":{"description":"A decimal quantity string.","type":"string"},"retirement_subtype":{"type":"string"},"ticker":{"nullable":true,"type":"string"},"updated_at":{"format":"date-time","nullable":true,"type":"string"}},"required":["id"],"type":"object"},"IncomeVsExpenses":{"properties":{"expenses":{"description":"A decimal amount string.","type":"string"},"income":{"description":"A decimal amount string.","type":"string"},"month":{"description":"The month as YYYY-MM.","type":"string"},"net":{"description":"A decimal amount string.","type":"string"},"savings_rate":{"description":"A decimal percentage string.","type":"string"}},"required":["month"],"type":"object"},"Liability":{"properties":{"account_id":{"format":"uuid","nullable":true,"type":"string"},"created_at":{"format":"date-time","nullable":true,"type":"string"},"currency":{"type":"string"},"current_balance":{"description":"A decimal amount string.","type":"string"},"id":{"format":"uuid","type":"string"},"interest_rate":{"description":"A decimal rate string.","nullable":true,"type":"string"},"monthly_payment":{"description":"A decimal amount string.","nullable":true,"type":"string"},"name":{"type":"string"},"principal":{"description":"A decimal amount string.","type":"string"},"start_date":{"format":"date","nullable":true,"type":"string"},"term_months":{"format":"int32","nullable":true,"type":"integer"},"type":{"type":"string"},"updated_at":{"format":"date-time","nullable":true,"type":"string"}},"required":["id"],"type":"object"},"LiquidityBreakdown":{"properties":{"illiquid":{"description":"A decimal amount string.","type":"string"},"investable":{"description":"A decimal amount string.","type":"string"},"liquid":{"description":"A decimal amount string.","type":"string"}},"type":"object"},"Money":{"description":"An exact monetary amount. amount is a decimal string, never a float.","properties":{"amount":{"description":"The decimal amount as a string.","type":"string"},"currency":{"description":"The ISO 4217 currency code.","type":"string"}},"required":["amount","currency"],"type":"object"},"MonthSummary":{"properties":{"month":{"description":"The month as YYYY-MM.","type":"string"},"net_savings":{"description":"A decimal amount string.","type":"string"},"savings_rate":{"description":"A decimal percentage string.","type":"string"},"total_expenses":{"description":"A decimal amount string.","type":"string"},"total_income":{"description":"A decimal amount string.","type":"string"},"transaction_count":{"format":"int32","type":"integer"}},"required":["month"],"type":"object"},"NetWorth":{"properties":{"base_currency":{"type":"string"},"breakdown":{"$ref":"#/components/schemas/NetWorthBreakdown"},"computed_at":{"format":"date-time","nullable":true,"type":"string"},"investable_net_worth":{"description":"A decimal amount string.","type":"string"},"is_manual":{"type":"boolean"},"liquid_net_worth":{"description":"A decimal amount string.","type":"string"},"net_worth":{"description":"A decimal amount string.","type":"string"},"snapshot_date":{"format":"date","type":"string"},"total_assets":{"description":"A decimal amount string.","type":"string"},"total_liabilities":{"description":"A decimal amount string.","type":"string"}},"type":"object"},"NetWorthBreakdown":{"properties":{"by_asset_type":{"items":{"$ref":"#/components/schemas/AllocationEntry"},"type":"array"},"by_currency":{"items":{"$ref":"#/components/schemas/AllocationEntry"},"type":"array"},"by_retirement_subtype":{"items":{"$ref":"#/components/schemas/AllocationEntry"},"type":"array"},"liquidity":{"$ref":"#/components/schemas/LiquidityBreakdown"}},"type":"object"},"NetWorthHistoryPoint":{"properties":{"base_currency":{"type":"string"},"date":{"format":"date","type":"string"},"investable_net_worth":{"description":"A decimal amount string.","type":"string"},"liquid_net_worth":{"description":"A decimal amount string.","type":"string"},"net_worth":{"description":"A decimal amount string.","type":"string"},"total_assets":{"description":"A decimal amount string.","type":"string"},"total_liabilities":{"description":"A decimal amount string.","type":"string"}},"required":["date"],"type":"object"},"Page":{"description":"Cursor pagination state. total_count is intentionally not exposed.","properties":{"has_more":{"type":"boolean"},"next_cursor":{"type":"string"}},"type":"object"},"Problem":{"additionalProperties":true,"description":"An RFC 9457 problem detail. type is https://api.403fin.io/problems/{slug} where slug is one of the ProblemType values. Extension members are flattened at the top level per RFC 9457 section 3.2. The tier-required problem carries the extensions current_tier, required_tier, and feature.","properties":{"current_tier":{"description":"Extension present on tier-required; the caller's current tier.","type":"string"},"detail":{"type":"string"},"feature":{"description":"Extension present on tier-required; the gated feature key.","type":"string"},"instance":{"type":"string"},"request_id":{"type":"string"},"required_tier":{"description":"Extension present on tier-required; the tier the feature needs.","type":"string"},"status":{"format":"int32","type":"integer"},"title":{"type":"string"},"type":{"description":"The problem type URI. Its slug is one of the ProblemType values.","format":"uri","type":"string"}},"required":["type","title","status"],"type":"object"},"ProblemType":{"description":"The stable, machine-readable problem type slug (the tail of Problem.type).","enum":["validation","tier-required","insufficient-scope","writes-disabled","forbidden","not-found","conflict","partner-approval-required","field-immutable","data-excluded","rate-limited","quota-exceeded","range-too-large","unavailable","internal","invalid-credential"],"type":"string"},"RecurringRule":{"properties":{"account_id":{"format":"uuid","nullable":true,"type":"string"},"confidence":{"type":"string"},"consecutive_missed":{"format":"int32","type":"integer"},"created_at":{"format":"date-time","nullable":true,"type":"string"},"currency":{"type":"string"},"display_name":{"description":"The requesting user's own label for this stream's merchant; per-viewer, absent when they have no rule for it. See \"Names on a transaction\".","nullable":true,"type":"string"},"expected_amount":{"description":"A decimal amount string.","type":"string"},"frequency":{"type":"string"},"id":{"format":"uuid","type":"string"},"last_matched_date":{"format":"date","nullable":true,"type":"string"},"logo_url":{"type":"string"},"match_count":{"format":"int32","type":"integer"},"merchant":{"type":"string"},"next_expected":{"format":"date","type":"string"},"source":{"type":"string"},"status":{"type":"string"},"stream_type":{"type":"string"},"updated_at":{"format":"date-time","nullable":true,"type":"string"}},"required":["id"],"type":"object"},"SpendingByCategoryRow":{"properties":{"amount":{"description":"A decimal amount string.","type":"string"},"category_id":{"format":"uuid","type":"string"},"category_name":{"type":"string"},"percentage":{"format":"double","type":"number"}},"required":["category_id"],"type":"object"},"SyncStatus":{"properties":{"all_healthy":{"type":"boolean"},"connections":{"items":{"$ref":"#/components/schemas/SyncStatusRow"},"type":"array"},"oldest_synced_at":{"format":"date-time","nullable":true,"type":"string"}},"required":["connections","all_healthy"],"type":"object"},"SyncStatusRow":{"properties":{"connection_id":{"format":"uuid","type":"string"},"healthy":{"type":"boolean"},"institution":{"type":"string"},"last_synced_at":{"format":"date-time","nullable":true,"type":"string"},"status":{"type":"string"}},"required":["connection_id","healthy"],"type":"object"},"TargetGroup":{"properties":{"allocation_type":{"type":"string"},"allocation_value":{"type":"string"},"id":{"format":"uuid","type":"string"},"items":{"items":{"$ref":"#/components/schemas/BudgetItem"},"type":"array"},"name":{"type":"string"},"sort_order":{"format":"int32","type":"integer"}},"required":["id"],"type":"object"},"Transaction":{"properties":{"account_id":{"format":"uuid","type":"string"},"amount":{"$ref":"#/components/schemas/Money"},"category_id":{"format":"uuid","nullable":true,"type":"string"},"check_number":{"nullable":true,"type":"string"},"created_at":{"format":"date-time","nullable":true,"type":"string"},"date":{"format":"date","type":"string"},"description":{"type":"string"},"display_name":{"description":"The requesting user's own label for this merchant; per-viewer, absent when they have no rule for it. See \"Names on a transaction\".","nullable":true,"type":"string"},"external_ref":{"nullable":true,"type":"string"},"id":{"format":"uuid","type":"string"},"is_pending":{"type":"boolean"},"location":{"allOf":[{"$ref":"#/components/schemas/TransactionLocation"}],"description":"Where the purchase happened. Redacted as ONE unit under transaction.location.","nullable":true},"memo":{"description":"The user-authored memo. Redacted under transaction.memo.","nullable":true,"type":"string"},"merchant":{"type":"string"},"payment_channel":{"nullable":true,"type":"string"},"posted_at":{"format":"date-time","nullable":true,"type":"string"},"provider_amount":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"What the bank recorded as the amount before a user edit displaced it. Present only on an edited bank-synced row; revert_to_provider [\"amount\"] restores it. Redacted together with amount under transaction.amount.","nullable":true},"provider_description":{"description":"The displayed description at the moment of the row's first user edit. Redacted with description under transaction.description.","nullable":true,"type":"string"},"provider_memo":{"description":"What the bank recorded as the memo before a user edit displaced it. Present-and-empty means the bank recorded nothing and reverting clears the field. Redacted with memo under transaction.memo.","nullable":true,"type":"string"},"provider_merchant_name":{"description":"What the bank recorded as the merchant before a user edit displaced it. Present-and-empty means the bank recorded nothing and reverting clears the field. Redacted with merchant under transaction.merchant.","nullable":true,"type":"string"},"provider_transaction_date":{"description":"The transaction date the bank recorded (YYYY-MM-DD) before a user edit moved it. Unredacted, like date itself.","format":"date","nullable":true,"type":"string"},"recurring_rule_id":{"format":"uuid","nullable":true,"type":"string"},"source":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array"},"transfer_pair_id":{"format":"uuid","nullable":true,"type":"string"},"updated_at":{"format":"date-time","nullable":true,"type":"string"},"version":{"description":"The optimistic-concurrency token. PATCH /v1/transactions/{id} requires it as expected_version; a stale value is a 409 and the caller re-reads. Always present, including on a row at version 0.","format":"int32","type":"integer"}},"required":["id"],"type":"object"},"TransactionAnnotateRequest":{"description":"The POST body for annotating a transaction — the narrow, additive edits the transactions.annotate:write scope covers. At least one field is required.\n\nThere is NO expected_version: the server reads the row and uses its current version, so a categorizing caller need not carry concurrency tokens. A row that changes underneath still loses the race (409).","properties":{"category_id":{"format":"uuid","type":"string"},"clear_location":{"type":"boolean"},"location":{"$ref":"#/components/schemas/TransactionLocation"},"memo":{"description":"An empty string clears the memo.","type":"string"},"tags":{"description":"Replaces every tag. An empty array clears them all.","items":{"type":"string"},"type":"array"}},"type":"object"},"TransactionCategorizeItem":{"description":"One assignment in a bulk categorize.","properties":{"category_id":{"format":"uuid","type":"string"},"expected_version":{"description":"Optional per item. Omit to apply regardless of the stored version; send it to have THIS item come back version_conflict rather than overwrite a change you did not see.","format":"int32","type":"integer"},"transaction_id":{"format":"uuid","type":"string"}},"required":["transaction_id","category_id"],"type":"object"},"TransactionCategorizeRequest":{"description":"The POST body for a bulk categorize: 1 to 100 items. A duplicate transaction_id is 400 for the whole batch — two assignments for one row would produce two results with no way to tell which describes the stored state. Account and category exclusions are applied server-side from the connection's own privacy settings and cannot be supplied here.","properties":{"items":{"items":{"$ref":"#/components/schemas/TransactionCategorizeItem"},"maxItems":100,"minItems":1,"type":"array"}},"required":["items"],"type":"object"},"TransactionCreateRequest":{"description":"The POST body for a manual transaction. account_id must name an account this connection can see: an excluded account and a nonexistent one are the same 404, so a hidden account cannot be discovered by probing. currency_code defaults to the account's currency.\n\nFields the first-party app has and this body does NOT: apply_to_merchant and create_merchant_rule (a programmatic write never teaches the categorizer), splits and receipt_ids (v1 exposes neither resource), use_subcategories, and category_from_ai_suggestion (a record of a HUMAN accepting a suggestion). Sending any of them is 400 validation.","properties":{"account_id":{"format":"uuid","type":"string"},"amount":{"description":"A decimal amount string, negative for spending. Never a float.","type":"string"},"category_id":{"format":"uuid","type":"string"},"currency_code":{"description":"ISO 4217 code. Defaults to the account's currency.","type":"string"},"date":{"description":"YYYY-MM-DD.","format":"date","type":"string"},"description":{"type":"string"},"location":{"$ref":"#/components/schemas/TransactionLocation"},"memo":{"type":"string"},"merchant":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array"}},"required":["account_id","amount","date"],"type":"object"},"TransactionDeleteResult":{"description":"The result of deleting a transaction.","properties":{"deleted":{"type":"boolean"},"id":{"format":"uuid","type":"string"}},"required":["deleted","id"],"type":"object"},"TransactionLocation":{"description":"A purchase location. Redacted as a single unit (transaction.location) — a city plus a postal code answers \"where were you\" as surely as a street line does, so the components are never hidden separately. On a write it is GROUP-REPLACE: the components sent become the whole stored location and omitted ones are cleared. Use clear_location to remove it entirely.","properties":{"city":{"type":"string"},"country":{"type":"string"},"postal_code":{"type":"string"},"state":{"type":"string"},"street":{"type":"string"}},"type":"object"},"TransactionUpdateRequest":{"description":"The PATCH body for a transaction. expected_version is REQUIRED — send the version you read; if the row changed since, you get 409 and re-read. At least one mutating field is required alongside it.\n\nA transaction's currency cannot be changed (422 field-immutable): create the row again in the currency you want. `tags: []` clears every tag, while omitting tags leaves them alone. location and clear_location are mutually exclusive. revert_to_provider hands named fields back to the bank, restoring what it recorded before the first user edit; naming a field this same request also sets is 400.\n\nEvery edit through this endpoint sets suppress_rule_learning (it will not create or update a merchant rule, vote in the global category model, or publish a categorization event) and refuse_split_replacement (a category change on a SPLIT row is refused, not applied over the slices).","properties":{"amount":{"description":"A decimal amount string. The row keeps the currency it settled in.","type":"string"},"category_id":{"format":"uuid","type":"string"},"clear_location":{"description":"Removes the whole stored location.","type":"boolean"},"date":{"format":"date","type":"string"},"description":{"type":"string"},"expected_version":{"format":"int32","type":"integer"},"location":{"$ref":"#/components/schemas/TransactionLocation"},"memo":{"description":"An empty string clears the memo.","type":"string"},"merchant":{"type":"string"},"revert_to_provider":{"description":"Restore these fields from what the bank recorded before the row's first user edit, handing ownership back to the provider so later syncs update them again. 409 when there is no captured value to revert to (an unedited row, or a manual one that never had a bank value).","items":{"enum":["amount","merchant","description","date","memo"],"type":"string"},"type":"array"},"tags":{"description":"Replaces every tag. An empty array clears them all.","items":{"type":"string"},"type":"array"}},"required":["expected_version"],"type":"object"},"UpcomingBill":{"properties":{"display_name":{"description":"The requesting user's own label for this bill's merchant; per-viewer, absent when they have no rule for it. See \"Names on a transaction\".","nullable":true,"type":"string"},"expected_amount":{"description":"A decimal amount string.","type":"string"},"expected_date":{"format":"date","type":"string"},"frequency":{"type":"string"},"logo_url":{"type":"string"},"merchant":{"type":"string"},"rule_id":{"format":"uuid","type":"string"},"stream_type":{"type":"string"}},"required":["rule_id"],"type":"object"}},"securitySchemes":{"apiKey":{"description":"An opaque ff_ credential presented in the X-API-Key header.","in":"header","name":"X-API-Key","type":"apiKey"},"bearerAuth":{"description":"An opaque ff_ credential (ff_ak_ / ff_at_) presented as a Bearer token.","scheme":"bearer","type":"http"}}},"info":{"description":"Premium-only public developer read API for Forbidden Finance. Every response is wrapped in the standard Envelope (data + pagination + redacted_fields + filtered). Errors are RFC 9457 problem+json documents. All monetary amounts are exact decimal strings, never floats. Timestamps are RFC 3339 (UTC). Authenticate with an opaque ff_ credential (Bearer or X-API-Key). This spec describes the surface mounted at /v1; the spec document itself (/v1/openapi.json) is intentionally not listed here.\n\n## Names on a transaction\n\nFour name fields can appear on a transaction or a recurring stream, and they answer different questions. `display_name` is the requesting user's OWN label for that merchant, coming from the display-name rules they set in the app; it is present only when they have a rule matching the row. `merchant` is the merchant name as the provider/bank reported it. `description` is the transaction's description. (`original_description`, the raw bank descriptor, is not exposed in v1.) The user-chosen label never overwrites the provider values: grouping, recurring matching, search and rule lookup all still key on `merchant`, so both are returned side by side.\n\nA client that wants \"the name the user sees in the app\" should use `display_name ?? merchant ?? description`. Recurring rules and upcoming bills follow the same rule, resolved through the stream's sample transaction.\n\n`display_name` is PER-VIEWER, not a property of the row. Two members of one household reading the same shared transaction get different values — each sees their own label, or none — so it must never be cached, keyed, or reconciled across users, and it is not a stable identifier. It is merchant identity for privacy purposes: a connection whose privacy settings redact `transaction.merchant` gets neither `merchant` nor `display_name`.\n\n## Where a transaction came from\n\nAttribution is a two-hop join, and no single payload carries the whole answer. A transaction carries `account_id`. An account carries `connection_id`, and — only when its own bank differs from its connection's — `institution_name` and `institution_logo_url`, because one connection can span several banks. A connection carries `institution`, `provider` (the rail: `plaid`, `quiltt`, `manual`) and, when that provider fronts another aggregator, `source_aggregator` (`finicity`, `mx`, …; Quiltt is a unified layer over several).\n\nSo: to name the bank behind a transaction, resolve the account, then take `account.institution_name` if present and the account's connection's `institution` otherwise. To name the pipe the data arrived through, read `source_aggregator ?? provider` on that connection. Manual accounts have no connection at all, and their `connection_id` is absent.\n\nThe institution fields on BOTH payloads are one privacy choice: a connection whose settings redact `connection.institution` gets no institution name and no logo anywhere — not on the connection, not on the account. `provider` and `source_aggregator` are never redacted; they name the rail, not the bank.\n\n## Writing through this API\n\nEvery write needs three things: a `:write` scope, the per-connection `writes_enabled` opt-in (a scope alone is not enough — the user arms writes separately), and an `Idempotency-Key`. Writes serve **200**, including creates: a replayed response has to be byte-identical to the original, and a replay cannot honestly report a 201.\n\nA retry with the same key and the same body replays the stored response (`Idempotency-Replayed: true`). The same key with a DIFFERENT body is a 409 — so a **corrected** retry, including retrying the failed items of a bulk categorize, needs a FRESH key. Reusing the old one gets you the old answer.\n\nTransaction scopes come in two sizes. `transactions.annotate:write` covers only the additive edits — category, tags, memo, location — through `POST /v1/transactions/{id}/annotate` and `POST /v1/transactions/categorize`. `transactions:write` is full edit (create, change any field, delete) and subsumes the annotate scope, so a connection granted it does not also need the narrow one. A categorizing integration should ask for the narrow scope and will then find create, PATCH, and DELETE refused with `insufficient-scope`.\n\nWrites made through this API never teach the categorizer. Every transaction edit is applied with `suppress_rule_learning` (no per-merchant rule is created or updated, no vote reaches the global category model, and no categorization event is published) and `refuse_split_replacement` (a category change on a SPLIT row is refused rather than flattening the user's own attribution). One integration's opinion does not become everyone's default.\n\n### Write refusals that are about the resource, not your request\n\nThese carry a fixed `detail` you can match on. The string is the same for every caller in that case; it never names the resource.\n\n* **409 conflict** — \"Bank-synced transactions cannot be deleted. The bank is\n  the source of truth for them.\" A sync would recreate the row.\n* **409 conflict** — \"System categories cannot be changed or deleted through\n  the API.\" The seeded defaults are app-only.\n* **409 conflict** — \"This goal mirrors a linked account's balance and records\n  contributions automatically. Adjust the target instead of contributing.\"\n  Read `tracking_mode` on the goal to predict this.\n* **409 conflict** — \"This account has reached its limit on custom categories.\n  Delete one before creating another.\" Not a rate limit; retrying will not\n  help.\n* **403 forbidden** — \"This connection's privacy settings hide a field this\n  request would change.\" A connection cannot edit what it is not allowed to\n  read. The detail names no field: which fields are hidden is itself the\n  configuration, so a per-field answer would let you enumerate it by probing.\n* **422 field-immutable** — \"A transaction's currency is set when it is\n  created and cannot be changed. Delete the transaction and create it again\n  in the currency you want.\"\n\n\nVersion conflicts (a stale `expected_version`) are **409 conflict** with the generic detail: re-read the row and retry with the version you got.","title":"Forbidden Finance Public API","version":"1.0.0"},"openapi":"3.0.3","paths":{"/v1/accounts":{"get":{"description":"Lists the caller's accounts. Each row carries connection_id, and carries institution_name only when the account's own bank differs from its connection's — see \"Where a transaction came from\" for the join.","operationId":"listAccounts","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/PageSize"},{"description":"Filter to accounts under a single bank connection.","in":"query","name":"connection_id","required":false,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Account"},"type":"array"}},"type":"object"}]}}},"description":"A page of accounts."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List accounts","tags":["accounts"]}},"/v1/accounts/{id}":{"get":{"description":"Returns one account, the middle hop of the attribution join: it resolves a transaction's account_id to a connection_id, and to the account's own institution when that differs from the connection's.","operationId":"getAccount","parameters":[{"$ref":"#/components/parameters/PathID"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/Account"}},"type":"object"}]}}},"description":"A single account."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get one account","tags":["accounts"]}},"/v1/accounts/{id}/balances":{"get":{"operationId":"getAccountBalances","parameters":[{"$ref":"#/components/parameters/PathID"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/AccountBalance"}},"type":"object"}]}}},"description":"The current balance snapshot for the account."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get an account balance snapshot","tags":["accounts"]}},"/v1/bills/upcoming":{"get":{"operationId":"listUpcomingBills","parameters":[{"description":"How many days ahead to project.","in":"query","name":"days_ahead","required":false,"schema":{"format":"int32","minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/UpcomingBill"},"type":"array"}},"type":"object"}]}}},"description":"The upcoming bills."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List projected upcoming bills","tags":["budgets"]}},"/v1/budgets":{"get":{"operationId":"listBudgets","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/PageSize"},{"description":"When true, return only active budgets.","in":"query","name":"active","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Budget"},"type":"array"}},"type":"object"}]}}},"description":"A page of budgets."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List budgets","tags":["budgets"]}},"/v1/budgets/method-switch":{"post":{"description":"Switches the active budget to a different method. This ARCHIVES the current budget and CREATES a new one, so confirm must be literal true (else 400 validation). Requires the budgets:write scope, writes_enabled, and an Idempotency-Key (a double-fired switch would create two budgets). A SHARED budget cannot be switched here — it returns 409 (partner-approval-required); that approval flow lives only in the app.","operationId":"switchBudgetMethod","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BudgetMethodSwitchRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/BudgetMethodSwitchResult"}},"type":"object"}]}}},"description":"The new budget id, with the previous budget archived."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Switch the active budget method","tags":["budgets"]}},"/v1/budgets/{id}":{"get":{"operationId":"getBudget","parameters":[{"$ref":"#/components/parameters/PathID"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/Budget"}},"type":"object"}]}}},"description":"A single budget."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get one budget","tags":["budgets"]}},"/v1/budgets/{id}/progress":{"get":{"operationId":"getBudgetProgress","parameters":[{"$ref":"#/components/parameters/PathID"},{"description":"The month to report, as YYYY-MM. Defaults to the current month.","in":"query","name":"month","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/BudgetProgress"}},"type":"object"}]}}},"description":"The budget progress view."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get budget spend progress","tags":["budgets"]}},"/v1/categories":{"get":{"operationId":"listCategories","responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Category"},"type":"array"}},"type":"object"}]}}},"description":"The category list."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List categories","tags":["transactions"]},"post":{"description":"Creates a custom category. Requires the categories:write scope, writes_enabled, and an Idempotency-Key. Categories nest one level, so parent_id must name a visible top-level category. Reaching the per-account category limit returns 409 (waiting does not help), never a rate limit.","operationId":"createCategory","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryCreateRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/Category"}},"type":"object"}]}}},"description":"The created category."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Create a custom category","tags":["transactions"]}},"/v1/categories/{id}":{"delete":{"description":"Deletes a custom category. Requires the categories:write scope, writes_enabled, and an Idempotency-Key. The body is optional: with none, the category's transactions become uncategorized; with merge_into_category_id they are reassigned to that category, which must itself be visible to this connection.\n\nSystem categories return 409, as on PATCH. A category with subcategories returns 409 — move or delete them first.","operationId":"deleteCategory","parameters":[{"$ref":"#/components/parameters/PathID"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryDeleteRequest"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/CategoryDeleteResult"}},"type":"object"}]}}},"description":"The deletion result, with the number of transactions reassigned."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Delete a category","tags":["transactions"]},"patch":{"description":"Renames, re-parents, or restyles a custom category. Requires the categories:write scope, writes_enabled, and an Idempotency-Key.\n\nA SYSTEM category (is_system true — the seeded defaults) returns 409 with the detail \"System categories cannot be changed or deleted through the API.\"; every other feature keys on that tree, so it is not a programmatic caller's to rewrite. An excluded or nonexistent category returns the same 404.","operationId":"updateCategory","parameters":[{"$ref":"#/components/parameters/PathID"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryUpdateRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/Category"}},"type":"object"}]}}},"description":"The updated category."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Update a category","tags":["transactions"]}},"/v1/connections":{"get":{"description":"Lists the caller's linked connections — the last hop of the attribution join. Each carries institution, provider (the rail) and, when that provider fronts another aggregator, source_aggregator.","operationId":"listConnections","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/PageSize"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/BankConnection"},"type":"array"}},"type":"object"}]}}},"description":"A page of bank connections."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List bank connections","tags":["accounts"]}},"/v1/debt/payoff-plan":{"post":{"operationId":"computeDebtPayoffPlan","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DebtPayoffPlanRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/DebtPayoffPlan"}},"type":"object"}]}}},"description":"The computed payoff plan."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Compute a debt payoff plan","tags":["assets"]}},"/v1/debt/summary":{"get":{"operationId":"getDebtSummary","responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/DebtSummary"}},"type":"object"}]}}},"description":"The debt summary, grouped by currency."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get the debt summary","tags":["assets"]}},"/v1/goals":{"get":{"operationId":"listGoals","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/PageSize"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Goal"},"type":"array"}},"type":"object"}]}}},"description":"A page of goals."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List goals","tags":["goals"]}},"/v1/goals/{id}":{"get":{"operationId":"getGoal","parameters":[{"$ref":"#/components/parameters/PathID"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/Goal"}},"type":"object"}]}}},"description":"A single goal."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get one goal","tags":["goals"]},"patch":{"description":"Updates a goal's mutable fields. Only name, description, target_amount, and target_date may be changed; any other field is a validation error. Requires the goals:write scope and writes_enabled on the connection. A goal linked exclusively to excluded accounts returns 404 (not-found), byte-identical to a nonexistent goal.","operationId":"updateGoal","parameters":[{"$ref":"#/components/parameters/PathID"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalUpdateRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/Goal"}},"type":"object"}]}}},"description":"The updated goal (redacted per connection scope)."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Update a goal","tags":["goals"]}},"/v1/goals/{id}/contributions":{"get":{"operationId":"getGoalContributions","parameters":[{"$ref":"#/components/parameters/PathID"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/PageSize"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/GoalContribution"},"type":"array"}},"type":"object"}]}}},"description":"A page of goal contributions."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List goal contributions","tags":["goals"]},"post":{"description":"Records a manual contribution toward a goal. Requires the goals:write scope, writes_enabled, and an Idempotency-Key (a double-fired contribution would otherwise post twice). currency_code defaults to the goal's currency. A goal linked exclusively to excluded accounts returns 404, byte-identical to a nonexistent goal.","operationId":"recordGoalContribution","parameters":[{"$ref":"#/components/parameters/PathID"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalContributionRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/GoalContributionResult"}},"type":"object"}]}}},"description":"The recorded contribution and the goal's updated progress."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Record a goal contribution","tags":["goals"]}},"/v1/goals/{id}/history":{"get":{"operationId":"getGoalHistory","parameters":[{"$ref":"#/components/parameters/PathID"},{"description":"Inclusive start date (YYYY-MM-DD).","in":"query","name":"start_date","required":true,"schema":{"format":"date","type":"string"}},{"$ref":"#/components/parameters/EndDate"},{"in":"query","name":"resolution","required":false,"schema":{"enum":["daily","weekly","monthly"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/GoalSnapshot"},"type":"array"}},"type":"object"}]}}},"description":"A series of goal snapshots."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get goal progress history","tags":["goals"]}},"/v1/goals/{id}/progress":{"get":{"operationId":"getGoalProgress","parameters":[{"$ref":"#/components/parameters/PathID"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/GoalProgress"}},"type":"object"}]}}},"description":"The goal progress view."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get goal progress","tags":["goals"]}},"/v1/holdings":{"get":{"operationId":"listHoldings","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/PageSize"},{"in":"query","name":"account_id","required":false,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Holding"},"type":"array"}},"type":"object"}]}}},"description":"A page of holdings."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List investment holdings","tags":["assets"]}},"/v1/insights/cash-flow-forecast":{"get":{"operationId":"insightsCashFlowForecast","parameters":[{"description":"How many days ahead to project.","in":"query","name":"days_ahead","required":false,"schema":{"format":"int32","minimum":1,"type":"integer"}},{"in":"query","name":"account_id","required":false,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/CashFlowForecast"}},"type":"object"}]}}},"description":"The cash-flow forecast."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Cash-flow forecast","tags":["insights"]}},"/v1/insights/income-vs-expenses":{"get":{"operationId":"insightsIncomeVsExpenses","parameters":[{"$ref":"#/components/parameters/StartDateRequired"},{"$ref":"#/components/parameters/EndDateRequired"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/IncomeVsExpenses"},"type":"array"}},"type":"object"}]}}},"description":"Monthly income/expense roll-ups."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Income versus expenses by month","tags":["insights"]}},"/v1/insights/month-summary":{"get":{"operationId":"insightsMonthSummary","parameters":[{"description":"The month to summarize, as YYYY-MM.","in":"query","name":"month","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/MonthSummary"}},"type":"object"}]}}},"description":"The month summary."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"One month's income/expense summary","tags":["insights"]}},"/v1/insights/spending-by-category":{"get":{"operationId":"insightsSpendingByCategory","parameters":[{"$ref":"#/components/parameters/StartDateRequired"},{"$ref":"#/components/parameters/EndDateRequired"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/SpendingByCategoryRow"},"type":"array"}},"type":"object"}]}}},"description":"Per-category spending shares."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Spending share by category","tags":["insights"]}},"/v1/liabilities":{"get":{"operationId":"listLiabilities","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/PageSize"},{"in":"query","name":"account_id","required":false,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Liability"},"type":"array"}},"type":"object"}]}}},"description":"A page of liabilities."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List liabilities","tags":["assets"]}},"/v1/net-worth":{"get":{"description":"Unavailable (data-excluded) for connections that exclude accounts: net worth is served from a stored snapshot that cannot be retro-filtered by account. Category exclusions do not affect net worth and are served normally.","operationId":"getNetWorth","responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/NetWorth"}},"type":"object"}]}}},"description":"The net-worth summary."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get the net-worth summary","tags":["assets"]}},"/v1/net-worth/history":{"get":{"description":"Unavailable (data-excluded) for connections that exclude accounts: historical snapshots cannot be retro-filtered by account.","operationId":"getNetWorthHistory","parameters":[{"description":"The look-back period token (for example 30d, 6mo, 1y).","in":"query","name":"period","required":false,"schema":{"type":"string"}},{"description":"The ISO 4217 currency the series is converted to.","in":"query","name":"base_currency","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/NetWorthHistoryPoint"},"type":"array"}},"type":"object"}]}}},"description":"The net-worth history series."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get the net-worth trend","tags":["assets"]}},"/v1/recurring":{"get":{"operationId":"listRecurring","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/PageSize"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/RecurringRule"},"type":"array"}},"type":"object"}]}}},"description":"A page of recurring rules."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List detected recurring rules","tags":["budgets"]}},"/v1/sync-status":{"get":{"operationId":"getSyncStatus","responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/SyncStatus"}},"type":"object"}]}}},"description":"The derived sync-status view."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Aggregate sync health across connections","tags":["accounts"]}},"/v1/transactions":{"get":{"description":"Lists the caller's transactions. A transaction names its account with account_id and nothing more about where it came from; resolve the account, then its connection, to name the bank and the rail.","operationId":"listTransactions","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/PageSize"},{"in":"query","name":"account_id","required":false,"schema":{"format":"uuid","type":"string"}},{"in":"query","name":"category_id","required":false,"schema":{"format":"uuid","type":"string"}},{"$ref":"#/components/parameters/StartDate"},{"$ref":"#/components/parameters/EndDate"},{"description":"When set, filter to pending (true) or posted (false) transactions.","in":"query","name":"pending","required":false,"schema":{"type":"boolean"}},{"in":"query","name":"source","required":false,"schema":{"enum":["bank_sync","manual","import"],"type":"string"}},{"description":"When true, return only transactions with no category.","in":"query","name":"uncategorized","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Transaction"},"type":"array"}},"type":"object"}]}}},"description":"A page of transactions."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"List transactions","tags":["transactions"]},"post":{"description":"Creates a manual transaction. Requires the transactions:write scope, writes_enabled, and an Idempotency-Key (a double-fired create would otherwise post the same spending twice).\n\nThe target account must be visible to this connection: an excluded account and a nonexistent one both return 404, byte-identical, so a hidden account cannot be found by probing which ids accept a write. The same holds for category_id.\n\nReturns 200, not 201. Every write on this API serves 200 so that a replayed response is byte-identical to the original — a replay cannot honestly claim to have created anything, and two different status codes for one stored response would be worse than the REST purism is worth.","operationId":"createTransaction","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionCreateRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/Transaction"}},"type":"object"}]}}},"description":"The created transaction (redacted per connection scope)."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Create a manual transaction","tags":["transactions"]}},"/v1/transactions/categorize":{"post":{"description":"Assigns categories to 1-100 transactions in one call. Requires the transactions.annotate:write scope (transactions:write reaches it too), writes_enabled, and an Idempotency-Key.\n\nThe outcome is PARTIAL by design: a mixed result is 200, applied_count says how many rows were written, and every item reports itself as applied, not_found, invalid_category, version_conflict, or split. To retry the ones that failed, send a NEW batch with a FRESH Idempotency-Key — reusing the original key replays the original response and changes nothing, and reusing it with a corrected body is a 409.\n\nExclusions are applied server-side from this connection's privacy settings: rows on excluded accounts come back not_found, and an excluded category comes back invalid_category.","operationId":"categorizeTransactions","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionCategorizeRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/BulkCategorizeResult"}},"type":"object"}]}}},"description":"Per-item outcomes plus the count of rows actually written."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Categorize transactions in bulk","tags":["transactions"]}},"/v1/transactions/search":{"get":{"operationId":"searchTransactions","parameters":[{"description":"The search query.","in":"query","name":"q","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/PageSize"},{"$ref":"#/components/parameters/StartDate"},{"$ref":"#/components/parameters/EndDate"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Transaction"},"type":"array"}},"type":"object"}]}}},"description":"A page of matching transactions."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Full-text transaction search","tags":["transactions"]}},"/v1/transactions/{id}":{"delete":{"description":"Deletes a manual or imported transaction. Requires the transactions:write scope, writes_enabled, and an Idempotency-Key.\n\nBank-synced transactions are NOT deletable and return 409 with the detail \"Bank-synced transactions cannot be deleted. The bank is the source of truth for them.\" — the next sync would recreate the row, so the deletion would not be a deletion. An excluded or nonexistent transaction returns the same 404.","operationId":"deleteTransaction","parameters":[{"$ref":"#/components/parameters/PathID"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/TransactionDeleteResult"}},"type":"object"}]}}},"description":"The deletion result."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Delete a transaction","tags":["transactions"]},"get":{"operationId":"getTransaction","parameters":[{"$ref":"#/components/parameters/PathID"}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/Transaction"}},"type":"object"}]}}},"description":"A single transaction."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Get one transaction","tags":["transactions"]},"patch":{"description":"Edits a transaction. Requires the transactions:write scope, writes_enabled, an Idempotency-Key, and expected_version in the body.\n\nBank-synced rows are fully editable here: the bank-recorded value of each field is captured at the FIRST user edit, so nothing is destroyed, and revert_to_provider puts it back and hands ownership to the provider again.\n\n409 covers three cases with the same status: expected_version did not match (re-read and retry), the row is a SPLIT parent and the requested category change would have replaced its slices, and revert_to_provider named a field with no captured bank value. A write touching a field this connection's privacy settings hide is 403; changing the currency is 422 field-immutable.","operationId":"updateTransaction","parameters":[{"$ref":"#/components/parameters/PathID"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionUpdateRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/Transaction"}},"type":"object"}]}}},"description":"The updated transaction (redacted per connection scope)."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Update a transaction","tags":["transactions"]}},"/v1/transactions/{id}/annotate":{"post":{"description":"Sets a transaction's category, tags, memo, or location — the additive edits the narrow transactions.annotate:write scope covers. Requires writes_enabled and an Idempotency-Key. A connection holding only this scope cannot create, delete, or otherwise edit a transaction.\n\nNo expected_version is needed: the server reads the row and uses its current version. A concurrent edit between that read and the write still loses the race and returns 409.","operationId":"annotateTransaction","parameters":[{"$ref":"#/components/parameters/PathID"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionAnnotateRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"properties":{"data":{"$ref":"#/components/schemas/Transaction"}},"type":"object"}]}}},"description":"The annotated transaction (redacted per connection scope)."},"default":{"$ref":"#/components/responses/Problem"}},"summary":"Annotate a transaction","tags":["transactions"]}}},"security":[{"bearerAuth":[]},{"apiKey":[]}],"servers":[{"url":"https://api.403fin.io"}],"tags":[{"name":"accounts"},{"name":"transactions"},{"name":"budgets"},{"name":"goals"},{"name":"assets"},{"name":"insights"}]}