Files
sure/docs/api/openapi.yaml
T
Brandon 686205c0ff feat(bills): schema and domain core for the bills subsystem (#3201)
* feat(bills): schema and domain core for the bills subsystem

First of three chunks carved out of #3083. This one carries the schema and the
domain layer: no bills pages, no calendar feed, no assistant tools. Nothing here
is reachable from the UI yet, so it changes no user-visible behavior on its own.

Schema, in a single migration with a full down:

- recurrence_rules, recurring_occurrences, recurring_allocations,
  recurring_price_changes and recurring_match_rejections
- bill columns on recurring_transactions (bill_type, payment_url, autopay,
  notes, anchor and end conditions, weekend adjustment, dedup scope)
- the four data backfills, in their original order

Domain layer:

- Schedule, the pure date PORO every cadence resolves through, and
  FrequencyPreset for the labels
- OccurrenceGenerator, Matcher, Allocator, PriceChangeDetector, Classifier,
  DeclaredBill, HistoryBackfiller and PaycheckPlanner
- Pipeline, tying detection to generation, plus the nightly job and rake task

Existing detection code changed in three places, each a bug this schema exposes:

- Cleaner used a flat two-month staleness threshold, which silently retired
  every quarterly and annual series
- SubscriptionAuditGenerator used a flat 45-day overdue threshold, meaningless
  at both ends of the frequency range
- CashFlowWarningGenerator read one projected entry per series, which only
  equalled the monthly amount because every series was monthly; weekly bills
  were under-counted fourfold in its 30-day projection

The JSON API travels with the model rather than the UI, because the status enum
widens here. The API accepts only active and inactive on write; suggested,
paused and ended are lifecycle states owned by detection, so the documented
enum stays truthful.

Uniqueness keys gain dedup_scope alongside amount, never instead of it: a
series that is not price-forked carries a blank scope, so amount is what keeps
two different prices apart.

Suite 7,550 runs, 0 failures. Rubocop and brakeman clean. Eager loading
verified, and the migration reverses and re-applies. Includes the first review round: orphan repair matches income and refuses coincidental twins, session imports persist occurrence mappings across chunks, semimonthly anchors canonicalize, classifier keywords match whole words, and the down refuses rather than failing when price-forked rows exist.

* Address second review round

Bound the cross-currency default allocation by the entry leftover and the
occurrence remainder, matching the same-currency path. Let keyword stems
carry a suffix again after the word-boundary fix silenced them. Skip an
incoherent recurrence rule row instead of rolling back the whole import.
Check rollback collisions per restored index so a refusal cannot land
after the bills tables are dropped. Replay the closed_at test through a
real second import. Preload the orphan repair associations and move the
allocator errors to locale keys.

* Match index NULL semantics in the rollback collision checks

GROUP BY treats NULLs as equal but the restored unique indexes do not:
account_id is nullable and indexed, so two accountless rows can never
collide under any of them. Excluding NULL accounts keeps the guard from
refusing a rollback PostgreSQL can perform. Verified live both ways:
accountless duplicates roll back, a real collision still refuses.

* Address maintainer review

Scope the payable debt-destination subquery to the row and its family
instead of scanning every account in the installation. Batch the cash
flow generator remaining-amount sums into one grouped query, matching
the two sibling sites. Enforce both window bounds in the after_count
branch so a future-anchored plan cannot leak past the requested end
date. Skip the explicit regeneration when the day column change will
fire the model callback anyway. Add the missing locale entry for the
allocation currency validation.
2026-08-31 23:41:38 +02:00

8278 lines
223 KiB
YAML

---
openapi: 3.0.3
info:
title: Sure API
version: v1
description: OpenAPI documentation generated from executable request specs.
servers:
- url: https://app.sure.am
description: Production
- url: http://localhost:3000
description: Local development
components:
securitySchemes:
apiKeyAuth:
type: apiKey
name: X-Api-Key
in: header
description: API key for authentication. Generate one from your account settings.
schemas:
Pagination:
type: object
required:
- page
- per_page
- total_count
- total_pages
properties:
page:
type: integer
minimum: 1
per_page:
type: integer
minimum: 1
total_count:
type: integer
minimum: 0
total_pages:
type: integer
minimum: 0
FamilyExportFile:
type: object
required:
- attached
properties:
attached:
type: boolean
byte_size:
type: integer
nullable: true
minimum: 0
content_type:
type: string
nullable: true
FamilyExport:
type: object
required:
- id
- status
- filename
- downloadable
- file
- created_at
- updated_at
properties:
id:
type: string
format: uuid
status:
type: string
enum:
- pending
- processing
- completed
- failed
filename:
type: string
downloadable:
type: boolean
download_path:
type: string
nullable: true
file:
"$ref": "#/components/schemas/FamilyExportFile"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
FamilyExportResponse:
type: object
required:
- data
properties:
data:
"$ref": "#/components/schemas/FamilyExport"
FamilyExportCollection:
type: object
required:
- data
- meta
properties:
data:
type: array
maxItems: 100
items:
"$ref": "#/components/schemas/FamilyExport"
meta:
"$ref": "#/components/schemas/Pagination"
ErrorResponse:
type: object
required:
- error
properties:
error:
type: string
message:
type: string
nullable: true
details:
oneOf:
- type: array
items:
type: string
- type: object
nullable: true
errors:
type: array
items:
type: string
nullable: true
description: Validation error messages (alternative to details used by trades,
valuations, etc.)
ErrorResponseWithImportId:
type: object
required:
- error
- import_id
properties:
error:
type: string
message:
type: string
nullable: true
import_id:
type: string
format: uuid
description: Import ID preserved for retry or inspection after upload succeeds
but publish fails
MfaRequiredResponse:
type: object
required:
- error
- mfa_required
properties:
error:
type: string
mfa_required:
type: boolean
ToolCall:
type: object
required:
- id
- function_name
- function_arguments
- created_at
properties:
id:
type: string
format: uuid
function_name:
type: string
function_arguments:
type: object
additionalProperties: true
function_result:
type: object
additionalProperties: true
nullable: true
created_at:
type: string
format: date-time
Message:
type: object
required:
- id
- type
- role
- content
- created_at
- updated_at
properties:
id:
type: string
format: uuid
type:
type: string
enum:
- user_message
- assistant_message
role:
type: string
enum:
- user
- assistant
content:
type: string
model:
type: string
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
tool_calls:
type: array
items:
"$ref": "#/components/schemas/ToolCall"
nullable: true
MessageResponse:
allOf:
- "$ref": "#/components/schemas/Message"
- type: object
required:
- chat_id
properties:
chat_id:
type: string
format: uuid
ai_response_status:
type: string
enum:
- pending
- complete
- failed
nullable: true
ai_response_message:
type: string
nullable: true
ChatResource:
type: object
required:
- id
- title
- created_at
- updated_at
properties:
id:
type: string
format: uuid
title:
type: string
error:
type: string
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ChatSummary:
allOf:
- "$ref": "#/components/schemas/ChatResource"
- type: object
required:
- message_count
properties:
message_count:
type: integer
minimum: 0
last_message_at:
type: string
format: date-time
nullable: true
ChatDetail:
allOf:
- "$ref": "#/components/schemas/ChatResource"
- type: object
required:
- messages
properties:
messages:
type: array
items:
"$ref": "#/components/schemas/Message"
pagination:
"$ref": "#/components/schemas/Pagination"
nullable: true
ChatCollection:
type: object
required:
- chats
- pagination
properties:
chats:
type: array
items:
"$ref": "#/components/schemas/ChatSummary"
pagination:
"$ref": "#/components/schemas/Pagination"
Insight:
type: object
required:
- id
- type
- title
- body
- priority
- status
properties:
id:
type: string
format: uuid
type:
type: string
title:
type: string
body:
type: string
priority:
type: string
enum:
- high
- medium
- low
status:
type: string
enum:
- active
- read
generated_at:
type: string
format: date-time
nullable: true
InsightCollection:
type: object
required:
- insights
properties:
insights:
type: array
items:
"$ref": "#/components/schemas/Insight"
PushSubscription:
type: object
required:
- id
- environment
- platform
- last_registered_at
properties:
id:
type: string
format: uuid
environment:
type: string
enum:
- sandbox
- production
platform:
type: string
enum:
- ios
last_registered_at:
type: string
format: date-time
RetryResponse:
type: object
required:
- message
- message_id
properties:
message:
type: string
message_id:
type: string
format: uuid
Account:
type: object
required:
- id
- name
- account_type
properties:
id:
type: string
format: uuid
name:
type: string
account_type:
type: string
nullable: true
status:
type: string
AccountDetail:
type: object
required:
- id
- name
- balance
- balance_cents
- cash_balance
- cash_balance_cents
- currency
- classification
- account_type
- status
- created_at
- updated_at
properties:
id:
type: string
format: uuid
name:
type: string
balance:
type: string
balance_cents:
type: integer
description: Signed balance in minor currency units
cash_balance:
type: string
cash_balance_cents:
type: integer
description: Signed cash balance in minor currency units
currency:
type: string
classification:
type: string
account_type:
type: string
nullable: true
subtype:
type: string
nullable: true
status:
type: string
enum:
- active
- draft
- disabled
- pending_deletion
institution_name:
type: string
nullable: true
institution_domain:
type: string
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
AccountCollection:
type: object
required:
- accounts
- pagination
properties:
accounts:
type: array
items:
"$ref": "#/components/schemas/AccountDetail"
pagination:
"$ref": "#/components/schemas/Pagination"
FamilySettings:
type: object
required:
- id
- currency
- locale
- date_format
- month_start_day
- moniker
- default_account_sharing
- custom_enabled_currencies
- enabled_currencies
- created_at
- updated_at
properties:
id:
type: string
format: uuid
name:
type: string
nullable: true
currency:
type: string
locale:
type: string
date_format:
type: string
country:
type: string
nullable: true
timezone:
type: string
nullable: true
month_start_day:
type: integer
minimum: 1
maximum: 28
moniker:
type: string
enum:
- Family
- Group
default_account_sharing:
type: string
enum:
- shared
- private
custom_enabled_currencies:
type: boolean
enabled_currencies:
type: array
items:
type: string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
BudgetSummary:
type: object
required:
- id
- start_date
- end_date
- name
- currency
- initialized
- current
- created_at
- updated_at
properties:
id:
type: string
format: uuid
start_date:
type: string
format: date
end_date:
type: string
format: date
name:
type: string
currency:
type: string
initialized:
type: boolean
current:
type: boolean
budgeted_spending:
type: string
nullable: true
budgeted_spending_cents:
type: integer
nullable: true
expected_income:
type: string
nullable: true
expected_income_cents:
type: integer
nullable: true
allocated_spending:
type: string
allocated_spending_cents:
type: integer
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
Budget:
type: object
required:
- id
- start_date
- end_date
- name
- currency
- initialized
- current
- created_at
- updated_at
properties:
id:
type: string
format: uuid
start_date:
type: string
format: date
end_date:
type: string
format: date
name:
type: string
currency:
type: string
initialized:
type: boolean
current:
type: boolean
budgeted_spending:
type: string
nullable: true
budgeted_spending_cents:
type: integer
nullable: true
expected_income:
type: string
nullable: true
expected_income_cents:
type: integer
nullable: true
allocated_spending:
type: string
allocated_spending_cents:
type: integer
actual_spending:
type: string
actual_spending_cents:
type: integer
actual_income:
type: string
actual_income_cents:
type: integer
available_to_spend:
type: string
available_to_spend_cents:
type: integer
available_to_allocate:
type: string
available_to_allocate_cents:
type: integer
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
BudgetCollection:
type: object
required:
- budgets
- pagination
properties:
budgets:
type: array
items:
"$ref": "#/components/schemas/BudgetSummary"
pagination:
"$ref": "#/components/schemas/Pagination"
BudgetCategorySummary:
type: object
required:
- id
- budget_id
- currency
- subcategory
- inherits_parent_budget
- rollover_enabled
- category
- created_at
- updated_at
properties:
id:
type: string
format: uuid
budget_id:
type: string
format: uuid
currency:
type: string
subcategory:
type: boolean
inherits_parent_budget:
type: boolean
rollover_enabled:
type: boolean
budgeted_spending:
type: string
budgeted_spending_cents:
type: integer
display_budgeted_spending:
type: string
display_budgeted_spending_cents:
type: integer
category:
type: object
required:
- id
- name
- color
- lucide_icon
properties:
id:
type: string
format: uuid
name:
type: string
color:
type: string
lucide_icon:
type: string
parent_id:
type: string
format: uuid
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
BudgetCategory:
type: object
required:
- id
- budget_id
- currency
- subcategory
- inherits_parent_budget
- rollover_enabled
- category
- created_at
- updated_at
properties:
id:
type: string
format: uuid
budget_id:
type: string
format: uuid
currency:
type: string
subcategory:
type: boolean
inherits_parent_budget:
type: boolean
rollover_enabled:
type: boolean
budgeted_spending:
type: string
budgeted_spending_cents:
type: integer
display_budgeted_spending:
type: string
display_budgeted_spending_cents:
type: integer
actual_spending:
type: string
actual_spending_cents:
type: integer
rolled_over_amount:
type: string
rolled_over_amount_cents:
type: integer
available_to_spend:
type: string
available_to_spend_cents:
type: integer
category:
type: object
required:
- id
- name
- color
- lucide_icon
properties:
id:
type: string
format: uuid
name:
type: string
color:
type: string
lucide_icon:
type: string
parent_id:
type: string
format: uuid
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
BudgetCategoryCollection:
type: object
required:
- budget_categories
- pagination
properties:
budget_categories:
type: array
items:
"$ref": "#/components/schemas/BudgetCategorySummary"
pagination:
"$ref": "#/components/schemas/Pagination"
Balance:
type: object
required:
- id
- date
- currency
- flows_factor
- balance
- balance_cents
- start_balance
- start_balance_cents
- end_balance
- end_balance_cents
- account
- created_at
- updated_at
properties:
id:
type: string
format: uuid
date:
type: string
format: date
currency:
type: string
flows_factor:
type: number
format: float
balance:
type: string
balance_cents:
type: integer
description: Balance in currency minor units
cash_balance:
type: string
nullable: true
cash_balance_cents:
type: integer
nullable: true
description: Cash balance in currency minor units
start_cash_balance:
type: string
start_cash_balance_cents:
type: integer
description: Starting cash balance in currency minor units
start_non_cash_balance:
type: string
start_non_cash_balance_cents:
type: integer
description: Starting non-cash balance in currency minor units
start_balance:
type: string
start_balance_cents:
type: integer
description: Starting total balance in currency minor units
cash_inflows:
type: string
cash_inflows_cents:
type: integer
description: Cash inflows in currency minor units
cash_outflows:
type: string
cash_outflows_cents:
type: integer
description: Cash outflows in currency minor units
non_cash_inflows:
type: string
non_cash_inflows_cents:
type: integer
description: Non-cash inflows in currency minor units
non_cash_outflows:
type: string
non_cash_outflows_cents:
type: integer
description: Non-cash outflows in currency minor units
net_market_flows:
type: string
net_market_flows_cents:
type: integer
description: Net market flows in currency minor units
cash_adjustments:
type: string
cash_adjustments_cents:
type: integer
description: Cash adjustments in currency minor units
non_cash_adjustments:
type: string
non_cash_adjustments_cents:
type: integer
description: Non-cash adjustments in currency minor units
end_cash_balance:
type: string
end_cash_balance_cents:
type: integer
description: Ending cash balance in currency minor units
end_non_cash_balance:
type: string
end_non_cash_balance_cents:
type: integer
description: Ending non-cash balance in currency minor units
end_balance:
type: string
end_balance_cents:
type: integer
description: Ending total balance in currency minor units
account:
"$ref": "#/components/schemas/BalanceAccount"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
BalanceAccount:
type: object
required:
- id
- name
- account_type
properties:
id:
type: string
format: uuid
name:
type: string
account_type:
type: string
nullable: true
BalanceCollection:
type: object
required:
- balances
- pagination
properties:
balances:
type: array
items:
"$ref": "#/components/schemas/Balance"
pagination:
"$ref": "#/components/schemas/Pagination"
Category:
type: object
required:
- id
- name
- color
- icon
properties:
id:
type: string
format: uuid
name:
type: string
color:
type: string
icon:
type: string
CategoryParent:
type: object
required:
- id
- name
properties:
id:
type: string
format: uuid
name:
type: string
CategoryDetail:
type: object
required:
- id
- name
- color
- icon
- subcategories_count
- created_at
- updated_at
properties:
id:
type: string
format: uuid
name:
type: string
color:
type: string
icon:
type: string
parent:
"$ref": "#/components/schemas/CategoryParent"
nullable: true
subcategories_count:
type: integer
minimum: 0
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
CategoryCollection:
type: object
required:
- categories
- pagination
properties:
categories:
type: array
items:
"$ref": "#/components/schemas/CategoryDetail"
pagination:
"$ref": "#/components/schemas/Pagination"
CategoryCreateRequest:
type: object
required:
- category
properties:
category:
type: object
required:
- name
properties:
name:
type: string
description: Category name (required, unique within family)
color:
type: string
description: 'Hex color code (e.g. #22c55e). Defaults to #6172F3 if
omitted; subcategories inherit parent color.'
icon:
type: string
description: Lucide icon name (e.g. "coffee"). Auto-suggested from the
name when omitted.
parent_id:
type: string
format: uuid
nullable: true
description: Parent category ID. Must belong to the same family. Categories
support up to 2 levels of nesting.
Merchant:
type: object
required:
- id
- name
properties:
id:
type: string
format: uuid
name:
type: string
MerchantDetail:
type: object
required:
- id
- name
- type
- created_at
- updated_at
properties:
id:
type: string
format: uuid
name:
type: string
type:
type: string
enum:
- FamilyMerchant
- ProviderMerchant
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
MerchantImportResult:
type: object
required:
- imported
- skipped
- merchants
properties:
imported:
type: integer
description: Number of merchants successfully created
skipped:
type: integer
description: Number of rows skipped (duplicates or invalid)
merchants:
type: array
items:
"$ref": "#/components/schemas/MerchantDetail"
Tag:
type: object
required:
- id
- name
- color
properties:
id:
type: string
format: uuid
name:
type: string
color:
type: string
TagDetail:
type: object
required:
- id
- name
- color
- created_at
- updated_at
properties:
id:
type: string
format: uuid
name:
type: string
color:
type: string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
TagCollection:
type: array
items:
"$ref": "#/components/schemas/TagDetail"
RuleAction:
type: object
required:
- id
- action_type
- created_at
- updated_at
properties:
id:
type: string
format: uuid
action_type:
type: string
value:
type: string
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
RuleCondition:
type: object
required:
- id
- condition_type
- operator
- sub_conditions
- created_at
- updated_at
properties:
id:
type: string
format: uuid
condition_type:
type: string
operator:
type: string
value:
type: string
nullable: true
sub_conditions:
type: array
items:
"$ref": "#/components/schemas/RuleCondition"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
Rule:
type: object
required:
- id
- resource_type
- active
- conditions
- actions
- created_at
- updated_at
properties:
id:
type: string
format: uuid
name:
type: string
nullable: true
resource_type:
type: string
enum:
- transaction
active:
type: boolean
effective_date:
type: string
format: date
nullable: true
conditions:
type: array
items:
"$ref": "#/components/schemas/RuleCondition"
actions:
type: array
items:
"$ref": "#/components/schemas/RuleAction"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
RuleResponse:
type: object
required:
- data
properties:
data:
"$ref": "#/components/schemas/Rule"
RuleCollection:
type: object
required:
- data
- meta
properties:
data:
type: array
items:
"$ref": "#/components/schemas/Rule"
meta:
type: object
required:
- current_page
- total_pages
- total_count
- per_page
properties:
current_page:
type: integer
next_page:
type: integer
nullable: true
prev_page:
type: integer
nullable: true
total_pages:
type: integer
total_count:
type: integer
per_page:
type: integer
RuleRun:
type: object
required:
- id
- rule_id
- rule_name
- execution_type
- status
- transactions_queued
- transactions_processed
- transactions_modified
- pending_jobs_count
- executed_at
- rule
- created_at
- updated_at
properties:
id:
type: string
format: uuid
rule_id:
type: string
format: uuid
rule_name:
type: string
nullable: true
execution_type:
type: string
enum:
- manual
- scheduled
status:
type: string
enum:
- pending
- success
- failed
transactions_queued:
type: integer
minimum: 0
transactions_processed:
type: integer
minimum: 0
transactions_modified:
type: integer
minimum: 0
pending_jobs_count:
type: integer
minimum: 0
executed_at:
type: string
format: date-time
error_message:
type: string
nullable: true
rule:
type: object
nullable: true
required:
- id
- resource_type
- active
properties:
id:
type: string
format: uuid
name:
type: string
nullable: true
resource_type:
type: string
active:
type: boolean
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
RuleRunResponse:
type: object
required:
- data
properties:
data:
"$ref": "#/components/schemas/RuleRun"
RuleRunCollection:
type: object
required:
- data
- meta
properties:
data:
type: array
items:
"$ref": "#/components/schemas/RuleRun"
meta:
type: object
required:
- current_page
- total_pages
- total_count
- per_page
properties:
current_page:
type: integer
next_page:
type: integer
nullable: true
prev_page:
type: integer
nullable: true
total_pages:
type: integer
total_count:
type: integer
per_page:
type: integer
Transfer:
type: object
required:
- id
- amount
- currency
properties:
id:
type: string
format: uuid
amount:
type: string
currency:
type: string
other_account:
"$ref": "#/components/schemas/Account"
nullable: true
RecurringTransaction:
type: object
required:
- id
- amount
- amount_cents
- currency
- expected_day_of_month
- last_occurrence_date
- next_expected_date
- status
- occurrence_count
- manual
- created_at
- updated_at
properties:
id:
type: string
format: uuid
amount:
type: string
amount_cents:
type: integer
description: Amount in currency minor units
currency:
type: string
expected_day_of_month:
type: integer
minimum: 1
maximum: 31
last_occurrence_date:
type: string
format: date
next_expected_date:
type: string
format: date
status:
type: string
enum:
- suggested
- active
- paused
- inactive
- ended
occurrence_count:
type: integer
minimum: 0
name:
type: string
nullable: true
manual:
type: boolean
payment_url:
type: string
nullable: true
description: Link to the biller portal where this bill is paid. Only http
and https are accepted; a bare host is stored as https.
autopay:
type: boolean
description: Whether this bill pays itself automatically.
notes:
type: string
nullable: true
description: Free-text notes shown alongside the bill.
bill_type:
type: string
enum:
- bill
- subscription
- installment
- income
- transfer
- other
description: What kind of obligation this is.
category_id:
type: string
format: uuid
nullable: true
anchor_date:
type: string
format: date
nullable: true
description: Reference occurrence that phases every-N cadences.
weekend_adjust:
type: string
enum:
- none
- skip
- before
- after
end_mode:
type: string
enum:
- never
- on_date
- after_count
end_on:
type: string
format: date
nullable: true
end_after_count:
type: integer
nullable: true
renews_on:
type: string
format: date
nullable: true
trial_ends_on:
type: string
format: date
nullable: true
cancelled_on:
type: string
format: date
nullable: true
recurrence_rules:
type: array
description: Repetition patterns; multiple rows express semimonthly and
similar multi-pattern cadences. Empty means legacy monthly on expected_day_of_month.
items:
type: object
properties:
frequency:
type: string
enum:
- weekly
- monthly
- yearly
interval:
type: integer
day_of_month:
type: integer
nullable: true
description: "-1 means the last day of the month."
weekday:
type: integer
nullable: true
weekday_ordinal:
type: integer
nullable: true
description: "-1 means the last such weekday."
month_of_year:
type: integer
nullable: true
expected_amount_min:
type: string
nullable: true
expected_amount_min_cents:
type: integer
nullable: true
description: Minimum expected amount in currency minor units
expected_amount_max:
type: string
nullable: true
expected_amount_max_cents:
type: integer
nullable: true
description: Maximum expected amount in currency minor units
expected_amount_avg:
type: string
nullable: true
expected_amount_avg_cents:
type: integer
nullable: true
description: Average expected amount in currency minor units
account:
"$ref": "#/components/schemas/Account"
nullable: true
merchant:
"$ref": "#/components/schemas/Merchant"
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
RecurringTransactionCollection:
type: object
required:
- recurring_transactions
- pagination
properties:
recurring_transactions:
type: array
items:
"$ref": "#/components/schemas/RecurringTransaction"
pagination:
"$ref": "#/components/schemas/Pagination"
Transaction:
type: object
required:
- id
- date
- amount
- currency
- name
- classification
- account
- tags
- created_at
- updated_at
properties:
id:
type: string
format: uuid
date:
type: string
format: date
amount:
type: string
currency:
type: string
name:
type: string
notes:
type: string
nullable: true
external_id:
type: string
nullable: true
source:
type: string
nullable: true
user_modified:
type: boolean
classification:
type: string
account:
"$ref": "#/components/schemas/Account"
category:
"$ref": "#/components/schemas/Category"
nullable: true
merchant:
"$ref": "#/components/schemas/Merchant"
nullable: true
tags:
type: array
items:
"$ref": "#/components/schemas/Tag"
transfer:
"$ref": "#/components/schemas/Transfer"
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
TransactionCollection:
type: object
required:
- transactions
- pagination
properties:
transactions:
type: array
items:
"$ref": "#/components/schemas/Transaction"
pagination:
"$ref": "#/components/schemas/Pagination"
TransferTransactionSide:
type: object
required:
- id
- entry_id
- date
- amount
- amount_cents
- currency
- name
- kind
- account
properties:
id:
type: string
format: uuid
entry_id:
type: string
format: uuid
date:
type: string
format: date
amount:
type: string
amount_cents:
type: integer
description: Signed amount in currency minor units
currency:
type: string
name:
type: string
kind:
type: string
account:
type: object
required:
- id
- name
- account_type
properties:
id:
type: string
format: uuid
name:
type: string
account_type:
type: string
nullable: true
TransferDecision:
type: object
required:
- id
- status
- date
- amount
- amount_cents
- currency
- transfer_type
- inflow_transaction
- outflow_transaction
- created_at
- updated_at
properties:
id:
type: string
format: uuid
status:
type: string
enum:
- pending
- confirmed
date:
type: string
format: date
amount:
type: string
amount_cents:
type: integer
description: Absolute transfer amount in currency minor units
currency:
type: string
transfer_type:
type: string
enum:
- transfer
- liability_payment
- loan_payment
notes:
type: string
nullable: true
source_fee_amount:
type: string
nullable: true
description: Fee charged to the source account
source_fee_currency:
type: string
nullable: true
destination_fee_amount:
type: string
nullable: true
description: Fee deducted from the destination account
destination_fee_currency:
type: string
nullable: true
inflow_transaction:
"$ref": "#/components/schemas/TransferTransactionSide"
outflow_transaction:
"$ref": "#/components/schemas/TransferTransactionSide"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
TransferDecisionCollection:
type: object
required:
- transfers
- pagination
properties:
transfers:
type: array
items:
"$ref": "#/components/schemas/TransferDecision"
pagination:
"$ref": "#/components/schemas/Pagination"
RejectedTransfer:
type: object
required:
- id
- inflow_transaction
- outflow_transaction
- created_at
- updated_at
properties:
id:
type: string
format: uuid
inflow_transaction:
"$ref": "#/components/schemas/TransferTransactionSide"
outflow_transaction:
"$ref": "#/components/schemas/TransferTransactionSide"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
RejectedTransferCollection:
type: object
required:
- rejected_transfers
- pagination
properties:
rejected_transfers:
type: array
items:
"$ref": "#/components/schemas/RejectedTransfer"
pagination:
"$ref": "#/components/schemas/Pagination"
Valuation:
type: object
required:
- id
- date
- amount
- currency
- kind
- account
- created_at
- updated_at
properties:
id:
type: string
format: uuid
date:
type: string
format: date
amount:
type: string
currency:
type: string
notes:
type: string
nullable: true
kind:
type: string
account:
"$ref": "#/components/schemas/Account"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ValuationCollection:
type: object
required:
- valuations
- pagination
properties:
valuations:
type: array
items:
"$ref": "#/components/schemas/Valuation"
pagination:
"$ref": "#/components/schemas/Pagination"
DeleteResponse:
type: object
required:
- message
properties:
message:
type: string
TransactionResponse:
type: object
required:
- id
- date
- amount
- currency
- name
- entryable_type
- account
properties:
id:
type: string
format: uuid
date:
type: string
format: date
amount:
type: string
currency:
type: string
name:
type: string
entryable_type:
type: string
account:
type: object
required:
- id
- name
- account_type
properties:
id:
type: string
format: uuid
name:
type: string
account_type:
type: string
nullable: true
ImportConfiguration:
type: object
properties:
date_col_label:
type: string
nullable: true
amount_col_label:
type: string
nullable: true
name_col_label:
type: string
nullable: true
category_col_label:
type: string
nullable: true
tags_col_label:
type: string
nullable: true
notes_col_label:
type: string
nullable: true
account_col_label:
type: string
nullable: true
date_format:
type: string
nullable: true
number_format:
type: string
nullable: true
signage_convention:
type: string
nullable: true
ImportStats:
type: object
required:
- rows_count
- valid_rows_count
- invalid_rows_count
- mappings_count
- unassigned_mappings_count
properties:
rows_count:
type: integer
minimum: 0
valid_rows_count:
type: integer
minimum: 0
invalid_rows_count:
type: integer
minimum: 0
mappings_count:
type: integer
minimum: 0
unassigned_mappings_count:
type: integer
minimum: 0
ImportVerificationReadback:
type: object
description: SureImport only. Expected NDJSON counts compared to family-scoped
database readback after publish.
properties:
status:
type: string
enum:
- not_verified
- matched
- mismatch
- failed
- reverted
checked_at:
type: string
format: date-time
nullable: true
expected_record_counts:
type: object
additionalProperties:
type: integer
before_counts:
type: object
additionalProperties:
type: integer
after_counts:
type: object
additionalProperties:
type: integer
actual_delta_counts:
type: object
additionalProperties:
type: integer
checked_counts:
type: object
additionalProperties:
type: integer
mismatches:
type: object
additionalProperties:
type: object
required:
- expected
- actual
properties:
expected:
type: integer
actual:
type: integer
error:
type: string
nullable: true
ImportVerification:
type: object
description: SureImport only. Captured at upload and completed after import
publish.
required:
- expected_record_counts
- readback
properties:
expected_record_counts:
type: object
additionalProperties:
type: integer
readback:
"$ref": "#/components/schemas/ImportVerificationReadback"
ImportPreflightContent:
type: object
required:
- filename
- content_type
- byte_size
properties:
filename:
type: string
content_type:
type: string
byte_size:
type: integer
minimum: 0
ImportPreflightError:
type: object
required:
- code
- message
properties:
code:
type: string
message:
type: string
ImportPreflightStats:
type: object
required:
- rows_count
properties:
rows_count:
type: integer
minimum: 0
description: CSV parsed non-header rows, or nonblank Sure NDJSON lines.
valid_rows_count:
type: integer
minimum: 0
description: SureImport only. Valid NDJSON records.
invalid_rows_count:
type: integer
minimum: 0
description: SureImport only. Invalid NDJSON records. CSV malformed content
returns a 422 instead.
entity_counts:
type: object
additionalProperties:
type: integer
nullable: true
record_type_counts:
type: object
additionalProperties:
type: integer
nullable: true
ImportPreflight:
type: object
required:
- type
- valid
- content
- stats
- errors
- warnings
properties:
type:
type: string
enum:
- TransactionImport
- TradeImport
- AccountImport
- MintImport
- ActualImport
- YnabImport
- CategoryImport
- RuleImport
- MerchantImport
- PdfImport
- QifImport
- SureImport
valid:
type: boolean
content:
"$ref": "#/components/schemas/ImportPreflightContent"
stats:
"$ref": "#/components/schemas/ImportPreflightStats"
headers:
type: array
items:
type: string
nullable: true
required_headers:
type: array
items:
type: string
nullable: true
missing_required_headers:
type: array
items:
type: string
nullable: true
errors:
type: array
items:
"$ref": "#/components/schemas/ImportPreflightError"
warnings:
type: array
items:
type: string
ImportPreflightResponse:
type: object
required:
- data
properties:
data:
"$ref": "#/components/schemas/ImportPreflight"
ImportStatusSummary:
type: object
required:
- uploaded
- configured
- terminal
properties:
uploaded:
type: boolean
configured:
type: boolean
terminal:
type: boolean
ImportStatusDetail:
allOf:
- "$ref": "#/components/schemas/ImportStatusSummary"
- type: object
required:
- cleaned
- publishable
- revertable
properties:
cleaned:
type: boolean
publishable:
type: boolean
revertable:
type: boolean
ImportSummary:
type: object
required:
- id
- type
- status
- created_at
- updated_at
- status_detail
properties:
id:
type: string
format: uuid
type:
type: string
enum:
- TransactionImport
- TradeImport
- AccountImport
- MintImport
- ActualImport
- YnabImport
- CategoryImport
- RuleImport
- MerchantImport
- PdfImport
- QifImport
- SureImport
status:
type: string
enum:
- pending
- complete
- importing
- reverting
- revert_failed
- failed
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
account_id:
type: string
format: uuid
nullable: true
rows_count:
type: integer
minimum: 0
error:
type: string
nullable: true
status_detail:
"$ref": "#/components/schemas/ImportStatusSummary"
ImportDetail:
type: object
required:
- id
- type
- status
- created_at
- updated_at
- status_detail
- configuration
- stats
properties:
id:
type: string
format: uuid
type:
type: string
enum:
- TransactionImport
- TradeImport
- AccountImport
- MintImport
- ActualImport
- YnabImport
- CategoryImport
- RuleImport
- MerchantImport
- PdfImport
- QifImport
- SureImport
status:
type: string
enum:
- pending
- complete
- importing
- reverting
- revert_failed
- failed
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
account_id:
type: string
format: uuid
nullable: true
error:
type: string
nullable: true
status_detail:
"$ref": "#/components/schemas/ImportStatusDetail"
configuration:
"$ref": "#/components/schemas/ImportConfiguration"
stats:
"$ref": "#/components/schemas/ImportStats"
verification:
"$ref": "#/components/schemas/ImportVerification"
ImportCollection:
type: object
required:
- data
- meta
properties:
data:
type: array
items:
"$ref": "#/components/schemas/ImportSummary"
meta:
type: object
required:
- current_page
- total_pages
- total_count
- per_page
properties:
current_page:
type: integer
minimum: 1
next_page:
type: integer
nullable: true
prev_page:
type: integer
nullable: true
total_pages:
type: integer
minimum: 0
total_count:
type: integer
minimum: 0
per_page:
type: integer
minimum: 1
ImportResponse:
type: object
required:
- data
properties:
data:
"$ref": "#/components/schemas/ImportDetail"
ImportSessionChunk:
type: object
required:
- id
- sequence
- status
- rows_count
- summary
- created_at
- updated_at
properties:
id:
type: string
format: uuid
sequence:
type: integer
minimum: 1
client_chunk_id:
type: string
nullable: true
status:
type: string
enum:
- pending
- importing
- complete
- failed
rows_count:
type: integer
minimum: 0
summary:
type: object
additionalProperties:
type: object
additionalProperties:
type: integer
error:
type: object
nullable: true
additionalProperties: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ImportSession:
type: object
required:
- id
- type
- status
- chunks_count
- summary
- chunks
- created_at
- updated_at
properties:
id:
type: string
format: uuid
type:
type: string
enum:
- SureImport
status:
type: string
enum:
- pending
- importing
- complete
- failed
client_session_id:
type: string
nullable: true
expected_chunks:
type: integer
nullable: true
minimum: 1
chunks_count:
type: integer
minimum: 0
summary:
type: object
additionalProperties:
type: object
additionalProperties:
type: integer
error:
type: object
nullable: true
additionalProperties: true
chunks:
type: array
items:
"$ref": "#/components/schemas/ImportSessionChunk"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ImportSessionResponse:
type: object
required:
- data
properties:
data:
"$ref": "#/components/schemas/ImportSession"
ProviderConnectionInstitution:
type: object
required:
- name
properties:
name:
type: string
nullable: true
domain:
type: string
nullable: true
url:
type: string
nullable: true
ProviderConnectionAccounts:
type: object
required:
- total_count
- linked_count
- unlinked_count
properties:
total_count:
type: integer
minimum: 0
linked_count:
type: integer
minimum: 0
unlinked_count:
type: integer
minimum: 0
ProviderConnectionSyncLatest:
type: object
required:
- id
- status
- created_at
properties:
id:
type: string
format: uuid
status:
type: string
created_at:
type: string
format: date-time
syncing_at:
type: string
format: date-time
nullable: true
completed_at:
type: string
format: date-time
nullable: true
failed_at:
type: string
format: date-time
nullable: true
error:
type: object
nullable: true
description: Sanitized latest sync error summary. Null when the latest sync
is not failed or stale.
required:
- present
properties:
present:
type: boolean
description: Always true when this object is present.
message:
type: string
nullable: true
description: Stable sanitized error category message; raw provider error
text is never exposed.
ProviderConnectionSync:
type: object
required:
- syncing
properties:
syncing:
type: boolean
status_summary:
type: string
nullable: true
last_synced_at:
type: string
format: date-time
nullable: true
latest:
allOf:
- "$ref": "#/components/schemas/ProviderConnectionSyncLatest"
nullable: true
ProviderConnection:
type: object
required:
- id
- provider
- provider_type
- name
- status
- requires_update
- credentials_configured
- scheduled_for_deletion
- pending_account_setup
- institution
- accounts
- sync
- created_at
- updated_at
properties:
id:
type: string
format: uuid
provider:
type: string
provider_type:
type: string
name:
type: string
status:
type: string
nullable: true
requires_update:
type: boolean
nullable: true
description: False when the provider item does not expose this status.
credentials_configured:
type: boolean
nullable: true
description: False when credential readiness is unknown.
scheduled_for_deletion:
type: boolean
nullable: true
description: False when the provider item does not expose this status.
pending_account_setup:
type: boolean
nullable: true
description: False when account setup state is unknown.
institution:
"$ref": "#/components/schemas/ProviderConnectionInstitution"
accounts:
"$ref": "#/components/schemas/ProviderConnectionAccounts"
sync:
"$ref": "#/components/schemas/ProviderConnectionSync"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ProviderConnectionCollection:
type: object
required:
- data
properties:
data:
type: array
items:
"$ref": "#/components/schemas/ProviderConnection"
ImportRowMapping:
type: object
required:
- key
- type
- value
- create_when_empty
- creatable
- mappable
properties:
key:
type: string
nullable: true
type:
type: string
value:
type: string
nullable: true
create_when_empty:
type: boolean
creatable:
type: boolean
mappable:
type: object
nullable: true
properties:
id:
type: string
format: uuid
type:
type: string
name:
type: string
nullable: true
ImportRowDiagnostic:
type: object
required:
- id
- row_number
- valid
- errors
- fields
- mappings
properties:
id:
type: string
format: uuid
row_number:
type: integer
minimum: 1
valid:
type: boolean
errors:
type: array
items:
type: string
fields:
type: object
properties:
account:
type: string
nullable: true
date:
type: string
nullable: true
qty:
type: string
nullable: true
ticker:
type: string
nullable: true
exchange_operating_mic:
type: string
nullable: true
price:
type: string
nullable: true
amount:
type: string
nullable: true
currency:
type: string
nullable: true
name:
type: string
nullable: true
category:
type: string
nullable: true
tags:
type: string
nullable: true
entity_type:
type: string
nullable: true
notes:
type: string
nullable: true
active:
type: boolean
nullable: true
effective_date:
type: string
nullable: true
conditions:
type: string
nullable: true
actions:
type: string
nullable: true
mappings:
type: object
properties:
account:
"$ref": "#/components/schemas/ImportRowMapping"
category:
"$ref": "#/components/schemas/ImportRowMapping"
account_type:
"$ref": "#/components/schemas/ImportRowMapping"
tags:
type: array
items:
"$ref": "#/components/schemas/ImportRowMapping"
ImportRowDiagnosticCollection:
type: object
required:
- data
- meta
properties:
data:
type: array
items:
"$ref": "#/components/schemas/ImportRowDiagnostic"
meta:
type: object
required:
- current_page
- total_pages
- total_count
- per_page
properties:
current_page:
type: integer
minimum: 1
next_page:
type: integer
nullable: true
prev_page:
type: integer
nullable: true
total_pages:
type: integer
minimum: 0
total_count:
type: integer
minimum: 0
per_page:
type: integer
minimum: 1
SyncableSummary:
type: object
required:
- type
- id
properties:
type:
type: string
id:
type: string
format: uuid
name:
type: string
nullable: true
SyncErrorSummary:
type: object
required:
- message
properties:
message:
type: string
SyncResource:
type: object
required:
- id
- status
- in_progress
- terminal
- syncable
- children_count
- created_at
- updated_at
properties:
id:
type: string
format: uuid
status:
type: string
enum:
- pending
- syncing
- completed
- failed
- stale
in_progress:
type: boolean
terminal:
type: boolean
syncable:
"$ref": "#/components/schemas/SyncableSummary"
parent_id:
type: string
format: uuid
nullable: true
children_count:
type: integer
minimum: 0
window_start_date:
type: string
format: date
nullable: true
window_end_date:
type: string
format: date
nullable: true
pending_at:
type: string
format: date-time
nullable: true
syncing_at:
type: string
format: date-time
nullable: true
completed_at:
type: string
format: date-time
nullable: true
failed_at:
type: string
format: date-time
nullable: true
error:
nullable: true
allOf:
- "$ref": "#/components/schemas/SyncErrorSummary"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
SyncResponse:
type: object
required:
- data
properties:
data:
nullable: true
allOf:
- "$ref": "#/components/schemas/SyncResource"
SyncCollection:
type: object
required:
- data
- meta
properties:
data:
type: array
maxItems: 100
items:
"$ref": "#/components/schemas/SyncResource"
meta:
"$ref": "#/components/schemas/Pagination"
Trade:
type: object
required:
- id
- date
- amount
- currency
- name
- qty
- price
- account
- created_at
- updated_at
properties:
id:
type: string
format: uuid
date:
type: string
format: date
amount:
type: string
currency:
type: string
name:
type: string
notes:
type: string
nullable: true
qty:
type: string
price:
type: string
investment_activity_label:
type: string
nullable: true
account:
"$ref": "#/components/schemas/Account"
security:
type: object
nullable: true
properties:
id:
type: string
format: uuid
ticker:
type: string
name:
type: string
nullable: true
category:
type: object
nullable: true
properties:
id:
type: string
format: uuid
name:
type: string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
TradeCollection:
type: object
required:
- trades
- pagination
properties:
trades:
type: array
items:
"$ref": "#/components/schemas/Trade"
pagination:
"$ref": "#/components/schemas/Pagination"
Holding:
type: object
required:
- id
- date
- qty
- price
- amount
- currency
- account
- security
- created_at
- updated_at
properties:
id:
type: string
format: uuid
date:
type: string
format: date
qty:
type: string
description: Quantity of shares held
price:
type: string
description: Formatted price per share
amount:
type: string
currency:
type: string
cost_basis_source:
type: string
nullable: true
account:
"$ref": "#/components/schemas/Account"
security:
type: object
required:
- id
- ticker
- name
properties:
id:
type: string
format: uuid
ticker:
type: string
name:
type: string
nullable: true
avg_cost:
type: string
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
HoldingCollection:
type: object
required:
- holdings
- pagination
properties:
holdings:
type: array
items:
"$ref": "#/components/schemas/Holding"
pagination:
"$ref": "#/components/schemas/Pagination"
Security:
type: object
required:
- id
- ticker
- kind
- offline
- created_at
- updated_at
properties:
id:
type: string
format: uuid
ticker:
type: string
name:
type: string
nullable: true
kind:
type: string
enum:
- standard
- cash
country_code:
type: string
nullable: true
exchange_mic:
type: string
nullable: true
exchange_acronym:
type: string
nullable: true
exchange_operating_mic:
type: string
nullable: true
exchange_name:
type: string
nullable: true
offline:
type: boolean
offline_reason:
type: string
nullable: true
website_url:
type: string
nullable: true
logo_url:
type: string
nullable: true
first_provider_price_on:
type: string
format: date
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
SecurityCollection:
type: object
required:
- securities
- pagination
properties:
securities:
type: array
items:
"$ref": "#/components/schemas/Security"
pagination:
"$ref": "#/components/schemas/Pagination"
SecurityPrice:
type: object
required:
- id
- date
- price
- price_amount
- currency
- provisional
- security
- created_at
- updated_at
properties:
id:
type: string
format: uuid
date:
type: string
format: date
price:
type: string
description: Formatted security price
price_amount:
type: string
description: Exact decimal security price
currency:
type: string
provisional:
type: boolean
security:
type: object
required:
- id
- ticker
properties:
id:
type: string
format: uuid
ticker:
type: string
name:
type: string
nullable: true
exchange_operating_mic:
type: string
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
SecurityPriceCollection:
type: object
required:
- security_prices
- pagination
properties:
security_prices:
type: array
items:
"$ref": "#/components/schemas/SecurityPrice"
pagination:
"$ref": "#/components/schemas/Pagination"
Money:
type: object
required:
- amount
- currency
- formatted
properties:
amount:
type: string
description: Numeric amount as string
currency:
type: string
description: ISO 4217 currency code
formatted:
type: string
description: Locale-formatted money string
BalanceSheet:
type: object
required:
- currency
- net_worth
- assets
- liabilities
properties:
currency:
type: string
description: Family primary currency
net_worth:
"$ref": "#/components/schemas/Money"
assets:
"$ref": "#/components/schemas/Money"
liabilities:
"$ref": "#/components/schemas/Money"
SuccessMessage:
type: object
required:
- message
properties:
message:
type: string
ResetInitiatedResponse:
type: object
required:
- message
- status
- job_id
- family_id
- status_url
properties:
message:
type: string
status:
type: string
enum:
- queued
job_id:
type: string
description: Informational Active Job identifier returned by the queue adapter;
reset status is family-scoped, not job-scoped.
family_id:
type: string
format: uuid
description: UUID of the family being reset.
status_url:
type: string
ResetStatusResponse:
type: object
required:
- status
- family_id
- reset_complete
- counts
properties:
status:
type: string
enum:
- complete
- data_remaining
description: Counts-based family reset status at response time.
family_id:
type: string
format: uuid
description: UUID of the family whose reset target counts were checked.
reset_complete:
type: boolean
description: True when all reset target counts are zero at response time.
This is a family data snapshot, not a durable per-job completion record.
counts:
type: object
required:
- account_statements
- family_exports
- imports
- import_sessions
- import_source_mappings
- import_rows
- import_mappings
- accounts
- account_shares
- account_providers
- entries
- transactions
- transfers
- rejected_transfers
- valuations
- trades
- holdings
- balances
- recurring_transactions
- rules
- rule_actions
- rule_conditions
- rule_runs
- budgets
- budget_categories
- categories
- tags
- taggings
- merchants
- family_merchant_associations
- provider_items
- active_storage_attachments
- plaid_items
additionalProperties:
type: integer
minimum: 0
properties:
account_statements:
type: integer
minimum: 0
family_exports:
type: integer
minimum: 0
imports:
type: integer
minimum: 0
import_sessions:
type: integer
minimum: 0
import_source_mappings:
type: integer
minimum: 0
import_rows:
type: integer
minimum: 0
import_mappings:
type: integer
minimum: 0
accounts:
type: integer
minimum: 0
account_shares:
type: integer
minimum: 0
account_providers:
type: integer
minimum: 0
entries:
type: integer
minimum: 0
transactions:
type: integer
minimum: 0
transfers:
type: integer
minimum: 0
rejected_transfers:
type: integer
minimum: 0
valuations:
type: integer
minimum: 0
trades:
type: integer
minimum: 0
holdings:
type: integer
minimum: 0
balances:
type: integer
minimum: 0
recurring_transactions:
type: integer
minimum: 0
rules:
type: integer
minimum: 0
rule_actions:
type: integer
minimum: 0
rule_conditions:
type: integer
minimum: 0
rule_runs:
type: integer
minimum: 0
budgets:
type: integer
minimum: 0
budget_categories:
type: integer
minimum: 0
categories:
type: integer
minimum: 0
tags:
type: integer
minimum: 0
taggings:
type: integer
minimum: 0
merchants:
type: integer
minimum: 0
family_merchant_associations:
type: integer
minimum: 0
provider_items:
type: integer
minimum: 0
active_storage_attachments:
type: integer
minimum: 0
plaid_items:
type: integer
minimum: 0
paths:
"/api/v1/accounts":
get:
summary: List accounts
tags:
- Accounts
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: include_disabled
in: query
required: false
description: Include disabled accounts in the response. Defaults to false.
schema:
type: boolean
responses:
'200':
description: accounts paginated
content:
application/json:
schema:
"$ref": "#/components/schemas/AccountCollection"
"/api/v1/accounts/{id}":
parameters:
- name: id
in: path
required: true
description: Account ID
schema:
type: string
format: uuid
get:
summary: Retrieve an account
tags:
- Accounts
security:
- apiKeyAuth: []
parameters:
- name: include_disabled
in: query
required: false
description: Allow retrieving a disabled account. Defaults to false.
schema:
type: boolean
responses:
'200':
description: account retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/AccountDetail"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: account not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/auth/signup":
post:
summary: Sign up a new user
tags:
- Auth
description: Creates a new user and family. The first user on a fresh instance
is assigned the super_admin role; later family creators are assigned an admin
role.
parameters: []
responses:
'201':
description: user created
content:
application/json:
schema:
type: object
properties:
access_token:
type: string
refresh_token:
type: string
token_type:
type: string
expires_in:
type: integer
created_at:
type: integer
user:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
first_name:
type: string
last_name:
type: string
ui_layout:
type: string
enum:
- dashboard
- intro
ai_enabled:
type: boolean
'422':
description: validation error
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: invite code required or invalid
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
user:
type: object
properties:
email:
type: string
format: email
description: User email address
password:
type: string
description: Password (min 8 chars, mixed case, number, special
char)
first_name:
type: string
last_name:
type: string
required:
- email
- password
device:
type: object
properties:
device_id:
type: string
description: Unique device identifier
device_name:
type: string
description: Human-readable device name
device_type:
type: string
description: Device type (e.g. ios, android)
os_version:
type: string
app_version:
type: string
required:
- device_id
- device_name
- device_type
- os_version
- app_version
invite_code:
type: string
nullable: true
description: Invite code (required when invites are enforced)
required:
- user
- device
required: true
"/api/v1/auth/login":
post:
summary: Log in with email and password
tags:
- Auth
parameters: []
responses:
'200':
description: login successful
content:
application/json:
schema:
type: object
properties:
access_token:
type: string
refresh_token:
type: string
token_type:
type: string
expires_in:
type: integer
created_at:
type: integer
user:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
first_name:
type: string
last_name:
type: string
ui_layout:
type: string
enum:
- dashboard
- intro
ai_enabled:
type: boolean
'401':
description: invalid credentials or MFA required
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
email:
type: string
format: email
password:
type: string
otp_code:
type: string
nullable: true
description: TOTP code if MFA is enabled
device:
type: object
properties:
device_id:
type: string
device_name:
type: string
device_type:
type: string
os_version:
type: string
app_version:
type: string
required:
- device_id
- device_name
- device_type
- os_version
- app_version
required:
- email
- password
- device
required: true
"/api/v1/auth/sso_exchange":
post:
summary: Exchange mobile SSO authorization code for tokens
tags:
- Auth
description: Exchanges a one-time authorization code (received via deep link
after mobile SSO) for OAuth tokens. The code is single-use and expires after
5 minutes.
parameters: []
responses:
'200':
description: tokens issued
content:
application/json:
schema:
type: object
properties:
access_token:
type: string
refresh_token:
type: string
token_type:
type: string
expires_in:
type: integer
created_at:
type: integer
user:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
first_name:
type: string
last_name:
type: string
ui_layout:
type: string
enum:
- dashboard
- intro
ai_enabled:
type: boolean
'401':
description: invalid or expired code
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
code:
type: string
description: One-time authorization code from mobile SSO callback
required:
- code
required: true
"/api/v1/auth/refresh":
post:
summary: Refresh an access token
tags:
- Auth
parameters: []
responses:
'200':
description: token refreshed
content:
application/json:
schema:
type: object
properties:
access_token:
type: string
refresh_token:
type: string
token_type:
type: string
expires_in:
type: integer
created_at:
type: integer
'401':
description: invalid refresh token
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'400':
description: missing refresh token
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
refresh_token:
type: string
description: The refresh token from a previous login or refresh
device:
type: object
properties:
device_id:
type: string
required:
- device_id
required:
- refresh_token
- device
required: true
"/api/v1/auth/sso_link":
post:
summary: Link an existing account via SSO
tags:
- Auth
description: Authenticates with email/password and links the SSO identity from
a previously issued linking code. Creates an OidcIdentity, logs the link via
SsoAuditLog, and issues mobile OAuth tokens.
parameters: []
responses:
'200':
description: account linked and tokens issued
content:
application/json:
schema:
type: object
properties:
access_token:
type: string
refresh_token:
type: string
token_type:
type: string
expires_in:
type: integer
created_at:
type: integer
user:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
first_name:
type: string
last_name:
type: string
ui_layout:
type: string
enum:
- dashboard
- intro
ai_enabled:
type: boolean
'400':
description: missing linking code
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'401':
description: invalid credentials or expired linking code
content:
application/json:
schema:
oneOf:
- "$ref": "#/components/schemas/ErrorResponse"
- "$ref": "#/components/schemas/MfaRequiredResponse"
'403':
description: SSO identity removed by an administrator
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
linking_code:
type: string
description: One-time linking code from mobile SSO onboarding redirect
email:
type: string
format: email
description: Email of the existing account to link
password:
type: string
description: Password for the existing account
required:
- linking_code
- email
- password
required: true
"/api/v1/auth/sso_create_account":
post:
summary: Create a new account via SSO
tags:
- Auth
description: Creates a new user and family from a previously issued linking
code. Links the SSO identity via OidcIdentity, logs the JIT account creation
via SsoAuditLog, and issues mobile OAuth tokens. The linking code must have
allow_account_creation enabled. The first user on a fresh instance is assigned
the super_admin role; later family creators are assigned an admin-capable
role.
parameters: []
responses:
'200':
description: account created and tokens issued
content:
application/json:
schema:
type: object
properties:
access_token:
type: string
refresh_token:
type: string
token_type:
type: string
expires_in:
type: integer
created_at:
type: integer
user:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
first_name:
type: string
last_name:
type: string
ui_layout:
type: string
enum:
- dashboard
- intro
ai_enabled:
type: boolean
'400':
description: missing linking code
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'401':
description: invalid or expired linking code
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: SSO identity removed or account creation disabled
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: user validation error
content:
application/json:
schema:
type: object
properties:
errors:
type: array
items:
type: string
requestBody:
content:
application/json:
schema:
type: object
properties:
linking_code:
type: string
description: One-time linking code from mobile SSO onboarding redirect
first_name:
type: string
description: First name (overrides value from SSO provider if provided)
last_name:
type: string
description: Last name (overrides value from SSO provider if provided)
required:
- linking_code
required: true
"/api/v1/auth/enable_ai":
patch:
summary: Enable AI features for the authenticated user
tags:
- Auth
security:
- apiKeyAuth: []
responses:
'200':
description: ai enabled
content:
application/json:
schema:
type: object
properties:
user:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
first_name:
type: string
nullable: true
last_name:
type: string
nullable: true
ui_layout:
type: string
enum:
- dashboard
- intro
ai_enabled:
type: boolean
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/balance_sheet":
get:
summary: Show balance sheet
tags:
- Balance Sheet
description: Returns the family balance sheet including net worth, total assets,
and total liabilities with amounts converted to the family's primary currency.
security:
- apiKeyAuth: []
responses:
'200':
description: balance sheet returned
content:
application/json:
schema:
"$ref": "#/components/schemas/BalanceSheet"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/balances":
get:
summary: List balance history records
tags:
- Balances
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: account_id
in: query
required: false
description: Filter by account ID
schema:
type: string
format: uuid
- name: currency
in: query
required: false
description: Filter by currency code
schema:
type: string
- name: start_date
in: query
required: false
description: Filter balances from this date
schema:
type: string
format: date
- name: end_date
in: query
required: false
description: Filter balances until this date
schema:
type: string
format: date
responses:
'200':
description: balances listed
content:
application/json:
schema:
"$ref": "#/components/schemas/BalanceCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: invalid filter
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/balances/{id}":
parameters:
- name: id
in: path
required: true
description: Balance ID
schema:
type: string
format: uuid
get:
summary: Retrieve a balance history record
tags:
- Balances
security:
- apiKeyAuth: []
responses:
'200':
description: balance retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/Balance"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: balance not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/budget_categories":
get:
summary: List budget categories
tags:
- Budget Categories
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: budget_id
in: query
required: false
schema:
type: string
format: uuid
description: Filter by budget ID
- name: category_id
in: query
required: false
schema:
type: string
format: uuid
description: Filter by category ID
- name: start_date
in: query
required: false
schema:
type: string
format: date
description: Filter budget categories whose budget starts on or after this
date
- name: end_date
in: query
required: false
schema:
type: string
format: date
description: Filter budget categories whose budget ends on or before this
date
responses:
'200':
description: budget categories listed
content:
application/json:
schema:
"$ref": "#/components/schemas/BudgetCategoryCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: invalid filter
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/budget_categories/{id}":
parameters:
- name: id
in: path
required: true
description: Budget category ID
schema:
type: string
format: uuid
get:
summary: Retrieve a budget category
tags:
- Budget Categories
security:
- apiKeyAuth: []
responses:
'200':
description: budget category retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/BudgetCategory"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: budget category not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/budgets":
get:
summary: List budgets
tags:
- Budgets
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: start_date
in: query
required: false
schema:
type: string
format: date
description: Filter budgets starting on or after this date
- name: end_date
in: query
required: false
schema:
type: string
format: date
description: Filter budgets ending on or before this date
responses:
'200':
description: budgets listed
content:
application/json:
schema:
"$ref": "#/components/schemas/BudgetCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: invalid date filter
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/budgets/{id}":
parameters:
- name: id
in: path
required: true
description: Budget ID
schema:
type: string
format: uuid
get:
summary: Retrieve a budget
tags:
- Budgets
security:
- apiKeyAuth: []
responses:
'200':
description: budget retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/Budget"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: budget not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/categories":
get:
summary: List categories
tags:
- Categories
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: roots_only
in: query
required: false
description: Return only root categories (no parent)
schema:
type: boolean
- name: parent_id
in: query
required: false
description: Filter by parent category ID
schema:
type: string
format: uuid
responses:
'200':
description: categories filtered by parent
content:
application/json:
schema:
"$ref": "#/components/schemas/CategoryCollection"
post:
summary: Create category
tags:
- Categories
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: subcategory created with parent
content:
application/json:
schema:
"$ref": "#/components/schemas/CategoryDetail"
'422':
description: validation error - duplicate name
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden - api key missing read_write scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'400':
description: bad request - missing category payload
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'401':
description: unauthorized - missing api key
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
"$ref": "#/components/schemas/CategoryCreateRequest"
required: true
"/api/v1/categories/{id}":
parameters:
- name: id
in: path
required: true
description: Category ID
schema:
type: string
get:
summary: Retrieve a category
tags:
- Categories
security:
- apiKeyAuth: []
responses:
'200':
description: subcategory retrieved with parent
content:
application/json:
schema:
"$ref": "#/components/schemas/CategoryDetail"
'404':
description: category not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/chats":
get:
summary: List chats
tags:
- Chats
security:
- apiKeyAuth: []
responses:
'200':
description: chats listed
content:
application/json:
schema:
"$ref": "#/components/schemas/ChatCollection"
'403':
description: AI features disabled
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
post:
summary: Create chat
tags:
- Chats
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: chat created
content:
application/json:
schema:
"$ref": "#/components/schemas/ChatDetail"
'422':
description: validation error
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
title:
type: string
example: Monthly budget review
message:
type: string
description: Optional initial message in the chat
model:
type: string
description: Optional OpenAI model identifier
required:
- title
required: true
"/api/v1/chats/{id}":
parameters:
- name: id
in: path
required: true
description: Chat ID
schema:
type: string
get:
summary: Retrieve a chat
tags:
- Chats
security:
- apiKeyAuth: []
responses:
'200':
description: chat retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/ChatDetail"
'404':
description: chat not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
patch:
summary: Update a chat
tags:
- Chats
security:
- apiKeyAuth: []
parameters: []
responses:
'200':
description: chat updated
content:
application/json:
schema:
"$ref": "#/components/schemas/ChatDetail"
'404':
description: chat not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: validation error
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
title:
type: string
example: Updated chat title
required: true
delete:
summary: Delete a chat
tags:
- Chats
security:
- apiKeyAuth: []
responses:
'204':
description: chat deleted
'404':
description: chat not found
"/api/v1/chats/{chat_id}/messages":
parameters:
- name: chat_id
in: path
required: true
description: Chat ID
schema:
type: string
post:
summary: Create a message
tags:
- Chat Messages
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: message created
content:
application/json:
schema:
"$ref": "#/components/schemas/MessageResponse"
'404':
description: chat not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: validation error
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
content:
type: string
model:
type: string
required:
- content
required: true
"/api/v1/chats/{chat_id}/messages/retry":
parameters:
- name: chat_id
in: path
required: true
description: Chat ID
schema:
type: string
post:
summary: Retry the last assistant response
tags:
- Chat Messages
security:
- apiKeyAuth: []
responses:
'202':
description: retry started
content:
application/json:
schema:
"$ref": "#/components/schemas/RetryResponse"
'404':
description: chat not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: no assistant message available
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/family_exports":
get:
summary: Lists family exports
tags:
- Family Exports
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
responses:
'200':
description: family exports listed
content:
application/json:
schema:
"$ref": "#/components/schemas/FamilyExportCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
post:
summary: Queues a family export
tags:
- Family Exports
security:
- apiKeyAuth: []
parameters: []
responses:
'202':
description: family export queued
content:
application/json:
schema:
"$ref": "#/components/schemas/FamilyExportResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: invalid params
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
additionalProperties: false
description: Family export creation does not accept request parameters.
"/api/v1/family_exports/{id}":
parameters:
- name: id
in: path
format: uuid
required: true
schema:
type: string
get:
summary: Shows a family export
tags:
- Family Exports
security:
- apiKeyAuth: []
responses:
'200':
description: family export shown
content:
application/json:
schema:
"$ref": "#/components/schemas/FamilyExportResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/family_exports/{id}/download":
parameters:
- name: id
in: path
format: uuid
required: true
schema:
type: string
get:
summary: Downloads a completed family export
tags:
- Family Exports
security:
- apiKeyAuth: []
responses:
'302':
description: family export download redirected
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'409':
description: export not ready
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/family_settings":
get:
summary: Retrieve family settings
description: Retrieve a read-only snapshot of non-secret family configuration.
tags:
- Family Settings
security:
- apiKeyAuth: []
responses:
'200':
description: family settings retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/FamilySettings"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/holdings":
get:
summary: List holdings
tags:
- Holdings
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: account_id
in: query
required: false
description: Filter by account ID
schema:
type: string
- name: account_ids
in: query
required: false
description: Filter by multiple account IDs
schema:
type: array
items:
type: string
- name: date
in: query
required: false
description: Filter by exact date
schema:
type: string
format: date
- name: start_date
in: query
required: false
description: Filter holdings from this date (inclusive)
schema:
type: string
format: date
- name: end_date
in: query
required: false
description: Filter holdings until this date (inclusive)
schema:
type: string
format: date
- name: security_id
in: query
required: false
description: Filter by security ID
schema:
type: string
responses:
'200':
description: holdings paginated
content:
application/json:
schema:
"$ref": "#/components/schemas/HoldingCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: invalid date filter
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/holdings/{id}":
parameters:
- name: id
in: path
required: true
description: Holding ID
schema:
type: string
get:
summary: Retrieve holding
tags:
- Holdings
security:
- apiKeyAuth: []
responses:
'200':
description: holding retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/Holding"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: holding not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/import_sessions":
post:
summary: Create import session
description: Create or idempotently retrieve a multi-file SureImport session
keyed by client_session_id.
tags:
- Import Sessions
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: import session created
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportSessionResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'409':
description: client session conflict
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: validation error
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
type:
type: string
enum:
- SureImport
description: Import session type. Only SureImport is supported.
client_session_id:
type: string
nullable: true
description: Client-provided idempotency key for the full import
session.
expected_chunks:
type: integer
minimum: 1
nullable: true
description: Expected number of ordered chunks before publish is
allowed.
"/api/v1/import_sessions/{id}":
parameters:
- name: id
in: path
required: true
description: Import session ID
schema:
type: string
get:
summary: Retrieve import session
description: Retrieve import session status, chunk status, per-entity summary
counts, and safe error details.
tags:
- Import Sessions
security:
- apiKeyAuth: []
responses:
'200':
description: import session retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportSessionResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: import session not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/import_sessions/{id}/chunks":
parameters:
- name: id
in: path
required: true
description: Import session ID
schema:
type: string
post:
summary: Upload import session chunk
description: Attach an ordered Sure NDJSON chunk to an import session. Chunks
are idempotent by sequence and client_chunk_id with content verification.
tags:
- Import Sessions
security:
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- sequence
- raw_file_content
properties:
sequence:
type: integer
minimum: 1
description: One-based chunk sequence. Earlier dependency chunks
must have lower sequence numbers.
client_chunk_id:
type: string
nullable: true
description: Client-provided idempotency key for this chunk.
raw_file_content:
type: string
description: Raw Sure NDJSON content. Each chunk is limited to 10MB.
multipart/form-data:
schema:
type: object
required:
- sequence
- file
properties:
sequence:
type: integer
minimum: 1
description: One-based chunk sequence. Earlier dependency chunks
must have lower sequence numbers.
client_chunk_id:
type: string
nullable: true
description: Client-provided idempotency key for this chunk.
file:
type: string
format: binary
description: Multipart Sure NDJSON file upload. Each chunk is limited
to 10MB.
parameters: []
responses:
'201':
description: chunk uploaded
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportSessionResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'409':
description: chunk conflict
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: import session not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: missing or invalid content
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/import_sessions/{id}/publish":
parameters:
- name: id
in: path
required: true
description: Import session ID
schema:
type: string
post:
summary: Publish import session
description: Queue ordered chunk processing for a SureImport session. Later
chunks can reference source IDs mapped by earlier chunks.
tags:
- Import Sessions
security:
- apiKeyAuth: []
responses:
'202':
description: import session publish queued
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportSessionResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: max_row_count_exceeded
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'409':
description: missing expected chunks
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'503':
description: enqueue failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: import session not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/imports":
get:
summary: List imports
description: List all imports for the user's family with pagination and filtering.
tags:
- Imports
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: status
in: query
required: false
description: Filter by status
schema:
type: string
enum:
- pending
- complete
- importing
- reverting
- revert_failed
- failed
- name: type
in: query
required: false
description: Filter by import type
schema:
type: string
enum:
- TransactionImport
- TradeImport
- AccountImport
- MintImport
- ActualImport
- YnabImport
- CategoryImport
- RuleImport
- MerchantImport
- PdfImport
- QifImport
- SureImport
responses:
'200':
description: imports filtered by type
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportCollection"
post:
summary: Create import
description: Create a new import from raw CSV content, inline Sure NDJSON content,
or an uploaded Sure NDJSON file. CSV content is limited to 10MB.
tags:
- Imports
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: import created
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportResponse"
'422':
description: validation error or publish rejection
content:
application/json:
schema:
oneOf:
- "$ref": "#/components/schemas/ErrorResponse"
- "$ref": "#/components/schemas/ErrorResponseWithImportId"
'500':
description: import uploaded but publish enqueue failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponseWithImportId"
requestBody:
content:
application/json:
schema:
type: object
properties:
raw_file_content:
type: string
description: Raw CSV or Sure NDJSON content as a string. CSV content
is limited to 10MB. Required for SureImport unless a multipart
file is uploaded.
type:
type: string
enum:
- TransactionImport
- TradeImport
- AccountImport
- MintImport
- ActualImport
- YnabImport
- CategoryImport
- RuleImport
- MerchantImport
- PdfImport
- QifImport
- SureImport
description: Import type (defaults to TransactionImport)
account_id:
type: string
format: uuid
description: Account ID to import into
publish:
type: string
description: Set to "true" to automatically queue for processing
if configuration is valid
date_col_label:
type: string
description: CSV imports only. Header name for the date column
amount_col_label:
type: string
description: CSV imports only. Header name for the amount column
name_col_label:
type: string
description: CSV imports only. Header name for the transaction name
column
category_col_label:
type: string
description: CSV imports only. Header name for the category column
tags_col_label:
type: string
description: CSV imports only. Header name for the tags column
notes_col_label:
type: string
description: CSV imports only. Header name for the notes column
account_col_label:
type: string
description: CSV imports only. Header name for the account column
when importing rows across multiple accounts
qty_col_label:
type: string
description: CSV trade imports only. Header name for the quantity
column
ticker_col_label:
type: string
description: CSV trade imports only. Header name for the ticker
column
price_col_label:
type: string
description: CSV trade imports only. Header name for the price column
entity_type_col_label:
type: string
description: CSV imports only. Header name for the entity type column
currency_col_label:
type: string
description: CSV imports only. Header name for the currency column
exchange_operating_mic_col_label:
type: string
description: CSV trade imports only. Header name for the exchange
operating MIC column
date_format:
type: string
description: CSV imports only. Date format pattern (e.g., "%m/%d/%Y")
number_format:
type: string
enum:
- '1,234.56'
- 1.234,56
- 1 234,56
- '1,234'
description: CSV imports only. Number format for parsing amounts
signage_convention:
type: string
enum:
- inflows_positive
- inflows_negative
description: CSV imports only. How to interpret positive/negative
amounts
col_sep:
type: string
enum:
- ","
- ";"
description: CSV imports only. Column separator
amount_type_strategy:
type: string
enum:
- signed_amount
- custom_column
description: CSV imports only. Amount parsing strategy
amount_type_inflow_value:
type: string
description: CSV imports only. Column value that marks an amount
as an inflow when using custom_column strategy
multipart/form-data:
schema:
type: object
properties:
raw_file_content:
type: string
description: Raw CSV or Sure NDJSON content as a string. CSV content
is limited to 10MB. Required for SureImport unless a multipart
file is uploaded.
type:
type: string
enum:
- TransactionImport
- TradeImport
- AccountImport
- MintImport
- ActualImport
- YnabImport
- CategoryImport
- RuleImport
- MerchantImport
- PdfImport
- QifImport
- SureImport
description: Import type (defaults to TransactionImport)
account_id:
type: string
format: uuid
description: Account ID to import into
publish:
type: string
description: Set to "true" to automatically queue for processing
if configuration is valid
date_col_label:
type: string
description: CSV imports only. Header name for the date column
amount_col_label:
type: string
description: CSV imports only. Header name for the amount column
name_col_label:
type: string
description: CSV imports only. Header name for the transaction name
column
category_col_label:
type: string
description: CSV imports only. Header name for the category column
tags_col_label:
type: string
description: CSV imports only. Header name for the tags column
notes_col_label:
type: string
description: CSV imports only. Header name for the notes column
account_col_label:
type: string
description: CSV imports only. Header name for the account column
when importing rows across multiple accounts
qty_col_label:
type: string
description: CSV trade imports only. Header name for the quantity
column
ticker_col_label:
type: string
description: CSV trade imports only. Header name for the ticker
column
price_col_label:
type: string
description: CSV trade imports only. Header name for the price column
entity_type_col_label:
type: string
description: CSV imports only. Header name for the entity type column
currency_col_label:
type: string
description: CSV imports only. Header name for the currency column
exchange_operating_mic_col_label:
type: string
description: CSV trade imports only. Header name for the exchange
operating MIC column
date_format:
type: string
description: CSV imports only. Date format pattern (e.g., "%m/%d/%Y")
number_format:
type: string
enum:
- '1,234.56'
- 1.234,56
- 1 234,56
- '1,234'
description: CSV imports only. Number format for parsing amounts
signage_convention:
type: string
enum:
- inflows_positive
- inflows_negative
description: CSV imports only. How to interpret positive/negative
amounts
col_sep:
type: string
enum:
- ","
- ";"
description: CSV imports only. Column separator
amount_type_strategy:
type: string
enum:
- signed_amount
- custom_column
description: CSV imports only. Amount parsing strategy
amount_type_inflow_value:
type: string
description: CSV imports only. Column value that marks an amount
as an inflow when using custom_column strategy
"/api/v1/imports/{id}":
parameters:
- name: id
in: path
required: true
description: Import ID
schema:
type: string
get:
summary: Retrieve an import
description: Retrieve detailed information about a specific import, including
configuration, row statistics, and SureImport readback verification when available.
tags:
- Imports
security:
- apiKeyAuth: []
responses:
'200':
description: import retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportResponse"
'404':
description: import not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/imports/{id}/rows":
parameters:
- name: id
in: path
required: true
description: Import ID
schema:
type: string
get:
summary: List import row diagnostics
description: List sanitized import rows with validation errors and mapping resolution
state.
tags:
- Imports
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
responses:
'200':
description: import rows listed
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportRowDiagnosticCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: import not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'500':
description: internal server error
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/imports/preflight":
post:
summary: Validate import content without creating an import
description: Validate CSV or Sure NDJSON import content and return counts, headers,
warnings, and validation errors without persisting an import or enqueueing
jobs. CSV content is limited to 10MB.
tags:
- Imports
security:
- apiKeyAuth: []
parameters: []
responses:
'200':
description: import content preflighted
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportPreflightResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: missing or invalid content
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: account not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
raw_file_content:
type: string
description: Raw CSV or Sure NDJSON content as a string. CSV content
is limited to 10MB.
file:
type: string
format: binary
description: CSV or Sure NDJSON upload when using multipart/form-data.
CSV files are limited to 10MB.
type:
type: string
enum:
- TransactionImport
- TradeImport
- AccountImport
- MintImport
- ActualImport
- YnabImport
- CategoryImport
- RuleImport
- MerchantImport
- PdfImport
- QifImport
- SureImport
description: Import type to validate (defaults to TransactionImport)
account_id:
type: string
format: uuid
description: Account ID used for account-scoped CSV import validation
date_col_label:
type: string
description: CSV imports only. Header name for the date column
amount_col_label:
type: string
description: CSV imports only. Header name for the amount column
name_col_label:
type: string
description: CSV imports only. Header name for the transaction name
column
category_col_label:
type: string
description: CSV imports only. Header name for the category column
tags_col_label:
type: string
description: CSV imports only. Header name for the tags column
notes_col_label:
type: string
description: CSV imports only. Header name for the notes column
account_col_label:
type: string
description: CSV imports only. Header name for the account column
qty_col_label:
type: string
description: CSV trade imports only. Header name for the quantity
column
ticker_col_label:
type: string
description: CSV trade imports only. Header name for the ticker
column
price_col_label:
type: string
description: CSV trade imports only. Header name for the price column
entity_type_col_label:
type: string
description: CSV imports only. Header name for the entity type column
currency_col_label:
type: string
description: CSV imports only. Header name for the currency column
exchange_operating_mic_col_label:
type: string
description: CSV trade imports only. Header name for the exchange
operating MIC column
date_format:
type: string
description: CSV imports only. Date format pattern
number_format:
type: string
enum:
- '1,234.56'
- 1.234,56
- 1 234,56
- '1,234'
description: CSV imports only. Number format for parsing amounts
signage_convention:
type: string
enum:
- inflows_positive
- inflows_negative
description: CSV imports only. How to interpret positive/negative
amounts
col_sep:
type: string
enum:
- ","
- ";"
description: CSV imports only. Column separator
rows_to_skip:
type: integer
minimum: 0
description: CSV imports only. Number of leading rows to skip before
reading headers
amount_type_strategy:
type: string
enum:
- signed_amount
- custom_column
description: CSV imports only. Amount parsing strategy
amount_type_inflow_value:
type: string
description: CSV imports only. Column value that marks an amount
as an inflow when using custom_column strategy
multipart/form-data:
schema:
type: object
properties:
raw_file_content:
type: string
description: Raw CSV or Sure NDJSON content as a string. CSV content
is limited to 10MB.
file:
type: string
format: binary
description: CSV or Sure NDJSON upload when using multipart/form-data.
CSV files are limited to 10MB.
type:
type: string
enum:
- TransactionImport
- TradeImport
- AccountImport
- MintImport
- ActualImport
- YnabImport
- CategoryImport
- RuleImport
- MerchantImport
- PdfImport
- QifImport
- SureImport
description: Import type to validate (defaults to TransactionImport)
account_id:
type: string
format: uuid
description: Account ID used for account-scoped CSV import validation
date_col_label:
type: string
description: CSV imports only. Header name for the date column
amount_col_label:
type: string
description: CSV imports only. Header name for the amount column
name_col_label:
type: string
description: CSV imports only. Header name for the transaction name
column
category_col_label:
type: string
description: CSV imports only. Header name for the category column
tags_col_label:
type: string
description: CSV imports only. Header name for the tags column
notes_col_label:
type: string
description: CSV imports only. Header name for the notes column
account_col_label:
type: string
description: CSV imports only. Header name for the account column
qty_col_label:
type: string
description: CSV trade imports only. Header name for the quantity
column
ticker_col_label:
type: string
description: CSV trade imports only. Header name for the ticker
column
price_col_label:
type: string
description: CSV trade imports only. Header name for the price column
entity_type_col_label:
type: string
description: CSV imports only. Header name for the entity type column
currency_col_label:
type: string
description: CSV imports only. Header name for the currency column
exchange_operating_mic_col_label:
type: string
description: CSV trade imports only. Header name for the exchange
operating MIC column
date_format:
type: string
description: CSV imports only. Date format pattern
number_format:
type: string
enum:
- '1,234.56'
- 1.234,56
- 1 234,56
- '1,234'
description: CSV imports only. Number format for parsing amounts
signage_convention:
type: string
enum:
- inflows_positive
- inflows_negative
description: CSV imports only. How to interpret positive/negative
amounts
col_sep:
type: string
enum:
- ","
- ";"
description: CSV imports only. Column separator
rows_to_skip:
type: integer
minimum: 0
description: CSV imports only. Number of leading rows to skip before
reading headers
amount_type_strategy:
type: string
enum:
- signed_amount
- custom_column
description: CSV imports only. Amount parsing strategy
amount_type_inflow_value:
type: string
description: CSV imports only. Column value that marks an amount
as an inflow when using custom_column strategy
"/api/v1/insights":
get:
summary: List proactive insights
tags:
- Insights
security:
- apiKeyAuth: []
responses:
'200':
description: insights listed
content:
application/json:
schema:
"$ref": "#/components/schemas/InsightCollection"
'403':
description: preview features disabled
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/merchants":
get:
summary: List merchants
tags:
- Merchants
security:
- apiKeyAuth: []
responses:
'200':
description: merchants listed
content:
application/json:
schema:
type: array
items:
"$ref": "#/components/schemas/MerchantDetail"
post:
summary: Import merchants from CSV
tags:
- Merchants
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: merchants imported
content:
application/json:
schema:
"$ref": "#/components/schemas/MerchantImportResult"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: missing file or invalid CSV
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
multipart/form-data:
schema:
type: object
required:
- file
properties:
file:
type: string
format: binary
required: true
description: 'CSV file with columns: name* (required), color, website_url'
"/api/v1/merchants/{id}":
parameters:
- name: id
in: path
required: true
description: Merchant ID
schema:
type: string
get:
summary: Retrieve a merchant
tags:
- Merchants
security:
- apiKeyAuth: []
responses:
'200':
description: merchant retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/MerchantDetail"
'404':
description: merchant not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/provider_connections":
get:
summary: Lists provider connection status summaries
description: List safe provider connection status metadata for the authenticated
user's family without exposing credentials, raw provider payloads, or raw
sync errors.
tags:
- Provider Connections
security:
- apiKeyAuth: []
responses:
'200':
description: provider connection status summaries listed
content:
application/json:
schema:
"$ref": "#/components/schemas/ProviderConnectionCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/push_subscriptions":
post:
summary: Register an APNs device token
tags:
- Push Subscriptions
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: token registered
content:
application/json:
schema:
"$ref": "#/components/schemas/PushSubscription"
'422':
description: invalid or conflicting subscription
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
required:
- token
- environment
- platform
properties:
token:
type: string
environment:
type: string
enum:
- sandbox
- production
platform:
type: string
enum:
- ios
required: true
"/api/v1/push_subscriptions/{id}":
parameters:
- name: id
in: path
required: true
schema:
type: string
delete:
summary: Unregister an APNs device token
tags:
- Push Subscriptions
security:
- apiKeyAuth: []
responses:
'204':
description: token unregistered
"/api/v1/recurring_transactions":
get:
summary: List recurring transactions
tags:
- Recurring Transactions
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: status
in: query
required: false
description: Filter by recurring status
schema:
type: string
enum:
- active
- inactive
- name: account_id
in: query
required: false
description: Filter by account ID
schema:
type: string
format: uuid
responses:
'200':
description: recurring transactions listed
content:
application/json:
schema:
"$ref": "#/components/schemas/RecurringTransactionCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: validation error - malformed account filter
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
post:
summary: Create recurring transaction
tags:
- Recurring Transactions
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: recurring transaction created
content:
application/json:
schema:
"$ref": "#/components/schemas/RecurringTransaction"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden - requires read_write scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: account not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: validation error - negative occurrence count
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
recurring_transaction:
type: object
properties:
account_id:
type: string
format: uuid
nullable: true
merchant_id:
type: string
format: uuid
nullable: true
name:
type: string
nullable: true
amount:
type: number
currency:
type: string
expected_day_of_month:
type: integer
minimum: 1
maximum: 31
last_occurrence_date:
type: string
format: date
next_expected_date:
type: string
format: date
status:
type: string
enum:
- active
- inactive
occurrence_count:
type: integer
minimum: 0
manual:
type: boolean
payment_url:
type: string
nullable: true
description: Link to the biller portal. Only http and https
are accepted; a bare host is stored as https.
autopay:
type: boolean
description: Whether this bill pays itself automatically.
notes:
type: string
nullable: true
description: Free-text notes shown alongside the bill.
expected_amount_min:
type: number
nullable: true
expected_amount_max:
type: number
nullable: true
expected_amount_avg:
type: number
nullable: true
required:
- amount
- currency
- expected_day_of_month
- last_occurrence_date
- next_expected_date
anyOf:
- required:
- name
- required:
- merchant_id
required:
- recurring_transaction
required: true
"/api/v1/recurring_transactions/{id}":
parameters:
- name: id
in: path
required: true
description: Recurring transaction ID
schema:
type: string
get:
summary: Retrieve recurring transaction
tags:
- Recurring Transactions
security:
- apiKeyAuth: []
responses:
'200':
description: recurring transaction retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/RecurringTransaction"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: recurring transaction not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
patch:
summary: Update recurring transaction
tags:
- Recurring Transactions
security:
- apiKeyAuth: []
parameters: []
responses:
'200':
description: recurring transaction updated
content:
application/json:
schema:
"$ref": "#/components/schemas/RecurringTransaction"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden - requires read_write scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: recurring transaction not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: validation error
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
recurring_transaction:
type: object
properties:
status:
type: string
enum:
- active
- inactive
expected_day_of_month:
type: integer
minimum: 1
maximum: 31
next_expected_date:
type: string
format: date
payment_url:
type: string
nullable: true
description: Link to the biller portal. Only http and https
are accepted; a bare host is stored as https. Send an empty
string to clear it.
autopay:
type: boolean
description: Whether this bill pays itself automatically.
notes:
type: string
nullable: true
description: Free-text notes shown alongside the bill.
required: true
delete:
summary: Delete recurring transaction
tags:
- Recurring Transactions
security:
- apiKeyAuth: []
responses:
'200':
description: recurring transaction deleted
content:
application/json:
schema:
"$ref": "#/components/schemas/SuccessMessage"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden - requires read_write scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: recurring transaction not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/rejected_transfers":
get:
summary: List rejected transfers
tags:
- Rejected Transfers
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: account_id
in: query
required: false
schema:
type: string
format: uuid
description: Filter rejected transfers involving this account
- name: start_date
in: query
required: false
schema:
type: string
format: date
description: Filter rejected transfers from this date
- name: end_date
in: query
required: false
schema:
type: string
format: date
description: Filter rejected transfers until this date
responses:
'200':
description: rejected transfers listed
content:
application/json:
schema:
"$ref": "#/components/schemas/RejectedTransferCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: invalid filter
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/rejected_transfers/{id}":
parameters:
- name: id
in: path
required: true
description: Rejected transfer ID
schema:
type: string
get:
summary: Retrieve a rejected transfer
tags:
- Rejected Transfers
security:
- apiKeyAuth: []
responses:
'200':
description: rejected transfer retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/RejectedTransfer"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: rejected transfer not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/rule_runs":
get:
summary: List rule runs
description: List rule run history for the authenticated user family.
tags:
- Rule Runs
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: rule_id
in: query
required: false
description: Filter by rule ID
schema:
type: string
format: uuid
- name: status
in: query
required: false
description: Filter by run status
schema:
type: string
enum:
- pending
- success
- failed
- name: execution_type
in: query
required: false
description: Filter by execution type
schema:
type: string
enum:
- manual
- scheduled
- name: start_executed_at
in: query
required: false
description: Filter runs executed at or after this timestamp
schema:
type: string
format: date-time
- name: end_executed_at
in: query
required: false
description: Filter runs executed at or before this timestamp
schema:
type: string
format: date-time
responses:
'200':
description: rule runs listed
content:
application/json:
schema:
"$ref": "#/components/schemas/RuleRunCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: invalid filter
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/rule_runs/{id}":
parameters:
- name: id
in: path
required: true
description: Rule run ID
schema:
type: string
format: uuid
get:
summary: Retrieve a rule run
description: Retrieve one rule run from the authenticated user family.
tags:
- Rule Runs
security:
- apiKeyAuth: []
responses:
'200':
description: rule run retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/RuleRunResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: rule run not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/rules":
get:
summary: List rules
tags:
- Rules
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: resource_type
in: query
required: false
description: Filter by rule resource type
schema:
type: string
enum:
- transaction
- name: active
in: query
required: false
description: Filter by active status
schema:
type: boolean
responses:
'200':
description: rules listed
content:
application/json:
schema:
"$ref": "#/components/schemas/RuleCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden - requires read scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: unsupported resource type
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/rules/{id}":
parameters:
- name: id
in: path
required: true
description: Rule ID
schema:
type: string
get:
summary: Retrieve a rule
tags:
- Rules
security:
- apiKeyAuth: []
responses:
'200':
description: rule retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/RuleResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden - requires read scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: rule not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/securities":
get:
summary: List securities referenced by family investment data
tags:
- Securities
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: ticker
in: query
required: false
description: Filter by ticker symbol
schema:
type: string
- name: exchange_operating_mic
in: query
required: false
description: Filter by exchange operating MIC
schema:
type: string
- name: kind
in: query
required: false
description: Filter by security kind
schema:
type: string
enum:
- standard
- cash
- name: offline
in: query
required: false
description: Filter by offline status. When supplied, must be true or false.
schema:
type: boolean
responses:
'200':
description: securities listed
content:
application/json:
schema:
"$ref": "#/components/schemas/SecurityCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: invalid filter
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/securities/{id}":
parameters:
- name: id
in: path
required: true
description: Security ID
schema:
type: string
format: uuid
get:
summary: Retrieve a security referenced by family investment data
tags:
- Securities
security:
- apiKeyAuth: []
responses:
'200':
description: security retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/Security"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: security not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/security_prices":
get:
summary: List security price history referenced by family investment data
tags:
- Security Prices
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: security_id
in: query
required: false
description: Filter by security ID
schema:
type: string
format: uuid
- name: currency
in: query
required: false
description: Filter by currency code
schema:
type: string
- name: start_date
in: query
required: false
description: Filter prices from this date
schema:
type: string
format: date
- name: end_date
in: query
required: false
description: Filter prices until this date
schema:
type: string
format: date
- name: provisional
in: query
required: false
description: Filter by provisional price status. When supplied, must be true
or false.
schema:
type: boolean
responses:
'200':
description: security prices listed
content:
application/json:
schema:
"$ref": "#/components/schemas/SecurityPriceCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: invalid filter
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/security_prices/{id}":
parameters:
- name: id
in: path
required: true
description: Security price ID
schema:
type: string
format: uuid
get:
summary: Retrieve a security price referenced by family investment data
tags:
- Security Prices
security:
- apiKeyAuth: []
responses:
'200':
description: security price retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/SecurityPrice"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: security price not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/syncs":
get:
summary: Lists sync history
description: List sanitized sync status history for the authenticated user's
family, accounts, and provider connections.
tags:
- Syncs
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
responses:
'200':
description: syncs listed
content:
application/json:
schema:
"$ref": "#/components/schemas/SyncCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/syncs/latest":
get:
summary: Shows the latest sync
description: 'Return the most recently created sanitized sync status for the
authenticated user''s family, or data: null when no sync exists.'
tags:
- Syncs
security:
- apiKeyAuth: []
responses:
'200':
description: latest sync shown
content:
application/json:
schema:
"$ref": "#/components/schemas/SyncResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/syncs/{id}":
parameters:
- name: id
in: path
format: uuid
required: true
schema:
type: string
get:
summary: Shows a sync
description: Return sanitized status metadata for a single family-scoped sync.
tags:
- Syncs
security:
- apiKeyAuth: []
responses:
'200':
description: sync shown
content:
application/json:
schema:
"$ref": "#/components/schemas/SyncResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/tags":
get:
summary: List tags
tags:
- Tags
security:
- apiKeyAuth: []
responses:
'200':
description: tags listed
content:
application/json:
schema:
"$ref": "#/components/schemas/TagCollection"
post:
summary: Create tag
tags:
- Tags
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: tag created with auto-assigned color
content:
application/json:
schema:
"$ref": "#/components/schemas/TagDetail"
'422':
description: validation error - missing name
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
tag:
type: object
properties:
name:
type: string
description: Tag name (required)
color:
type: string
description: Hex color code (optional, auto-assigned if not
provided)
required:
- name
required:
- tag
required: true
"/api/v1/tags/{id}":
parameters:
- name: id
in: path
required: true
description: Tag ID
schema:
type: string
get:
summary: Retrieve a tag
tags:
- Tags
security:
- apiKeyAuth: []
responses:
'200':
description: tag retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/TagDetail"
'404':
description: tag not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
patch:
summary: Update a tag
tags:
- Tags
security:
- apiKeyAuth: []
parameters: []
responses:
'200':
description: tag updated
content:
application/json:
schema:
"$ref": "#/components/schemas/TagDetail"
'404':
description: tag not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
tag:
type: object
properties:
name:
type: string
color:
type: string
required: true
delete:
summary: Delete a tag
tags:
- Tags
security:
- apiKeyAuth: []
responses:
'204':
description: tag deleted
'404':
description: tag not found
"/api/v1/trades":
get:
summary: List trades
tags:
- Trades
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: account_id
in: query
required: false
description: Filter by account ID
schema:
type: string
- name: account_ids
in: query
required: false
description: Filter by multiple account IDs
schema:
type: array
items:
type: string
- name: start_date
in: query
required: false
description: Filter trades from this date (inclusive)
schema:
type: string
format: date
- name: end_date
in: query
required: false
description: Filter trades until this date (inclusive)
schema:
type: string
format: date
responses:
'200':
description: trades paginated
content:
application/json:
schema:
"$ref": "#/components/schemas/TradeCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: invalid date filter
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
post:
summary: Create trade
tags:
- Trades
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: interest created
content:
application/json:
schema:
"$ref": "#/components/schemas/TransactionResponse"
'403':
description: forbidden - api key missing read_write scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'401':
description: unauthorized - missing api key
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: deposit without amount returns error
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: account not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
trade:
type: object
properties:
account_id:
type: string
format: uuid
description: Account ID (required)
date:
type: string
format: date
description: Trade date (required)
qty:
type: number
description: Quantity (required for buy/sell)
price:
type: number
description: Price (required for buy/sell)
amount:
type: number
description: Amount (required for dividend, deposit, withdrawal,
interest)
type:
type: string
enum:
- buy
- sell
- dividend
- deposit
- withdrawal
- interest
description: Trade type (required)
security_id:
type: string
format: uuid
description: Security ID (one of security_id, ticker, manual_ticker
required)
ticker:
type: string
description: Ticker symbol
manual_ticker:
type: string
description: Manual ticker for offline securities
currency:
type: string
description: Currency (defaults to account currency)
investment_activity_label:
type: string
description: Activity label (e.g. Buy, Sell)
category_id:
type: string
format: uuid
description: Category ID
transfer_account_id:
type: string
format: uuid
description: Destination/source account ID for linked transfers
required:
- account_id
- date
- type
required:
- trade
required: true
"/api/v1/trades/{id}":
parameters:
- name: id
in: path
required: true
description: Trade ID
schema:
type: string
get:
summary: Retrieve trade
tags:
- Trades
security:
- apiKeyAuth: []
responses:
'200':
description: trade retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/Trade"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: trade not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
patch:
summary: Update trade
tags:
- Trades
security:
- apiKeyAuth: []
parameters: []
responses:
'200':
description: trade updated
content:
application/json:
schema:
"$ref": "#/components/schemas/Trade"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden - api key missing read_write scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: trade not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
trade:
type: object
properties:
date:
type: string
format: date
qty:
type: number
price:
type: number
type:
type: string
enum:
- buy
- sell
- dividend
- deposit
- withdrawal
- interest
nature:
type: string
enum:
- inflow
- outflow
name:
type: string
notes:
type: string
currency:
type: string
investment_activity_label:
type: string
category_id:
type: string
format: uuid
required: true
delete:
summary: Delete trade
tags:
- Trades
security:
- apiKeyAuth: []
responses:
'200':
description: trade deleted
content:
application/json:
schema:
"$ref": "#/components/schemas/DeleteResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden - api key missing read_write scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: trade not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/transactions":
get:
summary: List transactions
tags:
- Transactions
security:
- apiKeyAuth: []
description: Returns global ledger history for accessible accounts, including
disabled accounts but excluding accounts pending deletion.
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: account_id
in: query
required: false
description: Filter by account ID
schema:
type: string
- name: category_id
in: query
required: false
description: Filter by category ID
schema:
type: string
- name: merchant_id
in: query
required: false
description: Filter by merchant ID
schema:
type: string
- name: start_date
in: query
required: false
description: Filter transactions from this date
schema:
type: string
format: date
- name: end_date
in: query
required: false
description: Filter transactions until this date
schema:
type: string
format: date
- name: min_amount
in: query
required: false
description: Filter by minimum amount
schema:
type: number
- name: max_amount
in: query
required: false
description: Filter by maximum amount
schema:
type: number
- name: type
in: query
required: false
description: Filter by transaction type
schema:
type: string
enum:
- income
- expense
- name: search
in: query
required: false
description: Search by name, notes, or merchant name
schema:
type: string
- name: account_ids
in: query
required: false
description: Filter by multiple account IDs
schema:
type: array
items:
type: string
- name: category_ids
in: query
required: false
description: Filter by multiple category IDs
schema:
type: array
items:
type: string
- name: merchant_ids
in: query
required: false
description: Filter by multiple merchant IDs
schema:
type: array
items:
type: string
- name: tag_ids
in: query
required: false
description: Filter by tag IDs
schema:
type: array
items:
type: string
responses:
'200':
description: transactions filtered by date range
content:
application/json:
schema:
"$ref": "#/components/schemas/TransactionCollection"
post:
summary: Create transaction
tags:
- Transactions
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: transaction created
content:
application/json:
schema:
"$ref": "#/components/schemas/Transaction"
'200':
description: transaction already exists for external idempotency key
content:
application/json:
schema:
"$ref": "#/components/schemas/Transaction"
'422':
description: validation error - missing required fields
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
transaction:
type: object
properties:
account_id:
type: string
format: uuid
description: Account ID (required)
date:
type: string
format: date
description: Transaction date
amount:
type: number
description: Transaction amount
name:
type: string
description: Transaction name/description
description:
type: string
description: Alternative to name field
notes:
type: string
description: Additional notes
currency:
type: string
description: Currency code (defaults to family currency)
category_id:
type: string
format: uuid
description: Category ID
merchant_id:
type: string
format: uuid
description: Merchant ID
nature:
type: string
enum:
- income
- expense
- inflow
- outflow
description: Transaction nature (determines sign)
external_id:
type: string
description: Optional external idempotency key scoped to account
and source
source:
type: string
description: Optional source namespace for external_id. Requires
external_id and defaults to api when external_id is provided
user_modified:
type: boolean
description: Whether provider syncs should preserve user-supplied
transaction changes
tag_ids:
type: array
items:
type: string
format: uuid
description: Array of tag IDs
required:
- account_id
- date
- amount
- name
required:
- transaction
required: true
"/api/v1/transactions/{id}":
parameters:
- name: id
in: path
schema:
type: string
format: uuid
required: true
description: Transaction ID
get:
summary: Retrieve a transaction
tags:
- Transactions
security:
- apiKeyAuth: []
responses:
'200':
description: transaction retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/Transaction"
'404':
description: transaction not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
patch:
summary: Update a transaction
tags:
- Transactions
security:
- apiKeyAuth: []
parameters: []
responses:
'200':
description: transaction updated
content:
application/json:
schema:
"$ref": "#/components/schemas/Transaction"
'404':
description: transaction not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
transaction:
type: object
properties:
date:
type: string
format: date
amount:
type: number
name:
type: string
description:
type: string
description: Alternative to name field
notes:
type: string
currency:
type: string
description: Currency code
category_id:
type: string
format: uuid
merchant_id:
type: string
format: uuid
nature:
type: string
enum:
- income
- expense
- inflow
- outflow
tag_ids:
type: array
items:
type: string
format: uuid
description: Array of tag IDs to assign. Omit to preserve existing
tags; use [] to clear all tags.
required: true
delete:
summary: Delete a transaction
tags:
- Transactions
security:
- apiKeyAuth: []
responses:
'200':
description: transaction deleted
content:
application/json:
schema:
"$ref": "#/components/schemas/DeleteResponse"
'404':
description: transaction not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/transfers":
get:
summary: List transfers
tags:
- Transfers
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: status
in: query
required: false
schema:
type: string
enum:
- pending
- confirmed
description: Filter by transfer status
- name: account_id
in: query
required: false
schema:
type: string
format: uuid
description: Filter transfers involving this account
- name: start_date
in: query
required: false
schema:
type: string
format: date
description: Filter transfers from this date
- name: end_date
in: query
required: false
schema:
type: string
format: date
description: Filter transfers until this date
responses:
'200':
description: transfers listed
content:
application/json:
schema:
"$ref": "#/components/schemas/TransferDecisionCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: invalid filter
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/transfers/{id}":
parameters:
- name: id
in: path
required: true
description: Transfer ID
schema:
type: string
get:
summary: Retrieve a transfer
tags:
- Transfers
security:
- apiKeyAuth: []
responses:
'200':
description: transfer retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/TransferDecision"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: transfer not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/users/reset":
delete:
summary: Reset account
tags:
- Users
description: Resets all financial data (accounts, categories, merchants, tags,
etc.) for the current user's family while keeping the user account intact.
The reset runs asynchronously in the background. The returned job_id is informational
only; reset status is family-scoped, not job-scoped. Requires admin role.
security:
- apiKeyAuth: []
responses:
'200':
description: account reset initiated
content:
application/json:
schema:
"$ref": "#/components/schemas/ResetInitiatedResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden - requires read_write scope and admin role
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'500':
description: reset enqueue failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/users/reset/status":
get:
summary: Retrieve reset status
tags:
- Users
description: Returns counts of family-owned data targeted by account reset.
Use this after DELETE /api/v1/users/reset to decide whether reset materialization
has completed. Completion is a counts-based family snapshot and may change
if new data is created after reset.
security:
- apiKeyAuth: []
responses:
'200':
description: reset status returned
content:
application/json:
schema:
"$ref": "#/components/schemas/ResetStatusResponse"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: forbidden - requires admin role
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/users/me":
delete:
summary: Delete account
tags:
- Users
description: Permanently deactivates the current user account and all associated
data. This action cannot be undone.
security:
- apiKeyAuth: []
responses:
'200':
description: account deleted
content:
application/json:
schema:
"$ref": "#/components/schemas/SuccessMessage"
'401':
description: unauthorized
'403':
description: insufficient scope
'422':
description: deactivation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/valuations":
get:
summary: List valuations
tags:
- Valuations
security:
- apiKeyAuth: []
parameters:
- name: page
in: query
required: false
description: 'Page number (default: 1)'
schema:
type: integer
- name: per_page
in: query
required: false
description: 'Items per page (default: 25, max: 100)'
schema:
type: integer
- name: account_id
in: query
required: false
description: Filter by account ID
schema:
type: string
format: uuid
- name: start_date
in: query
required: false
description: Filter valuations from this date
schema:
type: string
format: date
- name: end_date
in: query
required: false
description: Filter valuations until this date
schema:
type: string
format: date
responses:
'200':
description: valuations listed
content:
application/json:
schema:
"$ref": "#/components/schemas/ValuationCollection"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: invalid account filter
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
post:
summary: Create valuation
tags:
- Valuations
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: valuation created
content:
application/json:
schema:
"$ref": "#/components/schemas/Valuation"
'200':
description: existing valuation upserted
content:
application/json:
schema:
"$ref": "#/components/schemas/Valuation"
'422':
description: validation error - missing date
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: account not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
valuation:
type: object
properties:
account_id:
type: string
format: uuid
description: Account ID (required)
amount:
type: number
description: Valuation amount (required)
date:
type: string
format: date
description: Valuation date (required)
notes:
type: string
description: Additional notes
upsert:
type: boolean
description: Nested alternative to the top-level response-status
flag. Top-level upsert takes precedence when both are provided.
required:
- account_id
- amount
- date
upsert:
type: boolean
description: Response-status signal only. When true and a same-account
same-date valuation exists before the request, the endpoint returns
200 OK instead of 201 Created. The underlying reconciliation write
path is unchanged; this flag does not add duplicate-prevention
or safe-retry guarantees beyond existing same-date reconciliation
behavior.
required:
- valuation
required: true
"/api/v1/valuations/{id}":
parameters:
- name: id
in: path
required: true
description: Valuation ID (entry ID)
schema:
type: string
get:
summary: Retrieve a valuation
tags:
- Valuations
security:
- apiKeyAuth: []
responses:
'200':
description: valuation retrieved
content:
application/json:
schema:
"$ref": "#/components/schemas/Valuation"
'404':
description: valuation not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
patch:
summary: Update a valuation
tags:
- Valuations
security:
- apiKeyAuth: []
parameters: []
responses:
'200':
description: valuation updated with amount and date
content:
application/json:
schema:
"$ref": "#/components/schemas/Valuation"
'422':
description: validation error - only one of amount/date provided
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'404':
description: valuation not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
valuation:
type: object
properties:
amount:
type: number
description: New valuation amount (must provide with date)
date:
type: string
format: date
description: New valuation date (must provide with amount)
notes:
type: string
description: Additional notes
required: true