Files
sure/docs/api/openapi.yaml
Juan José Mata cade5b22f7 Document admin-only reset auth in OpenAPI docs (#1198)
* Document admin-only reset auth in OpenAPI docs

The DELETE /api/v1/users/reset endpoint now requires admin role
(ensure_admin). Update the rswag spec to:
- Set default user role to admin so the 200 test passes
- Add a 403 response case for non-admin users with read_write scope
- Clarify the description notes admin requirement
- Add SuccessMessage schema and users paths to openapi.yaml

https://claude.ai/code/session_01Tj8ToLRmVg5HLmHwq9KKDY

* Consolidate duplicate 403 responses for reset endpoint

OpenAPI keys responses by status code, so two 403 blocks caused the
first (insufficient scope) to be silently overwritten by the second
(non-admin). Merge into a single 403 whose description covers both
causes: requires read_write scope and admin role. The test exercises
the read-only key path which hits 403 via scope check.

https://claude.ai/code/session_01Tj8ToLRmVg5HLmHwq9KKDY

* Em-dash out of messages.

* Fix tests

* Fix tests

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-15 00:23:38 +01:00

2791 lines
72 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
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.)
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"
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
AccountDetail:
type: object
required:
- id
- name
- balance
- currency
- classification
- account_type
properties:
id:
type: string
format: uuid
name:
type: string
balance:
type: string
currency:
type: string
classification:
type: string
account_type:
type: string
AccountCollection:
type: object
required:
- accounts
- pagination
properties:
accounts:
type: array
items:
"$ref": "#/components/schemas/AccountDetail"
pagination:
"$ref": "#/components/schemas/Pagination"
Category:
type: object
required:
- id
- name
- classification
- color
- icon
properties:
id:
type: string
format: uuid
name:
type: string
classification:
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
- classification
- color
- icon
- subcategories_count
- created_at
- updated_at
properties:
id:
type: string
format: uuid
name:
type: string
classification:
type: string
enum:
- income
- expense
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"
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
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"
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
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
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"
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
DeleteResponse:
type: object
required:
- message
properties:
message:
type: string
SuccessMessage:
type: object
required:
- message
properties:
message:
type: string
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
properties:
rows_count:
type: integer
minimum: 0
valid_rows_count:
type: integer
minimum: 0
nullable: true
ImportSummary:
type: object
required:
- id
- type
- status
- created_at
- updated_at
properties:
id:
type: string
format: uuid
type:
type: string
enum:
- TransactionImport
- TradeImport
- AccountImport
- MintImport
- CategoryImport
- RuleImport
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
ImportDetail:
type: object
required:
- id
- type
- status
- created_at
- updated_at
properties:
id:
type: string
format: uuid
type:
type: string
enum:
- TransactionImport
- TradeImport
- AccountImport
- MintImport
- CategoryImport
- RuleImport
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
configuration:
"$ref": "#/components/schemas/ImportConfiguration"
stats:
"$ref": "#/components/schemas/ImportStats"
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"
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"
paths:
"/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"
"/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/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
responses:
'200':
description: accounts paginated
content:
application/json:
schema:
"$ref": "#/components/schemas/AccountCollection"
"/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/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"
"/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: classification
in: query
required: false
description: Filter by classification (income or expense)
schema:
type: string
enum:
- income
- expense
- 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"
"/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/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/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
- CategoryImport
- RuleImport
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.
tags:
- Imports
security:
- apiKeyAuth: []
parameters: []
responses:
'201':
description: import created
content:
application/json:
schema:
"$ref": "#/components/schemas/ImportResponse"
'422':
description: validation error - file too large
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
requestBody:
content:
application/json:
schema:
type: object
properties:
raw_file_content:
type: string
description: The raw CSV content as a string
type:
type: string
enum:
- TransactionImport
- TradeImport
- AccountImport
- MintImport
- CategoryImport
- RuleImport
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: Header name for the date column
amount_col_label:
type: string
description: Header name for the amount column
name_col_label:
type: string
description: Header name for the transaction name column
category_col_label:
type: string
description: Header name for the category column
tags_col_label:
type: string
description: Header name for the tags column
notes_col_label:
type: string
description: Header name for the notes column
date_format:
type: string
description: 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: Number format for parsing amounts
signage_convention:
type: string
enum:
- inflows_positive
- inflows_negative
description: How to interpret positive/negative amounts
col_sep:
type: string
enum:
- ","
- ";"
description: Column separator
required: true
"/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 and row statistics.
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/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: trade created
content:
application/json:
schema:
"$ref": "#/components/schemas/Trade"
'422':
description: validation error - missing security identifier
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)
price:
type: number
description: Price (required)
type:
type: string
enum:
- buy
- sell
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
required:
- account_id
- date
- qty
- price
- 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"
'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
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"
'404':
description: trade not found
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
"/api/v1/transactions":
get:
summary: List transactions
tags:
- 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: 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"
'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)
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
required: true
description: Transaction ID
schema:
type: string
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/valuations":
post:
summary: Create valuation
tags:
- Valuations
security:
- apiKeyAuth: []
parameters:
- name: Authorization
in: header
required: true
schema:
type: string
description: Bearer token with write scope
responses:
'201':
description: valuation created
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
required:
- account_id
- amount
- date
required:
- valuation
required: true
"/api/v1/valuations/{id}":
parameters:
- name: Authorization
in: header
required: true
schema:
type: string
description: Bearer token
- 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
"/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. Requires admin role.
security:
- apiKeyAuth: []
responses:
'200':
description: account reset initiated
content:
application/json:
schema:
"$ref": "#/components/schemas/SuccessMessage"
'401':
description: unauthorized
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: "forbidden \u2014 requires read_write scope and 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
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'403':
description: insufficient scope
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"
'422':
description: deactivation failed
content:
application/json:
schema:
"$ref": "#/components/schemas/ErrorResponse"