Pathayam

API reference

Every screen is already an endpoint

The app is server-rendered, so there is no second API to keep in sync. A personal token authenticates a script against the same routes your browser uses.

How it works

Authentication

The host in every example below is your own instance — the machine you self-host on, or your hosted subdomain. The public demo at demo.pathayam.app lets anyone in without an account and issues no tokens, so these examples will not run against it.

One header on every request: Authorization: Bearer bgt_…. Tokens are minted in Settings — never while viewing as another member — scoped read or read-write, and shown exactly once. One expires after a whole number of days from 1 to 3650, or never, and stops working the moment its member is removed.

Idempotency

Every write accepts Idempotency-Key. Replaying a key returns the original result instead of acting twice, so a timeout you never saw is safe to retry. The same key with a different body is refused with 422.

Responses

Reads return HTML — this is the same app your browser talks to. /export.json, the CSVs and /healthz return data. Writes redirect on success. Send Accept: application/json and a write answers { "redirect": …, "message": … } instead, an error comes back as { "error": … } with its status, and a request that is not signed in gets 401 rather than a redirect to the sign-in page.

Amounts are rupees in requests and integer paise internally, so nothing is ever stored as a float. Dates are YYYY-MM-DD (or DD-MM-YYYY, the way the app shows them) and months are YYYY-MM, both in IST; a month more than a hundred years from the rest of the budget is refused. Send fields form-encoded, as multipart, or as a flat JSON object — a number or true is read as the text a form would send, and a nested object is ignored.

Budget & money

Everything that moves money between envelopes. These are the routes that change what your budget says.

GET / The month grid

The budget screen for a month: groups, categories, assigned, activity and available, with Ready to Assign and the digest.

Scope

read or higher

Parameters

NameTypeDescription
monthstringoptionalYYYY-MM. Defaults to the current month in IST.
budgetstringoptionalWhich budget, in a household with more than one. Defaults to the last one viewed.

Responses

  • 200 — The rendered screen, as HTML.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 429 — Rate limit exceeded. Retry after the interval in the response.

Example

curl -sS https://pathayam.yourdomain.com/?month=2026-09 \
  -H "Authorization: Bearer bgt_…"
POST /assign Assign to a category

Set the amount assigned to one category in one month. This is an absolute value, not a delta — sending 4500 makes the assignment 4500, whatever it was before. A month too far back to take an assignment is refused.

Scope

read-write

Parameters

NameTypeDescription
monthstringoptionalYYYY-MM. Defaults to the current month in IST.
category_idstringrequiredThe category receiving the money.
amountnumberoptionalRupees. Accepts an expression such as 450+120. Empty sets it to 0.

Responses

  • 303 — Success. Follow Location to the updated screen.
  • 400 — A field was missing or could not be parsed; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 404 — An id that does not exist, or names something you cannot see.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS -X POST https://pathayam.yourdomain.com/assign \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d "month=2026-09&category_id=cat_7f3&amount=4500"
POST /move Move between envelopes

Move money from one category to another within the same month. Ready to Assign is unaffected — the total assigned does not change, only where it sits. This is how you cover an overspend. Both envelopes must be in the same budget — money between budgets is a transfer between their accounts, and a move is refused.

Scope

read-write

Parameters

NameTypeDescription
monthstringoptionalYYYY-MM. Defaults to the current month in IST.
from_category_idstringrequiredWhere the money comes from. rta takes it from Ready to Assign.
to_category_idstringrequiredWhere it goes.
amountnumberrequiredRupees.

Responses

  • 303 — Success. Follow Location to the updated screen.
  • 400 — A field was missing or could not be parsed; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 404 — An id that does not exist, or names something you cannot see.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS -X POST https://pathayam.yourdomain.com/move \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d "month=2026-09&from_category_id=cat_hol&to_category_id=cat_eat&amount=1320"
POST /hold Hold income for next month

Take an amount out of this month's Ready to Assign and place it at the top of next month's. Send 0 to release everything currently held.

Scope

read-write

Parameters

NameTypeDescription
monthstringoptionalThe month holding the money back. Defaults to the current month in IST.
budgetstringoptionalWhich budget, in a household with more than one. Defaults to the last one viewed.
amountnumberoptionalRupees. Empty or 0 releases the hold.

Responses

  • 303 — Success. Follow Location to the updated screen.
  • 400 — A field was missing or could not be parsed; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS -X POST https://pathayam.yourdomain.com/hold \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d "month=2026-09&amount=139900"
GETPOST /auto-assign Fund every target

GET previews what funding all targets would do. POST commits it. Money is spent down the budget in your own order, so a short month funds the top of the list first. It funds one budget, from that budget's Ready to Assign.

Scope

read to preview, read-write to commit

Parameters

NameTypeDescription
monthstringoptionalYYYY-MM. Defaults to the current month in IST.
budgetstringoptionalWhich budget, in a household with more than one. Defaults to the last one viewed.

Responses

  • 303 — Success. Follow Location to the updated screen.
  • 400 — A field was missing or could not be parsed; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS https://pathayam.yourdomain.com/auto-assign?month=2026-09 \
  -H "Authorization: Bearer bgt_…"
GET /explain/ready-to-assign Why that number

How Ready to Assign for a month is made up: income received to date, less what is assigned this month and earlier, what is assigned in future months, what is held for next month, and any cash overspending carried in.

Scope

read or higher

Parameters

NameTypeDescription
monthstringoptionalYYYY-MM. Defaults to the current month in IST.
budgetstringoptionalWhich budget, in a household with more than one. Defaults to the last one viewed.

Responses

  • 200 — The rendered screen, as HTML.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 429 — Rate limit exceeded. Retry after the interval in the response.

Example

curl -sS https://pathayam.yourdomain.com/explain/ready-to-assign?month=2026-09 \
  -H "Authorization: Bearer bgt_…"

More budget & money routes

MethodPathTokenParametersWhat it does
GET/movereadmonth, to, amountThe move form, with the destination and amount filled in when given.
GET/holdreadmonthThe hold form.
POST/copy-last-monthread-writemonthCopy last month's assignments into this one.
GET/explain/category/:idreadmonthHow one category's available figure is made up.
GET/overviewread—Runway, bills due soon and the month at a glance.
POST/budgets/personalread-write—Create your own personal budget if you do not have one, with its household envelope and a few starting envelopes.
GET/moreread—The hub of everything not in the main navigation.
GET/setupread—The first-run screen: start from a template or blank.
POST/setupread-writemonthly_income, has_emis, has_school_fees, has_domestic_help (each 1 for yes)Create starting categories from a template. Then to /accounts/new.
POST/setup/blankread-write—Start with no categories. Then to /accounts/new.

Accounts & cards

The accounts money sits in, and the cards drawn on them. An account is closed, never deleted.

GET /accounts List accounts

The accounts of the budget being viewed, plus the tracking accounts, with cleared, uncleared and working balances, grouped into budget, credit and tracking.

Scope

read or higher

Responses

  • 200 — The rendered screen, as HTML.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 429 — Rate limit exceeded. Retry after the interval in the response.

Example

curl -sS https://pathayam.yourdomain.com/accounts \
  -H "Authorization: Bearer bgt_…"
GET /cards Cards by due date

Every credit account in the budget being viewed, ordered by what falls due next, with what is owed, what the payment envelope holds, and anything unfunded.

Scope

read or higher

Responses

  • 200 — The rendered screen, as HTML.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 429 — Rate limit exceeded. Retry after the interval in the response.

Example

curl -sS https://pathayam.yourdomain.com/cards \
  -H "Authorization: Bearer bgt_…"

More accounts & cards routes

MethodPathTokenParametersWhat it does
GET/accounts/newread—The new-account form.
POST/accounts/newread-writename, subtype (required); kind, institution, last4, opening_balance, opening_date, statement_day, due_day, budget_id, visibility; deposit terms as belowCreate an account.
GET/accounts/:idreadcard (one card's spending)One account's register.
POST/accounts/:id/editread-writename (required); last4, statement_day, due_day, visibilityRename an account or change its details.
POST/accounts/:id/close
/accounts/:id/reopen
read-write—Close an account (its history is untouched) or reopen it.
POST/accounts/:id/depositread-writedeposit_kind (fd, rd, ppf, epf, other), deposit_principal, deposit_instalment, deposit_rate (a percentage), deposit_compounding, deposit_start_date, deposit_maturity_date, deposit_payout (cumulative or periodic)Set or change a deposit's terms, so it values itself. A fixed deposit, recurring deposit or other-asset tracking account only; refused (422) otherwise. A hand value dated on or after the basis still wins.
POST/accounts/:id/deposit/removeread-write—Take a deposit's terms off; it is worth its balance again.
GET/accounts/:id/statementread—The card-statement form.
POST/accounts/:id/statementread-writeamount, due_date (required); statement_date, minimum_dueRecord a credit-card statement, so funding advice uses the real bill.
GET/accounts/:id/reconcileread—The reconcile screen.
POST/accounts/:id/reconcileread-writebank_balance, as_of, clear (transaction ids, repeated), allow_adjustmentReconcile to a statement balance, clearing the transactions named. A difference is never guessed: it is shown, and booked as an adjustment only with allow_adjustment=1.
GET/accounts/:id/cardsread—Cards on one account, including add-ons.
POST/accounts/:id/cardsread-writelabel (required); last4, holder_member_idAdd an add-on card to an account.
POST/accounts/:id/cards/:cardId/closeread-write—Close an add-on card. 404 unless the card is visible and on that account.

Transactions

Where money actually arrives and leaves. Every change here is an event you can undo from Activity.

POST /add Record a transaction

Create one transaction. Money out must name a category — the requirement is enforced on the server, not only in the form. Money in is exempt, because its job is to land in Ready to Assign. A cleared entry dated inside a reconciled period answers with the “Already reconciled” question instead of saving; send it again with confirm_checkpoint=1 to save it and break that checkpoint.

Scope

read-write

Parameters

NameTypeDescription
account_idstringrequiredThe account it happened on.
directionstringoptionalin or out. Defaults to out.
amountnumberrequiredRupees, always positive. Direction carries the sign.
datestringoptionalYYYY-MM-DD. Defaults to today.
category_idstringoptionalOne envelope for the whole amount. Money out needs this or the lines below.
split_category_N, split_amount_NstringoptionalEnvelope lines, N from 0. Line 0 carries no amount and takes whatever the others leave.
payeestringoptionalFree text. Matched against known payees.
memostringoptionalA note to your future self.
tagsstringoptionalComma-separated.
clearedbooleanoptional1 when the bank has confirmed it.
owner_member_idstringoptionalWho spent it. A card with its own holder still wins.
confirm_checkpointstringoptional1 answers the “Already reconciled” question.

Responses

  • 303 — Success. Follow Location to the updated screen.
  • 200 — The “Already reconciled” question, as HTML. Nothing was saved.
  • 400 — A field was missing or could not be parsed; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 404 — An id that does not exist, or names something you cannot see.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS -X POST https://pathayam.yourdomain.com/add \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d "account_id=acc_hdfc&direction=out&amount=1240" \
  -d "date=2026-09-12&category_id=cat_gro&payee=Zepto"
POST /transfer Move between your accounts

A transfer is not spending and is never counted as such. Paying a credit card is a transfer from a budget account to that card. The two accounts must differ.

Scope

read-write

Parameters

NameTypeDescription
from_account_idstringrequiredSource account.
to_account_idstringrequiredDestination account.
amountnumberrequiredRupees.
datestringoptionalYYYY-MM-DD. Defaults to today.
fee_amountnumberoptionalA bank charge on the transfer, in rupees. Recorded as spending, and undone with it.
fee_category_idstringoptionalThe envelope the charge comes out of. Required with fee_amount.

Responses

  • 303 — Success. Follow Location to the updated screen.
  • 400 — A field was missing or could not be parsed; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 404 — An id that does not exist, or names something you cannot see.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS -X POST https://pathayam.yourdomain.com/transfer \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d "from_account_id=acc_hdfc&to_account_id=acc_card&amount=11600"
POST /transaction/:id/categorise File one transaction

Assign a category to an existing transaction. This is also the learning signal: filing the same payee twice proposes a rule.

Scope

read-write

Parameters

NameTypeDescription
idpathrequiredTransaction id.
category_idstringoptionalThe envelope it belongs to. Empty clears it.

Responses

  • 303 — Success. Follow Location to the updated screen.
  • 400 — A field was missing or could not be parsed; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 404 — An id that does not exist, or names something you cannot see.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS -X POST https://pathayam.yourdomain.com/transaction/txn_9a2/categorise \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d "category_id=cat_gro"
POST /transaction/:id/delete Delete a transaction

Soft delete, recorded in the event log and reversible for thirty days from Activity. Deleting one leg of a transfer deletes both. Refused while a loan instalment rests on it. One dated inside a reconciled period answers with the “Already reconciled” question first.

Scope

read-write

Parameters

NameTypeDescription
idpathrequiredTransaction id.
confirm_checkpointstringoptional1 answers the “Already reconciled” question.

Responses

  • 303 — Success. Follow Location to the updated screen.
  • 200 — The “Already reconciled” question, as HTML. Nothing was deleted.
  • 400 — A field was missing or could not be parsed; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 404 — An id that does not exist, or names something you cannot see.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS -X POST https://pathayam.yourdomain.com/transaction/txn_9a2/delete \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)"

More transactions routes

MethodPathTokenParametersWhat it does
GET/addreadaccountThe Add form, with an account chosen when given.
GET/transferreadfrom, to, amountThe transfer form, filled in when given.
GET/transaction/:idread—One transaction: its envelope lines, receipts and history.
POST/transaction/:idread-writethe /add fields, cleared_present, reimbursable_present, confirm_checkpointEdit a transaction. A field you do not send keeps its stored value, so date=07-09-2026 alone moves only the date. An empty memo clears it, empty tags removes them, an empty owner_member_id makes it the household's; a blank amount is refused. cleared=1 / reimbursable=1 set a flag and any other value unsets it. Inside a reconciled period it asks first (200).
POST/transaction/:id/settledread-write—Mark a reimbursable transaction as repaid.
POST/transaction/:id/attachread-writereceipt (a multipart file)Attach a photo or PDF receipt.
GET/attachment/:idread—A receipt's bytes, with its own content type.
POST/attachment/:id/deleteread-write—Remove a receipt.
POST/transaction/:id/convert-to-emiread-writetenure_months, annual_rate (required); amount, processing_fee, fee_category_id, name_suffixConvert a card purchase into an instalment plan. Each instalment's interest carries 18% GST as its own card charge.

Import, review & rules

Nothing posts itself. Everything arrives in Review first, and rules decide where it goes.

GET /review The review queue

Everything awaiting a decision: imported rows to confirm, suspected duplicates, uncategorised spending, money you are owed, overspent categories, unfunded cards, reconciliations that no longer hold, and proposed rules — only the parts you can see.

Scope

read or higher

Responses

  • 200 — The rendered screen, as HTML.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 429 — Rate limit exceeded. Retry after the interval in the response.

Example

curl -sS https://pathayam.yourdomain.com/review \
  -H "Authorization: Bearer bgt_…"
POST /import Import a CSV

Paste statement rows as CSV. A saved column mapping whose headers match the file is used on its own; otherwise the columns are guessed, and a guess that works is saved for next time. A file matching neither answers with the “Which column is which?” screen, whose answers go to /import/map. Rows land staged, never posted.

An export from YNAB or Actual Budget is recognised by its header. It covers every account it had there, so the account_id sent here does not apply to it: the answer is the “Which account is which?” screen, as HTML, and that screen posts to /import/budget-app. Nothing is written until then.

Scope

read-write

Parameters

NameTypeDescription
account_idstringrequiredWhich account these rows belong to. Required, and checked, even for a YNAB or Actual Budget file.
csvstringrequiredThe raw CSV text.
file_namestringoptionalShown in the import log. Defaults to pasted.csv.

Responses

  • 303 — Success. Follow Location to the updated screen.
  • 200 — The column-mapping screen, as HTML, when the file matches nothing known. Nothing was imported.
  • 200 — The account-matching screen, as HTML, for a YNAB or Actual Budget export. Nothing was imported.
  • 400 — A field was missing or could not be parsed; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 404 — An id that does not exist, or names something you cannot see.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS -X POST https://pathayam.yourdomain.com/import \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  --data-urlencode "csv@statement.csv" -d "account_id=acc_hdfc"
POST /import/budget-app Import from YNAB or Actual Budget

The second step of a YNAB or Actual Budget import, the one the account-matching screen from /import posts. Each account in the file is matched to one of yours, or left out. Rows land in Review like any other import, with transfers marked as transfers and split transactions kept split. Transactions only: budget assignments are not imported, and the message says so.

Scope

read-write

Parameters

NameTypeDescription
csvstringrequiredThe export, as raw CSV text.
account_0, account_1, …stringrequiredOne per account in the file, in the order the screen lists them: an account id, or skip to leave it out.
date_orderstringoptionaldd-mm-yyyy or mm-dd-yyyy. Needed only when every date in the file reads both ways round.
file_namestringoptionalShown in the import log. Defaults to budget-export.csv.

Responses

  • 303 — Imported. To /review when anything waits there, otherwise /import.
  • 200 — The account-matching screen again, as HTML, when every account was left out. Nothing was imported.
  • 400 — Not a YNAB or Actual Budget export, a date that cannot be read, a missing date_order, or an account with no choice; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 404 — An account id that does not exist, or names something you cannot see.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS -X POST https://pathayam.yourdomain.com/import/budget-app \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  --data-urlencode "csv@ynab-register.csv" \
  -d "account_0=acc_hdfc&account_1=acc_card&account_2=skip"
POST /import/map Say which column is which

Imports a file with the columns you name, and saves the mapping for the next file like it — only if it read at least one transaction. Columns are numbered from 0. Dates are read day first unless you choose otherwise, or the file proves month first; a column that proves both is refused rather than read half wrong.

Scope

read-write

Parameters

NameTypeDescription
account_idstringrequiredWhich account these rows belong to.
csvstringrequiredThe raw CSV text.
profile_namestringrequiredWhat to call the saved mapping.
date, narrationintegerrequiredTheir column numbers.
amount, or debit and creditintegerrequiredOne signed amount column, or a column each way.
direction, balance, referenceintegeroptionalA Dr / Cr column that signs an unsigned amount, the running balance, the bank reference.
header_rowintegeroptionalThe row holding the headers. Defaults to 0.
date_formatstringoptionalHow the dates are written: auto (the default), dd-mm-yyyy, mm-dd-yyyy or yyyy-mm-dd.

Responses

  • 303 — Success. Follow Location to the updated screen.
  • 400 — A column missing, or dates in both orders; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 404 — An id that does not exist, or names something you cannot see.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS -X POST https://pathayam.yourdomain.com/import/map \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  --data-urlencode "csv@export.csv" -d "account_id=acc_card&profile_name=Card+export" \
  -d "date=0&narration=1&amount=3&date_format=mm-dd-yyyy"
POST /import/pdf Upload a statement PDF

Multipart upload of a bank statement. The bank is recognised from its own layout, the file is decrypted in-process if it is password-protected, and the parse is checked against the statement's closing balance. A PDF no reader recognises answers with the column-mapping screen, and one that cannot be opened or read answers with the import screen saying why — both as HTML, with nothing imported.

Scope

read-write

Parameters

NameTypeDescription
statementfilerequiredThe PDF, as multipart form data.
account_idstringrequiredWhich account it belongs to.
passwordstringoptionalOmit it to let the app derive it from your saved identity.

Responses

  • 303 — Success. Follow Location to the updated screen.
  • 200 — The mapping or import screen, as HTML, when the file could not be imported as it is.
  • 400 — A field was missing or could not be parsed; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 404 — An id that does not exist, or names something you cannot see.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS -X POST https://pathayam.yourdomain.com/import/pdf \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "statement=@statement.pdf" -F "account_id=acc_hdfc"
POST /import/undo Undo an import batch

Reverses an entire import and every row it created, in one step — except rows edited since, which are left alone and counted in the message. An import that reaches a reconciled period answers with the “Already reconciled” question first.

Scope

read-write

Parameters

NameTypeDescription
batch_idstringrequiredFrom the import log.
confirm_checkpointstringoptional1 answers the “Already reconciled” question.

Responses

  • 303 — Success. Follow Location to the updated screen.
  • 200 — The “Already reconciled” question, as HTML. Nothing was undone.
  • 400 — A field was missing or could not be parsed; the message says which.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 403 — A read-only token.
  • 404 — An id that does not exist, or names something you cannot see.
  • 409 — The first request with this Idempotency-Key is still being processed.
  • 422 — Understood and refused; the message says why. Also an Idempotency-Key reused with a different body.

Example

curl -sS -X POST https://pathayam.yourdomain.com/import/undo \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)" -d "batch_id=imp_41c"

More import, review & rules routes

MethodPathTokenParametersWhat it does
POST/review/approveread-writestaged_id (required); category_id, confirm_checkpointPost a staged row, filed into the envelope given. Inside a reconciled period it asks first (200).
POST/review/mergeread-writestaged_id (required); confirm_checkpointMerge a staged row into the transaction you already had. Inside a reconciled period it asks first (200).
POST/review/rejectread-writestaged_id (required)Dismiss a staged row.
GET/importread—The import screen: paste, upload, saved mappings and the import log.
POST/import/profiles/:id/deleteread-write—Forget a saved column mapping.
GET/rulesread—Rules, and rules the app proposes from what you file.
POST/rules/testread-writename, field, op, value, category_idShow what a draft rule would match among recent transactions. Saves nothing. An unknown field or test is 422.
POST/rules/newread-writethe same as /rules/testSave a rule. It applies to anything imported from then on.
POST/rules/:id/applyread-writeconfirmWithout confirm=1, a preview (200) of what it would change in your history; with it, apply it.
POST/rules/confirm
/rules/dismiss
read-writerule_id (required)Accept or dismiss a proposed rule.
POST/rules/:id/deleteread-write—Delete a rule.
GET/payeesread—Payees, with how often and how much.
POST/payees/mergeread-writeloser_id, winner_id (required)Merge one payee into another; every raw string comes across.

Household & categories

Who has put what toward the shared money, and the envelopes themselves.

More household & categories routes

MethodPathTokenParametersWhat it does
GET/householdreadmonthWhat each member has committed to the shared budget this month.
POST/household/pick-upread-writeenvelope_id, amount (required); monthCommit more from your own household envelope. Another budget's envelope is 403; a non-positive amount is 422.
POST/household/call-it-evenread-writeenvelope_id, amount (required); giving_category_id, note, monthClose part of a balance by agreement; it becomes spending on the giving side.
GET/categoriesreadbudgetCategories and groups for the budget being viewed.
POST/groups/newread-writename (required)Add a category group to the budget being viewed.
POST/groups/:id/rename
/groups/:id/delete
read-writename (rename)Rename a group, or remove it. A group must be empty to be deleted.
POST/categories/newread-writegroup_id, name (required)Add a category. A group the app manages is refused.
POST/categories/:id/renameread-writename (required)Rename a category.
POST/categories/:id/hideread-writehiddenhidden=1 hides it, keeping its balance and history; anything else unhides it.
POST/categories/:id/deleteread-writeremap_toDelete a category, moving its history to remap_to.
POST/categories/:id/mergeread-writewinner_id (required)Merge into another category: assignments summed month by month, history and targets moved across. Refuses a payment category and a merge across budgets.
POST/categories/:id/targetread-writeamount, kind (monthly or refill), target_dateSet or clear a category's target. A date makes it a by-date target; an empty amount removes it.
POST/categories/:id/reorder
/groups/:id/reorder
read-writedirectionMove a category or group up or down.

Schedules, goals & loans

What is coming, what you are saving toward, and what you owe a lender. The loan routes answer 404 when FEATURE_LOANS is off, and a private loan answers 404 to anyone but its holder.

More schedules, goals & loans routes

MethodPathTokenParametersWhat it does
GET/schedulesreaddays (1 to 3650; default 60), budgetRecurring items and the cashflow calendar. More than 3650 days is 422.
GET/schedules/newread—The new-schedule form.
POST/schedules/newread-writename (required); amount, direction, account_id, category_id or envelope lines, recurrence, recurrence_ordinal, recurrence_weekday, next_due, is_subscriptionAdd a schedule, money in or out. An unknown recurrence is refused, not read as monthly. With monthly-nth-weekday, the ordinal (1 to 4, or −1 for last) and weekday (0 = Sunday) say which day.
POST/schedules/confirmread-writename (required); amount (paise), payee_id, account_id, category_id, recurrence, next_dueTurn a detected schedule into a real one. An amount the suggestion could not have made is 422.
POST/schedules/dismissread-writepayee_id (required)Stop suggesting that schedule.
POST/schedules/:id/editread-writethe /schedules/new fieldsChange a schedule, including its envelope lines.
POST/schedules/:id/deleteread-write—Remove a schedule. Everything it already recorded stays.
POST/schedules/:id/paid
/schedules/:id/skip
read-write—Mark this occurrence paid (or arrived), or skip it.
GET/goalsread—Savings goals.
POST/goals/newread-writename (required); target_amount, target_date, budget_idAdd a goal.
POST/goals/:id/editread-writename (required); target_amount, target_dateChange a goal.
POST/goals/:id/completeread-writeresolution (spend, roll or release); with roll: name, target_amount, target_dateComplete a goal, saying what happens to its money.
POST/goals/:id/deleteread-write—Delete a goal.
GET/loansread—Loans.
GET/loans/:idread—One loan: schedule, payments, interest.
GETPOST/loans/newread / read-writelender, loan_type, annual_rate, tenure_months (required); sanctioned, sanction_date, current_outstanding, history_from, first_instalment_date, interest_model, moratorium_months, nickname, holder_member_id, visibility, disbursement_destination, disbursement_account_id, repayment_account_idThe form, and creating a loan.
GETPOST/loans/what-ifread / read-writerate, months (required on POST); principal, amount, at_monthPrepayment and rate what-if. Records nothing.
POST/loans/:id/disburseread-writeamount (required); date, destination, destination_account_idRecord a tranche drawn down.
GETPOST/loans/:id/payread / read-writeamount, principal, interest, date, from_account_idRecord an instalment.
GETPOST/loans/:id/prepayread / read-writeamount, at_month, mode, charge, funding_category_id, previewPreview or record a prepayment.
GETPOST/loans/:id/rateread / read-writeeffective_from, annual_rate_pct (required on POST); keep, notePreview both options a rate reset allows, then record one.
GETPOST/loans/:id/statementread / read-writelender_outstanding (required); as_of, interest_paid_ytd, instalments_remainingRecord what the lender's statement says, to check the schedule against. A negative figure is 422.
POST/loans/:id/holderread-writeholder_member_id, visibilityChange whose loan it is and whether it is private.
POST/loans/:id/propertyread-writeproperty_use (self-occupied, let-out, or empty)What the house the loan paid for is used for. A self-occupied house's accrued interest goes into its holder's tax estimate under 24(b); let-out is shown, not deducted.
POST/loans/:id/settleread-writesettlement, settlement_account_id, charge, charge_account_id, charge_category_idSettle a loan early, recording the lender's foreclosure charge.
POST/loans/:id/closeread-write—File away a paid-off loan.
GET/loans/:id/schedule.csvread—The amortisation schedule, as CSV.

Family lending

Money lent to, or borrowed from, family — kept apart from the budget's envelopes. A private arrangement answers 404 to anyone but its holder.

More family lending routes

MethodPathTokenParametersWhat it does
GET/familyread—Everything lent and borrowed.
GET/family/:idreadconfirm (write-off shows the write-off question)One arrangement and its history.
POST/family/newread-writecounterparty (required); agreed_total, note, holder_member_id, visibilityStart an arrangement.
POST/family/:id/advance
/family/:id/repayment
read-writeamount, account_id (required); dateRecord money going out, or coming back.
POST/family/:id/write-offread-writecategory_id (required); confirmWrite off what is left, as spending from an envelope. Asks first without confirm=1.
POST/family/:id/closeread-write—Close it, keeping the history.

Portfolio & net worth

Holdings, hand-valued assets and the net-worth history. Every route here answers 404 when FEATURE_ASSETS is off.

More portfolio & net worth routes

MethodPathTokenParametersWhat it does
GET/portfolioread—Holdings, XIRR and other assets.
GET/portfolio/:idread—One instrument: lots, prices and gains.
GETPOST/portfolio/addread / read-writeGET: q, instrument, account. POST: name, account_id (required); instrument_id, scheme_code, symbol, kind, currency, listed, units, unit_price, amount, fees, fx_rate, trade_date, from_account_id, category_idSearch for an instrument and buy it.
GETPOST/portfolio/:id/sellread / read-writeunits (required); price, charges, fx_rate, date, to_account_idRecord a sale, realised first in, first out.
GETPOST/portfolio/casread / read-writestatement (multipart PDF), account_id (required); passwordRead a CDSL consolidated account statement and show what it found.
POST/portfolio/cas/confirmread-writetoken (required); schemeImport the holdings from a parsed CAS.
GET/portfolio/allocationread—Allocation by class, region and currency.
POST/portfolio/instrument/:id/classifyread-writeasset_classSet an instrument's asset class.
POST/portfolio/instrument/:id/listedread-writelisted, holding_idSay whether a bond is listed. Listed and held over 12 months is taxed at 12.5%; unlisted or unknown at the slab rate.
GETPOST/portfolio/:id/priceread / read-writeprice, as_ofRecord a price for one instrument.
GETPOST/portfolio/:id/splitread / read-writeratio (required); kind, dateRecord a split or bonus issue, for every open holding. The same action on the same date is refused a second time.
GETPOST/portfolio/:id/mergeread / read-writeratio (required); into_instrument_id, dateRecord a merger or scheme amalgamation. Cost and purchase dates carry forward.
POST/portfolio/refreshread-write—Fetch fresh prices.
GETPOST/portfolio/asset/newread / read-writename, subtype (required); value, as_of, holder_member_id, visibility, paid_from, paid_categoryAdd a hand-valued asset.
GETPOST/portfolio/asset/:id/addread / read-writespent (required); on, paid_from, paid_category, new_valueBuy more of a hand-valued asset: the payment leaving an account and the new value.
GETPOST/portfolio/asset/:id/disposeread / read-writeproceeds, on, into_account, remainingRecord that an asset is gone, and any money arriving. Values it at zero and closes the account; the history stays.
GETPOST/portfolio/asset/:id/revalueread / read-writevalue (required); as_ofSay what one asset is worth.
GETPOST/portfolio/valuationsread / read-writevalue-<id>, asof-<id> per assetUpdate every hand-valued asset in one sitting. Blank ones are left alone.
GET/net-worthread—Net worth by group — owed to family and other liabilities included — with a change breakdown that adds up to the total.
POST/net-worth/snapshotread-write—Take a dated snapshot.
GET/firereadage, swr, ret, locked, whoseFinancial-independence projection from trailing spending.

Insurance

The policy register: premiums due, paid, lapsed or ended. Only the last four characters of a policy number are kept. A private policy answers 404 to anyone but its holder.

More insurance routes

MethodPathTokenParametersWhat it does
GET/insuranceread—The register.
GET/insurance/:idread—One policy.
POST/insurance/new
/insurance/:id/edit
read-writeinsurer (required); kind, policy_number, sum_assured, premium, frequency, next_due, start_date, end_date, nominee, covered, tax_group, senior, category_id, holder_member_id, visibility, noteRecord or change a policy.
POST/insurance/:id/paidread-writebackMove the next due date on by the premium frequency. Posts no transaction.
POST/insurance/:id/statusread-writestatus (lapsed, ended or active)Change a policy's status. A lapsed or ended policy is kept.
POST/insurance/:id/deleteread-write—Remove a policy recorded by mistake (undoable from Activity).

Tax

An income-tax estimate for the signed-in member and one financial year — always your own, never another member's.

More tax routes

MethodPathTokenParametersWhat it does
GET/taxreadfy (the year it starts, e.g. 2026), from (policies fills 80C/80D from the insurance register)The estimate: slab tax, capital gains, 24(b) home-loan interest, TDS and challans netted to a balance or refund, and advance-tax instalments checked against what was paid by each date.
POST/taxread-writefy (required); gross, s80c, s80d, s80d_senior, s80d_parents, s80d_parents_senior, s80d_checkup, s80d_parents_checkup, other, hra_received, hra_rent, hra_basic, hra_metro, s24b, tdsSave the declaration. A blank s24b uses the interest accrued on loans marked self-occupied; a figure, even 0, is yours. A year whose rates the app does not have is refused.
POST/tax/paymentsread-writefy, kind (advance or self-assessment), date, amount, noteRecord a tax payment of your own. Advance tax must be dated inside its year.
POST/tax/payments/:id/deleteread-write—Remove one. Another member's payment is not found.

Months, reports & query

Closing a month, and reading the past back.

GET /query Filter transactions

The filterable, groupable table. Totals are summed over every row that matched, not the page rendered — the response says how many matched.

Scope

read or higher

Parameters

NameTypeDescription
periodstringoptionalthis-month (the default), last-month, last-3, last-12, fy, fy-prev or calendar.
categorystringoptionalRestrict to one envelope, by id.
accountstringoptionalRestrict to one account, by id.
qstringoptionalSubstring match on payee, memo, envelope, tags and the raw imported text.
scopestringoptionalA budget id, or all (the default).
group_bystringoptionalcategory (the default), payee, account, owner, month or tag.

Responses

  • 200 — The rendered screen, as HTML.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 429 — Rate limit exceeded. Retry after the interval in the response.

Example

curl -sS "https://pathayam.yourdomain.com/query?period=last-12&group_by=category" \
  -H "Authorization: Bearer bgt_…"
GET /query.csv Query results as CSV

The same filter, as CSV, uncapped. The figures match what the screen showed. A text cell that starts with =, +, -, @, a tab or a carriage return is prefixed with ', so a spreadsheet opens it as text rather than running it; plain numbers are left alone.

Scope

read or higher

Parameters

NameTypeDescription
…optionalAccepts every parameter /query does.

Responses

  • 200 — text/csv.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.

Example

curl -sS "https://pathayam.yourdomain.com/query.csv?period=fy" \
  -H "Authorization: Bearer bgt_…" -o spend.csv

More months, reports & query routes

MethodPathTokenParametersWhat it does
GET/searchreadthe /query parametersThe same table, titled Search.
GET/monthsread—Month-close history.
GET/months/:month/closeread—The close ritual for one month.
POST/months/:month/closeread-writenote, budgetClose a month. Not a month is 404.
POST/months/:month/reopenread-writebudgetReopen a closed month.
GET/reportsreadperiod (default last-12), scopeIncome against spending, trends and loan interest.

Settings, activity & health

Preferences, the change log and the instance's own health.

GET /activity Every change made

The append-only event log, newest first, with the undo for each entry — the latest hundred you can see. An undo is itself an entry — nothing is edited away.

Scope

read or higher

Parameters

NameTypeDescription
entitystringoptionalOnly changes to one kind of record, such as transaction.

Responses

  • 200 — The rendered screen, as HTML.
  • 401 — No token, or one that is revoked, expired or belongs to a removed member.
  • 429 — Rate limit exceeded. Retry after the interval in the response.

Example

curl -sS https://pathayam.yourdomain.com/activity \
  -H "Authorization: Bearer bgt_…"
POST /activity/:id/undo Undo one event

Reverses a single recorded change within thirty days. Refuses when the record is load-bearing for something later — a loan instalment or a portfolio lot — and says which. When the same record has changed since, it refuses unless you send force=1, which discards those later changes. An undo that would move a reconciled balance answers with the “Already reconciled” question first.

Scope

read-write

Parameters

NameTypeDescription
idpathrequiredEvent id from Activity.
forcestringoptional1 undoes it even though the record has changed since.
confirm_checkpointstringoptional1 answers the “Already reconciled” question.

Responses

  • 303 — Undone.
  • 200 — The “Already reconciled” question, as HTML. Nothing was undone.
  • 404 — No such change, or one you cannot see.
  • 422 — Refused — older than the thirty-day window, changed since, or something depends on it; the message says which.

Example

curl -sS -X POST https://pathayam.yourdomain.com/activity/evt_88c/undo \
  -H "Authorization: Bearer bgt_…" \
  -H "Idempotency-Key: $(uuidgen)"
GET /healthz Health probe

For your uptime monitor. Signed out, it says only whether the instance can serve — nothing about the data. With a token or a session it adds every health check, by name and state.

Scope

public — no token needed

Responses

  • 200 — application/json — {"status":"ok","serving":true,"version":…}.
  • 503 — Started, but the database does not answer.

Example

curl -sS https://pathayam.yourdomain.com/healthz

More settings, activity & health routes

MethodPathTokenParametersWhat it does
GET/settingsread—Household and personal settings.
POST/settings/themeread-writetheme (light, dark, system), return_toSet your theme. Anything else is 400.
POST/settings/theme-toggleread-writeseen, return_toFlip between light and dark, returning to a same-site path.
POST/settings/learningread-writeenabledenabled=1 suggests rules from what you file; anything else stops new suggestions. Confirmed rules keep working.
POST/settings/overspend-modelread-writemodel (reduce-rta or carry-negative)How cash overspending carries into the next month. Recomputes the history; anything else is 400.
POST/settings/identityread-writename (required); pan, dob, mobile, customer_id_axis, customer_id_hdfc, hsbc_card_last6; or clear=1Save or clear the details statement passwords are derived from. Never exported or logged. Refused on the demo.
POST/settings/digestread-writekind (repeated)Which digest notices you want; the rest are muted.
GET/healthread—The health dashboard, as HTML.
POST/health/backup
/health/verify
read-write—Run a backup now, or verify that the latest restores.
POST/gmail/fetchread-write—Fetch statements from the linked Gmail now. 403 while viewing as another member; 400 or 502 when Google refuses or cannot be reached.
GET/privacy
/terms
public—The privacy policy and terms of this instance.

Exports

Everything back out, in open formats. In every CSV here a text cell that starts with =, +, -, @, a tab or a carriage return is prefixed with ', so a spreadsheet opens it as text; plain numbers are left alone.

GET /export.json Export everything

The complete dataset you can see, in an open format: accounts, transactions, assignments, lots, loans and events. Not a summary — but another member's private accounts and envelopes, and what is recorded in them, are left out. The statement identity and any Gmail refresh token are excluded, and a test asserts their absence.

Scope

read or higher

Responses

  • 200 — application/json, sent as a download.

Example

curl -sS https://pathayam.yourdomain.com/export.json \
  -H "Authorization: Bearer bgt_…" -o pathayam-backup.json

More exports routes

MethodPathTokenParametersWhat it does
GET/export.csvread—Every transaction you can see, as CSV.
GET/portfolio/holdings.csv
/portfolio/lots.csv
/portfolio/prices.csv
read—Holdings, first-in-first-out lots, and price history. 404 when FEATURE_ASSETS is off.
GET/net-worth.csvread—The dated net-worth history.

Session only

These answer 403 to any token, because their path is on the deny-list below. Use them from a browser.

MethodPathTokenParametersWhat it does
GET/signinpublicnextThe sign-in page. Signed in, it redirects to /.
GET/auth/google
/auth/google/callback
/auth/oidc
/auth/oidc/callback
publicnext; code on callbacksGoogle and OpenID Connect sign-in. 503 when not configured; 429 after too many attempts.
POST/auth/passwordpublicemail, password, nextPassword sign-in. 404 unless LOCAL_LOGIN is set or a password already exists; 401 on a wrong one; 429 after too many attempts.
GETPOST/auth/first-runpublicname, email, passwordThe first member of an empty household. 404 unless LOCAL_LOGIN is set and no member exists.
GETPOST/auth/setup/:tokenpublicpassword, confirmA one-time password link, opened signed out: choose a first password and be signed in. Works once, for 72 hours. Any unusable link (unknown, used, expired, or no longer allowed) answers the same 410. The POST is rate limited like /auth/password.
POST/auth/devpublicmember_id (required)Development sign-in. 404 unless DEV_LOGIN is set.
POST/demo/enterpublicmember_idSign in to the demo as a fictional member, for one day. 404 unless DEMO_MODE is set.
POST/signoutsession—Sign out.
GET/settings/passwordsession—The password form.
POST/settings/passwordsessionpassword, confirm (required); currentSet or change your own password. Changing one needs the current password.
POST/sessions/revokesessionsession_id (required)Sign one of your devices out.
GET/tokenssession—Your API tokens.
POST/tokenssessionname (required); scope, expires_in_days (1 to 3650, or empty for never)Mint a token, shown once. 403 while viewing as another member.
POST/tokens/:id/revokesession—Revoke a token. It stops working at once.
POST/members/invitesessionemail (required)Let someone in. Refused on the demo.
GET/members/:id/removesession—What removing a member does, and how the balance between you can end.
POST/members/:id/removesessionresolutionRemove a member. With a balance outstanding, 400 until resolution says how it ends. Clears their password. Refused on the demo.
POST/members/:id/password-linksession—Make a one-time link that sets another member’s first password, shown once on the page it returns (200, never a redirect). 422 for yourself, a member with a password, one who has signed in before, or one holding records you cannot see; those come from npm run password-link on the server. Refused on the demo and while viewing as somebody.
POST/impersonate/start
/impersonate/writes
/impersonate/exit
sessionmember_id; allow, return_toView as another member, read-only unless writes are allowed. 404 unless ADMIN_DEBUG is set.
GET/gmail/connect
/gmail/callback
sessionstate, code on the callbackLink a Gmail mailbox.
POST/gmail/disconnectsession—Unlink it.

Writes and the same-origin check

Every write — any method that is not GET, HEAD or OPTIONS — passes one check before it reaches a route, so it covers routes added tomorrow too.

RuleWhat it means
Bearer exemptionA request with Authorization: Bearer … and no session cookie skips the whole check. A browser cannot be tricked into sending one on somebody's behalf, so your scripts need nothing extra. A request that also carries a session cookie is held to the rules below, whatever it says in Authorization.
The hostThe host the request was addressed to must be BASE_URL's hostname, with BASE_URL's port or none — or an IP address or localhost, which no DNS answer can re-point. Anything else is 403: That request was addressed to a different host than BASE_URL. This is what stops DNS rebinding.
Behind a proxyThe host is the Host header, or the right-most X-Forwarded-Host entry when TRUST_PROXY is set. A reverse proxy must pass one of them through, or every write from a browser is refused.
OriginWhen present it must be BASE_URL's origin or the origin the request was addressed by. Anything else is 403: That request came from somewhere else.
RefererWith no Origin, a Referer from one of the same origins will do. Referrer-Policy: same-origin means a same-origin form carries one and a cross-site one does not. With neither header, 403.

A script in a page of the app that uses the session cookie needs no work either: fetch from that page sends a matching Origin on its own.

What a token cannot reach

A deny-list of path prefixes rather than a note in a review checklist. A token that could mint tokens turns one leak into permanent access, and a token that could change the allow-list turns it into somebody else's household.

PrefixWhy it is blocked
/members, /settings/membersThe household allow-list: /members/invite and /members/:id/remove. A token must not change who is allowed in.
/tokensA token must not mint or revoke tokens, including itself.
/impersonateImpersonation is a session-only, logged, human action.
/authSign-in, SSO, and the development bypass state.
/signoutThere is nothing for a token to sign out of.
/settings/passwordA password is a way in that does not need the token, and would outlive revoking it.
/sessionsSigning the member's devices out is a human decision.
/gmail/connect, /gmail/callback, /gmail/disconnectLinking a mailbox changes what can reach the household. /gmail/fetch stays reachable.

Leaving should be as easy as arriving

One endpoint returns everything in an open format, and the database is a SQLite file you can open with anything. Nothing about the format needs this app to read it.

Read the guides