heybmoDocsOpen HeyBMO

API reference

Expenses

Spending and income, categories, budgets, recurring bills and receipts.

Base URL https://app.heybmo.comFor agents: llms.txt · OpenAPI

Operations

WritePOST /api/ops/expenses.add

Log spending or income

Log up to 100 amounts spent or received in one call (log several at once rather than one call each). Each has an amount, a currency (default: the home currency), a day (default: today), a category by name (default: Other), an optional merchant and note, and a receipt (receiptId) to put on it. Amounts in another currency are converted with that day’s stored rate, or the bank’s rate when given. If the person has no home currency yet, the first currency given becomes it; say so.

MCP tool expenses_addCLI heybmo expenses addLimit writeInput
entriesrequiredlist of objectUp to 100 items.Each item:
amountrequirednumberHow much, in the currency’s normal units (45.5 means 45.50), always positive.
currencytextA three-letter currency code (ISO 4217), e.g. MAD, USD, EUR, GBP. A currency named without its country is the home currency when the names match; otherwise ask which one.
daydateA day, as YYYY-MM-DD.
kindspending | incomespending (money out, the default) or income (money in). Default "spending".
categorytextA category name (expenses.categories). Up to 60 characters.
merchanttextDefault "". Up to 120 characters.
notetextDefault "". Up to 500 characters.
ratenumberThe rate the bank used: units of the home currency per one unit of this currency. Without it, the stored rate of that day is used.
receiptIdidA receipt (expenses.attachReceipt) to put on it.
addMissingCategoriestrue or falseMake categories that do not exist yet instead of refusing them. Default false.
curl https://app.heybmo.com/api/ops/expenses.add \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"entries":[{"amount":45,"category":"Food","merchant":"Café Clock","note":"Lunch"}]}}'
200 response
{
  "output": {
    "entries": [
      {
        "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "kind": "spending",
        "amount": 0,
        "currency": "…",
        "day": "2026-11-09",
        "category": "…",
        "categoryId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "merchant": "…",
        "note": "…",
        "homeAmount": 0,
        "homeCurrency": "…",
        "rate": 0,
        "rateDay": "2026-11-09",
        "rateSource": "same",
        "checkAmount": false,
        "recurringId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "receiptIds": [
          "01a19279-e9c3-7c73-9705-94f082fa3a65"
        ],
        "revision": 0,
        "createdAt": "2026-11-09T09:00:00.000Z",
        "updatedAt": "2026-11-09T09:00:00.000Z"
      }
    ],
    "homeCurrency": "…",
    "homeCurrencySet": false
  }
}
WritePOST /api/ops/expenses.update

Change entries

Change up to 100 entries: amount, currency, day, kind, category, merchant, note, the bank’s rate (null goes back to the day’s stored rate), or add a receipt (receiptId). Only what is given changes.

MCP tool expenses_updateCLI heybmo expenses updateLimit writeInput
entriesrequiredlist of objectUp to 100 items.Each item:
entryIdrequiredidAn entry id (from expenses.entries or expenses.add).
revisionnumberThe revision you last read. If it changed since, the change is refused so nothing is overwritten.
amountnumberHow much, in the currency’s normal units (45.5 means 45.50), always positive.
currencytextA three-letter currency code (ISO 4217), e.g. MAD, USD, EUR, GBP. A currency named without its country is the home currency when the names match; otherwise ask which one.
daydateA day, as YYYY-MM-DD.
kindspending | incomespending (money out, the default) or income (money in).
categorytextUp to 60 characters.
merchanttextUp to 120 characters.
notetextUp to 500 characters.
ratenumber or null
receiptIdidA receipt (expenses.attachReceipt) to add to it.
addMissingCategoriestrue or falseMake categories that do not exist yet instead of refusing them. Default false.
curl https://app.heybmo.com/api/ops/expenses.update \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"entries":[{"entryId":"01a19279-e9c3-7c73-9705-94f082fa3a65","amount":35}]}}'
200 response
{
  "output": {
    "entries": [
      {
        "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "kind": "spending",
        "amount": 0,
        "currency": "…",
        "day": "2026-11-09",
        "category": "…",
        "categoryId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "merchant": "…",
        "note": "…",
        "homeAmount": 0,
        "homeCurrency": "…",
        "rate": 0,
        "rateDay": "2026-11-09",
        "rateSource": "same",
        "checkAmount": false,
        "recurringId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "receiptIds": [
          "01a19279-e9c3-7c73-9705-94f082fa3a65"
        ],
        "revision": 0,
        "createdAt": "2026-11-09T09:00:00.000Z",
        "updatedAt": "2026-11-09T09:00:00.000Z"
      }
    ]
  }
}
WritePOST /api/ops/expenses.moveToCategory

Move matching entries to a category

Move every entry that matches the filters of expenses.entries (matching) to one category, however many there are. The matching entries must all be spending or all income: add kind to matching when unsure. For a few entries named by id, expenses.update works too.

MCP tool expenses_moveToCategoryCLI heybmo expenses moveToCategoryLimit writeSafe to retryInput
matchingrequiredobjectThe filters of expenses.entries.
categoryrequiredtextThe category to move them to (a name). Up to 60 characters.
addMissingCategoriestrue or falseMake the category when it does not exist yet instead of refusing. Default false.
curl https://app.heybmo.com/api/ops/expenses.moveToCategory \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"matching":{"query":"uber","kind":"spending"},"category":"Transport"}}'
200 response
{
  "output": {
    "moved": 0,
    "category": "…"
  }
}
ReadPOST /api/ops/expenses.entries

List or search entries

Entries spent or received, newest day first (or by amount, category or merchant), filtered by days, kind, categories, receipt, currency or words in the merchant and note. total is how many match across every page. For sums use expenses.totals instead of adding up pages.

MCP tool expenses_entriesCLI heybmo expenses entriesLimit readSafe to retryInput
fromdateFirst day, included.
todateLast day, included.
kindspending | incomeOnly spending or only income (default: both).
categorytextOnly this category (a name). Up to 60 characters.
categorieslist of textOnly entries in any of these categories (names). Up to 100 items.
hasReceipttrue or falsetrue: only entries with a receipt; false: only entries without one.
currencytextOnly entries in this currency.
querytextWords in the merchant or note. Up to 200 characters.
checkAmounttrue or falseOnly entries marked to check (true).
sortday | amount | category | whereday, amount (in the home currency), category (its name) or where (merchant). Default "day".
directionasc | descDefault: newest and largest first, names from A.
limitnumberDefault 50.
cursortextnextCursor from the previous page.
curl https://app.heybmo.com/api/ops/expenses.entries \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"from":"2026-10-01","to":"2026-10-31","category":"Food"}}'
200 response
{
  "output": {
    "entries": [
      {
        "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "kind": "spending",
        "amount": 0,
        "currency": "…",
        "day": "2026-11-09",
        "category": "…",
        "categoryId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "merchant": "…",
        "note": "…",
        "homeAmount": 0,
        "homeCurrency": "…",
        "rate": 0,
        "rateDay": "2026-11-09",
        "rateSource": "same",
        "checkAmount": false,
        "recurringId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "receiptIds": [
          "01a19279-e9c3-7c73-9705-94f082fa3a65"
        ],
        "revision": 0,
        "createdAt": "2026-11-09T09:00:00.000Z",
        "updatedAt": "2026-11-09T09:00:00.000Z"
      }
    ],
    "total": 0,
    "nextCursor": "…",
    "homeCurrency": "…",
    "waitingForRates": 0
  }
}
ReadPOST /api/ops/expenses.totals

Add up spending and income

Totals of spending and income in the home currency, for any days, grouped by category, month or day (or one total), with the same filters as expenses.entries. One call answers “how much did I spend on food in October”; never add up pages of entries instead. With limit, only the largest groups (e.g. the top 3 categories), and groupCount says how many there are.

MCP tool expenses_totalsCLI heybmo expenses totalsLimit readSafe to retryInput
fromdateFirst day, included.
todateLast day, included.
kindspending | incomeOnly spending or only income (default: both).
categorytextOnly this category (a name). Up to 60 characters.
categorieslist of textOnly entries in any of these categories (names). Up to 100 items.
hasReceipttrue or falsetrue: only entries with a receipt; false: only entries without one.
currencytextOnly entries in this currency.
querytextWords in the merchant or note. Up to 200 characters.
checkAmounttrue or falseOnly entries marked to check (true).
groupBycategory | month | day | noneDefault "category".
limitnumberOnly this many groups, the largest spending first (then income); groupCount says how many there are. Without it: every group, in key order.
curl https://app.heybmo.com/api/ops/expenses.totals \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"from":"2026-10-01","to":"2026-10-31","groupBy":"category"}}'
200 response
{
  "output": {
    "spending": 0,
    "income": 0,
    "net": 0,
    "entries": 0,
    "currency": "…",
    "groups": [
      {
        "spending": 0,
        "income": 0,
        "net": 0,
        "entries": 0,
        "key": "…"
      }
    ],
    "groupCount": 0,
    "waitingForRates": 0
  }
}
ReadPOST /api/ops/expenses.categories

List categories

The categories entries can have, spending ones and income ones. A space starts with Food, Transport, Home, Bills, Health, Fun, Shopping and Other, plus Salary and Other income.

MCP tool expenses_categoriesCLI heybmo expenses categoriesLimit readSafe to retryInput
includeArchivedtrue or falseDefault false.
curl https://app.heybmo.com/api/ops/expenses.categories \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{}}'
200 response
{
  "output": {
    "categories": [
      {
        "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "name": "…",
        "kind": "spending",
        "archived": false,
        "entries": 0
      }
    ]
  }
}
WritePOST /api/ops/expenses.addCategory

Add a category

Add a spending (default) or income category.

MCP tool expenses_addCategoryCLI heybmo expenses addCategoryLimit writeInput
namerequiredtextUp to 60 characters.
kindspending | incomespending (money out, the default) or income (money in). Default "spending".
curl https://app.heybmo.com/api/ops/expenses.addCategory \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"name":"Pets"}}'
200 response
{
  "output": {
    "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
    "name": "…",
    "kind": "spending",
    "archived": false
  }
}
WritePOST /api/ops/expenses.updateCategory

Rename or archive a category

Rename a category (its entries follow) or archive it: an archived category is no longer offered, but its entries keep it. archived: false brings it back.

MCP tool expenses_updateCategoryCLI heybmo expenses updateCategoryLimit writeSafe to retryInput
categoryIdrequiredidA category id (from expenses.categories).
nametextUp to 60 characters.
archivedtrue or false
curl https://app.heybmo.com/api/ops/expenses.updateCategory \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"categoryId":"01a19279-e9c3-7c73-9705-94f082fa3a65","name":"Groceries"}}'
200 response
{
  "output": {
    "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
    "name": "…",
    "kind": "spending",
    "archived": false
  }
}
ReadPOST /api/ops/expenses.budgets

See budgets

Each monthly budget with what was spent that month (default: this month), what is left and the share used, in the home currency. The budget for all spending comes first, then the most used (over budget first), then by category; limit keeps the first few, and total says how many there are.

MCP tool expenses_budgetsCLI heybmo expenses budgetsLimit readSafe to retryInput
monthtextA month, as YYYY-MM.
limitnumberHow many budgets to answer, the most relevant first. Default 50.
curl https://app.heybmo.com/api/ops/expenses.budgets \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{}}'
200 response
{
  "output": {
    "budgets": [
      {
        "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "category": "…",
        "budget": 0,
        "currency": "…",
        "amount": 0,
        "spent": 0,
        "left": 0,
        "share": 0,
        "alertAt": 0
      }
    ],
    "total": 0
  }
}
WritePOST /api/ops/expenses.setBudget

Set a monthly budget

Set the monthly budget for a spending category, or for all spending when no category is given, in the home currency. Setting it again changes the amount. alertAt is the heads-up share in percent (1 to 100, 80 by default, null for none): the person gets a heads-up in their inbox there and again at 100% (built in, nothing else to set up), and expenses.budgetReached fires at the same moments for automations.

MCP tool expenses_setBudgetCLI heybmo expenses setBudgetLimit writeSafe to retryInput
categorytextA spending category name; none means all. Up to 60 characters.
amountrequirednumberHow much, in the currency’s normal units (45.5 means 45.50), always positive.
alertAtnumber or nullHeads-up share in percent; null for none. Left as it was when not given (80 for a new budget).
curl https://app.heybmo.com/api/ops/expenses.setBudget \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"category":"Food","amount":2000}}'
200 response
{
  "output": {
    "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
    "category": "…",
    "budget": 0,
    "currency": "…",
    "amount": 0,
    "spent": 0,
    "left": 0,
    "share": 0,
    "alertAt": 0
  }
}
DeletePOST /api/ops/expenses.removeBudget

Remove a budget

Remove a monthly budget for good (entries are not touched). Ask the person first.

Deletes for good. The first call changes nothing and answers CONFIRMATION_REQUIRED with what would be deleted; call again with confirm: true once the person agrees.

MCP tool expenses_removeBudgetCLI heybmo expenses removeBudgetLimit deleteSafe to retryInput
budgetIdrequiredidA budget id (from expenses.budgets).
confirmtrue or falseSet to true once the person agreed. Without it nothing is deleted and you get a description of what would be.
curl https://app.heybmo.com/api/ops/expenses.removeBudget \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"budgetId":"01a19279-e9c3-7c73-9705-94f082fa3a65"}}'
200 response
{
  "output": {
    "removed": "01a19279-e9c3-7c73-9705-94f082fa3a65"
  }
}
DeletePOST /api/ops/expenses.delete

Move entries to the trash

Move entries to the trash: the ones named (entryIds), or every entry that matches the filters of expenses.entries (matching), however many. They no longer count in totals or budgets, and can be restored with expenses.restore for 30 days (everything one call moved at once, with its deletedAt), then they are deleted for good.

Goes to the trash: it can be restored for 30 days.

MCP tool expenses_deleteCLI heybmo expenses deleteLimit deleteSafe to retryInput
entryIdslist of idUp to 100 items.
matchingobjectEvery live entry that matches these filters (as in expenses.entries).
curl https://app.heybmo.com/api/ops/expenses.delete \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"entryIds":["01a19279-e9c3-7c73-9705-94f082fa3a65"]}}'
200 response
{
  "output": {
    "deleted": [
      "01a19279-e9c3-7c73-9705-94f082fa3a65"
    ],
    "count": 0,
    "deletedAt": "2026-11-09T09:00:00.000Z",
    "restoreUntil": "2026-11-09T09:00:00.000Z"
  }
}
WritePOST /api/ops/expenses.restore

Restore entries from the trash

Bring entries back from the trash: the ones named (entryIds), or everything one expenses.delete call moved there (its deletedAt). They count in totals and budgets again.

MCP tool expenses_restoreCLI heybmo expenses restoreLimit writeSafe to retryInput
entryIdslist of idUp to 100 items.
deletedAtdate and timeThe deletedAt of an expenses.delete answer: brings back all it moved.
curl https://app.heybmo.com/api/ops/expenses.restore \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"entryIds":["01a19279-e9c3-7c73-9705-94f082fa3a65"]}}'
200 response
{
  "output": {
    "entries": [
      {
        "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "kind": "spending",
        "amount": 0,
        "currency": "…",
        "day": "2026-11-09",
        "category": "…",
        "categoryId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "merchant": "…",
        "note": "…",
        "homeAmount": 0,
        "homeCurrency": "…",
        "rate": 0,
        "rateDay": "2026-11-09",
        "rateSource": "same",
        "checkAmount": false,
        "recurringId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "receiptIds": [
          "01a19279-e9c3-7c73-9705-94f082fa3a65"
        ],
        "revision": 0,
        "createdAt": "2026-11-09T09:00:00.000Z",
        "updatedAt": "2026-11-09T09:00:00.000Z"
      }
    ],
    "count": 0
  }
}
ReadPOST /api/ops/expenses.trash

List the trash

Entries in the trash, most recently deleted first, with until when each can be restored. total is how many the trash holds; nextCursor reads the next page.

MCP tool expenses_trashCLI heybmo expenses trashLimit readSafe to retryInput
limitnumberDefault 50.
cursortextnextCursor from the previous page.
curl https://app.heybmo.com/api/ops/expenses.trash \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{}}'
200 response
{
  "output": {
    "entries": [
      {
        "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "kind": "spending",
        "amount": 0,
        "currency": "…",
        "day": "2026-11-09",
        "category": "…",
        "categoryId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "merchant": "…",
        "note": "…",
        "homeAmount": 0,
        "homeCurrency": "…",
        "rate": 0,
        "rateDay": "2026-11-09",
        "rateSource": "same",
        "checkAmount": false,
        "recurringId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "receiptIds": [
          "01a19279-e9c3-7c73-9705-94f082fa3a65"
        ],
        "revision": 0,
        "createdAt": "2026-11-09T09:00:00.000Z",
        "updatedAt": "2026-11-09T09:00:00.000Z",
        "deletedAt": "2026-11-09T09:00:00.000Z",
        "restoreUntil": "2026-11-09T09:00:00.000Z"
      }
    ],
    "total": 0,
    "nextCursor": "…"
  }
}
DeletePOST /api/ops/expenses.deleteForever

Delete entries in the trash for good

Delete entries that are in the trash for good, with their receipts: the ones named (entryIds), or the whole trash (all: true), however big. It cannot be undone, so ask the person first. Entries not in the trash are refused: expenses.delete moves an entry there.

Deletes for good. The first call changes nothing and answers CONFIRMATION_REQUIRED with what would be deleted; call again with confirm: true once the person agrees.

MCP tool expenses_deleteForeverCLI heybmo expenses deleteForeverLimit deleteSafe to retryInput
entryIdslist of idEntries in the trash to delete for good (from expenses.trash). Up to 100 items.
alltrue or falsetrue: empty the whole trash instead.
confirmtrue or falseSet to true once the person agreed. Without it nothing is deleted and you get a description of what would be.
curl https://app.heybmo.com/api/ops/expenses.deleteForever \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"entryIds":["01a19279-e9c3-7c73-9705-94f082fa3a65"]}}'
200 response
{
  "output": {
    "entries": 0
  }
}
WritePOST /api/ops/expenses.attachReceipt

Attach a receipt

Store a photo or PDF of a receipt, on an entry or on its own. To log a receipt: attach it without entryId, read it with expenses.readReceipt, then log it with expenses.add and its receiptId (a receipt never logged is deleted after 7 days). Chat apps send small files (up to 5 MB) inline as base64 in data. Otherwise give size and sha256 (the CLI fills them from --file <path>): the answer has an upload link to PUT the bytes to, then call expenses.finishReceipt.

MCP tool expenses_attachReceiptCLI heybmo expenses attachReceiptLimit uploadInput
entryIdidThe entry it belongs to, when it is already logged.
namerequiredtextFile name, e.g. receipt.jpg. Up to 255 characters.
contentTyperequiredtextA picture (image/jpeg, image/png, image/heic...) or application/pdf. Up to 200 characters.
datatextThe whole file as base64 (up to 5 MB): stored in one step. Up to 6990508 characters.
sizenumberBytes, when uploading with the link instead.
sha256textHex SHA-256 of the bytes, when uploading.
curl https://app.heybmo.com/api/ops/expenses.attachReceipt \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"name":"receipt.jpg","contentType":"image/jpeg","data":"/9j/4AAQSkZJRg=="}}'
200 response
{
  "output": {
    "receipt": {
      "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
      "entryId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
      "name": "…",
      "contentType": "…",
      "size": 0,
      "status": "pending",
      "read": "pending"
    },
    "upload": {
      "url": "…",
      "method": "PUT",
      "headers": {},
      "expiresAt": "2026-11-09T09:00:00.000Z"
    },
    "finish": {
      "operation": "…",
      "input": {}
    }
  }
}
WritePOST /api/ops/expenses.finishReceipt

Finish attaching a receipt

Confirm a receipt after its bytes were uploaded with the link from attachReceipt.

MCP tool expenses_finishReceiptCLI heybmo expenses finishReceiptLimit writeSafe to retryInput
receiptIdrequiredidA receipt id (from expenses.attachReceipt or an entry).
curl https://app.heybmo.com/api/ops/expenses.finishReceipt \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"receiptId":"01a1927a-1071-7a71-994f-4180839c9a39"}}'
200 response
{
  "output": {
    "receipt": {
      "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
      "entryId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
      "name": "…",
      "contentType": "…",
      "size": 0,
      "status": "pending",
      "read": "pending"
    }
  }
}
ReadPOST /api/ops/expenses.readReceipt

Read a receipt

The text read out of a receipt (words in the photo, or the PDF text), plain guesses of its total, day, currency and shop, and a short link to view it. Text is read a few seconds after upload; until then the answer says so. Check the guesses against the text before logging.

MCP tool expenses_readReceiptCLI heybmo expenses readReceiptLimit readSafe to retryInput
receiptIdrequiredidA receipt id (from expenses.attachReceipt or an entry).
curl https://app.heybmo.com/api/ops/expenses.readReceipt \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"receiptId":"01a1927a-1071-7a71-994f-4180839c9a39"}}'
200 response
{
  "output": {
    "receipt": {
      "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
      "entryId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
      "name": "…",
      "contentType": "…",
      "size": 0,
      "status": "pending",
      "read": "pending"
    },
    "text": "…",
    "textCut": false,
    "guesses": {
      "total": {
        "amount": 0,
        "line": "…"
      },
      "day": {
        "value": "2026-11-09",
        "alternative": "2026-11-09",
        "line": "…"
      },
      "currency": "…",
      "merchant": "…"
    },
    "url": "…",
    "downloadUrl": "…",
    "next": "…"
  }
}
DeletePOST /api/ops/expenses.removeReceipt

Delete a receipt

Delete a receipt file for good. The entry it was on stays. This cannot be undone.

Deletes for good. The first call changes nothing and answers CONFIRMATION_REQUIRED with what would be deleted; call again with confirm: true once the person agrees.

MCP tool expenses_removeReceiptCLI heybmo expenses removeReceiptLimit deleteSafe to retryInput
receiptIdrequiredidA receipt id (from expenses.attachReceipt or an entry).
confirmtrue or falseSet to true once the person agreed. Without it nothing is deleted and you get a description of what would be.
curl https://app.heybmo.com/api/ops/expenses.removeReceipt \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"receiptId":"01a1927a-1071-7a71-994f-4180839c9a39"}}'
200 response
{
  "output": {
    "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
    "deleted": false
  }
}
ReadPOST /api/ops/expenses.recurring

List recurring bills and income

The recurring bills and income (rent, phone, salary...), each with how often it comes back and the next day its entry is written.

MCP tool expenses_recurringCLI heybmo expenses recurringLimit readSafe to retryInput

No input: send an empty object.

curl https://app.heybmo.com/api/ops/expenses.recurring \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{}}'
200 response
{
  "output": {
    "recurring": [
      {
        "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "kind": "spending",
        "amount": 0,
        "currency": "…",
        "category": "…",
        "categoryId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
        "merchant": "…",
        "note": "…",
        "variable": false,
        "repeat": {
          "repeat": "weekly",
          "every": 0,
          "dayOfMonth": 0,
          "weekday": 0,
          "starts": "2026-11-09",
          "until": "2026-11-09"
        },
        "nextDay": "2026-11-09",
        "paused": false,
        "skipDay": "2026-11-09",
        "revision": 0
      }
    ]
  }
}
WritePOST /api/ops/expenses.addRecurring

Add a recurring bill or income

A bill or income that comes back (rent on the 1st, salary on the 25th, a weekly class). From the next matching day on, its entry is written by itself each time, in the morning of the person’s time zone. For a bill whose amount changes (electricity), set variable: each entry repeats the last amount and is flagged checkAmount so the person can correct it.

MCP tool expenses_addRecurringCLI heybmo expenses addRecurringLimit writeInput
noterequiredtextWhat it is, e.g. Rent (written as each entry’s note). Up to 500 characters.
amountrequirednumberHow much, in the currency’s normal units (45.5 means 45.50), always positive.
currencytextA three-letter currency code (ISO 4217), e.g. MAD, USD, EUR, GBP. A currency named without its country is the home currency when the names match; otherwise ask which one.
kindspending | incomespending (money out, the default) or income (money in). Default "spending".
categorytextA category name (default: Other). Up to 60 characters.
merchanttextDefault "". Up to 120 characters.
variabletrue or falseThe amount changes each time. Default false.
repeatweekly | monthly | yearlyweekly, monthly or yearly. Default "monthly".
everynumberEvery N weeks, months or years (default 1). Default 1.
dayOfMonthnumber or "last"Monthly: the day of the month, or last (29-31 fall on the last day of shorter months); default: the day of starts.
weekdaynumberWeekly: 1 = Monday ... 7 = Sunday; default: the weekday of starts.
startsdateThe first day it can happen (default: today).
untildate or nullThe last day it can happen (null: no end).
addMissingCategoriestrue or falseMake the category if it does not exist yet instead of refusing it. Default false.
curl https://app.heybmo.com/api/ops/expenses.addRecurring \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"note":"Rent","amount":4500,"category":"Home","dayOfMonth":1}}'
200 response
{
  "output": {
    "recurring": {
      "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
      "kind": "spending",
      "amount": 0,
      "currency": "…",
      "category": "…",
      "categoryId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
      "merchant": "…",
      "note": "…",
      "variable": false,
      "repeat": {
        "repeat": "weekly",
        "every": 0,
        "dayOfMonth": 0,
        "weekday": 0,
        "starts": "2026-11-09",
        "until": "2026-11-09"
      },
      "nextDay": "2026-11-09",
      "paused": false,
      "skipDay": "2026-11-09",
      "revision": 0
    },
    "homeCurrency": "…",
    "homeCurrencySet": false
  }
}
WritePOST /api/ops/expenses.updateRecurring

Change a recurring bill or income

Change a recurring bill or income: its amount, currency, category, merchant, note, whether the amount varies, how often it comes back, or pause it (paused: true, false to resume). skipDay skips one day only (its nextDay from expenses.recurring, to skip the next one; null cancels). Entries already written stay as they are. Only what is given changes.

MCP tool expenses_updateRecurringCLI heybmo expenses updateRecurringLimit writeInput
recurringIdrequiredidA recurring bill or income id (from expenses.recurring).
revisionnumberThe revision you last read. If it changed since, the change is refused so nothing is overwritten.
notetextUp to 500 characters.
amountnumberHow much, in the currency’s normal units (45.5 means 45.50), always positive.
currencytextA three-letter currency code (ISO 4217), e.g. MAD, USD, EUR, GBP. A currency named without its country is the home currency when the names match; otherwise ask which one.
categorytextUp to 60 characters.
merchanttextUp to 120 characters.
variabletrue or false
pausedtrue or falseStop writing entries (false: start again).
skipDaydate or nullOne day to skip; null cancels it.
repeatweekly | monthly | yearlyweekly, monthly or yearly.
everynumberEvery N weeks, months or years (default 1).
dayOfMonthnumber or "last" or null
weekdaynumber or null
startsdateThe first day it can happen (default: today).
untildate or nullThe last day it can happen (null: no end).
addMissingCategoriestrue or falseDefault false.
curl https://app.heybmo.com/api/ops/expenses.updateRecurring \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"recurringId":"01a19279-e9c3-7c73-9705-94f082fa3a65","amount":4800}}'
200 response
{
  "output": {
    "recurring": {
      "id": "01a19279-e9c3-7c73-9705-94f082fa3a65",
      "kind": "spending",
      "amount": 0,
      "currency": "…",
      "category": "…",
      "categoryId": "01a19279-e9c3-7c73-9705-94f082fa3a65",
      "merchant": "…",
      "note": "…",
      "variable": false,
      "repeat": {
        "repeat": "weekly",
        "every": 0,
        "dayOfMonth": 0,
        "weekday": 0,
        "starts": "2026-11-09",
        "until": "2026-11-09"
      },
      "nextDay": "2026-11-09",
      "paused": false,
      "skipDay": "2026-11-09",
      "revision": 0
    }
  }
}
DeletePOST /api/ops/expenses.removeRecurring

Remove a recurring bill or income

Stop a recurring bill or income for good (to stop it for a while, pause it with expenses.updateRecurring). The entries it already wrote stay. Ask the person first.

Deletes for good. The first call changes nothing and answers CONFIRMATION_REQUIRED with what would be deleted; call again with confirm: true once the person agrees.

MCP tool expenses_removeRecurringCLI heybmo expenses removeRecurringLimit deleteSafe to retryInput
recurringIdrequiredidA recurring bill or income id (from expenses.recurring).
confirmtrue or falseSet to true once the person agreed. Without it nothing is deleted and you get a description of what would be.
curl https://app.heybmo.com/api/ops/expenses.removeRecurring \
  -H "Authorization: Bearer $HEYBMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"recurringId":"01a19279-e9c3-7c73-9705-94f082fa3a65"}}'
200 response
{
  "output": {
    "removed": "01a19279-e9c3-7c73-9705-94f082fa3a65"
  }
}