Accounting
Per-resource accounting API: entities, assets, incomes, expenses, liabilities and transfers, plus computed summary (wealth and balance sheet), ledger, cash-flows, liability schedules and projection.
List entities
GET /api/v1/accounting/entities
Bootstraps the personal entity and its default wallet on first call.
Responses
200— Entities. Entity[]401— Missing or invalid credential (session token or API key; only when auth is configured). Error
curl "$API_URL/api/v1/accounting/entities" \
-H "Authorization: Bearer $TOKEN"Create a business entity
POST /api/v1/accounting/entities
Business only. Also creates its default cash asset, “<name> Main Wallet”, at ฿0.
Request body (application/json)
Type: object
| Field | Type | Required | Description |
|---|---|---|---|
id | string | no | Optional client-generated UUID used as the record id. Repeating a create with the same id must not add a second record: the MCP create tools return the stored record with deduplicated: true, and the REST routes answer 409 DUPLICATE. |
type | "personal" | "business" | no | Only business can be created; the personal entity is created on first use. |
asOf | string | no | Client’s current month or day. For cycle: "one_time", a day-precision value such as 2026-10-26 is the date of the receipt (it sets dayOfMonth); a month-only value lands on the 1st. Otherwise the default month for startMonth. |
name | string | yes | |
currency | string | no | |
targetSafetyBuffer | number | no | |
active | boolean | no | |
notes | string | no | |
imageUrl | string | no |
Responses
201— Entity created. Entity400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X POST "$API_URL/api/v1/accounting/entities" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'Get an entity
GET /api/v1/accounting/entities/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
200— The entity. Entity401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error
curl "$API_URL/api/v1/accounting/entities/:id" \
-H "Authorization: Bearer $TOKEN"Update an entity
PATCH /api/v1/accounting/entities/{id}
Partial update. The entity type cannot change.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Request body (application/json)
Type: object
| Field | Type | Required | Description |
|---|---|---|---|
version | integer | no | Stored version the caller last read; a mismatch returns 409. |
type | "personal" | "business" | no | Must match the stored type; entity type cannot change. |
name | string | no | |
currency | string | null | no | |
targetSafetyBuffer | number | null | no | |
active | boolean | null | no | |
notes | string | null | no | |
imageUrl | string | null | no |
Responses
200— Entity updated. Entity400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X PATCH "$API_URL/api/v1/accounting/entities/:id" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'Delete a business entity
DELETE /api/v1/accounting/entities/{id}
The personal entity cannot be deleted. Deleting cascades to all of its records.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
200— Entity deleted. Ok400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X DELETE "$API_URL/api/v1/accounting/entities/:id" \
-H "Authorization: Bearer $TOKEN"List assets
GET /api/v1/accounting/assets
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entityId | query | string | no | |
kind | query | "cash" | "fixed" | "investment" | no |
Responses
200— Assets. Asset[]400— Validation Errorobject401— Missing or invalid credential (session token or API key; only when auth is configured). Error
curl "$API_URL/api/v1/accounting/assets" \
-H "Authorization: Bearer $TOKEN"Create an asset
POST /api/v1/accounting/assets
Body discriminated by kind. Fixed and investment assets accept paidFrom (creates the purchase transfer in the same write); fixed assets accept financing (creates a loan liability, paid at its level payment unless amount is given). A cash asset with isDefault clears the entity’s other defaults; an entity’s first cash asset is always the default.
Request body (application/json)
Type: object | object | object
Responses
201— Asset created. Asset400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X POST "$API_URL/api/v1/accounting/assets" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'Get an asset
GET /api/v1/accounting/assets/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
200— The asset. Asset401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error
curl "$API_URL/api/v1/accounting/assets/:id" \
-H "Authorization: Bearer $TOKEN"Update an asset
PATCH /api/v1/accounting/assets/{id}
Partial update. kind and entityId cannot change.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Request body (application/json)
Type: object
| Field | Type | Required | Description |
|---|---|---|---|
version | integer | no | Stored version the caller last read; a mismatch returns 409. |
name | string | no | |
category | string | no | |
active | boolean | null | no | |
notes | string | null | no | |
imageUrl | string | null | no | |
details | map of any | null | no | |
balance | number | null | no | |
balanceAsOf | string | null | no | |
currency | string | null | no | |
isDefault | boolean | null | no | |
cost | number | no | |
acquiredOn | string | no | |
depreciationMethod | "straight_line" | "none" | null | no | |
usefulLifeMonths | integer | null | no | |
salvageValue | number | null | no | |
instrument | string | no | |
symbol | string | null | no | |
units | number | null | no | |
costBasis | number | null | no | |
marketValue | number | null | no | |
valuedAsOf | string | null | no | |
expectedAnnualReturn | number | null | no |
Responses
200— Asset updated. Asset400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X PATCH "$API_URL/api/v1/accounting/assets/:id" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'Delete an asset
DELETE /api/v1/accounting/assets/{id}
Deleting the default cash asset promotes the oldest remaining cash asset of the entity.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
200— Asset deleted. Ok400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X DELETE "$API_URL/api/v1/accounting/assets/:id" \
-H "Authorization: Bearer $TOKEN"List incomes
GET /api/v1/accounting/incomes
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entityId | query | string | no | |
kind | query | "employment" | "business" | "investment" | "other" | no |
Responses
200— Incomes. Income[]400— Validation Errorobject401— Missing or invalid credential (session token or API key; only when auth is configured). Error
curl "$API_URL/api/v1/accounting/incomes" \
-H "Authorization: Bearer $TOKEN"Create an income
POST /api/v1/accounting/incomes
employment only for kind employment; sourceAssetId only for kind investment.
Request body (application/json)
Type: object | object | object | object
Responses
201— Income created. Income400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X POST "$API_URL/api/v1/accounting/incomes" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'Get an income
GET /api/v1/accounting/incomes/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
200— The income. Income401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error
curl "$API_URL/api/v1/accounting/incomes/:id" \
-H "Authorization: Bearer $TOKEN"Update an income
PATCH /api/v1/accounting/incomes/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Request body (application/json)
Type: object
| Field | Type | Required | Description |
|---|---|---|---|
version | integer | no | Stored version the caller last read; a mismatch returns 409. |
name | string | no | |
category | string | no | |
amount | number | no | |
cycle | "weekly" | "monthly" | "quarterly" | "semi-annual" | "yearly" | "one_time" | null | no | |
dayOfMonth | integer | null | no | |
startMonth | string | null | no | |
endMonth | string | null | no | |
taxCategory | "40_1" | "40_2" | "40_3" | "40_4" | "40_5" | "40_6" | "40_7" | "40_8" | null | no | |
depositAssetId | string | null | no | |
active | boolean | null | no | |
notes | string | null | no | |
imageUrl | string | null | no | |
details | map of any | null | no | |
employment | object | no | |
employment.employer | string | no | |
employment.title | string | no | |
sourceAssetId | string | no |
Responses
200— Income updated. Income400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X PATCH "$API_URL/api/v1/accounting/incomes/:id" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'Delete an income
DELETE /api/v1/accounting/incomes/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
200— Income deleted. Ok400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X DELETE "$API_URL/api/v1/accounting/incomes/:id" \
-H "Authorization: Bearer $TOKEN"Set the career salary
POST /api/v1/accounting/incomes/career
Updates the entity’s primary employment income (earliest startMonth), or creates one when salary > 0. A salary of 0 deactivates the primary income.
Request body (application/json)
Type: object
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | yes | |
salary | number | yes | |
paydayDayOfMonth | integer | no | |
title | string | no | |
company | string | no | |
imageUrl | string | no | |
startMonth | string | "" | no | YYYY-MM. Empty string keeps the existing start month. |
endMonth | string | "" | no | YYYY-MM. Empty string clears the end month. |
asOf | string | no | Client’s current month or day. For cycle: "one_time", a day-precision value such as 2026-10-26 is the date of the receipt (it sets dayOfMonth); a month-only value lands on the 1st. Otherwise the default month for startMonth. |
Responses
200— Primary income, or null when none exists.object400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X POST "$API_URL/api/v1/accounting/incomes/career" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'List expenses
GET /api/v1/accounting/expenses
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entityId | query | string | no |
Responses
200— Expenses. Expense[]400— Validation Errorobject401— Missing or invalid credential (session token or API key; only when auth is configured). Error
curl "$API_URL/api/v1/accounting/expenses" \
-H "Authorization: Bearer $TOKEN"Create an expense
POST /api/v1/accounting/expenses
Request body (application/json)
Type: object
| Field | Type | Required | Description |
|---|---|---|---|
id | string | no | Optional client-generated UUID used as the record id. Repeating a create with the same id must not add a second record: the MCP create tools return the stored record with deduplicated: true, and the REST routes answer 409 DUPLICATE. |
entityId | string | yes | |
asOf | string | no | Client’s current month or day. For cycle: "one_time", a day-precision value such as 2026-10-26 is the date of the receipt (it sets dayOfMonth); a month-only value lands on the 1st. Otherwise the default month for startMonth. |
name | string | yes | |
category | "ai" | "capital_deploy" | "connectivity" | "dev_tool" | "entertainment" | "family" | "food" | "groceries" | "healthcare" | "housing" | "infrastructure" | "marketing" | "office" | "other" | "saas" | "transit" | yes | Expense category. Accepts a key or a Thai/English label and stores the key. ai = ค่าสมาชิก AI (AI Subscriptions); capital_deploy = ลงทุนในธุรกิจ (Deploy Capital to Business); connectivity = อินเทอร์เน็ตและมือถือ (Internet & Mobile); dev_tool = เครื่องมือนักพัฒนาและ API (Developer Tools & APIs); entertainment = ค่าสมาชิกและค่าใช้จ่ายในชีวิต (Subscriptions & Living); family = ครอบครัวและค่าใช้จ่ายส่วนตัว (Family & Personal); food = อาหาร (Food & Dining); groceries = ของชำและของใช้ในบ้าน (Groceries & Household); healthcare = สุขภาพ (Healthcare & Wellness); housing = ที่อยู่อาศัยและค่าเช่า (Housing & Rent); infrastructure = โฮสติ้งและโครงสร้างพื้นฐาน (Hosting & Infrastructure); marketing = การตลาดและการขาย (Marketing & Sales); office = ออฟฟิศและพื้นที่ทำงาน (Office & Workspace); other = อื่น ๆ (Other); saas = SaaS และเครื่องมือทำงาน (SaaS & Productivity); transit = เดินทาง (Transit & Transport) |
amount | number | yes | |
cycle | "weekly" | "monthly" | "quarterly" | "semi-annual" | "yearly" | "one_time" | no | How often it repeats. An expense defaults to one_time (one receipt, dated by asOf, or today); an income or a liability defaults to monthly. one_time is counted only in startMonth; every other value repeats from startMonth on its own schedule. |
dayOfMonth | integer | no | Day of the month the cycle falls on. For cycle: "one_time" this is the date of the receipt; it defaults from a day-precision asOf, otherwise the 1st. |
startMonth | string | no | |
endMonth | string | no | |
paidFromAssetId | string | no | |
paidFromLiabilityId | string | no | |
assetId | string | no | |
incomeId | string | no | |
active | boolean | no | |
notes | string | no | |
imageUrl | string | no | |
details | map of any | no |
Responses
201— Expense created. Expense400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X POST "$API_URL/api/v1/accounting/expenses" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'Get an expense
GET /api/v1/accounting/expenses/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
200— The expense. Expense401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error
curl "$API_URL/api/v1/accounting/expenses/:id" \
-H "Authorization: Bearer $TOKEN"Update an expense
PATCH /api/v1/accounting/expenses/{id}
Partial update. details merges shallowly onto the stored details, so a patch for one key keeps the rest.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Request body (application/json)
Type: object
| Field | Type | Required | Description |
|---|---|---|---|
version | integer | no | Stored version the caller last read; a mismatch returns 409. |
asOf | string | no | Client’s current month or day. For cycle: "one_time", a day-precision value such as 2026-10-26 is the date of the receipt (it sets dayOfMonth); a month-only value lands on the 1st. Otherwise the default month for startMonth. |
name | string | no | |
category | "ai" | "capital_deploy" | "connectivity" | "dev_tool" | "entertainment" | "family" | "food" | "groceries" | "healthcare" | "housing" | "infrastructure" | "marketing" | "office" | "other" | "saas" | "transit" | no | Expense category. Accepts a key or a Thai/English label and stores the key. ai = ค่าสมาชิก AI (AI Subscriptions); capital_deploy = ลงทุนในธุรกิจ (Deploy Capital to Business); connectivity = อินเทอร์เน็ตและมือถือ (Internet & Mobile); dev_tool = เครื่องมือนักพัฒนาและ API (Developer Tools & APIs); entertainment = ค่าสมาชิกและค่าใช้จ่ายในชีวิต (Subscriptions & Living); family = ครอบครัวและค่าใช้จ่ายส่วนตัว (Family & Personal); food = อาหาร (Food & Dining); groceries = ของชำและของใช้ในบ้าน (Groceries & Household); healthcare = สุขภาพ (Healthcare & Wellness); housing = ที่อยู่อาศัยและค่าเช่า (Housing & Rent); infrastructure = โฮสติ้งและโครงสร้างพื้นฐาน (Hosting & Infrastructure); marketing = การตลาดและการขาย (Marketing & Sales); office = ออฟฟิศและพื้นที่ทำงาน (Office & Workspace); other = อื่น ๆ (Other); saas = SaaS และเครื่องมือทำงาน (SaaS & Productivity); transit = เดินทาง (Transit & Transport) |
amount | number | no | |
cycle | "weekly" | "monthly" | "quarterly" | "semi-annual" | "yearly" | "one_time" | null | no | |
dayOfMonth | integer | null | no | |
startMonth | string | null | no | |
endMonth | string | null | no | |
paidFromAssetId | string | null | no | |
paidFromLiabilityId | string | null | no | |
assetId | string | null | no | |
incomeId | string | null | no | |
active | boolean | null | no | |
notes | string | null | no | |
imageUrl | string | null | no | |
details | map of any | null | no |
Responses
200— Expense updated. Expense400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X PATCH "$API_URL/api/v1/accounting/expenses/:id" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'Delete an expense
DELETE /api/v1/accounting/expenses/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
200— Expense deleted. Ok400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X DELETE "$API_URL/api/v1/accounting/expenses/:id" \
-H "Authorization: Bearer $TOKEN"List liabilities
GET /api/v1/accounting/liabilities
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entityId | query | string | no | |
kind | query | "loan" | "revolving" | "other" | no |
Responses
200— Liabilities. Liability[]400— Validation Errorobject401— Missing or invalid credential (session token or API key; only when auth is configured). Error
curl "$API_URL/api/v1/accounting/liabilities" \
-H "Authorization: Bearer $TOKEN"Create a liability
POST /api/v1/accounting/liabilities
Body discriminated by kind. A loan without amount is paid at its level payment, calculateLoanPayment(principal, annualRate, termMonths), and its payments start in the origination month unless startMonth is given.
Request body (application/json)
Type: object | object | object
Responses
201— Liability created. Liability400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X POST "$API_URL/api/v1/accounting/liabilities" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'Get a liability
GET /api/v1/accounting/liabilities/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
200— The liability. Liability401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error
curl "$API_URL/api/v1/accounting/liabilities/:id" \
-H "Authorization: Bearer $TOKEN"Update a liability
PATCH /api/v1/accounting/liabilities/{id}
Partial update. loan and revolving merge shallowly onto the stored details.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Request body (application/json)
Type: object
| Field | Type | Required | Description |
|---|---|---|---|
version | integer | no | Stored version the caller last read; a mismatch returns 409. |
name | string | no | |
category | string | no | |
cycle | "weekly" | "monthly" | "quarterly" | "semi-annual" | "yearly" | "one_time" | null | no | |
dayOfMonth | integer | null | no | |
startMonth | string | null | no | |
endMonth | string | null | no | |
paidFromAssetId | string | null | no | |
active | boolean | null | no | |
notes | string | null | no | |
imageUrl | string | null | no | |
details | map of any | null | no | |
amount | number | no | |
loan | object | no | Merged shallowly onto the stored loan details. |
loan.principal | number | no | |
loan.annualRate | number | no | Defaults to 0 (0% installment plan). |
loan.termMonths | integer | no | |
loan.originatedOn | string | no | |
loan.financedAssetId | string | no | |
loan.proceedsAssetId | string | no | |
revolving | object | no | Merged shallowly onto the stored revolving details. |
revolving.creditLimit | number | no | |
revolving.balance | number | no | Defaults to 0. |
revolving.balanceAsOf | string | no | Defaults to the start month. |
revolving.annualRate | number | no |
Responses
200— Liability updated. Liability400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X PATCH "$API_URL/api/v1/accounting/liabilities/:id" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'Delete a liability
DELETE /api/v1/accounting/liabilities/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
200— Liability deleted. Ok400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X DELETE "$API_URL/api/v1/accounting/liabilities/:id" \
-H "Authorization: Bearer $TOKEN"Get a liability’s month-by-month schedule
GET /api/v1/accounting/liabilities/{id}/schedule
Loans: payments, interest and principal per month, including extra-payment transfers. Revolving: opening balance, interest, card charges and payments per month. Liabilities of kind other have no schedule (400).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
through | query | string | no | Last month YYYY-MM. Loans default to the payoff month or the end of the term, whichever comes first (capped at 600 months). Revolving balances default to the current UTC month. |
id | path | string | yes |
Responses
200— Loan or revolving schedule. LoanSchedule | RevolvingSchedule400— Validation error, or the liability has no schedule. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Liability not found. Error
curl "$API_URL/api/v1/accounting/liabilities/:id/schedule" \
-H "Authorization: Bearer $TOKEN"List transfers
GET /api/v1/accounting/transfers
Responses
200— Transfers. Transfer[]401— Missing or invalid credential (session token or API key; only when auth is configured). Error
curl "$API_URL/api/v1/accounting/transfers" \
-H "Authorization: Bearer $TOKEN"Create a transfer
POST /api/v1/accounting/transfers
Source: fromAssetId or fromEntityId (its default cash asset). Destination: exactly one of toAssetId, toEntityId (its default cash asset) or toLiabilityId.
Request body (application/json)
Type: object
| Field | Type | Required | Description |
|---|---|---|---|
id | string | no | Optional client-generated UUID used as the record id. Repeating a create with the same id must not add a second record: the MCP create tools return the stored record with deduplicated: true, and the REST routes answer 409 DUPLICATE. |
amount | number | yes | |
date | string | yes | |
cycle | "one_time" | "monthly" | no | |
note | string | no | |
fromAssetId | string | no | |
fromEntityId | string | no | Resolves to the entity’s default cash asset. |
toAssetId | string | no | |
toEntityId | string | no | Resolves to the entity’s default cash asset. |
toLiabilityId | string | no |
Responses
201— Transfer created. Transfer400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X POST "$API_URL/api/v1/accounting/transfers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'Get a transfer
GET /api/v1/accounting/transfers/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
200— The transfer. Transfer401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error
curl "$API_URL/api/v1/accounting/transfers/:id" \
-H "Authorization: Bearer $TOKEN"Update a transfer
PATCH /api/v1/accounting/transfers/{id}
Partial update. Name a new source or destination to re-resolve that side.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Request body (application/json)
Type: object
| Field | Type | Required | Description |
|---|---|---|---|
version | integer | no | Stored version the caller last read; a mismatch returns 409. |
amount | number | no | |
date | string | no | |
cycle | "one_time" | "monthly" | no | |
note | string | no | |
fromAssetId | string | no | |
fromEntityId | string | no | Resolves to the entity’s default cash asset. |
toAssetId | string | no | |
toEntityId | string | no | Resolves to the entity’s default cash asset. |
toLiabilityId | string | no |
Responses
200— Transfer updated. Transfer400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X PATCH "$API_URL/api/v1/accounting/transfers/:id" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'Delete a transfer
DELETE /api/v1/accounting/transfers/{id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes |
Responses
200— Transfer deleted. Ok400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Record not found. Error409— Version conflict or duplicate id. Error
curl -X DELETE "$API_URL/api/v1/accounting/transfers/:id" \
-H "Authorization: Bearer $TOKEN"Get wealth metrics and balance sheet per entity and consolidated
GET /api/v1/accounting/summary
Per entity: wealth metrics and balance sheet at month. Consolidated: metrics plus the balance sheet summed over all entities.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
month | query | string | yes | YYYY-MM. |
asOf | query | string | no | Today (YYYY-MM-DD); current values are as of this day when inside month. |
Responses
200— Summary for the month. Summary400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error
curl "$API_URL/api/v1/accounting/summary" \
-H "Authorization: Bearer $TOKEN"Get computed monthly ledger
GET /api/v1/accounting/ledger/{yearMonth}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entityId | query | string | no | |
yearMonth | path | string | yes |
Responses
200— Monthly ledger. MonthlyLedger400— Validation Errorobject401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Entity not found. Error
curl "$API_URL/api/v1/accounting/ledger/:yearMonth" \
-H "Authorization: Bearer $TOKEN"Get the cash-flow feed for a month
GET /api/v1/accounting/cash-flows
Dated money movements of one entity (default: personal) for one month. direction filters items; totals (moneyIn, moneyOut, spending) always cover every item.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
month | query | string | yes | YYYY-MM. |
entityId | query | string | no | Defaults to the personal entity. |
direction | query | "in" | "out" | no | Filters items only; totals always cover every item. |
includeDepreciation | query | "true" | "false" | no | Adds non-cash depreciation items. Defaults to false. |
Responses
200— Cash-flow feed and totals. CashFlowsResponse400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Entity not found. Error
curl "$API_URL/api/v1/accounting/cash-flows" \
-H "Authorization: Bearer $TOKEN"Get treasury balance per month around a centre month
GET /api/v1/accounting/projection
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entityId | query | string | no | Defaults to the personal entity. |
month | query | string | no | Centre month YYYY-MM. Defaults to the UTC month. |
pastMonths | query | integer | no | |
futureMonths | query | integer | no | |
asOf | query | string | no |
Responses
200— Projection points and waterfall figures. Projection400— Validation error. Error401— Missing or invalid credential (session token or API key; only when auth is configured). Error404— Entity not found. Error
curl "$API_URL/api/v1/accounting/projection" \
-H "Authorization: Bearer $TOKEN"Schemas
Entity
A personal or business entity that owns money flows.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
name | string | yes | |
type | "personal" | "business" | yes | |
currency | string | yes | ISO currency code, typically THB or USD. |
targetSafetyBuffer | number | no | |
active | boolean | yes | |
notes | string | no | |
imageUrl | string | no | |
version | integer | yes | Starts at 1; every update bumps it. PATCH may send it back to detect conflicts. |
createdAt | string | yes | ISO timestamp. |
updatedAt | string | yes | ISO timestamp. |
Error
| Field | Type | Required | Description |
|---|---|---|---|
error | string | yes |
Ok
| Field | Type | Required | Description |
|---|---|---|---|
ok | true | yes |
Asset
An asset, discriminated by kind.
Type: CashAsset | FixedAsset | InvestmentAsset
Income
An income stream.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
entityId | string | yes | |
kind | "employment" | "business" | "investment" | "other" | yes | |
category | string | yes | salary, bonus, freelance, sales, dividend, interest, rent… |
name | string | yes | |
amount | number | yes | |
cycle | "weekly" | "monthly" | "quarterly" | "semi-annual" | "yearly" | "one_time" | yes | |
dayOfMonth | integer | no | Day the money moves; absent = the flow type’s default day. |
startMonth | string | yes | YYYY-MM. First month the flow happens. |
endMonth | string | no | YYYY-MM. Last month; absent = open-ended. |
taxCategory | "40_1" | "40_2" | "40_3" | "40_4" | "40_5" | "40_6" | "40_7" | "40_8" | no | |
depositAssetId | string | no | Cash asset the income lands in; absent = the default cash asset. |
sourceAssetId | string | no | Asset that generates it. Investment income only. |
employment | object | no | Present only when kind is employment. |
employment.employer | string | no | |
employment.title | string | no | |
active | boolean | yes | |
notes | string | no | |
imageUrl | string | no | |
details | map of any | yes | Display-only extras the engine never reads. |
version | integer | yes | Starts at 1; every update bumps it. PATCH may send it back to detect conflicts. |
createdAt | string | yes | ISO timestamp. |
updatedAt | string | yes | ISO timestamp. |
Expense
A recurring or one-time cost.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
entityId | string | yes | |
name | string | yes | |
category | string | yes | |
amount | number | yes | |
cycle | "weekly" | "monthly" | "quarterly" | "semi-annual" | "yearly" | "one_time" | yes | |
dayOfMonth | integer | no | Day the money moves; absent = the flow type’s default day. |
startMonth | string | yes | YYYY-MM. First month the flow happens. |
endMonth | string | no | YYYY-MM. Last month; absent = open-ended. |
paidFromAssetId | string | no | |
paidFromLiabilityId | string | no | Revolving liability (credit card) it is charged to. |
assetId | string | no | Asset this is a cost of owning. |
incomeId | string | no | Income this is a cost of earning. |
active | boolean | yes | |
notes | string | no | |
imageUrl | string | no | |
details | map of any | yes | Display-only extras the engine never reads. |
version | integer | yes | Starts at 1; every update bumps it. PATCH may send it back to detect conflicts. |
createdAt | string | yes | ISO timestamp. |
updatedAt | string | yes | ISO timestamp. |
Liability
A debt. The schedule is the planned payment.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
entityId | string | yes | |
kind | "loan" | "revolving" | "other" | yes | |
name | string | yes | |
category | string | yes | |
amount | number | yes | |
cycle | "weekly" | "monthly" | "quarterly" | "semi-annual" | "yearly" | "one_time" | yes | |
dayOfMonth | integer | no | Day the money moves; absent = the flow type’s default day. |
startMonth | string | yes | YYYY-MM. First month the flow happens. |
endMonth | string | no | YYYY-MM. Last month; absent = open-ended. |
paidFromAssetId | string | no | |
loan | LoanDetails | no | |
revolving | RevolvingDetails | no | |
active | boolean | yes | |
notes | string | no | |
imageUrl | string | no | |
details | map of any | yes | Display-only extras the engine never reads. |
version | integer | yes | Starts at 1; every update bumps it. PATCH may send it back to detect conflicts. |
createdAt | string | yes | ISO timestamp. |
updatedAt | string | yes | ISO timestamp. |
LoanSchedule
Month-by-month loan schedule.
| Field | Type | Required | Description |
|---|---|---|---|
liabilityId | string | yes | |
kind | "loan" | yes | |
entries | LoanScheduleEntry[] | yes |
RevolvingSchedule
Month-by-month revolving balance.
| Field | Type | Required | Description |
|---|---|---|---|
liabilityId | string | yes | |
kind | "revolving" | yes | |
entries | RevolvingScheduleEntry[] | yes |
Transfer
Money moved from an asset to an asset, or onto a liability.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
amount | number | yes | |
date | string | yes | YYYY-MM-DD or YYYY-MM. |
cycle | "one_time" | "monthly" | yes | |
note | string | yes | |
fromAssetId | string | yes | |
toAssetId | string | no | |
toLiabilityId | string | no | |
version | integer | yes | Starts at 1; every update bumps it. PATCH may send it back to detect conflicts. |
createdAt | string | yes | ISO timestamp. |
updatedAt | string | yes | ISO timestamp. |
Summary
Wealth metrics per entity and consolidated, for one month.
| Field | Type | Required | Description |
|---|---|---|---|
yearMonth | string | yes | |
asOf | string | no | |
entities | object[] | yes | |
entities[].entityId | string | yes | |
entities[].metrics | object | yes | |
entities[].metrics.cashAssetsValue | number | yes | |
entities[].metrics.gearAssetsValue | number | yes | |
entities[].metrics.totalAssetsValue | number | yes | |
entities[].metrics.currentLiquid | number | yes | |
entities[].metrics.eomLiquid | number | yes | |
entities[].metrics.currentWealth | number | yes | |
entities[].metrics.eomWealth | number | yes | |
entities[].metrics.liquidPct | number | yes | |
entities[].metrics.gearPct | number | yes | |
entities[].metrics.totalIncome | number | yes | |
entities[].metrics.totalExpense | number | yes | |
entities[].metrics.liabilityMonthly | number | yes | |
entities[].metrics.liabPct | number | yes | |
entities[].metrics.runwayMonths | number | null | yes | |
entities[].metrics.isDefaultAlive | boolean | yes | |
entities[].balanceSheet | BalanceSheet | yes | |
consolidated | object | yes | |
consolidated.totalTreasury | number | yes | |
consolidated.totalEomTreasury | number | yes | |
consolidated.personalCash | number | yes | |
consolidated.businessCash | number | yes | |
consolidated.balanceSheet | BalanceSheet | yes |
MonthlyLedger
Computed cash ledger for one entity and month.
| Field | Type | Required | Description |
|---|---|---|---|
yearMonth | string | yes | |
summary | object | yes | |
summary.entityId | string | no | |
summary.yearMonth | string | no | |
summary.startingTreasury | number | yes | |
summary.endingTreasury | number | yes | |
summary.totalInflow | number | yes | |
summary.totalCashOutflow | number | yes | |
summary.totalDepreciation | number | yes | |
summary.netCashFlow | number | yes | |
summary.runwayMonths | number | null | no | |
summary.isDefaultAlive | boolean | no | |
actionItems | object[] | yes | |
actionItems[].id | string | yes | |
actionItems[].entityId | string | no | |
actionItems[].yearMonth | string | no | |
actionItems[].title | string | yes | |
actionItems[].date | string | yes | |
actionItems[].amount | number | yes | |
actionItems[].type | "subscription" | "depreciation" | "revenue" | "capital_in" | "capital_out" | "inflow" | "outflow" | yes | |
actionItems[].category | string | no | |
actionItems[].reference | string | no |
CashFlowsResponse
Cash-flow feed of one entity for one month, with totals over all items.
| Field | Type | Required | Description |
|---|---|---|---|
yearMonth | string | yes | |
entityId | string | yes | |
items | CashFlowItem[] | yes | |
totals | object | yes | |
totals.moneyIn | number | yes | Cash in, from cash items only. |
totals.moneyOut | number | yes | Cash out, from cash items only. |
totals.spending | number | yes | Sum of spendingAmount over every item. |
spendingByCategory | object[] | yes | Spending by expense category for the month, over the unfiltered feed. Always contains “uncategorised”; the buckets sum to totals.spending. Answer “how much on X this month” from here, never by grouping items. |
spendingByCategory[].category | string | yes | An expense key, or “uncategorised” for spending without one. |
spendingByCategory[].spending | number | yes | |
spendingByCategory[].count | integer | yes | Items in the bucket. |
spendingByCategory[].share | number | yes | spending / totals.spending, 4 dp; 0 when nothing was spent. |
Projection
Treasury balance per month around a centre month.
| Field | Type | Required | Description |
|---|---|---|---|
points | object[] | yes | |
points[].offset | integer | yes | |
points[].yearMonth | string | yes | |
points[].balance | number | yes | |
totalIncome | number | yes | |
totalExpense | number | yes | |
liabilityMonthly | number | yes | |
projectedNet | number | yes |
CashAsset
Cash and cash equivalents: bank accounts, e-wallets, cash on hand.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
entityId | string | yes | Owning Entity.id. |
name | string | yes | |
category | string | yes | Free-form sub-type: bank, e_wallet, laptop, vehicle, property, stock… |
active | boolean | yes | |
notes | string | no | |
imageUrl | string | no | |
details | map of any | yes | Display-only extras the engine never reads. |
kind | "cash" | yes | |
balance | number | yes | Balance at the start of balanceAsOf. |
balanceAsOf | string | yes | YYYY-MM, or YYYY-MM-DD for a balance recorded mid-month. |
currency | string | yes | |
isDefault | boolean | yes | Where unlinked money of the entity lands. One per entity. |
version | integer | yes | Starts at 1; every update bumps it. PATCH may send it back to detect conflicts. |
createdAt | string | yes | ISO timestamp. |
updatedAt | string | yes | ISO timestamp. |
FixedAsset
PP&E bought once and depreciated over its useful life.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
entityId | string | yes | Owning Entity.id. |
name | string | yes | |
category | string | yes | Free-form sub-type: bank, e_wallet, laptop, vehicle, property, stock… |
active | boolean | yes | |
notes | string | no | |
imageUrl | string | no | |
details | map of any | yes | Display-only extras the engine never reads. |
kind | "fixed" | yes | |
cost | number | yes | |
acquiredOn | string | yes | YYYY-MM or YYYY-MM-DD. |
depreciationMethod | "straight_line" | "none" | yes | |
usefulLifeMonths | integer | no | Required unless depreciationMethod is none. |
salvageValue | number | yes | |
version | integer | yes | Starts at 1; every update bumps it. PATCH may send it back to detect conflicts. |
createdAt | string | yes | ISO timestamp. |
updatedAt | string | yes | ISO timestamp. |
InvestmentAsset
Valued at market, grown by an expected return, never depreciated.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
entityId | string | yes | Owning Entity.id. |
name | string | yes | |
category | string | yes | Free-form sub-type: bank, e_wallet, laptop, vehicle, property, stock… |
active | boolean | yes | |
notes | string | no | |
imageUrl | string | no | |
details | map of any | yes | Display-only extras the engine never reads. |
kind | "investment" | yes | |
instrument | string | yes | |
symbol | string | no | |
units | number | no | |
costBasis | number | yes | |
marketValue | number | yes | |
valuedAsOf | string | yes | YYYY-MM. |
expectedAnnualReturn | number | no | Yearly return as a fraction (0.07 = 7%). |
currency | string | yes | |
version | integer | yes | Starts at 1; every update bumps it. PATCH may send it back to detect conflicts. |
createdAt | string | yes | ISO timestamp. |
updatedAt | string | yes | ISO timestamp. |
LoanDetails
Amortizing loan terms.
| Field | Type | Required | Description |
|---|---|---|---|
principal | number | yes | |
annualRate | number | yes | Yearly interest rate as a fraction (0.065 = 6.5%). |
termMonths | integer | yes | |
originatedOn | string | yes | YYYY-MM or YYYY-MM-DD. |
financedAssetId | string | no | |
proceedsAssetId | string | no |
RevolvingDetails
Revolving credit: a card or credit line.
| Field | Type | Required | Description |
|---|---|---|---|
creditLimit | number | no | |
balance | number | yes | |
balanceAsOf | string | yes | YYYY-MM. |
annualRate | number | no |
LoanScheduleEntry
One month of a loan.
| Field | Type | Required | Description |
|---|---|---|---|
yearMonth | string | yes | |
opening | number | yes | |
interest | number | yes | |
payment | number | yes | Scheduled payment actually made, capped at what is owed. |
extra | number | yes | Extra payments applied, capped at what is owed after payment. |
principal | number | yes | Principal repaid; negative when the balance grows. |
closing | number | yes |
RevolvingScheduleEntry
One month of a revolving balance.
| Field | Type | Required | Description |
|---|---|---|---|
yearMonth | string | yes | |
opening | number | yes | |
interest | number | yes | |
charges | number | yes | Monthly-equivalent card charges that month. |
payment | number | yes | Scheduled payment made, capped at what is owed. |
extra | number | yes | Transfers into the card applied that month, capped at what is still owed. |
payments | number | yes | payment + extra. |
closing | number | yes |
BalanceSheet
Position at the end of one month.
| Field | Type | Required | Description |
|---|---|---|---|
cash | number | yes | End-of-month treasury of the cash wallets. |
fixedAssets | number | yes | Remaining book value of fixed (non-cash, non-investment) assets. |
investments | number | yes | Value of investment assets. |
totalAssets | number | yes | cash + fixedAssets + investments. |
debt | number | yes | Loan and revolving balances owed. |
netWorth | number | yes | totalAssets - debt. |
CashFlowItem
One dated money movement in the cash-flow feed.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Unique within the month’s feed. |
sourceType | "income" | "expense" | "liability" | "transfer" | "asset" | "account" | yes | |
sourceId | string | yes | The record to open when tapped. |
flowType | "income" | "expense" | "debt_payment" | "asset_purchase" | "investment" | "transfer" | "loan_proceeds" | "depreciation" | yes | |
direction | "in" | "out" | yes | |
date | string | yes | YYYY-MM-DD. |
name | string | yes | |
amount | number | yes | |
isCash | boolean | yes | False for card charges and depreciation: they move no cash. |
spendingAmount | number | yes | The part of the amount that counts as spending. |
category | string | no | On an expense item, the canonical expense category (see spendingByCategory). On other items, the ledger flow class (revenue, salary, transfer, …). |
cashAssetId | string | no | |
chargedToLiabilityId | string | no | Revolving liability a card charge lands on. |
interest | number | no | Loan payments only. |
principal | number | no | Loan payments only. |