mirror of
https://github.com/we-promise/sure.git
synced 2026-09-06 15:14:19 +00:00
* feat(budgets): carry a category's unspent budget into the next month
A budget category resets to zero every month, so anything non-monthly
(annual insurance, a holiday fund, car servicing) has no place to
accumulate. Two columns on budget_categories turn a category into a real
envelope: `rollover_enabled`, opt-in per category and off by default, and
`rolled_over_amount`, the surplus carried in from the previous month.
rolled_over(n) = rollover_enabled
? max(0, budgeted(n-1) + rolled_over(n-1) - actual(n-1))
: 0
v1 floors at zero: only a surplus carries, never an overspend.
The amount is materialized, not derived. March depends on February which
depends on January, so computing it on read would walk the whole chain on
every budget render. Budget::RolloverCalculator recomputes it in a single
forward pass and writes once via upsert_all, from Budget.find_or_bootstrap
and from BudgetCategoriesController#update -- allocations and the toggle
being the only inputs. No Transaction hook: a past month's actuals can
change after the fact, and the page load is a fine moment to catch up.
Scope kept deliberately narrow. `Budget#budgeted_spending`,
`#allocated_spending` and `#available_to_allocate` are untouched -- the top
of the budget page still answers "I planned to spend X, I've allocated Y".
The carry is per-envelope information, surfaced as `Budget#total_rolled_over`
and never folded into those totals.
What the carry does change is consumption: `available_to_spend`,
`percent_of_budget_spent` and `budgeted?` all count it, or a category funded
entirely by rollover would read as unbudgeted and get an alert pill while it
still had money left. `display_budgeted_spending` stays the month's
allocation alone -- the card shows the two figures side by side.
Details worth knowing:
- A parent's carry is net of its ring-fenced subcategories'. A parent's
allocation already contains theirs and its actuals already contain their
spending; those subcategories carry their own surplus, so counting the
parent's raw leftover would roll the same money over twice.
- Chains never mix: household with household, a member's personal budgets
with their own. A missing month is a gap the carry crosses, not a month
budgeted at zero.
- The carry stops at a currency change. sync_budget_categories stamps
categories with family.currency at sync time while a budget freezes its
own at creation, so the guard is on budget_category.currency -- the unit
the amount is actually denominated in.
- upsert_all writes with `update_only`, so a concurrent request that moves
an allocation between our read and our write doesn't get it clobbered by
the stale value we loaded.
- copy_from! copies the toggle, never the amount.
Cost for families that never turn it on: one EXISTS query per budget page
load, measured, including on the reports page which also bootstraps a
budget. With rollover on, the walk starts at the first month that uses it
rather than at the two-year history bound.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyD26wsXjsYfpTgAGL1n1Z
* fix(budgets): pin the household rollover chain to a viewer-independent scope
Addresses review feedback on #3143.
The household budget (user_id NULL) has no owner to scope actuals by, and
`IncomeStatement` falls back to `Current.user` when nobody says otherwise.
The calculator therefore computed one shared `rolled_over_amount` through
whichever member happened to load the page, and each viewer overwrote the
other's number -- last one wins, and a member could infer spending in
accounts they cannot see. `Budget#income_statement_accounts` can now be
overridden, and the calculator pins the household chain to the whole
family so the shared row holds one number. Personal chains are untouched:
they already scope to their owner's accounts and were always deterministic.
`copy_from!` runs after `find_or_bootstrap` has already recomputed the
chain, so copying `rollover_enabled` left the target sitting on a zero carry
until the next page load. It now recomputes before its transaction commits.
The toggle tooltip described the wrong direction. `incoming_carry` checks
the flag of the month being computed, so the toggle governs what that month
*receives* from the previous one, not what it sends forward. Reworded in
English and French.
The concurrency regression test now drives its concurrent write through
`Budget#budget_category_actual_spending`, a public seam, instead of stubbing
a private method of the calculator from another class's test suite.
Each guard was confirmed load-bearing by reverting it and watching its test
fail. bin/rails test: 6939 runs, 0 failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyD26wsXjsYfpTgAGL1n1Z
* fix(budgets): let the rollover choice stand instead of resetting each month
`rollover_enabled` lives on budget_categories, one row per (budget,
category), so a month created by `find_or_bootstrap` was born with the flag
off. Switching rollover on for Vacations in January and simply opening
February dropped January's surplus on the floor -- the user had to re-arm
the toggle every month, or go through "copy from previous budget". The
feature's headline case, a category funded 50/month accumulating over a
year, did not work as shipped.
New rows now inherit the flag from the last initialized budget of the same
owner, the same chain the carry itself walks. Turning the toggle off on a
given month still overrides it from there on, so the per-month escape hatch
survives.
The flag stays on budget_categories rather than moving to Category, which is
where comparable products (Monarch, Copilot, Lunch Money) put it. Categories
here are family-wide while budgets are per owner, so a category-level flag
would force one member's rollover choice onto everyone's personal budget and
onto the household budget. budget_categories is the only table carrying both
the category and the owner. A regression test covers that isolation.
Naming follows the same products: the toggle reads "Rollover", the noun, not
"Roll over", the verb -- which also matches `rollover_enabled` and the
calculator. Both tooltips now describe the property rather than a direction
("keep this category's unspent money from one month to the next"). The
previous wording named the direction the flag actually gates, incoming,
which is accurate but the opposite of the mental model every comparable
product installs; describing the property is true under either reading. The
French card string switched to "+%{amount} de report" so it no longer has to
agree in number with a currency noun it cannot see.
bin/rails test: 6942 runs, 0 failures. The inheritance was confirmed
load-bearing by removing it and watching its tests fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyD26wsXjsYfpTgAGL1n1Z
* fix(budgets): make a rollover opt-out stop the money in both directions
`incoming_carry` gates what a month receives, but `leftover_for` computed
what it sends regardless of the toggle. So switching rollover off for one
month and back on the next handed the opted-out month's whole allocation to
the month after: the surplus the user meant to forfeit reappeared a month
later. Reproduced at 100, where 0 was expected.
The outgoing carry is now gated on the same flag, which also skips the
actuals lookup for opted-out rows. "Off" now means this envelope does not
roll over, in either direction -- the reading the standing toggle and the
tooltip both promise.
Found by CodeRabbit on #3143. It only became wrong with the standing-choice
inheritance in 2b1cff5a: while the flag was per-month, "off" plausibly meant
"do not accept", and the previous month's surplus reaching a re-armed month
was defensible. Once the flag reads as a property of the envelope, it isn't.
bin/rails test: 6943 runs, 0 failures. Confirmed load-bearing by removing
the guard and watching the new three-month test fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyD26wsXjsYfpTgAGL1n1Z
* fix(budgets): serialize rollover recomputes for a chain with an advisory lock
`recompute!` reads the whole chain into memory, walks it, then upserts.
Nothing made that atomic: two overlapping recomputes for the same
(family, owner) chain could both load it, and the one that started first
could land its now-stale `rolled_over_amount` on top of the other's.
`update_only` keeps an upsert off allocations, but the carry is the very
column this writes, so nothing protected it. The wrong value survived until
the next page load recomputed it.
The read-then-write now runs inside a transaction holding
`pg_advisory_xact_lock` keyed on the chain, and the walk was extracted so
the guard is legible. The cheap `first_relevant_budget_date` check still
runs first and unlocked, so families that never enabled rollover pay one
query and never contend; the date is re-read under the lock because the
chain may have moved while waiting. The key names the (family, owner) pair,
so a household recompute and a member's personal recompute don't queue
behind each other.
This reverses the spec's "no advisory lock" guidance, at the request of an
upstream maintainer reviewing #3143.
On the test: under transactional fixtures a second connection cannot see the
data, so a true two-connection interleaving test isn't practical here. The
regression test asserts what is observable in-process -- the lock is taken,
it is taken before the write, and two chains produce different keys.
Removing `lock_chain!` makes it fail.
bin/rails test: 6944 runs, 0 failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyD26wsXjsYfpTgAGL1n1Z
* feat(api): expose the rollover toggle and carried amount on budget categories
`available_to_spend` started counting the carry in this branch, so an API
client could receive a category budgeted at 500 with 700 available and
nothing in the payload to account for the difference. The two fields that
explain it are now serialized.
`rollover_enabled` ships with the stored fields, so the summary rendered by
the index action carries it. `rolled_over_amount` sits with the derived
amounts behind `include_derived_amounts`, next to the `available_to_spend`
it accounts for -- the index deliberately omits both, unchanged.
Schemas updated in spec/swagger_helper.rb (BudgetCategory and
BudgetCategorySummary), docs regenerated with rswag, and behavioural
coverage added to the Minitest controller test: the show action returns the
toggle and the carry, and the index returns the toggle without the derived
amount.
Note on docs/api/openapi.yaml: 64 of the 72 added lines are not from this
change. The committed file had drifted from what rswag generates -- specs
for the merchant CSV import and transfer source fees had been added without
regenerating -- and the mandated `rake rswag:specs:swaggerize` picks them up.
Verified by regenerating on a clean tree, where those 64 lines appear on
their own. Hand-trimming them back out would leave the generated file not
matching its generator, so they are included; happy to split them into their
own commit if a maintainer prefers.
bin/rails test: 6945 runs, 0 failures.
ruby test/support/verify_api_endpoint_consistency.rb: OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CyD26wsXjsYfpTgAGL1n1Z
* feat(budgets): move money between envelopes in one gesture
Overspending one category and covering it from another meant editing two
allocations by hand, with no atomicity: the budget could sit
over-allocated between the two saves, and a failure left it there.
`BudgetCategory.move_allocation!` does both sides in one transaction.
Deliberately no new table — v1 stores the resulting allocations and keeps
no history of the move itself.
Refused, each with its own localized message: an amount at or below zero,
more than the source has, two categories from different budgets, a
category and itself, "Uncategorized" (synthesized on read, it has no row),
and — the one that is not obvious — a category and its own direct parent
or child. `sync_parent_budgeted_spending!` rebuilds a parent from the sum
of its children plus its reserve, so money moved across that boundary
would be re-derived away and the total would not be conserved.
Lock order is the delicate part. `update_budgeted_spending!` locks its own
row and, for a subcategory, its parent, so two simultaneous moves in
opposite directions could each hold what the other needs. Every row the
operation will touch — both ends and their parents — is locked up front by
ascending id.
The rollover chain is recomputed by the caller AFTER the move commits,
never inside it. `Budget::RolloverCalculator` takes a transaction-scoped
advisory lock, and taking it while these row locks are held would invert
the order `#update` already established: one request holding rows and
waiting for the advisory lock, another holding the advisory lock and
waiting for those rows. A model test pins that `move_allocation!` never
recomputes on its own.
The recompute is not optional. A move is neutral for
`Budget#allocated_spending`, but not for the carry: `leftover_for` is
budgeted + rolled_over − actual, so moving money changes what both
envelopes hand to the next month.
UI is one native `<dialog>` shared by the page rather than one per row,
opened from a discreet button on each envelope that has something to give.
The Stimulus controller has 6 targets and disables the options the server
would refuse anyway, so an impossible move is never offered.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJ1npaGEHr6t2HW1rYZdt4
* fix(budgets): use the design-system dialog, and stop a parent lending its children's money
Addresses review feedback on #3164.
**The move dialog was hand-rolled.** `DS::Dialog` already exists and already
carries focus trapping, Escape, click-outside, focus restore and the
design-system chrome; rewriting those by hand is how they end up subtly wrong,
and the guidelines say to reach for the primitive first. It keeps the
one-dialog-for-the-page shape — the list holds dozens of rows and a per-row
dialog would be dozens of copies of the same markup — via `auto_open: false`
and `disable_frame: true`.
**It also stayed open after a successful move,** still showing the previous
source and amount. It now closes on `turbo:submit-end`, and only when Turbo
reports success: closing on submit alone would hide the reason a move was
refused.
**Submit was enabled with nowhere to send.** A lone envelope, or one whose only
peers are its own parent and children, offered a button whose only outcome was
a server error. The form now says so and disables itself.
**A parent could send away its children's money.** `budgeted_spending` on a
parent already contains its individually funded subcategories' allocations, so
comparing against the gross figure let a move spend what a child had
ring-fenced. The parent dropped below the sum of its children, and the next
edit to any child rebuilt it — the money appeared to teleport back. The
movable amount for a parent is now its own reserve.
`test "moving the whole allocation is allowed, moving one cent more is not"`
moved a parent's gross amount and passed: it encoded that bug. It now uses a
leaf as its source, where "the whole allocation" is the whole of it, and the
parent boundary gets its own pair of tests.
**A negative carry could be written.** The calculator floors it at zero but
writes through `upsert_all`, and a negative `rolled_over_amount` would quietly
subtract from `available_to_spend`. Now a CHECK constraint, verified by
replaying the migration on a throwaway database.
bin/rails test: 6967 runs, 28020 assertions, 0 failures. RuboCop, erb_lint,
Brakeman and biome clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye
* fix(budgets): carry the rollover choice into months already open, and mend the API schema
Addresses the remaining review feedback on #3164.
**Enabling rollover skipped months that already existed.** Inheritance runs
when `sync_budget_categories` creates a missing row, so it only ever reaches
months that do not exist yet. A user who opened March, then went back to
January and switched rollover on, left March sitting at `false` — created
before the choice was made, so it had nothing to inherit — and the chain died
there.
The toggle is a standing choice about the envelope, which is what
`inherited_rollover_flags` already says: "turning it off on a given month still
overrides it from there on." Applying the choice forward closes the hole
without a tri-state column. Later months take the most recent decision, which
is the one the user just made; earlier months keep theirs.
**`rollover_enabled` was emitted but not required.** The shared partial always
sends it in both list and detail responses. Added to the `required` list of
`BudgetCategorySummary` and `BudgetCategory` in `spec/swagger_helper.rb`, then
regenerated.
**`type: file` is not valid OpenAPI 3.0.3.** A Swagger 2.0 leftover in the
merchants import spec, which generated clients that send nothing the controller
can read. It surfaced now because this branch is the first to regenerate
`openapi.yaml` since it was written — `origin/main` has no occurrence of it.
Spelled as a string with `format: binary` instead.
Regeneration produced a four-line diff, so the checked-in document was already
in sync otherwise.
bin/rails test: 6969 runs, 28023 assertions, 0 failures. RuboCop and Brakeman
clean; 324 rswag examples pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye
* fix(budgets): stop the API serving a carry the web pages would have refreshed
Addresses the remaining P1 on #3164, and its duplicate on #3143.
The objection was that nothing recomputes when a sync, an edit or a
recategorisation changes spending in an earlier month. On the web that is by
design and measured: every surface showing the carry goes through
`Budget.find_or_bootstrap`, so it recomputes on the way in, and the alternative
— recomputing on every transaction write — buys nothing a page load does not
already give.
The API is the case that argument does not cover, and the review was right
about it. `Api::V1::BudgetCategoriesController` reads `rolled_over_amount`
straight off the column, so it was the one surface that could serve a stale
carry indefinitely, until somebody happened to open the budget page.
It now recomputes the chains it is about to read. A read that writes is a
smell, but it is the same bargain the budget page already makes, applied to the
surface that was missed: the walk is per family, and the calculator's leading
EXISTS makes it a single query that writes nothing for a family that never
turned rollover on.
Also from review: the move dialog's amount field allowed `min: 0` while
`move_allocation!` rejects zero as non-positive. Browser validation now matches
the server contract, at the currency step.
bin/rails test: 6970 runs, 28025 assertions, 0 failures. Confirmed load-bearing
by removing the callback and watching the new API test fail. RuboCop, erb_lint
and Brakeman clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye
* Fix budget rollover schema delta
* Remove duplicate rollover test class
---------
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: sure-admin <sure-admin@splashblot.com>
8153 lines
219 KiB
YAML
8153 lines
219 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
|
|
rolled_over_amount:
|
|
type: string
|
|
rolled_over_amount_cents:
|
|
type: integer
|
|
actual_spending:
|
|
type: string
|
|
actual_spending_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:
|
|
- active
|
|
- inactive
|
|
occurrence_count:
|
|
type: integer
|
|
minimum: 0
|
|
name:
|
|
type: string
|
|
nullable: true
|
|
manual:
|
|
type: boolean
|
|
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
|
|
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.
|
|
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/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/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/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/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/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
|
|
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
|
|
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
|