# API reference > Every Oatmilk action, generated from the definitions the API enforces. The OpenAPI 3.1 document is at https://app.getoatmilk.com/api/openapi.json. - [Transactions](https://app.getoatmilk.com/docs/api/transactions.md): Entries, bank and card lines, categories, tags, accounts, reconciliation and statements. (115 actions) - [Inbox and uploads](https://app.getoatmilk.com/docs/api/inbox.md): Receipts, file uploads, company mail, connected inboxes and processing jobs. (56 actions) - [Matching](https://app.getoatmilk.com/docs/api/matching.md): Match receipts to bank transactions and review the decisions. (11 actions) - [Autopilot](https://app.getoatmilk.com/docs/api/autopilot.md): What needs a person, Autopilot runs, receipt reminders and the investigator. (30 actions) - [Invoicing](https://app.getoatmilk.com/docs/api/invoicing.md): Invoices, customers and vendors, and how they pay. (50 actions) - [Documents and signing](https://app.getoatmilk.com/docs/api/documents.md): Agreements, e-signatures and kept records such as tax returns. (60 actions) - [Contractors](https://app.getoatmilk.com/docs/api/contractors.md): The finance side of contractors: directory, timesheets, payouts and recruiting. (144 actions) - [Contractor portal](https://app.getoatmilk.com/docs/api/contractor.md): A contractor's own hours, pay, agreements and profile, with a contractor sign-in. (43 actions) - [Reports and tax](https://app.getoatmilk.com/docs/api/reports.md): Reports, exports, insights, subscriptions and tax preparation. (75 actions) - [Compliance](https://app.getoatmilk.com/docs/api/compliance.md): The compliance checklist, deadlines and reminders. (10 actions) - [Stripe](https://app.getoatmilk.com/docs/api/stripe.md): Stripe activity, payouts, exceptions and tax confirmation. (14 actions) - [Connectors and webhooks](https://app.getoatmilk.com/docs/api/connectors.md): Webhooks, Wise, Notion, Google Drive and other data sources. (59 actions) - [AI and memory](https://app.getoatmilk.com/docs/api/ai.md): AI settings, classifier guidance, memory, the sandbox and compliance research. (45 actions) - [Administration](https://app.getoatmilk.com/docs/api/admin.md): Company settings, people, API keys, email, runs and suggestions. (95 actions) - [History and rollback](https://app.getoatmilk.com/docs/api/history.md): Every change anyone made to the company, who made it and how, and previewed rollbacks. (7 actions) - [Budgets and reserves](https://app.getoatmilk.com/docs/api/budgets.md): Spending plans, tax and accountant costs, reserve accounts and funding gaps before filing. (8 actions) # Group: Transactions > Entries, bank and card lines, categories, tags, accounts, reconciliation and statements. MCP toolset: `transactions` (https://app.getoatmilk.com/api/mcp?toolset=transactions) ## accounts - [`accounts.create`](https://app.getoatmilk.com/docs/api/accounts.create.md) — Create an RBC or other manually imported bank account. - [`accounts.lifecycle`](https://app.getoatmilk.com/docs/api/accounts.lifecycle.md) — Mark an account inactive from an explicit date or since its last verified statement, or reactivate it. Preserves historical records and earlier statement obligations. Requires the current revision and an idempotency key. - [`accounts.list`](https://app.getoatmilk.com/docs/api/accounts.list.md) — List available bank and payment accounts. - [`accounts.update`](https://app.getoatmilk.com/docs/api/accounts.update.md) — Set an account display name using the current revision and an idempotency key. Provider identity and currency remain unchanged. ## accounts.lifecycle - [`accounts.lifecycle.preview`](https://app.getoatmilk.com/docs/api/accounts.lifecycle.preview.md) — Read the account and its latest verified statement end date before marking it inactive. Returns the next-day cutoff, or null when no verified statement is available. ## cards - [`cards.claim`](https://app.getoatmilk.com/docs/api/cards.claim.md) — Update the caller's card claims atomically: identifierIds claims unassigned cards, releaseIdentifierIds releases only cards the caller personally claimed, seenIdentifierIds records what they reviewed, and idempotencyKey prevents duplicate saves. Other people's cards and finance-managed assignments cannot be changed; administrators reassign those in Cards. - [`cards.claimable`](https://app.getoatmilk.com/docs/api/cards.claimable.md) — Show unassigned company cards (last four digits, printed name and account only), the caller's original mine list of last-four strings, and ownedCards for editing. Own cards are marked removable only when the caller linked them. No transaction details are returned. The caller may edit an earlier answer, or is asked about unseen unassigned cards when holding none. - [`cards.list`](https://app.getoatmilk.com/docs/api/cards.list.md) — List the card and account numbers (last four digits) Oatmilk uses to tell which account paid, with each one's account, cardholder and whether a person or Autopilot added it, whether each account is a bank account or a credit card, the members who can hold a card (with their names), and suggested cardholders for cards nobody holds yet, matched from the name printed on the card. - [`cards.save`](https://app.getoatmilk.com/docs/api/cards.save.md) — Add a card or account number (kind card_last4 or account_last4 and four digits) to an account, or change its label, cardholder or whether it is on, with idempotencyKey (and id with expectedRevision for changes). Receipts paid with that card are placed on its account. A number active on another account is refused. - [`cards.setAccountType`](https://app.getoatmilk.com/docs/api/cards.setAccountType.md) — Mark an account as a bank account or a credit card with accountId, accountType, expectedRevision and idempotencyKey. Payments between a bank account and a credit card are then treated as transfers. ## cards.prompt - [`cards.prompt.dismiss`](https://app.getoatmilk.com/docs/api/cards.prompt.dismiss.md) — Answer "None of these" to "Which of these cards are yours?", with the seenIdentifierIds you were shown and idempotencyKey. You're asked again only about cards added later. ## categories - [`categories.addRecommended`](https://app.getoatmilk.com/docs/api/categories.addRecommended.md) — Add the recommended categories the organization doesn't have yet, archived until someone turns them on. Supply idempotencyKey and optional keys to add only some. Existing categories are never renamed or moved. Finance access required. - [`categories.create`](https://app.getoatmilk.com/docs/api/categories.create.md) — Create an accounting category using name and idempotencyKey, with an optional top-level parentId (subcategories are one level deep), description (up to 500 characters, read by the AI) and rules: merchant words that always use this category. Requires finance access. - [`categories.list`](https://app.getoatmilk.com/docs/api/categories.list.md) — List organization categories with parent_id, description, display path ("Events › Hackathons"), taxonomy_key, the caller's last_used_at and rule_count. Contributors see active categories only. - [`categories.update`](https://app.getoatmilk.com/docs/api/categories.update.md) — Rename, move (parentId, or null for top level), describe or deactivate a category with a revision check, or choose it for automatic filing with useFor (income, bank_fees, other, contractors, interest_income). A category with active subcategories can't be deactivated. Finance access required. ## entries - [`entries.attachReceipt`](https://app.getoatmilk.com/docs/api/entries.attachReceipt.md) — Attach a receipt entry to an existing compatible bank entry without duplicating expenses. Requires both current revisions and an idempotency key. - [`entries.bulkCategorize`](https://app.getoatmilk.com/docs/api/entries.bulkCategorize.md) — Categorize a set of entries with their current revisions. Finance access required. - [`entries.context`](https://app.getoatmilk.com/docs/api/entries.context.md) — Read what Oatmilk knows about where and when a transaction happened, by entryId: the trip it belongs to, its place pin, whether it looks like a hotel hold (with the reason and the date a final charge is expected), and purchases within about a kilometre around the same dates. Contributors can read only transactions they own. - [`entries.create`](https://app.getoatmilk.com/docs/api/entries.create.md) — Manually create a receipt entry from preserved evidence when extraction needs correction. Requires the submission revision, exact minor-unit amounts and an idempotency key. - [`entries.delete`](https://app.getoatmilk.com/docs/api/entries.delete.md) — Delete a transaction from the books with id, expectedRevision, reason (3 to 1000 characters), idempotencyKey and optional choices: bankLines keep (default, the line goes back to Reconcile) or exclude (a manual CSV line leaves the books too), receipts dismiss (default, labelled not a transaction) or keep, and email not_transaction (default, the company email is archived as general mail) or keep. A submitted reimbursement claim is withdrawn, the transaction leaves its trip and open questions close. It is voided, never erased: files, history and audit stay, and entries.restore puts it back. Refused when entries.delete.preview lists a blocker. Finance access required. - [`entries.get`](https://app.getoatmilk.com/docs/api/entries.get.md) — Get an entry by id with its evidence and allocations. - [`entries.list`](https://app.getoatmilk.com/docs/api/entries.list.md) — Search accounting entries by query, status, account, category or date. Contributor results contain only their own entries. - [`entries.restore`](https://app.getoatmilk.com/docs/api/entries.restore.md) — Restore a deleted transaction with id, expectedRevision (its revision after deleting), reason and idempotencyKey. Its bank matches, excluded bank lines, receipt labels, the email's folder, a withdrawn claim, its trip and its questions come back when nothing changed since; a bank line matched to something else since is a conflict. Finance access required. - [`entries.retryStep`](https://app.getoatmilk.com/docs/api/entries.retryStep.md) — Restart a supported processing step for one transaction using its current revision and an idempotency key. Inspect the activity before choosing only_step or from_here; unsupported modes, protected human decisions, closed periods, and stale revisions are rejected. Review restarts require an administrator. - [`entries.update`](https://app.getoatmilk.com/docs/api/entries.update.md) — Edit an entry using id, expectedRevision, idempotencyKey, merchant, date, amountMinor, currency, categoryId, paymentAccountId, type, status and notes. Closed periods cannot be changed. A matched entry keeps its amount, currency and account, and its type must follow the bank movement: money in is income, refund or transfer; money out is expense, fee, income_refund or transfer. Pass learn: false when putting an earlier value back, as Undo does, so the correction isn't retained as vendor learning. ## entries.delete - [`entries.delete.preview`](https://app.getoatmilk.com/docs/api/entries.delete.preview.md) — Before deleting a transaction, read everything tied to it by id: its bank lines (and whether each can be excluded with it), the receipts and company email it came from, a reimbursement claim, its trip, splits, tags and open questions, the default for each choice, and anything that blocks deleting it (a closed period, a Stripe record, the book record of a synced Wise movement, an approved or paid reimbursement). A deleted transaction returns who deleted it, when and why. Finance access required. ## entries.deleted - [`entries.deleted.list`](https://app.getoatmilk.com/docs/api/entries.deleted.list.md) — List deleted transactions, newest first, with limit and offset: merchant, date and amount, who deleted each, when and why, what else changed, and whether it was restored. Finance access required. ## entries.evidence - [`entries.evidence.confirm`](https://app.getoatmilk.com/docs/api/entries.evidence.confirm.md) — Verify and append a prepared supporting original to its exact transaction with entryId, submissionId and idempotencyKey. Returns the preserved evidence and an extraction-only job; reading never creates purchases or changes financial fields. Duplicate originals on the same transaction are reused. - [`entries.evidence.prepare`](https://app.getoatmilk.com/docs/api/entries.evidence.prepare.md) — Prepare an immutable supporting-original upload for an active editable transaction. Supply entryId, filename, mimeType, sizeBytes, sha256 and a stable idempotencyKey. Skip PUT when alreadyUploaded, then confirm. Adds documents alongside existing originals without creating purchases or changing financial fields. ## entries.hold - [`entries.hold.release.undo`](https://app.getoatmilk.com/docs/api/entries.hold.release.undo.md) — Undo Oatmilk's pairing of money back on a card with the hotel hold it gave back, by the money back's releaseEntryId with an idempotencyKey. The pair is never made again, a card fee booked with the hold is a fee again, the money back is reviewed again, and a hold it resolved is open again. Finance access required; a closed month blocks it. - [`entries.hold.set`](https://app.getoatmilk.com/docs/api/entries.hold.set.md) — Say whether a card charge is a hotel hold with entryId, state (confirmed_hold or not_a_hold) and an idempotencyKey. Confirming books the charge as a transfer, so it leaves expense totals and tax while staying visible and matched; pass resolvedByEntryId to say which later charge replaced it. Declining puts the purchase back. A person's decision is final: Oatmilk never overturns it. Closed periods can't change. Contributors can decide only transactions they own. ## entries.place - [`entries.place.set`](https://app.getoatmilk.com/docs/api/entries.place.set.md) — Save where a purchase was made with entryId, lat, lon, a label, an optional address and an idempotencyKey. A place a person set is never replaced by an automatic lookup. Contributors can set only the place of transactions they own. ## entries.tags - [`entries.tags.set`](https://app.getoatmilk.com/docs/api/entries.tags.set.md) — Add, remove or dismiss project tags on up to 100 entries at once with entryIds, add, remove and dismiss (tag ids) and idempotencyKey; pass the same operationId with every batch of one bulk change. Adding a tag Oatmilk suggested accepts it; dismissing a suggestion stops it being suggested for that entry. Tags added through the API or MCP are recorded as such and don't confirm rules; finance's dashboard choices do. A rule tags open transactions by itself only for a tag with start and end dates, after two confirmations from separate actions and no rejection, from the day it qualified. Contributors can tag only their own receipts until finance reviews them. Tags don't change amounts, categories or tax. ## exchangeRates - [`exchangeRates.get`](https://app.getoatmilk.com/docs/api/exchangeRates.get.md) — Get the Bank of Canada daily exchange rate for a day, as units of quote for one base (for example base USD, quote CAD, date 2026-09-25). Pairs without CAD are crossed through CAD; a weekend or holiday uses the closest earlier business day. Returns the rate, the day it was published for, the series and a source description to keep with a match or tax review. Reads bankofcanada.ca and never changes anything. ## imports - [`imports.commit`](https://app.getoatmilk.com/docs/api/imports.commit.md) — Import an RBC CSV with accountId, csv, filename, mapping and idempotencyKey. Preserves the original and deduplicates transactions. Autopilot classifies the new lines afterwards unless skipAi is true. - [`imports.download`](https://app.getoatmilk.com/docs/api/imports.download.md) — Get an authorized short-lived download for the original statement or CSV import evidence. - [`imports.preview`](https://app.getoatmilk.com/docs/api/imports.preview.md) — Validate a CSV import using accountId, csv text and mapping with date, description, amount or debit/credit, and dateFormat. Returns errors without booking transactions. ## imports.undo - [`imports.undo.apply`](https://app.getoatmilk.com/docs/api/imports.undo.apply.md) — Undo explicitly selected import-owned groups atomically using the current preview fingerprint, reason, and idempotency key. Keep reviewed or edited transactions and detach their incorrect imported bank links when safe; remove only proved untouched derived entries. Preserves originals and audit. Closed periods and payment dependencies block changes. - [`imports.undo.history`](https://app.getoatmilk.com/docs/api/imports.undo.history.md) — Read paged organization import, Undo, and Restore history with actual ownership counts and audited reasons. Optional batch and account filters keep the list scoped. Restore eligibility is checked by its lazy preview. - [`imports.undo.preview`](https://app.getoatmilk.com/docs/api/imports.undo.preview.md) — Preview selective Undo for one manual CSV or uploaded statement import, or guarded Restore for one complete Undo operation. Resolve batch, statement, transaction, or bank-row context inside the organization. Returns paged groups, treatment choices, blockers, and an exact whole-graph fingerprint while preserving originals. - [`imports.undo.restore`](https://app.getoatmilk.com/docs/api/imports.undo.restore.md) — Restore one entire Undo operation atomically using its current operation revision, inverse preview fingerprint, reason, and idempotency key. The server verifies exact after-state, current relationships, source ownership, and open periods. Later edits or conflicting allocations block restoration. ## investigations.questions - [`investigations.questions.answer`](https://app.getoatmilk.com/docs/api/investigations.questions.answer.md) — Answer a question with its id, one of its optionId answers (or none_of_these when the person settled it by hand), an optional note of up to 1000 characters and an idempotencyKey. An offered answer does only what Oatmilk wrote down when it asked (confirm or decline a hold, put a receipt on a hotel's charge or take it off, link a transaction to a trip); none_of_these only closes the question. Every answer is audited, and a question closes once answered. Contributors can answer only questions about transactions they own. - [`investigations.questions.list`](https://app.getoatmilk.com/docs/api/investigations.questions.list.md) — List the questions Oatmilk asked a person while investigating, newest first, optionally for one entryId or only the open ones. Each has a title, two to four answers, whether a note is allowed, and the answer when given. Contributors see only questions about transactions they own. ## investigations.review - [`investigations.review.run`](https://app.getoatmilk.com/docs/api/investigations.review.run.md) — Run the second opinion now on one bank line (entryId) with an idempotencyKey: an agent with read-only lookups of the organization's transactions, hotel holds, trips, receipts and memory decides what the line is, pairs money back with the hold it gives back, or writes one specific question. Its answer is checked before Autopilot applies it, and its reasoning is kept in the transaction's activity. Administrators only. ## investigations - [`investigations.run`](https://app.getoatmilk.com/docs/api/investigations.run.md) — Run the receipt investigator now for one transaction or receipt (entryId) with an idempotencyKey: it gathers what Oatmilk knows, asks the investigator for a judgement, links a receipt charged to a hotel room to the hotel's charge, and asks a person one plain question when it can't tell. Returns the run id to follow. Administrator access required. ## merchants - [`merchants.confirm`](https://app.getoatmilk.com/docs/api/merchants.confirm.md) — Confirm what Oatmilk filled in for a merchant, optionally correcting displayName, websiteDomain, description, industry or kind in edits. Confirmed and corrected fields belong to the person and are never overwritten by a later lookup. Needs the merchant key and an idempotency key; pass expectedRevision from merchants.get to detect changes. - [`merchants.enrich`](https://app.getoatmilk.com/docs/api/merchants.enrich.md) — Look a merchant up on the web and fill in its profile: what it is, its website and industry, each backed by a quote from a page. Pass key for one merchant, or leave it out to look up the next businesses that were never looked up, up to the organization's limit. Runs in the background and returns a run id. Only the merchant's name is sent to the search; never amounts or who paid. Category changes are suggestions for a person to approve. - [`merchants.get`](https://app.getoatmilk.com/docs/api/merchants.get.md) — Read one merchant by its key: the profile, monthly spend per currency, the five latest transactions, the web pages the profile was read from, and any category suggestion waiting for review. Finance access required. - [`merchants.list`](https://app.getoatmilk.com/docs/api/merchants.list.md) — List the merchants the organization paid, with what Oatmilk knows about each (description, industry, website, kind), spend per currency net of refunds over the last 12 months or from/to dates, last paid date, category with its source (rule, history or confirmed) and whether it needs a look. Names written differently, such as "Slack Technologies, LLC" and "SLACK.COM", are one merchant. Filter with search or filter=needs_look. Finance access required. ## merchants.settings - [`merchants.settings.get`](https://app.getoatmilk.com/docs/api/merchants.settings.get.md) — Read whether new merchants are looked up automatically and how many web searches one run may use. - [`merchants.settings.update`](https://app.getoatmilk.com/docs/api/merchants.settings.update.md) — Turn automatic merchant lookups on or off and set the most web searches one run may use (1 to 25). Needs the current revision after the first save. Administrator access required. ## places - [`places.search`](https://app.getoatmilk.com/docs/api/places.search.md) — Search for a place or an address by name: pass query (up to 120 characters) and optionally near, the point the results should lean toward, as {lat, lon} or "lat,lon". Returns up to five results from OpenStreetMap, each with a label, an address, lat and lon; a person then saves one with entries.place.set. Run it only when a person submits a search, never while they type. Each person gets 30 new lookups an hour, and only the words searched leave Oatmilk. ## places.settings - [`places.settings.get`](https://app.getoatmilk.com/docs/api/places.settings.get.md) — Read whether Oatmilk looks for the location of older card purchases in the background: backfill, on by default. It places up to 20 purchases from the last 120 days every five minutes and asks a person only when it cannot tell. - [`places.settings.update`](https://app.getoatmilk.com/docs/api/places.settings.update.md) — Turn the background search for the location of older card purchases on or off with backfill (true or false) and an idempotencyKey. Turning it off never removes a place already saved. Administrator access required. ## receipt.tax - [`receipt.tax.confirm`](https://app.getoatmilk.com/docs/api/receipt.tax.confirm.md) — Confirm GST/HST on a receipt or bank purchase, including an explicitly verified zero, with current revision, original source reason and idempotency key. Generic taxes remain separate; original printed components are preserved. ## reconciliation - [`reconciliation.close`](https://app.getoatmilk.com/docs/api/reconciliation.close.md) — Explicitly close an account period after its opening, movement and closing balances reconcile. Finance access required; reopening needs an administrator and the accounting:admin scope (reconciliation.reopen). - [`reconciliation.createEntry`](https://app.getoatmilk.com/docs/api/reconciliation.createEntry.md) — Create an entry from an imported bank transaction with an explicit type and optional category. Reuses existing entries on retry. - [`reconciliation.list`](https://app.getoatmilk.com/docs/api/reconciliation.list.md) — List reconciliation records and unmatched items using account and date filters. - [`reconciliation.match`](https://app.getoatmilk.com/docs/api/reconciliation.match.md) — Allocate a bank transaction to an entry with entryId, transactionId, amountMinor in the receipt currency, expectedRevision and idempotencyKey. Cross-currency matches also require bankAmountMinor, exchangeRate (bank major units per receipt major unit), exchangeRateDate and exchangeRateSource. Finance must review foreign-exchange matches explicitly. - [`reconciliation.reopen`](https://app.getoatmilk.com/docs/api/reconciliation.reopen.md) — Reopen a closed accounting period with an audited administrator reason. - [`reconciliation.split`](https://app.getoatmilk.com/docs/api/reconciliation.split.md) — Split an entry among categories using exact minor-unit amounts, revision and idempotency key. - [`reconciliation.unmatch`](https://app.getoatmilk.com/docs/api/reconciliation.unmatch.md) — Remove an allocation using allocationId, expectedRevision and idempotencyKey. Finance only, with audit history. ## reimbursements - [`reimbursements.approve`](https://app.getoatmilk.com/docs/api/reimbursements.approve.md) — Approve up to 100 submitted reimbursement claims in one atomic review, recording receipt validity and whether the claimant was an employee or officer at purchase for GST/HST treatment. - [`reimbursements.bindRecipient`](https://app.getoatmilk.com/docs/api/reimbursements.bindRecipient.md) — Bind an existing Wise recipient to an active member after verifying its name, currency and business profile. No bank details are returned. - [`reimbursements.candidates`](https://app.getoatmilk.com/docs/api/reimbursements.candidates.md) — For one outgoing bank transfer, list each active member with their approved unpaid claims in the transfer's currency and the exact set of claims that adds up to the transfer, when there is exactly one. Read only; linking still uses reimbursements.link. - [`reimbursements.claimPurchase`](https://app.getoatmilk.com/docs/api/reimbursements.claimPurchase.md) — Create a personal expense claim for an active member from an existing unallocated receipt purchase, with its current revision, exact original evidence and explicit confirmation of who paid. Never creates or changes the expense. - [`reimbursements.completePayment`](https://app.getoatmilk.com/docs/api/reimbursements.completePayment.md) — Complete an awaiting-receipts bank reimbursement only with approved original receipt purchases for the same member, currency and exact total, using current payment, bank and entry revisions. - [`reimbursements.forEntry`](https://app.getoatmilk.com/docs/api/reimbursements.forEntry.md) — Read the reimbursement claim and status for one purchase, if present. Members can read only their own. - [`reimbursements.get`](https://app.getoatmilk.com/docs/api/reimbursements.get.md) — Read one reimbursement claim and its purchase and payment status. Members can read only their own. - [`reimbursements.identify`](https://app.getoatmilk.com/docs/api/reimbursements.identify.md) — Classify one historical Wise transfer against approved member claims. A dry run returns evidence; apply links only one exact named payee and claim match. - [`reimbursements.link`](https://app.getoatmilk.com/docs/api/reimbursements.link.md) — Link an existing outgoing bank transfer to approved expense claims with the same member, currency and exact total. The bank entry becomes a transfer, not another expense. - [`reimbursements.list`](https://app.getoatmilk.com/docs/api/reimbursements.list.md) — List reimbursement claims and their submitted, approved, rejected, prepared, funded, paid or returned status. Members see only their own claims; finance can review organization claims. - [`reimbursements.matchPayment`](https://app.getoatmilk.com/docs/api/reimbursements.matchPayment.md) — Match a funded Wise reimbursement to its imported transfer using Wise's exact transfer reference and source amount. The bank entry becomes a transfer. - [`reimbursements.prepare`](https://app.getoatmilk.com/docs/api/reimbursements.prepare.md) — Prepare one Wise reimbursement for approved claims belonging to the same member and currency. Preparation never sends funds. - [`reimbursements.recipients`](https://app.getoatmilk.com/docs/api/reimbursements.recipients.md) — List verified Wise recipient bindings for members without exposing account details. - [`reimbursements.recordPayment`](https://app.getoatmilk.com/docs/api/reimbursements.recordPayment.md) — Record an existing untouched outgoing bank payment to an explicitly confirmed active member as a reimbursement transfer awaiting receipt-backed claims. Never invents purchases or tax and never sends money. - [`reimbursements.refreshRecipient`](https://app.getoatmilk.com/docs/api/reimbursements.refreshRecipient.md) — Refresh the verified Wise recipient on an unsent prepared reimbursement after the recipient binding changes. Requires the payment revision and never sends funds. - [`reimbursements.reject`](https://app.getoatmilk.com/docs/api/reimbursements.reject.md) — Reject a submitted reimbursement with a reason and expected revision. - [`reimbursements.send`](https://app.getoatmilk.com/docs/api/reimbursements.send.md) — Send one prepared Wise reimbursement for approved claims belonging to one member and currency. Requires a current administrator, the accounting:admin integration scope, the payment's current revision and an idempotency key. A real Wise payout may occur; a sidebar agent asks the person who started its chat to approve this action. Never auto-sends. - [`reimbursements.submit`](https://app.getoatmilk.com/docs/api/reimbursements.submit.md) — Submit a reimbursement claim for a personally paid expense from your own original receipt. Oatmilk never duplicates the expense; check its status with reimbursements.list, reimbursements.get or reimbursements.forEntry. - [`reimbursements.syncPayment`](https://app.getoatmilk.com/docs/api/reimbursements.syncPayment.md) — Read a previously funded Wise reimbursement status and update the claim only when Wise confirms payout or return. Never sends funds. ## statements - [`statements.download`](https://app.getoatmilk.com/docs/api/statements.download.md) — Get a short-lived private link to a statement file by id. format original (the default): the unchanged original, an uploaded or Wise statement PDF or photo (pass inline: true to open it in the browser) or a CSV or Wise sync export. format pdf: a PDF of it, the original when it is one, otherwise a PDF Oatmilk renders from Wise's statement data, the CSV's lines or the photo, labelled as prepared by Oatmilk and kept for later downloads. Only statement files can be opened this way. Read-only. - [`statements.lines`](https://app.getoatmilk.com/docs/api/statements.lines.md) — Read statement lines. With id: the lines Oatmilk read from that statement file, whether they were checked against its period and its opening and closing balances (verified), whether they are in the books (booked), and the reason when they were not. With accountId, from and to (up to 400 days): the account's lines in the books for those dates, each with the id of a checked statement that covers its date (verifiedBy, or null), and the statement files of that period. Amounts are signed integer strings in minor units of the account's currency; money out is negative. Read-only. - [`statements.list`](https://app.getoatmilk.com/docs/api/statements.list.md) — List the original statement files Oatmilk keeps (uploaded PDF and photo statements, Wise's own monthly statement PDFs, and CSV exports), each with its account, bank, last four digits, period, balances and status: queued, reading, imported (with how many lines were new and how many were already in the books), review (with its plain reason and reasonCode; a statement waiting on its account also has suggestedAccountId, the likeliest existing account, and newAccount, an account drafted from the statement with name, provider, currency, lastFour and accountType, when its number isn't saved on any account; reasonCode ACCOUNT_NEW means no account Oatmilk has could be it), duplicate (of which statement), kept or failed. Also says, for every account and month, whether a statement covers the whole month (statement), part of it (partial), only lines synced from Wise (feed), nothing (missing) or a time before the account's first activity (before). Each file also says whether a PDF can be downloaded (pdf: original, or rendered by Oatmilk from Wise's statement data, a CSV or a photo), how its lines were read (readWith: vision, or text for a reading from the PDF's text layer made before vision), the reader's own warnings, a person's mark (flagged with a note, or checked) and checks: why it waits for a person (review, failed, flagged, old_reader, unsure). Wise sync data is listed once per account and window (the newest copy). Filter with from and to (up to 36 months; the last 12 when left out), or allPeriods: true to find files across the entire saved history, accountId or accountIds (up to 50), includeCoverage: false for only the file list without account-month coverage (coverageIncluded: false and empty month grids; omitted or true retains full coverage), bank, status (or attention for review and failed, or check for every file that waits for a person; toCheck counts them), search, limit and offset. Read-only. - [`statements.review`](https://app.getoatmilk.com/docs/api/statements.review.md) — Decide what happens to uploaded statements, with idempotencyKey, decision and either id with expectedRevision or items (up to 100 of { id, expectedRevision }). assign (with accountId) checks the saved reading again for that account without reading the file, and with remember: true also saves the statement's account number and cardholders' cards on that account (never one already on another account) and checks the other statements waiting on it again; new_account (administrators only, with the accounting:admin scope for API keys) adds the account the statement is for once, from the first statement's newAccount with any of name, provider, currency, lastFour and accountType in newAccount overriding it, saves its last four digits so later statements match it, assigns every named statement to it, checks every other statement waiting on that account again, and answers with the account and how many statements it is checking (rechecking); read_again reads the file again (model: zai/glm-5.3-flash by default, or openai/gpt-6-luna, google/gemini-3.8-flash or openai/gpt-6.1-sol) and reads the account number, each cardholder's card and every line afresh; an imported statement keeps its account; keep keeps a statement that needs review as the original without importing its lines; flag (with an optional note) puts it in front of a person; unflag removes the flag; confirm records that a person checked it was read correctly. Nothing is booked unless the lines add up to the statement's balances, and an imported statement's account can't change here. With items, the answer lists each statement changed and each one skipped with why. ## summaries - [`summaries.get`](https://app.getoatmilk.com/docs/api/summaries.get.md) — Read the separately generated purchase summary and supporting source references. Never treats a summary as financial evidence. - [`summaries.update`](https://app.getoatmilk.com/docs/api/summaries.update.md) — Edit, dismiss or retry a purchase summary independently of receipt processing and human notes, with its current revision and idempotency key. ## tags.ai - [`tags.ai.list`](https://app.getoatmilk.com/docs/api/tags.ai.list.md) — List what the project classifier suggested for a project (tagId, optional verdict likely or ask) that nobody has accepted or dismissed yet: each transaction's id, date, merchant, amount, currency and the classifier's probability. Accept with accounting_entries_tags_set add, or say no with dismiss. - [`tags.ai.run`](https://app.getoatmilk.com/docs/api/tags.ai.run.md) — Ask the project classifier which transactions belong to a project, with tagId, idempotencyKey, optional limit (1 to 100, default 40) and recheck (look again at transactions it already answered). It reads transactions in the project's dates (and the 30 days before), or for a project without dates those that mention its name, other names or keywords, and uses the project's description, keywords and Notion page text. Likely matches become suggestions; unsure ones become questions (accounting_tags_ai_list verdict ask). A sure match tags a transaction by itself only while Autopilot is on and may finish work, for a project with a start and end date around an open transaction with no tag, dated on or after the project was set up; earlier transactions only get suggestions. An answer without probabilities counts as unsure. Returns counts. ## tags - [`tags.create`](https://app.getoatmilk.com/docs/api/tags.create.md) — Create a project tag with name and idempotencyKey, and optionally parentId (a group; groups are one level deep), color (gray, red, orange, amber, green, teal, blue, purple or pink), description, aliases (other names people write), startsOn/endsOn (the event window) and budgetMinor with budgetCurrency. Active tag names are unique. - [`tags.delete`](https://app.getoatmilk.com/docs/api/tags.delete.md) — Delete a project tag that was never used, with id, expectedRevision and idempotencyKey. A tag on any transaction, or holding other tags, can't be deleted: archive it or merge it instead. - [`tags.get`](https://app.getoatmilk.com/docs/api/tags.get.md) — Get one project tag with the tags inside it, its totals, profit and loss by category, a monthly timeline and the merchant rules Oatmilk learned for it. A group includes the tags inside it, counting each transaction once. - [`tags.list`](https://app.getoatmilk.com/docs/api/tags.list.md) — List project tags (which event or program a transaction is for): name, group (parent_id, one level deep), color, description, other names, event window (starts_on, ends_on), budget, archived state, the caller's last_used_at, and for finance spend, income, net and transaction counts by currency. A group's rollup counts each transaction once. Pass includeArchived to include archived tags. Contributors see active tags without totals or budgets. - [`tags.merge`](https://app.getoatmilk.com/docs/api/tags.merge.md) — Merge one project tag into another with sourceId, targetId, the source's expectedRevision and idempotencyKey: every transaction tagged with the source gets the target instead (once), learned rules and the tags inside a merged group move too, and the source is archived. The audit records every transaction moved. - [`tags.report`](https://app.getoatmilk.com/docs/api/tags.report.md) — Profit and loss by project tag for optional from/to dates and currency: spend, income and transaction counts per tag and per group, the deduplicated tagged total, untagged transactions, and overlap (transactions tagged to more than one project, which count in each). Currencies stay separate. - [`tags.suggest`](https://app.getoatmilk.com/docs/api/tags.suggest.md) — Suggest project tags for up to 100 entries (entryIds) from each tag's event window, learned merchant rules and notes or descriptions that mention the tag or its other names. Each suggestion has a score from 0 to 100 and its reasons. Suggestions don't count in totals until accepted with accounting_entries_tags_set. - [`tags.update`](https://app.getoatmilk.com/docs/api/tags.update.md) — Change a project tag with id, expectedRevision and idempotencyKey: rename, recolor, describe, set other names, move to a group or out of one (parentId null), set or clear the event window and budget, or archive and restore it (archived true or false). Archiving a group archives the tags inside it; restoring the group restores them. ## tags.notion - [`tags.notion.link`](https://app.getoatmilk.com/docs/api/tags.notion.link.md) — Link a project to a Notion page or database row with page, sourceOfTruth (notion: name, dates, status, budget and description come from Notion; oatmilk: Oatmilk writes them to Notion) and idempotencyKey. Pass tagId (and optionally the project's expectedRevision) to link an existing project, or leave it out to create a project from the page (optionally parentId and color). For a database row, properties is required: the Notion property for each of dates, status, budget and description, or null to leave it out (accounting_tags_notion_preview suggests them; nothing is mapped silently). Linking syncs once straight away and never empties a filled field on either side: where one side is empty the filled value is kept, reads the page text as context for the classifier, and asks the classifier to suggest transactions for the project. - [`tags.notion.preview`](https://app.getoatmilk.com/docs/api/tags.notion.preview.md) — Read a Notion page or database row before linking it to a project: supply page (a notion.so link or page ID) and, for an existing project, tagId. Returns its title, whether it is a row and in which database, suggested properties for dates, status, budget and description with every property that could hold each (choices) and the value each would read (propertyValues), the start of the page text the classifier would use, any problems, and with tagId the project's own values and what the first sync would change for each source of truth (firstSync). Review it, then pass the properties you chose to accounting_tags_notion_link. - [`tags.notion.resolve`](https://app.getoatmilk.com/docs/api/tags.notion.resolve.md) — Resolve a sync conflict on one field (name, dates, status, budget or description) with tagId, field, keep (notion writes Notion's value to Oatmilk; oatmilk writes Oatmilk's value to Notion) and idempotencyKey. Then syncs the project. - [`tags.notion.sync`](https://app.getoatmilk.com/docs/api/tags.notion.sync.md) — Sync a linked project with Notion now, with tagId and idempotencyKey (linked projects also sync about hourly). Fields change only on the side that isn't the source of truth. A field the other side changed since they last agreed is not overwritten: it is returned in project.conflicts for a person to resolve with accounting_tags_notion_resolve. - [`tags.notion.unlink`](https://app.getoatmilk.com/docs/api/tags.notion.unlink.md) — Stop syncing a project with Notion, with tagId and idempotencyKey. The project keeps its current name, dates, budget and description; nothing changes in Notion. ## tags.project - [`tags.project.update`](https://app.getoatmilk.com/docs/api/tags.project.update.md) — Set a project tag's status (planned, active or completed) or keywords (up to 20 words the classifier looks for) with tagId, idempotencyKey and optionally expectedRevision (the project's revision from tags.get). Changing keywords asks the project classifier to look at its transactions again. When the project syncs from Notion, a status set here that differs from Notion's is a conflict to resolve with accounting_tags_notion_resolve. ## transactions - [`transactions.correct`](https://app.getoatmilk.com/docs/api/transactions.correct.md) — Correct an unallocated CSV bank row with its revision, reason and idempotency key. Original statement evidence and previous import identities remain preserved. Closed periods and Wise rows cannot be edited. - [`transactions.get`](https://app.getoatmilk.com/docs/api/transactions.get.md) — Get one imported bank or card transaction by id: amount, date, account, statement text, what it is matched to (allocations with their transactions and entries), and the facts the bank supplied such as merchant, card, cardholder and exchange details. - [`transactions.list`](https://app.getoatmilk.com/docs/api/transactions.list.md) — Search imported bank and card transactions with search text, accountId, currency, from/to dates, allocation (matched, unmatched or partial), direction (in or out), minAmount/maxAmount in minor units, limit and offset. Each line includes allocatedMinor and the entry ids it is matched to. - [`transactions.restore`](https://app.getoatmilk.com/docs/api/transactions.restore.md) — Restore a previously reversed CSV bank row with a revision check and audited reason. - [`transactions.reverse`](https://app.getoatmilk.com/docs/api/transactions.reverse.md) — Exclude an erroneous unallocated CSV bank row with an audited reason. Preserves evidence and duplicate detection. ## transactions.documents - [`transactions.documents.download`](https://app.getoatmilk.com/docs/api/transactions.documents.download.md) — Get a short-lived download for a document Oatmilk fetched from the bank provider for a transaction, such as Wise's transfer confirmation for a contractor payment. Ids are listed on entries.get as providerDocuments. ## trips - [`trips.create`](https://app.getoatmilk.com/docs/api/trips.create.md) — Create a trip with title, startDate, optional endDate and placeLabel, and an idempotencyKey. Finance access required. - [`trips.get`](https://app.getoatmilk.com/docs/api/trips.get.md) — Read one trip by id: its summary, every transaction in it with its role (lodging, hold, room_charge, meal, transport or other), confidence and note, and the place pins to draw on a map. Contributors see only their own transactions in it. - [`trips.linkEntry`](https://app.getoatmilk.com/docs/api/trips.linkEntry.md) — Put a transaction in a trip with tripId, entryId, an optional role (lodging, hold, room_charge, meal, transport or other) and an idempotencyKey. A transaction is in one trip at a time; linking it here moves it. Contributors can link only transactions they own. - [`trips.list`](https://app.getoatmilk.com/docs/api/trips.list.md) — List trips: purchases made while travelling, grouped by stay, with title, place, dates, status (upcoming, active or past), how many transactions belong to each, what it cost per currency (holds left out, hotel credits taken off) and its map centre. Contributors see only trips that include a transaction they own. - [`trips.unlinkEntry`](https://app.getoatmilk.com/docs/api/trips.unlinkEntry.md) — Take a transaction out of a trip with tripId, entryId and an idempotencyKey. Oatmilk never puts it back. Contributors can unlink only transactions they own. - [`trips.update`](https://app.getoatmilk.com/docs/api/trips.update.md) — Rename a trip or change its dates or place with id, the fields to change and an idempotencyKey. A trip a person edited is never rewritten by Oatmilk. Finance access required. ## accounts.create Create an RBC or other manually imported bank account. `POST /api/v1/accounting/accounts.create` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_accounts_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | Yes | A display name. 1–100 characters. | | `provider` | enum | Yes | One of: `rbc`, `other`. | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accounts.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Synthetic Ventures Inc.", "provider": "rbc", "currency": "CAD" }' ``` Reference page: https://app.getoatmilk.com/docs/api/accounts.create ## accounts.lifecycle Mark an account inactive from an explicit date or since its last verified statement, or reactivate it. Preserves historical records and earlier statement obligations. Requires the current revision and an idempotency key. `POST /api/v1/accounting/accounts.lifecycle` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_accounts_lifecycle` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `active` | boolean | Yes | Whether the record is turned on. | | `inactiveSince` | string | | | | `sinceLastStatement` | true | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accounts.lifecycle \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "active": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/accounts.lifecycle ## accounts.lifecycle.preview Read the account and its latest verified statement end date before marking it inactive. Returns the next-day cutoff, or null when no verified statement is available. `GET | POST /api/v1/accounting/accounts.lifecycle.preview` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_accounts_lifecycle_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/accounts.lifecycle.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/accounts.lifecycle.preview ## accounts.list List available bank and payment accounts. `GET | POST /api/v1/accounting/accounts.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_list_accounts` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accounts.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/accounts.list ## accounts.update Set an account display name using the current revision and an idempotency key. Provider identity and currency remain unchanged. `POST /api/v1/accounting/accounts.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_accounts_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `name` | string | Yes | A display name. 1–100 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accounts.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "name": "Synthetic Ventures Inc.", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/accounts.update ## cards.claim Update the caller's card claims atomically: identifierIds claims unassigned cards, releaseIdentifierIds releases only cards the caller personally claimed, seenIdentifierIds records what they reviewed, and idempotencyKey prevents duplicate saves. Other people's cards and finance-managed assignments cannot be changed; administrators reassign those in Cards. `POST /api/v1/accounting/cards.claim` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_cards_claim` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `identifierIds` | array of strings (ID) | Yes | A list of record IDs. at most 10 items. | | `releaseIdentifierIds` | array of strings (ID) | | A list of record IDs. at most 10 items. Default `[]`. | | `seenIdentifierIds` | array of strings (ID) | | A list of record IDs. at most 200 items. Default `[]`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/cards.claim \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "identifierIds": [ "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/cards.claim ## cards.claimable Show unassigned company cards (last four digits, printed name and account only), the caller's original mine list of last-four strings, and ownedCards for editing. Own cards are marked removable only when the caller linked them. No transaction details are returned. The caller may edit an earlier answer, or is asked about unseen unassigned cards when holding none. `GET | POST /api/v1/accounting/cards.claimable` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_cards_claimable` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/cards.claimable \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/cards.claimable ## cards.list List the card and account numbers (last four digits) Oatmilk uses to tell which account paid, with each one's account, cardholder and whether a person or Autopilot added it, whether each account is a bank account or a credit card, the members who can hold a card (with their names), and suggested cardholders for cards nobody holds yet, matched from the name printed on the card. `GET | POST /api/v1/accounting/cards.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_cards_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/cards.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/cards.list ## cards.prompt.dismiss Answer "None of these" to "Which of these cards are yours?", with the seenIdentifierIds you were shown and idempotencyKey. You're asked again only about cards added later. `POST /api/v1/accounting/cards.prompt.dismiss` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_cards_prompt_dismiss` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `seenIdentifierIds` | array of strings (ID) | Yes | A list of record IDs. 1–200 items. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/cards.prompt.dismiss \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "seenIdentifierIds": [ "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/cards.prompt.dismiss ## cards.save Add a card or account number (kind card_last4 or account_last4 and four digits) to an account, or change its label, cardholder or whether it is on, with idempotencyKey (and id with expectedRevision for changes). Receipts paid with that card are placed on its account. A number active on another account is refused. `POST /api/v1/accounting/cards.save` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_cards_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `accountId` | string (ID) | | The ID of a bank, card or payment account, from accounts.list. | | `kind` | enum | | Which kind of record or job this is. One of: `card_last4`, `account_last4`. | | `value` | string | | Four-digit year. | | `label` | string | | at most 80 characters. | | `cardholderUserId` | string | | at most 200 characters. | | `cardholderName` | string | | at most 120 characters. | | `active` | boolean | | Whether the record is turned on. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/cards.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/cards.save ## cards.setAccountType Mark an account as a bank account or a credit card with accountId, accountType, expectedRevision and idempotencyKey. Payments between a bank account and a credit card are then treated as transfers. `POST /api/v1/accounting/cards.setAccountType` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_cards_set_account_type` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `accountId` | string (ID) | Yes | The ID of a bank, card or payment account, from accounts.list. | | `accountType` | enum | Yes | One of: `bank`, `credit_card`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/cards.setAccountType \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "accountId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "accountType": "bank", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/cards.setAccountType ## categories.addRecommended Add the recommended categories the organization doesn't have yet, archived until someone turns them on. Supply idempotencyKey and optional keys to add only some. Existing categories are never renamed or moved. Finance access required. `POST /api/v1/accounting/categories.addRecommended` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_categories_add_recommended` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `keys` | array of strings | | 1–200 items; each Matches ^[a-z][a-z0-9_]{1,63}$. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/categories.addRecommended \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/categories.addRecommended ## categories.create Create an accounting category using name and idempotencyKey, with an optional top-level parentId (subcategories are one level deep), description (up to 500 characters, read by the AI) and rules: merchant words that always use this category. Requires finance access. `POST /api/v1/accounting/categories.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_create_category` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | Yes | A display name. 1–100 characters. | | `parentId` | string (ID) | | The ID of the related record. | | `description` | string | | A short description. at most 500 characters. | | `rules` | array of strings | | at most 20 items; each 2–200 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/categories.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Synthetic Ventures Inc." }' ``` Reference page: https://app.getoatmilk.com/docs/api/categories.create ## categories.list List organization categories with parent_id, description, display path ("Events › Hackathons"), taxonomy_key, the caller's last_used_at and rule_count. Contributors see active categories only. `GET | POST /api/v1/accounting/categories.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_list_categories` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/categories.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/categories.list ## categories.update Rename, move (parentId, or null for top level), describe or deactivate a category with a revision check, or choose it for automatic filing with useFor (income, bank_fees, other, contractors, interest_income). A category with active subcategories can't be deactivated. Finance access required. `POST /api/v1/accounting/categories.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_update_category` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `name` | string | | A display name. 1–100 characters. | | `active` | boolean | | Whether the record is turned on. | | `parentId` | string (ID) or null | | | | `description` | string | | A short description. at most 500 characters. | | `useFor` | array of enum values | | One of: `income`, `bank_fees`, `other`, `contractors`, `interest_income`. 1–5 items. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/categories.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/categories.update ## entries.attachReceipt Attach a receipt entry to an existing compatible bank entry without duplicating expenses. Requires both current revisions and an idempotency key. `POST /api/v1/accounting/entries.attachReceipt` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_attach_receipt` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `receiptEntryId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `receiptExpectedRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.attachReceipt \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "receiptEntryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedRevision": 3, "receiptExpectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.attachReceipt ## entries.bulkCategorize Categorize a set of entries with their current revisions. Finance access required. `POST /api/v1/accounting/entries.bulkCategorize` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entries` | array of objects | Yes | 1–100 items. | | `entries[].id` | string (ID) | Yes | The record's ID. | | `entries[].expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `categoryId` | string (ID) | Yes | The ID of a category, from categories.list. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.bulkCategorize \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entries": [ { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 } ], "categoryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.bulkCategorize ## entries.context Read what Oatmilk knows about where and when a transaction happened, by entryId: the trip it belongs to, its place pin, whether it looks like a hotel hold (with the reason and the date a final charge is expected), and purchases within about a kilometre around the same dates. Contributors can read only transactions they own. `GET | POST /api/v1/accounting/entries.context` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_entries_context` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/entries.context \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'entryId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.context ## entries.create Manually create a receipt entry from preserved evidence when extraction needs correction. Requires the submission revision, exact minor-unit amounts and an idempotency key. `POST /api/v1/accounting/entries.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_create_record` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `submissionId` | string (ID) | Yes | The ID of an upload, returned by uploads.prepare or uploads.inline. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `merchant` | string | Yes | 1–500 characters. | | `date` | string | Yes | A date, as YYYY-MM-DD. | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `taxMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `taxKnown` | boolean | | | | `categoryId` | string (ID) | | The ID of a category, from categories.list. | | `paymentAccountId` | string (ID) | | The card or account the purchase was paid with, from accounts.list. | | `type` | enum | Yes | One of: `expense`, `income`, `income_refund`, `refund`, `transfer`, `fee`. | | `notes` | string | | Notes kept with the record. at most 10000 characters. Default `""`. | | `sourceFactIndex` | integer | | 0 to 19. | | `status` | enum | | Only include records with this status. One of: `reviewed`. | | `duplicateDecision` | enum | | One of: `keep_separate`. | | `tripId` | string (ID) | | The ID of the related record. | | `tripRole` | enum | | One of: `lodging`, `hold`, `room_charge`, `meal`, `transport`, `other`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "submissionId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "merchant": "example", "date": "2026-09-30", "currency": "CAD", "amountMinor": "1250", "taxMinor": "1250", "type": "expense" }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.create ## entries.delete Delete a transaction from the books with id, expectedRevision, reason (3 to 1000 characters), idempotencyKey and optional choices: bankLines keep (default, the line goes back to Reconcile) or exclude (a manual CSV line leaves the books too), receipts dismiss (default, labelled not a transaction) or keep, and email not_transaction (default, the company email is archived as general mail) or keep. A submitted reimbursement claim is withdrawn, the transaction leaves its trip and open questions close. It is voided, never erased: files, history and audit stay, and entries.restore puts it back. Refused when entries.delete.preview lists a blocker. Finance access required. `POST /api/v1/accounting/entries.delete` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_entries_delete` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `choices` | object | | No other fields. Default `{}`. | | `choices.bankLines` | enum | | One of: `keep`, `exclude`. | | `choices.receipts` | enum | | One of: `dismiss`, `keep`. | | `choices.email` | enum | | An email address. One of: `not_transaction`, `keep`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.delete \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.delete ## entries.delete.preview Before deleting a transaction, read everything tied to it by id: its bank lines (and whether each can be excluded with it), the receipts and company email it came from, a reimbursement claim, its trip, splits, tags and open questions, the default for each choice, and anything that blocks deleting it (a closed period, a Stripe record, the book record of a synced Wise movement, an approved or paid reimbursement). A deleted transaction returns who deleted it, when and why. Finance access required. `GET | POST /api/v1/accounting/entries.delete.preview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_entries_delete_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/entries.delete.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.delete.preview ## entries.deleted.list List deleted transactions, newest first, with limit and offset: merchant, date and amount, who deleted each, when and why, what else changed, and whether it was restored. Finance access required. `GET | POST /api/v1/accounting/entries.deleted.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_entries_deleted_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 10000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.deleted.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/entries.deleted.list ## entries.evidence.confirm Verify and append a prepared supporting original to its exact transaction with entryId, submissionId and idempotencyKey. Returns the preserved evidence and an extraction-only job; reading never creates purchases or changes financial fields. Duplicate originals on the same transaction are reused. `POST /api/v1/accounting/entries.evidence.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_entries_evidence_confirm` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `submissionId` | string (ID) | Yes | The ID of an upload, returned by uploads.prepare or uploads.inline. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.evidence.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "submissionId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.evidence.confirm ## entries.evidence.prepare Prepare an immutable supporting-original upload for an active editable transaction. Supply entryId, filename, mimeType, sizeBytes, sha256 and a stable idempotencyKey. Skip PUT when alreadyUploaded, then confirm. Adds documents alongside existing originals without creating purchases or changing financial fields. `POST /api/v1/accounting/entries.evidence.prepare` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_entries_evidence_prepare` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `filename` | string | Yes | The file's name, including its extension. 1–500 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `image/jpeg`, `image/png`, `image/webp`, `image/heic`, `image/heif`, `application/pdf`, `message/rfc822`. | | `sizeBytes` | integer | Yes | The file's size in bytes. 1 to 52428800. | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.evidence.prepare \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "filename": "receipt.jpg", "mimeType": "image/jpeg", "sizeBytes": 1, "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.evidence.prepare ## entries.get Get an entry by id with its evidence and allocations. `GET | POST /api/v1/accounting/entries.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_get_record` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/entries.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.get ## entries.hold.release.undo Undo Oatmilk's pairing of money back on a card with the hotel hold it gave back, by the money back's releaseEntryId with an idempotencyKey. The pair is never made again, a card fee booked with the hold is a fee again, the money back is reviewed again, and a hold it resolved is open again. Finance access required; a closed month blocks it. `POST /api/v1/accounting/entries.hold.release.undo` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_entries_hold_release_undo` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `releaseEntryId` | string (ID) | Yes | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.hold.release.undo \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "releaseEntryId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.hold.release.undo ## entries.hold.set Say whether a card charge is a hotel hold with entryId, state (confirmed_hold or not_a_hold) and an idempotencyKey. Confirming books the charge as a transfer, so it leaves expense totals and tax while staying visible and matched; pass resolvedByEntryId to say which later charge replaced it. Declining puts the purchase back. A person's decision is final: Oatmilk never overturns it. Closed periods can't change. Contributors can decide only transactions they own. `POST /api/v1/accounting/entries.hold.set` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_entries_hold_set` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `state` | enum | Yes | One of: `confirmed_hold`, `not_a_hold`. | | `resolvedByEntryId` | string (ID) | | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.hold.set \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "state": "confirmed_hold" }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.hold.set ## entries.list Search accounting entries by query, status, account, category or date. Contributor results contain only their own entries. `GET | POST /api/v1/accounting/entries.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_search_records` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `includeReversed` | boolean | | Also include reversed. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `search` | string | | Text to search for. at most 200 characters. | | `status` | enum | | Only include records with this status. One of: `needs_review`, `reviewed`. | | `categoryId` | string (ID) | | The ID of a category, from categories.list. | | `accountId` | string (ID) | | The ID of a bank, card or payment account, from accounts.list. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `limit` | integer | | How many results to return at most. 1 to 500. Default `100`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | | `deleted` | boolean | | | | `autopilotState` | enum | | One of: `working`, `needs_you`, `done`. | | `ask` | enum | | One of: `forward_receipt`, `review_document`, `contractor_invoice`, `choose_contractor`, `choose_category`, `confirm_business`, `confirm_income`, `confirm_tax`, `match_payout`, `approve`, `import_statement`, `reimbursement_receipts`. | | `source` | enum | | Where the record came from. One of: `stripe`, `receipt`, `bank`. | | `type` | enum | | One of: `expense`, `income`, `income_refund`, `refund`, `transfer`, `fee`. | | `minAmount` | string | | The smallest amount to include, in cents, written as a string. Whole number written as a string. | | `maxAmount` | string | | The largest amount to include, in cents, written as a string. Whole number written as a string. | | `sortBy` | enum | | Which field to sort by. One of: `merchant`, `date`, `type`, `status`, `amount`. | | `sortDirection` | enum | | asc for oldest or smallest first, desc for newest or largest first. One of: `asc`, `desc`. | | `tagId` | string (ID) | | The ID of a project tag, from tags.list. | | `untagged` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` ### Example response ```json { "data": [ { "id": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "organization_id": "org_synthetic", "submission_id": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "submitted_by": "user_synthetic", "merchant": "Synthetic Office Supply", "date": "2026-09-18", "currency": "CAD", "amount_minor": "4520", "tax_minor": "520", "category_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "payment_account_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "type": "expense", "status": "reviewed", "notes": "", "revision": 3, "human_corrected": false, "autopilot_state": "done", "tags": [] } ] } ``` Reference page: https://app.getoatmilk.com/docs/api/entries.list ## entries.place.set Save where a purchase was made with entryId, lat, lon, a label, an optional address and an idempotencyKey. A place a person set is never replaced by an automatic lookup. Contributors can set only the place of transactions they own. `POST /api/v1/accounting/entries.place.set` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_entries_place_set` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `lat` | number | Yes | -90 to 90. | | `lon` | number | Yes | -180 to 180. | | `label` | string | Yes | 1–200 characters. | | `address` | string or null | | | | `city` | string or null | | | | `region` | string or null | | | | `countryCode` | string or null | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.place.set \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "lat": 1, "lon": 1, "label": "example" }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.place.set ## entries.restore Restore a deleted transaction with id, expectedRevision (its revision after deleting), reason and idempotencyKey. Its bank matches, excluded bank lines, receipt labels, the email's folder, a withdrawn claim, its trip and its questions come back when nothing changed since; a bank line matched to something else since is a conflict. Finance access required. `POST /api/v1/accounting/entries.restore` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_entries_restore` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.restore \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.restore ## entries.retryStep Restart a supported processing step for one transaction using its current revision and an idempotency key. Inspect the activity before choosing only_step or from_here; unsupported modes, protected human decisions, closed periods, and stale revisions are rejected. Review restarts require an administrator. `POST /api/v1/accounting/entries.retryStep` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_entries_retry_step` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `step` | enum | Yes | One of: `vendor_context`, `classifier`, `matching`, `receipt_read`, `review`. | | `mode` | enum | Yes | One of: `only_step`, `from_here`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.retryStep \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedRevision": 3, "step": "vendor_context", "mode": "only_step" }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.retryStep ## entries.tags.set Add, remove or dismiss project tags on up to 100 entries at once with entryIds, add, remove and dismiss (tag ids) and idempotencyKey; pass the same operationId with every batch of one bulk change. Adding a tag Oatmilk suggested accepts it; dismissing a suggestion stops it being suggested for that entry. Tags added through the API or MCP are recorded as such and don't confirm rules; finance's dashboard choices do. A rule tags open transactions by itself only for a tag with start and end dates, after two confirmations from separate actions and no rejection, from the day it qualified. Contributors can tag only their own receipts until finance reviews them. Tags don't change amounts, categories or tax. `POST /api/v1/accounting/entries.tags.set` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_entries_tags_set` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryIds` | array of strings (ID) | Yes | IDs of accounting entries, from entries.list or attention.mine. 1–100 items. | | `add` | array of strings (ID) | | at most 20 items. Default `[]`. | | `remove` | array of strings (ID) | | at most 20 items. Default `[]`. | | `dismiss` | array of strings (ID) | | at most 20 items. Default `[]`. | | `operationId` | string | | 8–200 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.tags.set \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryIds": [ "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" ], "add": [ "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.tags.set ## entries.update Edit an entry using id, expectedRevision, idempotencyKey, merchant, date, amountMinor, currency, categoryId, paymentAccountId, type, status and notes. Closed periods cannot be changed. A matched entry keeps its amount, currency and account, and its type must follow the bank movement: money in is income, refund or transfer; money out is expense, fee, income_refund or transfer. Pass learn: false when putting an earlier value back, as Undo does, so the correction isn't retained as vendor learning. `POST /api/v1/accounting/entries.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `receiptReviewSource` | object | | No other fields. | | `receiptReviewSource.submissionId` | string (ID) | Yes | The ID of an upload, returned by uploads.prepare or uploads.inline. | | `receiptReviewSource.sourceFactIndex` | integer | Yes | 0 to 19. | | `receiptReviewSource.sourceExtractionRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `receiptReviewSource.sourceFact` | map | Yes | | | `duplicateDecision` | enum | | One of: `keep_separate`. | | `merchant` | string | | 1–500 characters. | | `date` | string | | A date, as YYYY-MM-DD. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `amountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `taxMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `categoryId` | string (ID) or null | | The ID of a category, from categories.list. | | `paymentAccountId` | string (ID) or null | | The card or account the purchase was paid with, from accounts.list. | | `type` | enum | | One of: `expense`, `income`, `income_refund`, `refund`, `transfer`, `fee`. | | `status` | enum | | Only include records with this status. One of: `needs_review`, `reviewed`. | | `notes` | string | | Notes kept with the record. at most 10000 characters. | | `splits` | array of objects | | at most 50 items. | | `splits[].categoryId` | string (ID) | Yes | The ID of a category, from categories.list. | | `splits[].amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `learn` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/entries.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/entries.update ## exchangeRates.get Get the Bank of Canada daily exchange rate for a day, as units of quote for one base (for example base USD, quote CAD, date 2026-09-25). Pairs without CAD are crossed through CAD; a weekend or holiday uses the closest earlier business day. Returns the rate, the day it was published for, the series and a source description to keep with a match or tax review. Reads bankofcanada.ca and never changes anything. `GET | POST /api/v1/accounting/exchangeRates.get` Permissions: `accounting:read` · Roles: admin, finance · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_exchange_rates_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `base` | string | Yes | Three-letter currency code, such as CAD. | | `quote` | string | Yes | Three-letter currency code, such as CAD. | | `date` | string | Yes | A date, as YYYY-MM-DD. Date as YYYY-MM-DD. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/exchangeRates.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'base=CAD' \ --data-urlencode 'quote=USD' \ --data-urlencode 'date=2026-09-30' ``` Reference page: https://app.getoatmilk.com/docs/api/exchangeRates.get ## imports.commit Import an RBC CSV with accountId, csv, filename, mapping and idempotencyKey. Preserves the original and deduplicates transactions. Autopilot classifies the new lines afterwards unless skipAi is true. `POST /api/v1/accounting/imports.commit` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_commit_bank_import` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `accountId` | string (ID) | Yes | The ID of a bank, card or payment account, from accounts.list. | | `csv` | string | Yes | at most 5000000 characters. | | `filename` | string | | The file's name, including its extension. 1–255 characters. Default `"statement.csv"`. | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `mapping` | object | Yes | No other fields. | | `mapping.date` | string | Yes | A date, as YYYY-MM-DD. at least 1 characters. | | `mapping.description` | string | Yes | A short description. at least 1 characters. | | `mapping.amount` | string | | | | `mapping.debit` | string | | | | `mapping.credit` | string | | | | `mapping.reference` | string | | | | `mapping.dateFormat` | enum | Yes | One of: `YYYY-MM-DD`, `MM/DD/YYYY`, `DD/MM/YYYY`. | | `mapping.columns` | array of strings | | 2–50 items; each 1–100 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `skipAi` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/imports.commit \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "accountId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "csv": "example", "currency": "CAD", "mapping": { "date": "2026-09-30", "description": "Synthetic example from the docs", "dateFormat": "YYYY-MM-DD" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/imports.commit ## imports.download Get an authorized short-lived download for the original statement or CSV import evidence. `GET | POST /api/v1/accounting/imports.download` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_get_import_evidence` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/imports.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/imports.download ## imports.preview Validate a CSV import using accountId, csv text and mapping with date, description, amount or debit/credit, and dateFormat. Returns errors without booking transactions. `POST /api/v1/accounting/imports.preview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_preview_bank_import` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `accountId` | string (ID) | Yes | The ID of a bank, card or payment account, from accounts.list. | | `csv` | string | Yes | at most 5000000 characters. | | `filename` | string | | The file's name, including its extension. 1–255 characters. Default `"statement.csv"`. | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `mapping` | object | Yes | No other fields. | | `mapping.date` | string | Yes | A date, as YYYY-MM-DD. at least 1 characters. | | `mapping.description` | string | Yes | A short description. at least 1 characters. | | `mapping.amount` | string | | | | `mapping.debit` | string | | | | `mapping.credit` | string | | | | `mapping.reference` | string | | | | `mapping.dateFormat` | enum | Yes | One of: `YYYY-MM-DD`, `MM/DD/YYYY`, `DD/MM/YYYY`. | | `mapping.columns` | array of strings | | 2–50 items; each 1–100 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/imports.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "accountId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "csv": "example", "currency": "CAD", "mapping": { "date": "2026-09-30", "description": "Synthetic example from the docs", "dateFormat": "YYYY-MM-DD" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/imports.preview ## imports.undo.apply Undo explicitly selected import-owned groups atomically using the current preview fingerprint, reason, and idempotency key. Keep reviewed or edited transactions and detach their incorrect imported bank links when safe; remove only proved untouched derived entries. Preserves originals and audit. Closed periods and payment dependencies block changes. `POST /api/v1/accounting/imports.undo.apply` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Destructive: confirm with a person first MCP tool: `accounting_imports_undo_apply` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `batchId` | string (ID) | Yes | The ID of the related record. | | `groupIds` | array of strings | Yes | A list of record IDs. 1–10001 items; each at most 64 characters. | | `entryTreatments` | array of objects | | at most 10000 items. | | `entryTreatments[].entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `entryTreatments[].treatment` | enum | Yes | One of: `keep_entry`, `remove_derived_entry`. | | `previewFingerprint` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/imports.undo.apply \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "batchId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "groupIds": [ "example" ], "previewFingerprint": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/imports.undo.apply ## imports.undo.history Read paged organization import, Undo, and Restore history with actual ownership counts and audited reasons. Optional batch and account filters keep the list scoped. Restore eligibility is checked by its lazy preview. `GET | POST /api/v1/accounting/imports.undo.history` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_imports_undo_history` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `batchId` | string (ID) | | The ID of the related record. | | `accountId` | string (ID) | | The ID of a bank, card or payment account, from accounts.list. | | `cursor` | string | | Where the next page starts: the nextCursor value from the previous response. 1–512 characters. | | `limit` | integer | | How many results to return at most. 1 to 200. Default `50`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/imports.undo.history \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/imports.undo.history ## imports.undo.preview Preview selective Undo for one manual CSV or uploaded statement import, or guarded Restore for one complete Undo operation. Resolve batch, statement, transaction, or bank-row context inside the organization. Returns paged groups, treatment choices, blockers, and an exact whole-graph fingerprint while preserving originals. `GET | POST /api/v1/accounting/imports.undo.preview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_imports_undo_preview` ### Fields #### mode: "undo" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `mode` | "undo" | Yes | | | `target` | object | Yes | No other fields. | | `target.kind` | enum | Yes | Which kind of record or job this is. One of: `batch`, `statement`, `entry`, `bank_transaction`. | | `target.id` | string (ID) | Yes | The record's ID. | | `cursor` | string | | Where the next page starts: the nextCursor value from the previous response. 1–512 characters. | | `limit` | integer | | How many results to return at most. 1 to 500. Default `200`. | #### mode: "restore" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `mode` | "restore" | Yes | | | `operationId` | string (ID) | Yes | The ID of the related record. | | `cursor` | string | | Where the next page starts: the nextCursor value from the previous response. 1–512 characters. | | `limit` | integer | | How many results to return at most. 1 to 500. Default `200`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/imports.undo.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mode": "undo", "target": { "kind": "batch", "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/imports.undo.preview ## imports.undo.restore Restore one entire Undo operation atomically using its current operation revision, inverse preview fingerprint, reason, and idempotency key. The server verifies exact after-state, current relationships, source ownership, and open periods. Later edits or conflicting allocations block restoration. `POST /api/v1/accounting/imports.undo.restore` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_imports_undo_restore` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `operationId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `previewFingerprint` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/imports.undo.restore \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "operationId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "expectedRevision": 3, "previewFingerprint": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/imports.undo.restore ## investigations.questions.answer Answer a question with its id, one of its optionId answers (or none_of_these when the person settled it by hand), an optional note of up to 1000 characters and an idempotencyKey. An offered answer does only what Oatmilk wrote down when it asked (confirm or decline a hold, put a receipt on a hotel's charge or take it off, link a transaction to a trip); none_of_these only closes the question. Every answer is audited, and a question closes once answered. Contributors can answer only questions about transactions they own. `POST /api/v1/accounting/investigations.questions.answer` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_investigations_questions_answer` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `optionId` | string | Yes | 1–40 characters. | | `note` | string | | A short note, kept with the record. at most 1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/investigations.questions.answer \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "optionId": "example" }' ``` Reference page: https://app.getoatmilk.com/docs/api/investigations.questions.answer ## investigations.questions.list List the questions Oatmilk asked a person while investigating, newest first, optionally for one entryId or only the open ones. Each has a title, two to four answers, whether a note is allowed, and the answer when given. Contributors see only questions about transactions they own. `GET | POST /api/v1/accounting/investigations.questions.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_investigations_questions_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | | The ID of an accounting entry, from entries.list or attention.mine. | | `open` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/investigations.questions.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/investigations.questions.list ## investigations.review.run Run the second opinion now on one bank line (entryId) with an idempotencyKey: an agent with read-only lookups of the organization's transactions, hotel holds, trips, receipts and memory decides what the line is, pairs money back with the hold it gives back, or writes one specific question. Its answer is checked before Autopilot applies it, and its reasoning is kept in the transaction's activity. Administrators only. `POST /api/v1/accounting/investigations.review.run` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_investigations_review_run` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/investigations.review.run \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/investigations.review.run ## investigations.run Run the receipt investigator now for one transaction or receipt (entryId) with an idempotencyKey: it gathers what Oatmilk knows, asks the investigator for a judgement, links a receipt charged to a hotel room to the hotel's charge, and asks a person one plain question when it can't tell. Returns the run id to follow. Administrator access required. `POST /api/v1/accounting/investigations.run` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_investigations_run` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/investigations.run \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/investigations.run ## merchants.confirm Confirm what Oatmilk filled in for a merchant, optionally correcting displayName, websiteDomain, description, industry or kind in edits. Confirmed and corrected fields belong to the person and are never overwritten by a later lookup. Needs the merchant key and an idempotency key; pass expectedRevision from merchants.get to detect changes. `POST /api/v1/accounting/merchants.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_merchants_confirm` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `key` | string | Yes | 1–120 characters. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `edits` | object | | No other fields. | | `edits.displayName` | string | | 1–200 characters. | | `edits.websiteDomain` | string | | at most 253 characters; Matches ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$. | | `edits.description` | string | | A short description. 1–240 characters. | | `edits.industry` | string | | 1–80 characters. | | `edits.kind` | enum | | Which kind of record or job this is. One of: `unknown`, `business`, `person`, `government`, `financial`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/merchants.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "key": "example" }' ``` Reference page: https://app.getoatmilk.com/docs/api/merchants.confirm ## merchants.enrich Look a merchant up on the web and fill in its profile: what it is, its website and industry, each backed by a quote from a page. Pass key for one merchant, or leave it out to look up the next businesses that were never looked up, up to the organization's limit. Runs in the background and returns a run id. Only the merchant's name is sent to the search; never amounts or who paid. Category changes are suggestions for a person to approve. `POST /api/v1/accounting/merchants.enrich` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_merchants_enrich` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `key` | string | | 1–120 characters. | | `force` | boolean | | Default `false`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/merchants.enrich \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/merchants.enrich ## merchants.get Read one merchant by its key: the profile, monthly spend per currency, the five latest transactions, the web pages the profile was read from, and any category suggestion waiting for review. Finance access required. `GET | POST /api/v1/accounting/merchants.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_merchants_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `key` | string | Yes | 1–120 characters. | | `from` | string | | The first date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/merchants.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'key=example' ``` Reference page: https://app.getoatmilk.com/docs/api/merchants.get ## merchants.list List the merchants the organization paid, with what Oatmilk knows about each (description, industry, website, kind), spend per currency net of refunds over the last 12 months or from/to dates, last paid date, category with its source (rule, history or confirmed) and whether it needs a look. Names written differently, such as "Slack Technologies, LLC" and "SLACK.COM", are one merchant. Filter with search or filter=needs_look. Finance access required. `GET | POST /api/v1/accounting/merchants.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_merchants_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `search` | string | | Text to search for. at most 200 characters. | | `filter` | enum | | One of: `all`, `needs_look`. Default `"all"`. | | `sort` | enum | | One of: `spend`, `name`, `recent`. Default `"spend"`. | | `from` | string | | The first date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `limit` | integer | | How many results to return at most. 1 to 200. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/merchants.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/merchants.list ## merchants.settings.get Read whether new merchants are looked up automatically and how many web searches one run may use. `GET | POST /api/v1/accounting/merchants.settings.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_merchants_settings_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/merchants.settings.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/merchants.settings.get ## merchants.settings.update Turn automatic merchant lookups on or off and set the most web searches one run may use (1 to 25). Needs the current revision after the first save. Administrator access required. `POST /api/v1/accounting/merchants.settings.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_merchants_settings_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `enrichmentEnabled` | boolean | | | | `perRunCap` | integer | | 1 to 25. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/merchants.settings.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "enrichmentEnabled": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/merchants.settings.update ## places.search Search for a place or an address by name: pass query (up to 120 characters) and optionally near, the point the results should lean toward, as {lat, lon} or "lat,lon". Returns up to five results from OpenStreetMap, each with a label, an address, lat and lon; a person then saves one with entries.place.set. Run it only when a person submits a search, never while they type. Each person gets 30 new lookups an hour, and only the words searched leave Oatmilk. `GET | POST /api/v1/accounting/places.search` Permissions: `accounting:read` · Roles: admin, finance, contributor · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_places_search` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `query` | string | Yes | Text to search for. 1–120 characters. | | `near` | object or string | | | | `near.lat` | number | Yes | -90 to 90. | | `near.lon` | number | Yes | -180 to 180. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/places.search \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'query=office supplies' ``` Reference page: https://app.getoatmilk.com/docs/api/places.search ## places.settings.get Read whether Oatmilk looks for the location of older card purchases in the background: backfill, on by default. It places up to 20 purchases from the last 120 days every five minutes and asks a person only when it cannot tell. `GET | POST /api/v1/accounting/places.settings.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_places_settings_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/places.settings.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/places.settings.get ## places.settings.update Turn the background search for the location of older card purchases on or off with backfill (true or false) and an idempotencyKey. Turning it off never removes a place already saved. Administrator access required. `POST /api/v1/accounting/places.settings.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_places_settings_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `backfill` | boolean | Yes | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/places.settings.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "backfill": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/places.settings.update ## receipt.tax.confirm Confirm GST/HST on a receipt or bank purchase, including an explicitly verified zero, with current revision, original source reason and idempotency key. Generic taxes remain separate; original printed components are preserved. `POST /api/v1/accounting/receipt.tax.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `taxMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/receipt.tax.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "taxMinor": "1250", "reason": "Synthetic example from the docs", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/receipt.tax.confirm ## reconciliation.close Explicitly close an account period after its opening, movement and closing balances reconcile. Finance access required; reopening needs an administrator and the accounting:admin scope (reconciliation.reopen). `POST /api/v1/accounting/reconciliation.close` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required Not available over MCP: Accountants' close periods grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `accountId` | string (ID) | Yes | The ID of a bank, card or payment account, from accounts.list. | | `from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `openingMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string, may be negative. | | `closingMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string, may be negative. | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reconciliation.close \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "accountId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "from": "2026-09-01", "to": "2026-09-30", "openingMinor": "1250", "closingMinor": "1250", "currency": "CAD" }' ``` Reference page: https://app.getoatmilk.com/docs/api/reconciliation.close ## reconciliation.createEntry Create an entry from an imported bank transaction with an explicit type and optional category. Reuses existing entries on retry. `POST /api/v1/accounting/reconciliation.createEntry` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_create_bank_record` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `categoryId` | string (ID) | | The ID of a category, from categories.list. | | `type` | enum | | One of: `expense`, `income`, `income_refund`, `refund`, `transfer`, `fee`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reconciliation.createEntry \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/reconciliation.createEntry ## reconciliation.list List reconciliation records and unmatched items using account and date filters. `GET | POST /api/v1/accounting/reconciliation.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_reconciliation` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `includeReversed` | boolean | | Also include reversed. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `search` | string | | Text to search for. at most 200 characters. | | `status` | enum | | Only include records with this status. One of: `needs_review`, `reviewed`. | | `categoryId` | string (ID) | | The ID of a category, from categories.list. | | `accountId` | string (ID) | | The ID of a bank, card or payment account, from accounts.list. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `limit` | integer | | How many results to return at most. 1 to 500. Default `100`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reconciliation.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/reconciliation.list ## reconciliation.match Allocate a bank transaction to an entry with entryId, transactionId, amountMinor in the receipt currency, expectedRevision and idempotencyKey. Cross-currency matches also require bankAmountMinor, exchangeRate (bank major units per receipt major unit), exchangeRateDate and exchangeRateSource. Finance must review foreign-exchange matches explicitly. `POST /api/v1/accounting/reconciliation.match` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_match_transaction` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `bankAmountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `exchangeRate` | string | | Matches ^(?:0\|[1-9]\d{0,8})(?:\.\d{1,18})?$. | | `exchangeRateDate` | string | | | | `exchangeRateSource` | string | | 3–1000 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reconciliation.match \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "amountMinor": "1250", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/reconciliation.match ## reconciliation.reopen Reopen a closed accounting period with an audited administrator reason. `POST /api/v1/accounting/reconciliation.reopen` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` Not available over MCP: Accountants' close periods grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reconciliation.reopen \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/reconciliation.reopen ## reconciliation.split Split an entry among categories using exact minor-unit amounts, revision and idempotency key. `POST /api/v1/accounting/reconciliation.split` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `splits` | array of objects | Yes | 1–50 items. | | `splits[].categoryId` | string (ID) | Yes | The ID of a category, from categories.list. | | `splits[].amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reconciliation.split \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "splits": [ { "categoryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "amountMinor": "1250" } ], "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/reconciliation.split ## reconciliation.unmatch Remove an allocation using allocationId, expectedRevision and idempotencyKey. Finance only, with audit history. `POST /api/v1/accounting/reconciliation.unmatch` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_unmatch_transaction` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `allocationId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reconciliation.unmatch \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "allocationId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/reconciliation.unmatch ## reimbursements.approve Approve up to 100 submitted reimbursement claims in one atomic review, recording receipt validity and whether the claimant was an employee or officer at purchase for GST/HST treatment. `POST /api/v1/accounting/reimbursements.approve` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_reimbursements_approve` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `claims` | array of objects | Yes | 1–100 items. | | `claims[].id` | string (ID) | Yes | The record's ID. | | `claims[].expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `claims[].receiptValid` | boolean | Yes | | | `claims[].employeeAtPurchase` | boolean | Yes | | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.approve \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "claims": [ { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "receiptValid": true, "employeeAtPurchase": true } ], "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.approve ## reimbursements.bindRecipient Bind an existing Wise recipient to an active member after verifying its name, currency and business profile. No bank details are returned. `POST /api/v1/accounting/reimbursements.bindRecipient` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_reimbursements_bind_recipient` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `userId` | string | Yes | The ID of a person in your company. 1–200 characters. | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `recipientId` | string | Yes | The ID of one signer on a document. Matches ^\d{1,30}$. | | `holderName` | string | Yes | 2–200 characters. | | `identityMismatchConfirmed` | boolean | | | | `identityMismatchReason` | string | | 10–1000 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.bindRecipient \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "userId": "example", "currency": "CAD", "recipientId": "1250", "holderName": "Synthetic Ventures Inc.", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.bindRecipient ## reimbursements.candidates For one outgoing bank transfer, list each active member with their approved unpaid claims in the transfer's currency and the exact set of claims that adds up to the transfer, when there is exactly one. Read only; linking still uses reimbursements.link. `GET | POST /api/v1/accounting/reimbursements.candidates` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_reimbursements_candidates` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/reimbursements.candidates \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'transactionId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.candidates ## reimbursements.claimPurchase Create a personal expense claim for an active member from an existing unallocated receipt purchase, with its current revision, exact original evidence and explicit confirmation of who paid. Never creates or changes the expense. `POST /api/v1/accounting/reimbursements.claimPurchase` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_reimbursements_claim_purchase` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `expectedEntryRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `userId` | string | Yes | The ID of a person in your company. 1–200 characters. | | `receiptEvidenceId` | string (ID) | Yes | The ID of the related record. | | `personallyPaid` | true | Yes | | | `onBehalfConfirmed` | boolean | | | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.claimPurchase \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedEntryRevision": 3, "userId": "example", "receiptEvidenceId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "personallyPaid": true, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.claimPurchase ## reimbursements.completePayment Complete an awaiting-receipts bank reimbursement only with approved original receipt purchases for the same member, currency and exact total, using current payment, bank and entry revisions. `POST /api/v1/accounting/reimbursements.completePayment` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_reimbursements_complete_payment` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `paymentId` | string (ID) | Yes | The ID of the related record. | | `expectedPaymentRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `expectedTransactionRevision` | integer | Yes | The bank transaction's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `expectedBankEntryId` | string (ID) | Yes | The ID of the related record. | | `expectedBankEntryRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `claimIds` | array of strings (ID) | Yes | A list of record IDs. 1–100 items. | | `payeeConfirmed` | true | Yes | | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.completePayment \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "paymentId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedPaymentRevision": 3, "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedTransactionRevision": 3, "expectedBankEntryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedBankEntryRevision": 3, "claimIds": [ "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" ], "payeeConfirmed": true, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.completePayment ## reimbursements.forEntry Read the reimbursement claim and status for one purchase, if present. Members can read only their own. `GET | POST /api/v1/accounting/reimbursements.forEntry` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_reimbursements_for_entry` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/reimbursements.forEntry \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'entryId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.forEntry ## reimbursements.get Read one reimbursement claim and its purchase and payment status. Members can read only their own. `GET | POST /api/v1/accounting/reimbursements.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_reimbursements_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/reimbursements.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.get ## reimbursements.identify Classify one historical Wise transfer against approved member claims. A dry run returns evidence; apply links only one exact named payee and claim match. `POST /api/v1/accounting/reimbursements.identify` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance MCP tool: `accounting_reimbursements_identify` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `apply` | boolean | | Default `false`. | | `expectedTransactionRevision` | integer | | The bank transaction's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.identify \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.identify ## reimbursements.link Link an existing outgoing bank transfer to approved expense claims with the same member, currency and exact total. The bank entry becomes a transfer, not another expense. `POST /api/v1/accounting/reimbursements.link` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_reimbursements_link` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `claimIds` | array of strings (ID) | Yes | A list of record IDs. 1–100 items. | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `expectedTransactionRevision` | integer | Yes | The bank transaction's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `expectedBankEntryId` | string (ID) | | The ID of the related record. | | `expectedBankEntryRevision` | integer | | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `payeeConfirmed` | true | Yes | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.link \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "claimIds": [ "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" ], "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedTransactionRevision": 3, "reason": "Synthetic example from the docs", "payeeConfirmed": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.link ## reimbursements.list List reimbursement claims and their submitted, approved, rejected, prepared, funded, paid or returned status. Members see only their own claims; finance can review organization claims. `GET | POST /api/v1/accounting/reimbursements.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_reimbursements_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | enum | | Only include records with this status. One of: `submitted`, `approved`, `rejected`, `paid`. | | `limit` | integer | | How many results to return at most. 1 to 200. Default `100`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.list ## reimbursements.matchPayment Match a funded Wise reimbursement to its imported transfer using Wise's exact transfer reference and source amount. The bank entry becomes a transfer. `POST /api/v1/accounting/reimbursements.matchPayment` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_reimbursements_match_payment` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `paymentId` | string (ID) | Yes | The ID of the related record. | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `expectedBankEntryId` | string (ID) | | The ID of the related record. | | `expectedBankEntryRevision` | integer | | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.matchPayment \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "paymentId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.matchPayment ## reimbursements.prepare Prepare one Wise reimbursement for approved claims belonging to the same member and currency. Preparation never sends funds. `POST /api/v1/accounting/reimbursements.prepare` Permissions: `accounting:read`, `accounting:write` · Roles: admin · Idempotency key required MCP tool: `accounting_reimbursements_prepare` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `claimIds` | array of strings (ID) | Yes | A list of record IDs. 1–100 items. | | `returnReviewReason` | string | | 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.prepare \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "claimIds": [ "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.prepare ## reimbursements.recipients List verified Wise recipient bindings for members without exposing account details. `GET | POST /api/v1/accounting/reimbursements.recipients` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_reimbursements_recipients` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.recipients \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.recipients ## reimbursements.recordPayment Record an existing untouched outgoing bank payment to an explicitly confirmed active member as a reimbursement transfer awaiting receipt-backed claims. Never invents purchases or tax and never sends money. `POST /api/v1/accounting/reimbursements.recordPayment` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_reimbursements_record_payment` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `expectedTransactionRevision` | integer | Yes | The bank transaction's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `expectedBankEntryId` | string (ID) | | The ID of the related record. | | `expectedBankEntryRevision` | integer | | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `userId` | string | Yes | The ID of a person in your company. 1–200 characters. | | `payeeConfirmed` | true | Yes | | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.recordPayment \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedTransactionRevision": 3, "userId": "example", "payeeConfirmed": true, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.recordPayment ## reimbursements.refreshRecipient Refresh the verified Wise recipient on an unsent prepared reimbursement after the recipient binding changes. Requires the payment revision and never sends funds. `POST /api/v1/accounting/reimbursements.refreshRecipient` Permissions: `accounting:read`, `accounting:write` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_reimbursements_refresh_recipient` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `paymentId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.refreshRecipient \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "paymentId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.refreshRecipient ## reimbursements.reject Reject a submitted reimbursement with a reason and expected revision. `POST /api/v1/accounting/reimbursements.reject` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_reimbursements_reject` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.reject \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.reject ## reimbursements.send Send one prepared Wise reimbursement for approved claims belonging to one member and currency. Requires a current administrator, the accounting:admin integration scope, the payment's current revision and an idempotency key. A real Wise payout may occur; a sidebar agent asks the person who started its chat to approve this action. Never auto-sends. `POST /api/v1/accounting/reimbursements.send` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_reimbursements_send` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.send \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.send ## reimbursements.submit Submit a reimbursement claim for a personally paid expense from your own original receipt. Oatmilk never duplicates the expense; check its status with reimbursements.list, reimbursements.get or reimbursements.forEntry. `POST /api/v1/accounting/reimbursements.submit` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_reimbursements_submit` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.submit \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.submit ## reimbursements.syncPayment Read a previously funded Wise reimbursement status and update the claim only when Wise confirms payout or return. Never sends funds. `POST /api/v1/accounting/reimbursements.syncPayment` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_reimbursements_sync_payment` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reimbursements.syncPayment \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/reimbursements.syncPayment ## statements.download Get a short-lived private link to a statement file by id. format original (the default): the unchanged original, an uploaded or Wise statement PDF or photo (pass inline: true to open it in the browser) or a CSV or Wise sync export. format pdf: a PDF of it, the original when it is one, otherwise a PDF Oatmilk renders from Wise's statement data, the CSV's lines or the photo, labelled as prepared by Oatmilk and kept for later downloads. Only statement files can be opened this way. Read-only. `GET | POST /api/v1/accounting/statements.download` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_statements_download` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `inline` | boolean | | | | `format` | enum | | One of: `original`, `pdf`. Default `"original"`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/statements.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/statements.download ## statements.lines Read statement lines. With id: the lines Oatmilk read from that statement file, whether they were checked against its period and its opening and closing balances (verified), whether they are in the books (booked), and the reason when they were not. With accountId, from and to (up to 400 days): the account's lines in the books for those dates, each with the id of a checked statement that covers its date (verifiedBy, or null), and the statement files of that period. Amounts are signed integer strings in minor units of the account's currency; money out is negative. Read-only. `GET | POST /api/v1/accounting/statements.lines` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_statements_lines` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `accountId` | string (ID) | | The ID of a bank, card or payment account, from accounts.list. | | `from` | string (date) | | The first date to include, as YYYY-MM-DD. | | `to` | string (date) | | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/statements.lines \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/statements.lines ## statements.list List the original statement files Oatmilk keeps (uploaded PDF and photo statements, Wise's own monthly statement PDFs, and CSV exports), each with its account, bank, last four digits, period, balances and status: queued, reading, imported (with how many lines were new and how many were already in the books), review (with its plain reason and reasonCode; a statement waiting on its account also has suggestedAccountId, the likeliest existing account, and newAccount, an account drafted from the statement with name, provider, currency, lastFour and accountType, when its number isn't saved on any account; reasonCode ACCOUNT_NEW means no account Oatmilk has could be it), duplicate (of which statement), kept or failed. Also says, for every account and month, whether a statement covers the whole month (statement), part of it (partial), only lines synced from Wise (feed), nothing (missing) or a time before the account's first activity (before). Each file also says whether a PDF can be downloaded (pdf: original, or rendered by Oatmilk from Wise's statement data, a CSV or a photo), how its lines were read (readWith: vision, or text for a reading from the PDF's text layer made before vision), the reader's own warnings, a person's mark (flagged with a note, or checked) and checks: why it waits for a person (review, failed, flagged, old_reader, unsure). Wise sync data is listed once per account and window (the newest copy). Filter with from and to (up to 36 months; the last 12 when left out), or allPeriods: true to find files across the entire saved history, accountId or accountIds (up to 50), includeCoverage: false for only the file list without account-month coverage (coverageIncluded: false and empty month grids; omitted or true retains full coverage), bank, status (or attention for review and failed, or check for every file that waits for a person; toCheck counts them), search, limit and offset. Read-only. `GET | POST /api/v1/accounting/statements.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_statements_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string (date) | | The first date to include, as YYYY-MM-DD. | | `to` | string (date) | | The last date to include, as YYYY-MM-DD. | | `accountId` | string (ID) | | The ID of a bank, card or payment account, from accounts.list. | | `accountIds` | array of strings (ID) or string | | | | `bank` | string | | 1–60 characters. | | `status` | enum | | Only include records with this status. One of: `queued`, `reading`, `imported`, `review`, `duplicate`, `kept`, `failed`, `attention`, `check`. | | `search` | string | | Text to search for. at most 200 characters. | | `allPeriods` | boolean | | | | `includeCoverage` | boolean | | Also include coverage. | | `limit` | integer | | How many results to return at most. 1 to 200. Default `100`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/statements.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/statements.list ## statements.review Decide what happens to uploaded statements, with idempotencyKey, decision and either id with expectedRevision or items (up to 100 of { id, expectedRevision }). assign (with accountId) checks the saved reading again for that account without reading the file, and with remember: true also saves the statement's account number and cardholders' cards on that account (never one already on another account) and checks the other statements waiting on it again; new_account (administrators only, with the accounting:admin scope for API keys) adds the account the statement is for once, from the first statement's newAccount with any of name, provider, currency, lastFour and accountType in newAccount overriding it, saves its last four digits so later statements match it, assigns every named statement to it, checks every other statement waiting on that account again, and answers with the account and how many statements it is checking (rechecking); read_again reads the file again (model: zai/glm-5.3-flash by default, or openai/gpt-6-luna, google/gemini-3.8-flash or openai/gpt-6.1-sol) and reads the account number, each cardholder's card and every line afresh; an imported statement keeps its account; keep keeps a statement that needs review as the original without importing its lines; flag (with an optional note) puts it in front of a person; unflag removes the flag; confirm records that a person checked it was read correctly. Nothing is booked unless the lines add up to the statement's balances, and an imported statement's account can't change here. With items, the answer lists each statement changed and each one skipped with why. `POST /api/v1/accounting/statements.review` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_statements_review` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `items` | array of objects | | 1–100 items. | | `items[].id` | string (ID) | Yes | The record's ID. | | `items[].expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `decision` | enum | Yes | What you decided. One of: `assign`, `new_account`, `read_again`, `keep`, `flag`, `unflag`, `confirm`. | | `accountId` | string (ID) | | The ID of a bank, card or payment account, from accounts.list. | | `note` | string | | A short note, kept with the record. at most 500 characters. | | `newAccount` | object | | No other fields. | | `newAccount.name` | string | | A display name. 1–100 characters. | | `newAccount.provider` | enum | | One of: `rbc`, `other`. | | `newAccount.currency` | string | | Three-letter currency code, such as CAD or USD. | | `newAccount.lastFour` | string or null | | | | `newAccount.accountType` | enum | | One of: `bank`, `credit_card`. | | `model` | enum | | One of: `openai/gpt-6.1-sol`, `openai/gpt-6-luna`, `google/gemini-3.8-flash`, `zai/glm-5.3-flash`. | | `remember` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/statements.review \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "decision": "assign", "items": [ { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 } ], "accountId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" }' ``` Reference page: https://app.getoatmilk.com/docs/api/statements.review ## summaries.get Read the separately generated purchase summary and supporting source references. Never treats a summary as financial evidence. `GET | POST /api/v1/accounting/summaries.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_summaries_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/summaries.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'entryId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/summaries.get ## summaries.update Edit, dismiss or retry a purchase summary independently of receipt processing and human notes, with its current revision and idempotency key. `POST /api/v1/accounting/summaries.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_summaries_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `decision` | enum | Yes | What you decided. One of: `edit`, `dismiss`, `retry`. | | `summary` | string | | 1–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/summaries.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedRevision": 3, "decision": "edit", "summary": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/summaries.update ## tags.ai.list List what the project classifier suggested for a project (tagId, optional verdict likely or ask) that nobody has accepted or dismissed yet: each transaction's id, date, merchant, amount, currency and the classifier's probability. Accept with accounting_entries_tags_set add, or say no with dismiss. `GET | POST /api/v1/accounting/tags.ai.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tags_ai_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `tagId` | string (ID) | Yes | The ID of a project tag, from tags.list. | | `verdict` | enum | | One of: `likely`, `ask`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/tags.ai.list \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'tagId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.ai.list ## tags.ai.run Ask the project classifier which transactions belong to a project, with tagId, idempotencyKey, optional limit (1 to 100, default 40) and recheck (look again at transactions it already answered). It reads transactions in the project's dates (and the 30 days before), or for a project without dates those that mention its name, other names or keywords, and uses the project's description, keywords and Notion page text. Likely matches become suggestions; unsure ones become questions (accounting_tags_ai_list verdict ask). A sure match tags a transaction by itself only while Autopilot is on and may finish work, for a project with a start and end date around an open transaction with no tag, dated on or after the project was set up; earlier transactions only get suggestions. An answer without probabilities counts as unsure. Returns counts. `POST /api/v1/accounting/tags.ai.run` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_tags_ai_run` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `tagId` | string (ID) | Yes | The ID of a project tag, from tags.list. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `40`. | | `recheck` | boolean | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.ai.run \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "tagId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.ai.run ## tags.create Create a project tag with name and idempotencyKey, and optionally parentId (a group; groups are one level deep), color (gray, red, orange, amber, green, teal, blue, purple or pink), description, aliases (other names people write), startsOn/endsOn (the event window) and budgetMinor with budgetCurrency. Active tag names are unique. `POST /api/v1/accounting/tags.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_tags_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | Yes | A display name. 1–80 characters. | | `description` | string | | A short description. at most 500 characters. | | `color` | enum | | One of: `gray`, `red`, `orange`, `amber`, `green`, `teal`, `blue`, `purple`, `pink`. | | `aliases` | array of strings | | at most 10 items; each 2–60 characters. | | `parentId` | string (ID) | | The ID of the related record. | | `startsOn` | string | | | | `endsOn` | string | | | | `budgetMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `budgetCurrency` | string | | Three-letter currency code, such as CAD. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Synthetic Ventures Inc." }' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.create ## tags.delete Delete a project tag that was never used, with id, expectedRevision and idempotencyKey. A tag on any transaction, or holding other tags, can't be deleted: archive it or merge it instead. `POST /api/v1/accounting/tags.delete` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_tags_delete` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.delete \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.delete ## tags.get Get one project tag with the tags inside it, its totals, profit and loss by category, a monthly timeline and the merchant rules Oatmilk learned for it. A group includes the tags inside it, counting each transaction once. `GET | POST /api/v1/accounting/tags.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tags_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/tags.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.get ## tags.list List project tags (which event or program a transaction is for): name, group (parent_id, one level deep), color, description, other names, event window (starts_on, ends_on), budget, archived state, the caller's last_used_at, and for finance spend, income, net and transaction counts by currency. A group's rollup counts each transaction once. Pass includeArchived to include archived tags. Contributors see active tags without totals or budgets. `GET | POST /api/v1/accounting/tags.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_tags_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `includeArchived` | boolean | | Also include archived records. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/tags.list ## tags.merge Merge one project tag into another with sourceId, targetId, the source's expectedRevision and idempotencyKey: every transaction tagged with the source gets the target instead (once), learned rules and the tags inside a merged group move too, and the source is archived. The audit records every transaction moved. `POST /api/v1/accounting/tags.merge` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_tags_merge` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `sourceId` | string (ID) | Yes | The ID of the related record. | | `targetId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.merge \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "sourceId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "targetId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.merge ## tags.notion.link Link a project to a Notion page or database row with page, sourceOfTruth (notion: name, dates, status, budget and description come from Notion; oatmilk: Oatmilk writes them to Notion) and idempotencyKey. Pass tagId (and optionally the project's expectedRevision) to link an existing project, or leave it out to create a project from the page (optionally parentId and color). For a database row, properties is required: the Notion property for each of dates, status, budget and description, or null to leave it out (accounting_tags_notion_preview suggests them; nothing is mapped silently). Linking syncs once straight away and never empties a filled field on either side: where one side is empty the filled value is kept, reads the page text as context for the classifier, and asks the classifier to suggest transactions for the project. `POST /api/v1/accounting/tags.notion.link` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_tags_notion_link` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `tagId` | string (ID) | | The ID of a project tag, from tags.list. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `page` | string | Yes | 1–2000 characters. | | `sourceOfTruth` | enum | Yes | One of: `oatmilk`, `notion`. | | `properties` | object | | No other fields. | | `properties.dates` | string or null | | | | `properties.status` | string or null | | Only include records with this status. | | `properties.budget` | string or null | | | | `properties.description` | string or null | | A short description. | | `parentId` | string (ID) | | The ID of the related record. | | `color` | enum | | One of: `gray`, `red`, `orange`, `amber`, `green`, `teal`, `blue`, `purple`, `pink`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.notion.link \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "page": "example", "sourceOfTruth": "oatmilk" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.notion.link ## tags.notion.preview Read a Notion page or database row before linking it to a project: supply page (a notion.so link or page ID) and, for an existing project, tagId. Returns its title, whether it is a row and in which database, suggested properties for dates, status, budget and description with every property that could hold each (choices) and the value each would read (propertyValues), the start of the page text the classifier would use, any problems, and with tagId the project's own values and what the first sync would change for each source of truth (firstSync). Review it, then pass the properties you chose to accounting_tags_notion_link. `GET | POST /api/v1/accounting/tags.notion.preview` Permissions: `accounting:read` · Roles: admin, finance · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_tags_notion_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `page` | string | Yes | 1–2000 characters. | | `tagId` | string (ID) | | The ID of a project tag, from tags.list. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/tags.notion.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'page=example' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.notion.preview ## tags.notion.resolve Resolve a sync conflict on one field (name, dates, status, budget or description) with tagId, field, keep (notion writes Notion's value to Oatmilk; oatmilk writes Oatmilk's value to Notion) and idempotencyKey. Then syncs the project. `POST /api/v1/accounting/tags.notion.resolve` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_tags_notion_resolve` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `tagId` | string (ID) | Yes | The ID of a project tag, from tags.list. | | `field` | enum | Yes | One of: `name`, `dates`, `status`, `budget`, `description`. | | `keep` | enum | Yes | One of: `oatmilk`, `notion`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.notion.resolve \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "tagId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "field": "name", "keep": "oatmilk" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.notion.resolve ## tags.notion.sync Sync a linked project with Notion now, with tagId and idempotencyKey (linked projects also sync about hourly). Fields change only on the side that isn't the source of truth. A field the other side changed since they last agreed is not overwritten: it is returned in project.conflicts for a person to resolve with accounting_tags_notion_resolve. `POST /api/v1/accounting/tags.notion.sync` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_tags_notion_sync` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `tagId` | string (ID) | Yes | The ID of a project tag, from tags.list. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.notion.sync \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "tagId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.notion.sync ## tags.notion.unlink Stop syncing a project with Notion, with tagId and idempotencyKey. The project keeps its current name, dates, budget and description; nothing changes in Notion. `POST /api/v1/accounting/tags.notion.unlink` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_tags_notion_unlink` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `tagId` | string (ID) | Yes | The ID of a project tag, from tags.list. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.notion.unlink \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "tagId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.notion.unlink ## tags.project.update Set a project tag's status (planned, active or completed) or keywords (up to 20 words the classifier looks for) with tagId, idempotencyKey and optionally expectedRevision (the project's revision from tags.get). Changing keywords asks the project classifier to look at its transactions again. When the project syncs from Notion, a status set here that differs from Notion's is a conflict to resolve with accounting_tags_notion_resolve. `POST /api/v1/accounting/tags.project.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_tags_project_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `tagId` | string (ID) | Yes | The ID of a project tag, from tags.list. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `status` | enum | | Only include records with this status. One of: `planned`, `active`, `completed`. | | `keywords` | array of strings | | at most 20 items; each 2–60 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.project.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "tagId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "status": "planned" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.project.update ## tags.report Profit and loss by project tag for optional from/to dates and currency: spend, income and transaction counts per tag and per group, the deduplicated tagged total, untagged transactions, and overlap (transactions tagged to more than one project, which count in each). Currencies stay separate. `GET | POST /api/v1/accounting/tags.report` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tags_report` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.report \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/tags.report ## tags.suggest Suggest project tags for up to 100 entries (entryIds) from each tag's event window, learned merchant rules and notes or descriptions that mention the tag or its other names. Each suggestion has a score from 0 to 100 and its reasons. Suggestions don't count in totals until accepted with accounting_entries_tags_set. `GET | POST /api/v1/accounting/tags.suggest` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_tags_suggest` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryIds` | array of strings (ID) | | IDs of accounting entries, from entries.list or attention.mine. 1–100 items. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.suggest \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "entryIds": [ "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.suggest ## tags.update Change a project tag with id, expectedRevision and idempotencyKey: rename, recolor, describe, set other names, move to a group or out of one (parentId null), set or clear the event window and budget, or archive and restore it (archived true or false). Archiving a group archives the tags inside it; restoring the group restores them. `POST /api/v1/accounting/tags.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_tags_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `name` | string | | A display name. 1–80 characters. | | `description` | string | | A short description. at most 500 characters. | | `color` | enum | | One of: `gray`, `red`, `orange`, `amber`, `green`, `teal`, `blue`, `purple`, `pink`. | | `aliases` | array of strings | | at most 10 items; each 2–60 characters. | | `parentId` | string (ID) or null | | | | `startsOn` | string or null | | | | `endsOn` | string or null | | | | `budgetMinor` | string or null | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `budgetCurrency` | string or null | | | | `archived` | boolean | | Whether the record is archived. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tags.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/tags.update ## transactions.correct Correct an unallocated CSV bank row with its revision, reason and idempotency key. Original statement evidence and previous import identities remain preserved. Closed periods and Wise rows cannot be edited. `POST /api/v1/accounting/transactions.correct` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_correct_bank_import` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `date` | string | | A date, as YYYY-MM-DD. | | `amountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string, may be negative. | | `description` | string | | A short description. 1–2000 characters. | | `reference` | string | | at most 1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/transactions.correct \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/transactions.correct ## transactions.documents.download Get a short-lived download for a document Oatmilk fetched from the bank provider for a transaction, such as Wise's transfer confirmation for a contractor payment. Ids are listed on entries.get as providerDocuments. `GET | POST /api/v1/accounting/transactions.documents.download` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_transactions_documents_download` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/transactions.documents.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/transactions.documents.download ## transactions.get Get one imported bank or card transaction by id: amount, date, account, statement text, what it is matched to (allocations with their transactions and entries), and the facts the bank supplied such as merchant, card, cardholder and exchange details. `GET | POST /api/v1/accounting/transactions.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_transactions_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/transactions.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/transactions.get ## transactions.list Search imported bank and card transactions with search text, accountId, currency, from/to dates, allocation (matched, unmatched or partial), direction (in or out), minAmount/maxAmount in minor units, limit and offset. Each line includes allocatedMinor and the entry ids it is matched to. `GET | POST /api/v1/accounting/transactions.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_list_transactions` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `includeReversed` | boolean | | Also include reversed. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `search` | string | | Text to search for. at most 200 characters. | | `accountId` | string (ID) | | The ID of a bank, card or payment account, from accounts.list. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `allocation` | enum | | One of: `matched`, `unmatched`, `partial`. | | `direction` | enum | | One of: `in`, `out`. | | `minAmount` | string | | The smallest amount to include, in cents, written as a string. Whole number written as a string. | | `maxAmount` | string | | The largest amount to include, in cents, written as a string. Whole number written as a string. | | `tagId` | string (ID) | | The ID of a project tag, from tags.list. | | `untagged` | boolean | | | | `limit` | integer | | How many results to return at most. 1 to 500. Default `100`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/transactions.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/transactions.list ## transactions.restore Restore a previously reversed CSV bank row with a revision check and audited reason. `POST /api/v1/accounting/transactions.restore` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_restore_bank_import` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/transactions.restore \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/transactions.restore ## transactions.reverse Exclude an erroneous unallocated CSV bank row with an audited reason. Preserves evidence and duplicate detection. `POST /api/v1/accounting/transactions.reverse` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_reverse_bank_import` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/transactions.reverse \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/transactions.reverse ## trips.create Create a trip with title, startDate, optional endDate and placeLabel, and an idempotencyKey. Finance access required. `POST /api/v1/accounting/trips.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_trips_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `title` | string | Yes | A short title. 1–200 characters. | | `startDate` | string | Yes | A date, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `endDate` | string | | A date, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `placeLabel` | string | | at most 200 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/trips.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "title": "Synthetic services agreement", "startDate": "2026-09-30" }' ``` Reference page: https://app.getoatmilk.com/docs/api/trips.create ## trips.get Read one trip by id: its summary, every transaction in it with its role (lodging, hold, room_charge, meal, transport or other), confidence and note, and the place pins to draw on a map. Contributors see only their own transactions in it. `GET | POST /api/v1/accounting/trips.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_trips_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/trips.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/trips.get ## trips.linkEntry Put a transaction in a trip with tripId, entryId, an optional role (lodging, hold, room_charge, meal, transport or other) and an idempotencyKey. A transaction is in one trip at a time; linking it here moves it. Contributors can link only transactions they own. `POST /api/v1/accounting/trips.linkEntry` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_trips_link_entry` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `tripId` | string (ID) | Yes | The ID of the related record. | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `role` | enum | | A person's access level in the company. One of: `lodging`, `hold`, `room_charge`, `meal`, `transport`, `other`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/trips.linkEntry \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "tripId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/trips.linkEntry ## trips.list List trips: purchases made while travelling, grouped by stay, with title, place, dates, status (upcoming, active or past), how many transactions belong to each, what it cost per currency (holds left out, hotel credits taken off) and its map centre. Contributors see only trips that include a transaction they own. `GET | POST /api/v1/accounting/trips.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_trips_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/trips.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/trips.list ## trips.unlinkEntry Take a transaction out of a trip with tripId, entryId and an idempotencyKey. Oatmilk never puts it back. Contributors can unlink only transactions they own. `POST /api/v1/accounting/trips.unlinkEntry` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_trips_unlink_entry` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `tripId` | string (ID) | Yes | The ID of the related record. | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/trips.unlinkEntry \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "tripId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/trips.unlinkEntry ## trips.update Rename a trip or change its dates or place with id, the fields to change and an idempotencyKey. A trip a person edited is never rewritten by Oatmilk. Finance access required. `POST /api/v1/accounting/trips.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_trips_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `title` | string | | A short title. 1–200 characters. | | `startDate` | string | | A date, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `endDate` | string or null | | | | `placeLabel` | string or null | | | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/trips.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "title": "Synthetic services agreement" }' ``` Reference page: https://app.getoatmilk.com/docs/api/trips.update # Group: Inbox and uploads > Receipts, file uploads, company mail, connected inboxes and processing jobs. MCP toolset: `inbox` (https://app.getoatmilk.com/api/mcp?toolset=inbox) ## documentRequests - [`documentRequests.list`](https://app.getoatmilk.com/docs/api/documentRequests.list.md) — Read this organization's document requests and their receipt status. - [`documentRequests.recipients`](https://app.getoatmilk.com/docs/api/documentRequests.recipients.md) — Search active contractors and team members by name or email for a document request. - [`documentRequests.revoke`](https://app.getoatmilk.com/docs/api/documentRequests.revoke.md) — Revoke an outstanding document request and cancel queued delivery. - [`documentRequests.send`](https://app.getoatmilk.com/docs/api/documentRequests.send.md) — Email a document request with an expiring private upload link and an authenticated reply path. Requires a recipient and idempotency key. ## emails - [`emails.submitBatch`](https://app.getoatmilk.com/docs/api/emails.submitBatch.md) — Submit original RFC822 emails using idempotencyKey and emails [{rawEmail,filename,metadata}]. Up to 20 emails and 3 MB total per request. Claimed sender metadata never authorizes a different user. Preserves original email bytes and returns processing jobs. ## evidence - [`evidence.download`](https://app.getoatmilk.com/docs/api/evidence.download.md) — Get an authorized short-lived private evidence download by evidenceId. ## evidence.inboxSearch - [`evidence.inboxSearch.cancel`](https://app.getoatmilk.com/docs/api/evidence.inboxSearch.cancel.md) — Stop an inbox search with id and idempotencyKey. Purchases not yet searched are cancelled, and no further inbox is read; one being read finishes and copies nothing more. Only the member who started it or an administrator can stop it. - [`evidence.inboxSearch.get`](https://app.getoatmilk.com/docs/api/evidence.inboxSearch.get.md) — Read an inbox search (id), or the latest search of each purchase (entryIds, comma-separated, up to 100): its status, runId, and each purchase's state and outcome with counts of emails found and screened. Contributors see only their own searches. Mail content is never returned, except the sender, subject and date of a receipt that was copied into company mail, for people who may read company mail. - [`evidence.inboxSearch.start`](https://app.getoatmilk.com/docs/api/evidence.inboxSearch.start.md) — Search the caller's own connected Gmail or Outlook inbox for the receipt, invoice, or order confirmation of purchases that still need one, with entryIds (1 to 25) and idempotencyKey. Integrations also need the mailboxes:search scope; plain read and write access never grants it. Only the caller's own inbox is ever searched, and the request is the consent for this search only: each of their inboxes is read at most once per purchase and at most one email is copied for it, and it never changes daily checks or the investigator's consent. A purchase on another member's card is not searched (it stays with that member's ask), and a contributor can only search purchases on their own cards. A merchant email or nearby processor order may be copied after financial screening. On a member-requested search only, a recent Gmail forward whose preview is truncated may instead be copied after a bounded raw read proves the single forwarded original has a confirmed order, nearby original date, merchant, exact bank-currency grand total and card suffix, and the credential screen clears it; one later judged not to be financial evidence is removed again. The purchase it was found for is suggested to matching first, which still compares it with every likely transaction: it is attached only when matching would pick that purchase anyway, and left for review otherwise. Returns the search with each purchase's state (searching, reading, attached, review, not found or skipped) and its runId. With no usable inbox the search waits for the caller to connect or resume one (status waiting_for_inbox) and says which providers are available. A member can start 60 searches an hour, run five at once and search 250 purchases a day. ## inbox - [`inbox.address`](https://app.getoatmilk.com/docs/api/inbox.address.md) — Read the address you forward receipts and invoices to. It's the organization's accounting address; anything sent to it from your verified email is filed as yours. Needs only accounting:read. Finance reads every receiving address with mail.inboundAddresses. - [`inbox.list`](https://app.getoatmilk.com/docs/api/inbox.list.md) — List receipt submissions and their review or processing state. ## intake - [`intake.classify`](https://app.getoatmilk.com/docs/api/intake.classify.md) — Suggest what an uploaded file is and where it belongs from file-type and name rules, CSV columns, readable text and the accounting classifier. Returns kind, probability, accepted, reasons and a destination such as the bank account a statement belongs to. Nothing is filed until intake.process. - [`intake.complete`](https://app.getoatmilk.com/docs/api/intake.complete.md) — Verify an uploaded intake file's size, SHA-256 hash and type by id with an idempotency key. Returns the item, ready to classify or file. - [`intake.dismiss`](https://app.getoatmilk.com/docs/api/intake.dismiss.md) — Dismiss an uploaded file that should not be filed. With removeOriginal: true, the stored original is deleted too, for an accidental upload. Adding the same file again brings it back (and uploads it again when its original was removed). Filed files can't be dismissed. - [`intake.fromMail`](https://app.getoatmilk.com/docs/api/intake.fromMail.md) — File an attachment of an email in the workspace's mail (messageId, evidenceId, idempotencyKey) through universal intake: it becomes an uploaded file like a dropped one, ready for intake.classify and intake.process, and remembers the email it came from. Attachments of mail held for an administrator (quarantined, or holding sign-in or security details) can't be filed. The same file already added returns that upload with duplicate: true. - [`intake.get`](https://app.getoatmilk.com/docs/api/intake.get.md) — Read one uploaded file with its suggestion, decision and result. Contributors can read only their own uploads. - [`intake.inline`](https://app.getoatmilk.com/docs/api/intake.inline.md) — Add any file Oatmilk should file in one call, for clients that can't upload to a link: filename, mimeType, contentBase64 (the file's bytes in base64, at most 2 MB), idempotencyKey and optional presetKind. Oatmilk checks the bytes, stores them privately and completes the upload, as intake.prepare, the upload and intake.complete do; a file already added returns duplicate: true. Then call intake.classify and intake.process. Retrying with the same key and file is safe. Contributors add receipts only. Larger files use intake.prepare. - [`intake.list`](https://app.getoatmilk.com/docs/api/intake.list.md) — List uploaded files and where each one went. Filter with status (open, done, dismissed, all or one status), mine, search and since. Contributors see only their own uploads. - [`intake.prepare`](https://app.getoatmilk.com/docs/api/intake.prepare.md) — Prepare a private upload for any file Oatmilk should file: a receipt, vendor bill, issued invoice, bank statement, contractor agreement, tax or company document. Supply filename, mimeType, sizeBytes, sha256, idempotencyKey and optional presetKind. A file already added to the organization returns duplicate: true with addedAt. Otherwise PUT the unchanged bytes to uploadUrl, then call intake.complete. Contributors can file receipts only. - [`intake.process`](https://app.getoatmilk.com/docs/api/intake.process.md) — File an uploaded item as a confirmed kind (receipt, vendor_bill, issued_invoice, bank_statement, contractor_agreement, tax_document, company_document or other) with an optional target: accountId or newAccount for a CSV statement; contractorId, or newContractor (displayName, email, optional legalName, and confirmedNew once a person checked they aren't someone with the same or a close name), for an agreement, a tax document or another record kept for a contractor; taxYear for a tax document; paymentAccountId for a receipt. An agreement for a contractor that matches the one already on file (same start and end dates, rate and role) is refused with SAME_AGREEMENT until sameAgreement says what to do: link (file this upload as that agreement, adding nothing), replace (keep this copy as the agreement and void the uploaded one on file; not for one signed in Oatmilk) or keep (keep both, without reading this one's terms). Receipts and bills enter receipt processing, CSV statements are imported, agreements are kept as signed agreements, and other records go to the documents store. An issued invoice waits for review in the invoice importer; send invoiceIds once it is imported. Idempotent. Contributors can file receipts only. - [`intake.read`](https://app.getoatmilk.com/docs/api/intake.read.md) — Read what an uploaded file says, by id, without filing or changing it: its words (text, at most 20,000 characters, with truncated true when cut short), its page count (pages), how many pages were read (readPages), whether the whole file was read (complete), and any warnings. Photos and PDFs are read with vision once and the reading is kept, so classifying the file later doesn't read it again; CSV, text, Word and email files are read directly. Spreadsheets come back without text. Payment and sign-in links are hidden, and a file holding sign-in or security details is refused. Contributors can read only their own uploads. ## jobs - [`jobs.get`](https://app.getoatmilk.com/docs/api/jobs.get.md) — Get truthful asynchronous processing status by jobId. ## mail.activity - [`mail.activity.get`](https://app.getoatmilk.com/docs/api/mail.activity.get.md) — Read the details of one entry in a visible email's activity, with id (the email) and eventId (the entry's id from mail.get activity): what Oatmilk's review concluded and how sure it was, a person's answer, how an entry was recorded and why, or which draft fields changed. Never returns the email's text. ## mail - [`mail.approve`](https://app.getoatmilk.com/docs/api/mail.approve.md) — Approve a cleared financial draft for accounting with explicit treatment and evidence provenance. - [`mail.askAdmin`](https://app.getoatmilk.com/docs/api/mail.askAdmin.md) — Ask the organization's other administrators by email to approve the new sender of a financial email, with id and idempotencyKey. Sends at most one request per member for each email and never approves the sender itself. - [`mail.attachEvidence`](https://app.getoatmilk.com/docs/api/mail.attachEvidence.md) — Attach a cleared financial email to an existing bank purchase with current revisions and a reason. Keeps the original document kind and tax uncertainty; creates no expense and never reuses evidence for another purchase. - [`mail.download`](https://app.getoatmilk.com/docs/api/mail.download.md) — Prepare an explicit short-lived download of authorized company mail evidence. - [`mail.get`](https://app.getoatmilk.com/docs/api/mail.get.md) — Read visible company mail with immutable evidence metadata, unbooked financial drafts, the email's activity log, Oatmilk's latest review (with its one open question, if any) and how adding it to the books is going. - [`mail.inboundAddresses`](https://app.getoatmilk.com/docs/api/mail.inboundAddresses.md) — List the organization's receiving email addresses. Contributors see only the accounting address. - [`mail.list`](https://app.getoatmilk.com/docs/api/mail.list.md) — Search visible company mail with search: literal text in the subject, sender, body, attachment filenames and extracted document text. searchScope headers limits it to subject and sender. archived false (default) searches inbox mail, true archived mail, all both. Results include items and total; use limit and offset to continue, then mail.get for full details. Restricted messages require explicit security permission. Connected personal inbox receipt searches use evidence.inboxSearch.start/get instead. - [`mail.purchaseCandidates`](https://app.getoatmilk.com/docs/api/mail.purchaseCandidates.md) — Find existing bank purchases to attach an authorized financial email as supporting evidence. Candidates remain separate; selecting a match requires human review. - [`mail.rerun`](https://app.getoatmilk.com/docs/api/mail.rerun.md) — Process an email again from its saved original, with id, expectedRevision, idempotencyKey and from: everything (screen, classify, read the documents with vision, fraud check and second opinion; the default, also called reclassify), reading (keep the classifier's answer and read the documents again) or review (keep the reading and ask Oatmilk's second opinion again). Every later step runs again with the workspace's current memory, people, vendors and bank lines. An email already added to the books can't be processed again: use matching.rerunEntry for its entry. A person's corrected draft is kept. Returns jobId and generation; follow the steps with runs.get (subjectType mail, subjectId the email id), then read the result with mail.get. - [`mail.rerunMany`](https://app.getoatmilk.com/docs/api/mail.rerunMany.md) — Process up to 50 emails again in one request, with items [{id, expectedRevision}], from (everything, reading or review, as in mail.rerun) and idempotencyKey. Each email gets its own job; returns queued [{id, jobId, generation}] and failed [{id, code, message}] so one stale or already-added email doesn't stop the rest. - [`mail.retry`](https://app.getoatmilk.com/docs/api/mail.retry.md) — Queue company mail rescreening while preserving original evidence and human draft corrections. mail.rerun does the same and can start from a later step. - [`mail.update`](https://app.getoatmilk.com/docs/api/mail.update.md) — Mark company mail read, archived or spam with revision and idempotency checks. ## mail.drafts - [`mail.drafts.create`](https://app.getoatmilk.com/docs/api/mail.drafts.create.md) — Create an unbooked human financial draft from eligible cleared mail after an explicit review reason. - [`mail.drafts.update`](https://app.getoatmilk.com/docs/api/mail.drafts.update.md) — Save reviewed company financial facts with current message and draft revisions. ## mail.fraud - [`mail.fraud.decide`](https://app.getoatmilk.com/docs/api/mail.fraud.decide.md) — Decide about an email's fraud warning with id, expectedRevision, decision (genuine or fraud) and idempotencyKey. genuine clears the warning so a person can add the email to the books; fraud archives it, turns automation off for it and keeps it out of the books. Both are written to the email's activity with who decided. Nothing is ever approved automatically. - [`mail.fraud.rescan`](https://app.getoatmilk.com/docs/api/mail.fraud.rescan.md) — Check recent company mail for fraud again with the current rules: days (1 to 30, default 14) and limit (1 to 100, default 50), within a short time budget. Returns checked, flagged, raised, skipped and more. It adds or keeps warnings (an earlier reviewer clearing stands only for the same weak history signals), keeps every person's decision, and turns automation off for flagged mail. ## mail.review - [`mail.review.answer`](https://app.getoatmilk.com/docs/api/mail.review.answer.md) — Answer the one question Oatmilk's review asked about an email, with id, reviewId, choiceId (a, b or c) and idempotencyKey. The choice's effect comes from the stored question: leave the draft, fix it, fix it and add it to the books through the same checks as mail.approve, or archive the email. Choices that change date, currency, total or tax only update the draft; a separate full draft review is required before booking. - [`mail.review.memory`](https://app.getoatmilk.com/docs/api/mail.review.memory.md) — Save or dismiss the memory entry Oatmilk suggested while reviewing an email, with id, reviewId, decision (save or dismiss) and idempotencyKey. Nothing is added to the organization's memory unless an administrator saves it. ## mail.rules - [`mail.rules.list`](https://app.getoatmilk.com/docs/api/mail.rules.list.md) — Read exact sponsored vendor sender rules. Administrator access required. - [`mail.rules.save`](https://app.getoatmilk.com/docs/api/mail.rules.save.md) — Manage exact vendor sender authorization with an active finance sponsor and revision checks. ## mailboxes.connect - [`mailboxes.connect.link`](https://app.getoatmilk.com/docs/api/mailboxes.connect.link.md) — Get the link where the signed-in member connects their own Gmail or Outlook inbox in Oatmilk, with optional provider and dailyChecks. Nothing is connected and no sign-in starts: connecting asks the inbox owner to agree in the provider's own window, so only they can do it. Give the person the url; connected inboxes then appear in mailboxes.list. - [`mailboxes.connect.start`](https://app.getoatmilk.com/docs/api/mailboxes.connect.start.md) — Start connecting the signed-in member's own Gmail or Outlook inbox with read-only access. Returns the provider's consent URL; the member finishes in the browser. dailyChecks (default on for Connected inboxes, explicitly off for onboarding and Find in my inbox) decides whether Oatmilk checks the new inbox every day, starting 90 days back; without it the inbox is read only when the member asks, from today. Reconnecting an inbox keeps it paused if it was, and only turns daily checks on, never off. Optional returnTo (a workspace page or /onboarding) brings them back there, popup reports back to the page that opened the window and closes it, and inboxSearchId starts that member's waiting inbox search as soon as the inbox connects. mode temporary (with inboxSearchId, never dailyChecks) asks for read-only access for that one search instead: no offline access, the token is held sealed for at most an hour, bound to that search and member, never checked daily or by anything else, never listed as a connected inbox, and revoked (where the provider allows) and wiped when the search finishes, is stopped or expires. These choices stay on the server behind a single-use nonce, and the exchange uses PKCE. Dashboard only. ## mailboxes - [`mailboxes.disconnect`](https://app.getoatmilk.com/docs/api/mailboxes.disconnect.md) — Disconnect a connected inbox and delete its stored access at once, with id, expectedRevision and idempotencyKey. Emails already imported stay in company mail. Only the member who connected it or an administrator can disconnect it. - [`mailboxes.list`](https://app.getoatmilk.com/docs/api/mailboxes.list.md) — List connected Gmail and Outlook inboxes: provider, address, who connected it, whether it is paused (scan_enabled false), checked daily (daily_checks) and offered for other members' receipts (search_for_others), the last scan, how many financial emails were imported, the latest check (a run with its status and summary), whether it is still catching up on older mail, whether scheduled checks are on, and which providers this deployment supports. Contributors see only their own. Tokens are never returned. - [`mailboxes.scan`](https://app.getoatmilk.com/docs/api/mailboxes.scan.md) — Check your own connected inbox for receipts and invoices now instead of waiting for the daily check; nobody can check another member's inbox, a paused one must be resumed first, and integrations need the mailboxes:search scope. Only financial emails are imported. Returns right away with the check's runId (status started, or busy with the running check's runId when one is already reading the inbox); follow it with runs.get for its steps and counts. - [`mailboxes.update`](https://app.getoatmilk.com/docs/api/mailboxes.update.md) — Change a connected inbox with id, expectedRevision and idempotencyKey: scanEnabled pauses (false) or resumes (true) all reading, dailyChecks turns daily checks on (reading back 90 days) or off, searchForOthers lets Autopilot search it for other members' receipts, and scanFrom changes how far back it reads. Only the member who connected it can resume it or widen what is read, and only from the dashboard; an administrator can pause it or turn those off, and integrations can only narrow. ## submissions - [`submissions.get`](https://app.getoatmilk.com/docs/api/submissions.get.md) — Get a submission by submissionId, including its evidence, jobs, and resulting entry references even when extraction failed. - [`submissions.release`](https://app.getoatmilk.com/docs/api/submissions.release.md) — Release quarantined accounting intake after administrator review. Restricted mail also requires security consent. - [`submissions.retry`](https://app.getoatmilk.com/docs/api/submissions.retry.md) — Retry preserved receipt processing using submissionId, revision and idempotencyKey. - [`submissions.tripSuggestions`](https://app.getoatmilk.com/docs/api/submissions.tripSuggestions.md) — Suggest visible trips for one receipt using submissionId and sourceFactIndex. Compares the receipt date and printed location even when currency is unknown; returns confidence and a reason without linking anything. ## uploads - [`uploads.confirm`](https://app.getoatmilk.com/docs/api/uploads.confirm.md) — Confirm uploaded evidence using submissionId and idempotencyKey. Returns a durable processing job ID; completion must be checked separately. - [`uploads.inline`](https://app.getoatmilk.com/docs/api/uploads.inline.md) — Add a receipt, invoice or original email in one call, for clients that can't upload to a link: filename, mimeType, contentBase64 (the file's bytes in base64, at most 2 MB), idempotencyKey, and optional receiptFor (the purchase it belongs to, as in uploads.prepare) and paymentAccountId. Oatmilk checks the bytes, stores them privately and starts processing; it returns submissionId and jobId. Retrying with the same key and file is safe. Larger files use uploads.prepare and uploads.confirm. - [`uploads.prepare`](https://app.getoatmilk.com/docs/api/uploads.prepare.md) — Prepare a private receipt or original email upload. Supply filename, mimeType, sizeBytes, sha256, idempotencyKey. If alreadyUploaded is true, the immutable original was verified: skip PUT and still confirm the returned submissionId. Otherwise upload unchanged bytes to uploadUrl, then confirm. Reuse the same idempotency key and payload on retry; changed payloads conflict. Authenticated identity is retained regardless of email headers. To add the receipt for one purchase, pass receiptFor with that entry id: it must still need a receipt or invoice (contributors: a purchase on their own card), the upload goes on its account, and matching compares the receipt with that purchase only. ## documentRequests.list Read this organization's document requests and their receipt status. `GET | POST /api/v1/accounting/documentRequests.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_document_requests_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | | The ID of an accounting entry, from entries.list or attention.mine. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documentRequests.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/documentRequests.list ## documentRequests.recipients Search active contractors and team members by name or email for a document request. `GET | POST /api/v1/accounting/documentRequests.recipients` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_document_requests_recipients` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `query` | string | | Text to search for. at most 100 characters. Default `""`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documentRequests.recipients \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/documentRequests.recipients ## documentRequests.revoke Revoke an outstanding document request and cancel queued delivery. `POST /api/v1/accounting/documentRequests.revoke` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Destructive: confirm with a person first MCP tool: `accounting_document_requests_revoke` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documentRequests.revoke \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/documentRequests.revoke ## documentRequests.send Email a document request with an expiring private upload link and an authenticated reply path. Requires a recipient and idempotency key. `POST /api/v1/accounting/documentRequests.send` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_document_requests_send` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | | The ID of an accounting entry, from entries.list or attention.mine. | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `email` | string (email) | Yes | An email address. at most 254 characters. | | `title` | string | Yes | A short title. 3–160 characters. | | `message` | string | | at most 2000 characters. Default `""`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documentRequests.send \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "email": "finance@example.com", "title": "Synthetic services agreement" }' ``` Reference page: https://app.getoatmilk.com/docs/api/documentRequests.send ## emails.submitBatch Submit original RFC822 emails using idempotencyKey and emails [{rawEmail,filename,metadata}]. Up to 20 emails and 3 MB total per request. Claimed sender metadata never authorizes a different user. Preserves original email bytes and returns processing jobs. `POST /api/v1/accounting/emails.submitBatch` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_submit_emails` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | | `emails` | array of objects | Yes | 1–20 items. | | `emails[].rawEmail` | string | Yes | 1–2000000 characters. | | `emails[].filename` | string | | The file's name, including its extension. at most 255 characters. Default `"original.eml"`. | | `emails[].metadata` | map | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/emails.submitBatch \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "emails": [ { "rawEmail": "finance@example.com" } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/emails.submitBatch ## evidence.download Get an authorized short-lived private evidence download by evidenceId. `GET | POST /api/v1/accounting/evidence.download` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_get_evidence` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `evidenceId` | string (ID) | Yes | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/evidence.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'evidenceId=1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d' ``` Reference page: https://app.getoatmilk.com/docs/api/evidence.download ## evidence.inboxSearch.cancel Stop an inbox search with id and idempotencyKey. Purchases not yet searched are cancelled, and no further inbox is read; one being read finishes and copies nothing more. Only the member who started it or an administrator can stop it. `POST /api/v1/accounting/evidence.inboxSearch.cancel` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_evidence_inbox_search_cancel` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/evidence.inboxSearch.cancel \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/evidence.inboxSearch.cancel ## evidence.inboxSearch.get Read an inbox search (id), or the latest search of each purchase (entryIds, comma-separated, up to 100): its status, runId, and each purchase's state and outcome with counts of emails found and screened. Contributors see only their own searches. Mail content is never returned, except the sender, subject and date of a receipt that was copied into company mail, for people who may read company mail. `GET | POST /api/v1/accounting/evidence.inboxSearch.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_evidence_inbox_search_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `entryIds` | string | | IDs of accounting entries, from entries.list or attention.mine. at most 3800 characters; Matches ^[0-9a-f-]{36}(,[0-9a-f-]{36}){0,99}$. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/evidence.inboxSearch.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/evidence.inboxSearch.get ## evidence.inboxSearch.start Search the caller's own connected Gmail or Outlook inbox for the receipt, invoice, or order confirmation of purchases that still need one, with entryIds (1 to 25) and idempotencyKey. Integrations also need the mailboxes:search scope; plain read and write access never grants it. Only the caller's own inbox is ever searched, and the request is the consent for this search only: each of their inboxes is read at most once per purchase and at most one email is copied for it, and it never changes daily checks or the investigator's consent. A purchase on another member's card is not searched (it stays with that member's ask), and a contributor can only search purchases on their own cards. A merchant email or nearby processor order may be copied after financial screening. On a member-requested search only, a recent Gmail forward whose preview is truncated may instead be copied after a bounded raw read proves the single forwarded original has a confirmed order, nearby original date, merchant, exact bank-currency grand total and card suffix, and the credential screen clears it; one later judged not to be financial evidence is removed again. The purchase it was found for is suggested to matching first, which still compares it with every likely transaction: it is attached only when matching would pick that purchase anyway, and left for review otherwise. Returns the search with each purchase's state (searching, reading, attached, review, not found or skipped) and its runId. With no usable inbox the search waits for the caller to connect or resume one (status waiting_for_inbox) and says which providers are available. A member can start 60 searches an hour, run five at once and search 250 purchases a day. `POST /api/v1/accounting/evidence.inboxSearch.start` Permissions: `accounting:read`, `accounting:write`, `mailboxes:search` · Roles: admin, finance, contributor · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_evidence_inbox_search_start` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryIds` | array of strings (ID) | Yes | IDs of accounting entries, from entries.list or attention.mine. 1–25 items. | | `surface` | enum | | One of: `home`, `entry`, `ask`, `transactions`, `api`, `other`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/evidence.inboxSearch.start \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryIds": [ "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/evidence.inboxSearch.start ## inbox.address Read the address you forward receipts and invoices to. It's the organization's accounting address; anything sent to it from your verified email is filed as yours. Needs only accounting:read. Finance reads every receiving address with mail.inboundAddresses. `GET | POST /api/v1/accounting/inbox.address` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_inbox_address` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/inbox.address \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/inbox.address ## inbox.list List receipt submissions and their review or processing state. `GET | POST /api/v1/accounting/inbox.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_list_submissions` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | enum | | Only include records with this status. One of: `prepared`, `queued`, `processing`, `review`, `completed`, `quarantined`, `failed`. | | `source` | enum | | Where the record came from. One of: `email`, `upload`, `mcp`. | | `sort` | enum | | One of: `newest`, `oldest`. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `cursor` | string (date-time) | | Where the next page starts: the nextCursor value from the previous response. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/inbox.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/inbox.list ## intake.classify Suggest what an uploaded file is and where it belongs from file-type and name rules, CSV columns, readable text and the accounting classifier. Returns kind, probability, accepted, reasons and a destination such as the bank account a statement belongs to. Nothing is filed until intake.process. `POST /api/v1/accounting/intake.classify` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_intake_classify` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/intake.classify \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/intake.classify ## intake.complete Verify an uploaded intake file's size, SHA-256 hash and type by id with an idempotency key. Returns the item, ready to classify or file. `POST /api/v1/accounting/intake.complete` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_intake_complete` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/intake.complete \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/intake.complete ## intake.dismiss Dismiss an uploaded file that should not be filed. With removeOriginal: true, the stored original is deleted too, for an accidental upload. Adding the same file again brings it back (and uploads it again when its original was removed). Filed files can't be dismissed. `POST /api/v1/accounting/intake.dismiss` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_intake_dismiss` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `removeOriginal` | boolean | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/intake.dismiss \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/intake.dismiss ## intake.fromMail File an attachment of an email in the workspace's mail (messageId, evidenceId, idempotencyKey) through universal intake: it becomes an uploaded file like a dropped one, ready for intake.classify and intake.process, and remembers the email it came from. Attachments of mail held for an administrator (quarantined, or holding sign-in or security details) can't be filed. The same file already added returns that upload with duplicate: true. `POST /api/v1/accounting/intake.fromMail` Permissions: `accounting:read`, `accounting:write`, `mail:read` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_intake_from_mail` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `messageId` | string (ID) | Yes | The ID of the related record. | | `evidenceId` | string (ID) | Yes | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/intake.fromMail \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "messageId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "evidenceId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/intake.fromMail ## intake.get Read one uploaded file with its suggestion, decision and result. Contributors can read only their own uploads. `GET | POST /api/v1/accounting/intake.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_intake_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/intake.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/intake.get ## intake.inline Add any file Oatmilk should file in one call, for clients that can't upload to a link: filename, mimeType, contentBase64 (the file's bytes in base64, at most 2 MB), idempotencyKey and optional presetKind. Oatmilk checks the bytes, stores them privately and completes the upload, as intake.prepare, the upload and intake.complete do; a file already added returns duplicate: true. Then call intake.classify and intake.process. Retrying with the same key and file is safe. Contributors add receipts only. Larger files use intake.prepare. `POST /api/v1/accounting/intake.inline` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_intake_inline` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | string | Yes | The file's name, including its extension. 1–255 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `application/pdf`, `image/jpeg`, `image/png`, `image/webp`, `image/heic`, `image/heif`, `message/rfc822`, `text/csv`, `text/plain`, `text/markdown`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`. | | `contentBase64` | string | Yes | The file's exact bytes, encoded as base64. 4–2796204 characters. | | `presetKind` | enum | | One of: `receipt`, `vendor_bill`, `issued_invoice`, `bank_statement`, `contractor_agreement`, `tax_document`, `company_document`, `other`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/intake.inline \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "filename": "receipt.pdf", "mimeType": "application/pdf", "contentBase64": "SGVsbG8sIE9hdG1pbGsh" }' ``` Reference page: https://app.getoatmilk.com/docs/api/intake.inline ## intake.list List uploaded files and where each one went. Filter with status (open, done, dismissed, all or one status), mine, search and since. Contributors see only their own uploads. `GET | POST /api/v1/accounting/intake.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_intake_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | enum | | Only include records with this status. One of: `open`, `done`, `dismissed`, `all`, `uploading`, `uploaded`, `classifying`, `suggested`, `confirmed`, `processing`, `failed`. Default `"all"`. | | `mine` | boolean | | | | `search` | string | | Text to search for. at most 200 characters. | | `since` | string (date-time) | | | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/intake.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/intake.list ## intake.prepare Prepare a private upload for any file Oatmilk should file: a receipt, vendor bill, issued invoice, bank statement, contractor agreement, tax or company document. Supply filename, mimeType, sizeBytes, sha256, idempotencyKey and optional presetKind. A file already added to the organization returns duplicate: true with addedAt. Otherwise PUT the unchanged bytes to uploadUrl, then call intake.complete. Contributors can file receipts only. `POST /api/v1/accounting/intake.prepare` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_intake_prepare` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | string | Yes | The file's name, including its extension. 1–255 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `application/pdf`, `image/jpeg`, `image/png`, `image/webp`, `image/heic`, `image/heif`, `message/rfc822`, `text/csv`, `text/plain`, `text/markdown`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`. | | `sizeBytes` | integer | Yes | The file's size in bytes. 1 to 52428800. | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `source` | enum | | Where the record came from. One of: `drop`, `paste`, `picker`, `api`. Default `"api"`. | | `presetKind` | enum | | One of: `receipt`, `vendor_bill`, `issued_invoice`, `bank_statement`, `contractor_agreement`, `tax_document`, `company_document`, `other`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/intake.prepare \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "filename": "receipt.pdf", "mimeType": "application/pdf", "sizeBytes": 1, "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/intake.prepare ## intake.process File an uploaded item as a confirmed kind (receipt, vendor_bill, issued_invoice, bank_statement, contractor_agreement, tax_document, company_document or other) with an optional target: accountId or newAccount for a CSV statement; contractorId, or newContractor (displayName, email, optional legalName, and confirmedNew once a person checked they aren't someone with the same or a close name), for an agreement, a tax document or another record kept for a contractor; taxYear for a tax document; paymentAccountId for a receipt. An agreement for a contractor that matches the one already on file (same start and end dates, rate and role) is refused with SAME_AGREEMENT until sameAgreement says what to do: link (file this upload as that agreement, adding nothing), replace (keep this copy as the agreement and void the uploaded one on file; not for one signed in Oatmilk) or keep (keep both, without reading this one's terms). Receipts and bills enter receipt processing, CSV statements are imported, agreements are kept as signed agreements, and other records go to the documents store. An issued invoice waits for review in the invoice importer; send invoiceIds once it is imported. Idempotent. Contributors can file receipts only. `POST /api/v1/accounting/intake.process` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_intake_process` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `kind` | enum | Yes | Which kind of record or job this is. One of: `receipt`, `vendor_bill`, `issued_invoice`, `bank_statement`, `contractor_agreement`, `tax_document`, `company_document`, `other`. | | `subtype` | string | | Matches ^[a-z][a-z0-9_]{1,40}$. | | `target` | object | | No other fields. | | `target.accountId` | string (ID) | | The ID of a bank, card or payment account, from accounts.list. | | `target.newAccount` | object | | No other fields. | | `target.newAccount.name` | string | Yes | A display name. 1–100 characters. | | `target.newAccount.provider` | enum | Yes | One of: `rbc`, `other`. | | `target.newAccount.currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `target.newAccount.lastFour` | string or null | | Default `null`. | | `target.newAccount.accountType` | enum | | One of: `bank`, `credit_card`. Default `"bank"`. | | `target.contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `target.newContractor` | object | | No other fields. | | `target.newContractor.displayName` | string | Yes | 1–200 characters. | | `target.newContractor.legalName` | string | | 1–300 characters. | | `target.newContractor.email` | string (email) | Yes | An email address. at most 320 characters. | | `target.newContractor.confirmedNew` | boolean | | | | `target.sameAgreement` | enum | | One of: `link`, `replace`, `keep`. | | `target.taxYear` | integer | | 1990 to 2100. | | `target.paymentAccountId` | string (ID) | | The card or account the purchase was paid with, from accounts.list. | | `via` | enum | | One of: `suggestion`, `manual`, `preset`. Default `"manual"`. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `invoiceIds` | array of strings (ID) | | A list of record IDs. 1–50 items. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/intake.process \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "kind": "receipt" }' ``` Reference page: https://app.getoatmilk.com/docs/api/intake.process ## intake.read Read what an uploaded file says, by id, without filing or changing it: its words (text, at most 20,000 characters, with truncated true when cut short), its page count (pages), how many pages were read (readPages), whether the whole file was read (complete), and any warnings. Photos and PDFs are read with vision once and the reading is kept, so classifying the file later doesn't read it again; CSV, text, Word and email files are read directly. Spreadsheets come back without text. Payment and sign-in links are hidden, and a file holding sign-in or security details is refused. Contributors can read only their own uploads. `GET | POST /api/v1/accounting/intake.read` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_intake_read` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/intake.read \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/intake.read ## jobs.get Get truthful asynchronous processing status by jobId. `GET | POST /api/v1/accounting/jobs.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_processing_status` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `jobId` | string (ID) | Yes | The ID of a background job, returned when the work was started. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/jobs.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'jobId=4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a' ``` ### Example response ```json { "data": { "id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e", "submission_id": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "status": "completed", "stage": "match", "generation": 1, "attempts": 1, "error_code": null, "next_attempt_at": "2026-09-30T14:00:05Z" } } ``` Reference page: https://app.getoatmilk.com/docs/api/jobs.get ## mail.activity.get Read the details of one entry in a visible email's activity, with id (the email) and eventId (the entry's id from mail.get activity): what Oatmilk's review concluded and how sure it was, a person's answer, how an entry was recorded and why, or which draft fields changed. Never returns the email's text. `GET | POST /api/v1/accounting/mail.activity.get` Permissions: `mail:read` · Roles: admin, finance MCP tool: `accounting_mail_activity_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `eventId` | string (ID) | Yes | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/mail.activity.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' \ --data-urlencode 'eventId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.activity.get ## mail.approve Approve a cleared financial draft for accounting with explicit treatment and evidence provenance. `POST /api/v1/accounting/mail.approve` Permissions: `mail:read`, `mail:write`, `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_mail_approve` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | | `draftRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `treatment` | enum | Yes | One of: `paid`, `provisional`. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.approve \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "draftRevision": 3, "treatment": "paid", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.approve ## mail.askAdmin Ask the organization's other administrators by email to approve the new sender of a financial email, with id and idempotencyKey. Sends at most one request per member for each email and never approves the sender itself. `POST /api/v1/accounting/mail.askAdmin` Permissions: `mail:read`, `mail:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_mail_ask_admin` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.askAdmin \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.askAdmin ## mail.attachEvidence Attach a cleared financial email to an existing bank purchase with current revisions and a reason. Keeps the original document kind and tax uncertainty; creates no expense and never reuses evidence for another purchase. `POST /api/v1/accounting/mail.attachEvidence` Permissions: `mail:read`, `mail:write`, `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_mail_attach_evidence` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `entryRevision` | integer | Yes | The entry's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.attachEvidence \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedRevision": 3, "entryRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.attachEvidence ## mail.download Prepare an explicit short-lived download of authorized company mail evidence. `GET | POST /api/v1/accounting/mail.download` Permissions: `mail:read` · Roles: admin, finance Not available over MCP: Accountants' financial email grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `evidenceId` | string (ID) | Yes | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/mail.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' \ --data-urlencode 'evidenceId=1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.download ## mail.drafts.create Create an unbooked human financial draft from eligible cleared mail after an explicit review reason. `POST /api/v1/accounting/mail.drafts.create` Permissions: `mail:read`, `mail:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_mail_drafts_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.drafts.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.drafts.create ## mail.drafts.update Save reviewed company financial facts with current message and draft revisions. `POST /api/v1/accounting/mail.drafts.update` Permissions: `mail:read`, `mail:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_mail_drafts_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | | `messageRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `facts` | object | Yes | No other fields. | | `facts.receipts` | array of objects | Yes | at most 20 items. | | `facts.receipts[].merchant` | string or null | Yes | | | `facts.receipts[].merchantAddress` | string or null | | | | `facts.receipts[].travelAddress` | string or null | | | | `facts.receipts[].date` | string or null | Yes | A date, as YYYY-MM-DD. | | `facts.receipts[].currency` | string or null | Yes | Three-letter currency code, such as CAD or USD. | | `facts.receipts[].total` | string or null | Yes | | | `facts.receipts[].subtotal` | string or null | Yes | | | `facts.receipts[].tax` | string or null | Yes | | | `facts.receipts[].tip` | string or null | Yes | | | `facts.receipts[].discount` | string or null | Yes | | | `facts.receipts[].type` | enum | Yes | One of: `expense`, `income`, `income_refund`, `refund`, `transfer`, `fee`. | | `facts.receipts[].invoiceNumber` | string or null | Yes | | | `facts.receipts[].description` | string | Yes | A short description. at most 1000 characters. | | `facts.receipts[].taxes` | array of objects | Yes | at most 10 items. | | `facts.receipts[].taxes[].label` | string | Yes | at most 100 characters. | | `facts.receipts[].taxes[].amount` | string or null | Yes | | | `facts.receipts[].sourceEvidenceIds` | array of strings (ID) | Yes | A list of record IDs. 1–40 items. | | `facts.receipts[].lineItems` | array of objects | | at most 50 items. | | `facts.receipts[].lineItems[].description` | string | Yes | A short description. 1–500 characters. | | `facts.receipts[].lineItems[].amount` | string or null | Yes | | | `facts.receipts[].lineItems[].basis` | enum | Yes | One of: `gross`, `net`, `unknown`. | | `facts.receipts[].lineItems[].sourceEvidenceIds` | array of strings | Yes | A list of record IDs. 1–10 items. | | `facts.receipts[].warnings` | array of strings | Yes | at most 20 items; each at most 500 characters. | | `facts.receipts[].documentKind` | enum | Yes | One of: `receipt`, `invoice`, `statement`, `refund_notice`, `credit_note`, `other`, `unknown`. | | `facts.receipts[].paymentState` | enum | Yes | One of: `paid`, `unpaid`, `partial`, `refunded`, `unknown`. | | `facts.receipts[].categoryId` | string (ID) or null | | The ID of a category, from categories.list. | | `facts.receipts[].documentClaims` | object or null | | | | `facts.receipts[].documentClaims.kind` | enum | Yes | Which kind of record or job this is. One of: `receipt`, `invoice`, `credit_note`, `refund_notice`, `statement`, `other`, `unknown`. | | `facts.receipts[].documentClaims.issuer` | object or null | Yes | | | `facts.receipts[].documentClaims.issuer.name` | string or null | Yes | A display name. | | `facts.receipts[].documentClaims.issuer.taxId` | string or null | Yes | | | `facts.receipts[].documentClaims.issuer.email` | string or null | Yes | An email address. | | `facts.receipts[].documentClaims.customer` | object or null | Yes | | | `facts.receipts[].documentClaims.customer.name` | string or null | Yes | A display name. | | `facts.receipts[].documentClaims.customer.taxId` | string or null | Yes | | | `facts.receipts[].documentClaims.customer.email` | string or null | Yes | An email address. | | `facts.receipts[].documentClaims.issueDate` | string or null | Yes | The date the document was issued, as YYYY-MM-DD. | | `facts.receipts[].documentClaims.dueDate` | string or null | Yes | The date payment is due, as YYYY-MM-DD. | | `facts.receipts[].documentClaims.taxPointDate` | string or null | Yes | | | `facts.receipts[].documentClaims.paymentState` | enum | Yes | One of: `paid`, `unpaid`, `partial`, `refunded`, `unknown`. | | `facts.receipts[].documentClaims.amountPaid` | string or null | Yes | | | `facts.receipts[].documentClaims.amountDue` | string or null | Yes | | | `facts.receipts[].documentClaims.relatedInvoiceNumber` | string or null | Yes | | | `facts.receipts[].paymentCard` | object or null | | | | `facts.receipts[].paymentCard.brand` | string or null | Yes | | | `facts.receipts[].paymentCard.lastFour` | string or null | Yes | | | `facts.receipts[].paymentBreakdown` | object or null | | | | `facts.receipts[].paymentBreakdown.cardAmount` | string or null | Yes | | | `facts.receipts[].paymentBreakdown.giftCardAmount` | string or null | Yes | | | `facts.receipts[].paymentBreakdown.creditAmount` | string or null | Yes | | | `facts.receipts[].paymentBreakdown.pointsValue` | string or null | Yes | | | `facts.receipts[].otherCharges` | array of objects | | at most 20 items. | | `facts.receipts[].otherCharges[].label` | string | Yes | at most 100 characters. | | `facts.receipts[].otherCharges[].amount` | string or null | Yes | | | `facts.warnings` | array of strings | Yes | at most 20 items; each at most 500 characters. | | `facts.documentReadings` | array of objects | | at most 60 items. | | `facts.documentReadings[].evidenceId` | string (ID) | Yes | The ID of the related record. | | `facts.documentReadings[].filename` | string | Yes | The file's name, including its extension. at most 255 characters. | | `facts.documentReadings[].mimeType` | string | Yes | The file's type, such as image/jpeg or application/pdf. at most 200 characters. | | `facts.documentReadings[].status` | enum | Yes | Only include records with this status. One of: `read`, `unreadable`, `unsupported`. | | `facts.documentReadings[].text` | string | Yes | at most 12000 characters. | | `facts.documentReadings[].sourceEvidenceIds` | array of strings (ID) | Yes | A list of record IDs. at most 60 items. | | `facts.documentReadings[].warnings` | array of strings | Yes | at most 20 items; each at most 500 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.drafts.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "messageRevision": 3, "facts": { "receipts": [ { "merchant": "example", "date": "2026-09-30", "currency": "CAD", "total": "1.5", "subtotal": "1.5", "tax": "1.5", "tip": "1.5", "discount": "1.5", "type": "expense", "invoiceNumber": "example", "description": "Synthetic example from the docs", "taxes": [ { "label": "example", "amount": "1.5" } ], "sourceEvidenceIds": [ "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" ], "warnings": [ "example" ], "documentKind": "receipt", "paymentState": "paid" } ], "warnings": [ "example" ] } }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.drafts.update ## mail.fraud.decide Decide about an email's fraud warning with id, expectedRevision, decision (genuine or fraud) and idempotencyKey. genuine clears the warning so a person can add the email to the books; fraud archives it, turns automation off for it and keeps it out of the books. Both are written to the email's activity with who decided. Nothing is ever approved automatically. `POST /api/v1/accounting/mail.fraud.decide` Permissions: `mail:read`, `mail:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_mail_fraud_decide` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | | `decision` | enum | Yes | What you decided. One of: `genuine`, `fraud`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.fraud.decide \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "decision": "genuine" }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.fraud.decide ## mail.fraud.rescan Check recent company mail for fraud again with the current rules: days (1 to 30, default 14) and limit (1 to 100, default 50), within a short time budget. Returns checked, flagged, raised, skipped and more. It adds or keeps warnings (an earlier reviewer clearing stands only for the same weak history signals), keeps every person's decision, and turns automation off for flagged mail. `POST /api/v1/accounting/mail.fraud.rescan` Permissions: `mail:read`, `mail:write` · Roles: admin, finance MCP tool: `accounting_mail_fraud_rescan` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `days` | integer | | 1 to 30. Default `14`. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.fraud.rescan \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.fraud.rescan ## mail.get Read visible company mail with immutable evidence metadata, unbooked financial drafts, the email's activity log, Oatmilk's latest review (with its one open question, if any) and how adding it to the books is going. `GET | POST /api/v1/accounting/mail.get` Permissions: `mail:read` · Roles: admin, finance Not available over MCP: Accountants' financial email grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/mail.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.get ## mail.inboundAddresses List the organization's receiving email addresses. Contributors see only the accounting address. `GET | POST /api/v1/accounting/mail.inboundAddresses` Permissions: `accounting:read`, `mail:read` · Roles: admin, finance, contributor Not available over MCP: It lists every receiving address and needs mail:read, which a contributor's API key can't hold. ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.inboundAddresses \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/mail.inboundAddresses ## mail.list Search visible company mail with search: literal text in the subject, sender, body, attachment filenames and extracted document text. searchScope headers limits it to subject and sender. archived false (default) searches inbox mail, true archived mail, all both. Results include items and total; use limit and offset to continue, then mail.get for full details. Restricted messages require explicit security permission. Connected personal inbox receipt searches use evidence.inboxSearch.start/get instead. `GET | POST /api/v1/accounting/mail.list` Permissions: `mail:read` · Roles: admin, finance Not available over MCP: Accountants' financial email grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `mailbox` | string | | Matches ^[a-z][a-z0-9_-]{1,39}$. | | `purpose` | enum | | One of: `unclassified`, `financial`, `billing_notice`, `account_access`, `general`, `spam`, `uncertain`. | | `status` | enum | | Only include records with this status. One of: `received`, `processing`, `ready`, `failed`, `quarantined`, `promoted`. | | `read` | boolean | | | | `archived` | boolean or "all" | | false searches the inbox, true searches archived mail, all searches both. One of: `all`. | | `search` | string | | Case-insensitive literal text, including the email body and read attachments by default. at most 200 characters. | | `searchScope` | enum | | all searches subject, sender, body, attachment filenames and extracted document text. headers searches only subject and sender. One of: `all`, `headers`. Default `"all"`. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 9007199254740991. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/mail.list ## mail.purchaseCandidates Find existing bank purchases to attach an authorized financial email as supporting evidence. Candidates remain separate; selecting a match requires human review. `GET | POST /api/v1/accounting/mail.purchaseCandidates` Permissions: `mail:read`, `accounting:read` · Roles: admin, finance MCP tool: `accounting_mail_purchase_candidates` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `query` | string | | Text to search for. at most 100 characters. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/mail.purchaseCandidates \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.purchaseCandidates ## mail.rerun Process an email again from its saved original, with id, expectedRevision, idempotencyKey and from: everything (screen, classify, read the documents with vision, fraud check and second opinion; the default, also called reclassify), reading (keep the classifier's answer and read the documents again) or review (keep the reading and ask Oatmilk's second opinion again). Every later step runs again with the workspace's current memory, people, vendors and bank lines. An email already added to the books can't be processed again: use matching.rerunEntry for its entry. A person's corrected draft is kept. Returns jobId and generation; follow the steps with runs.get (subjectType mail, subjectId the email id), then read the result with mail.get. `POST /api/v1/accounting/mail.rerun` Permissions: `mail:read`, `mail:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_mail_rerun` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | | `from` | enum | | The first date to include, as YYYY-MM-DD. One of: `everything`, `reading`, `review`. Default `"everything"`. | | `reason` | string | | A short note saying why, kept in the record's history. 1–500 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.rerun \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.rerun ## mail.rerunMany Process up to 50 emails again in one request, with items [{id, expectedRevision}], from (everything, reading or review, as in mail.rerun) and idempotencyKey. Each email gets its own job; returns queued [{id, jobId, generation}] and failed [{id, code, message}] so one stale or already-added email doesn't stop the rest. `POST /api/v1/accounting/mail.rerunMany` Permissions: `mail:read`, `mail:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_mail_rerun_many` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `items` | array of objects | Yes | 1–50 items. | | `items[].id` | string (ID) | Yes | The record's ID. | | `items[].expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `from` | enum | | The first date to include, as YYYY-MM-DD. One of: `everything`, `reading`, `review`. Default `"everything"`. | | `reason` | string | | A short note saying why, kept in the record's history. 1–500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.rerunMany \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "items": [ { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.rerunMany ## mail.retry Queue company mail rescreening while preserving original evidence and human draft corrections. mail.rerun does the same and can start from a later step. `POST /api/v1/accounting/mail.retry` Permissions: `mail:read`, `mail:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_mail_retry` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.retry \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.retry ## mail.review.answer Answer the one question Oatmilk's review asked about an email, with id, reviewId, choiceId (a, b or c) and idempotencyKey. The choice's effect comes from the stored question: leave the draft, fix it, fix it and add it to the books through the same checks as mail.approve, or archive the email. Choices that change date, currency, total or tax only update the draft; a separate full draft review is required before booking. `POST /api/v1/accounting/mail.review.answer` Permissions: `mail:read`, `mail:write`, `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_mail_review_answer` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `reviewId` | string (ID) | Yes | The ID of the related record. | | `choiceId` | enum | Yes | One of: `a`, `b`, `c`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.review.answer \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "reviewId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "choiceId": "a" }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.review.answer ## mail.review.memory Save or dismiss the memory entry Oatmilk suggested while reviewing an email, with id, reviewId, decision (save or dismiss) and idempotencyKey. Nothing is added to the organization's memory unless an administrator saves it. `POST /api/v1/accounting/mail.review.memory` Permissions: `mail:read`, `mail:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_mail_review_memory` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `reviewId` | string (ID) | Yes | The ID of the related record. | | `decision` | enum | Yes | What you decided. One of: `save`, `dismiss`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.review.memory \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "reviewId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "decision": "save" }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.review.memory ## mail.rules.list Read exact sponsored vendor sender rules. Administrator access required. `GET | POST /api/v1/accounting/mail.rules.list` Permissions: `mail:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_mail_rules_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.rules.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/mail.rules.list ## mail.rules.save Manage exact vendor sender authorization with an active finance sponsor and revision checks. `POST /api/v1/accounting/mail.rules.save` Permissions: `mail:read`, `mail:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_mail_rules_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `email` | string (email) | Yes | An email address. at most 320 characters. | | `sponsorUserId` | string | Yes | 1–200 characters. | | `active` | boolean | Yes | Whether the record is turned on. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.rules.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "email": "finance@example.com", "sponsorUserId": "example", "active": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.rules.save ## mail.update Mark company mail read, archived or spam with revision and idempotency checks. `POST /api/v1/accounting/mail.update` Permissions: `mail:read`, `mail:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_mail_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | | `read` | boolean | | | | `archived` | boolean | | Whether the record is archived. | | `spam` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mail.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "read": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/mail.update ## mailboxes.connect.link Get the link where the signed-in member connects their own Gmail or Outlook inbox in Oatmilk, with optional provider and dailyChecks. Nothing is connected and no sign-in starts: connecting asks the inbox owner to agree in the provider's own window, so only they can do it. Give the person the url; connected inboxes then appear in mailboxes.list. `GET | POST /api/v1/accounting/mailboxes.connect.link` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_mailboxes_connect_link` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `provider` | enum | | One of: `gmail`, `outlook`. | | `dailyChecks` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mailboxes.connect.link \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/mailboxes.connect.link ## mailboxes.connect.start Start connecting the signed-in member's own Gmail or Outlook inbox with read-only access. Returns the provider's consent URL; the member finishes in the browser. dailyChecks (default on for Connected inboxes, explicitly off for onboarding and Find in my inbox) decides whether Oatmilk checks the new inbox every day, starting 90 days back; without it the inbox is read only when the member asks, from today. Reconnecting an inbox keeps it paused if it was, and only turns daily checks on, never off. Optional returnTo (a workspace page or /onboarding) brings them back there, popup reports back to the page that opened the window and closes it, and inboxSearchId starts that member's waiting inbox search as soon as the inbox connects. mode temporary (with inboxSearchId, never dailyChecks) asks for read-only access for that one search instead: no offline access, the token is held sealed for at most an hour, bound to that search and member, never checked daily or by anything else, never listed as a connected inbox, and revoked (where the provider allows) and wiped when the search finishes, is stopped or expires. These choices stay on the server behind a single-use nonce, and the exchange uses PKCE. Dashboard only. `POST /api/v1/accounting/mailboxes.connect.start` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor Not available over MCP: The inbox owner agrees in the provider's own window. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `provider` | enum | Yes | One of: `gmail`, `outlook`. | | `loginHint` | string (email) | | at most 320 characters. | | `returnTo` | string | | at most 500 characters; Matches ^(?:\/onboarding(?:[?#][^\s\\]*)?\|\/(?:accounting\|ops\|inbox\|checklist\|calendar\|ai\|compliance\|chief-of-staff\|logs\|history\|developers\|settings\|finance\|legal\|people\|insights)(?:[/?#][^\s\\]*)?\|\/(?:[?#][^\s\\]*)?)$. | | `inboxSearchId` | string (ID) | | The ID of the related record. | | `popup` | boolean | | | | `dailyChecks` | boolean | | | | `mode` | enum | | One of: `persistent`, `temporary`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mailboxes.connect.start \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "gmail" }' ``` Reference page: https://app.getoatmilk.com/docs/api/mailboxes.connect.start ## mailboxes.disconnect Disconnect a connected inbox and delete its stored access at once, with id, expectedRevision and idempotencyKey. Emails already imported stay in company mail. Only the member who connected it or an administrator can disconnect it. `POST /api/v1/accounting/mailboxes.disconnect` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_mailboxes_disconnect` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mailboxes.disconnect \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/mailboxes.disconnect ## mailboxes.list List connected Gmail and Outlook inboxes: provider, address, who connected it, whether it is paused (scan_enabled false), checked daily (daily_checks) and offered for other members' receipts (search_for_others), the last scan, how many financial emails were imported, the latest check (a run with its status and summary), whether it is still catching up on older mail, whether scheduled checks are on, and which providers this deployment supports. Contributors see only their own. Tokens are never returned. `GET | POST /api/v1/accounting/mailboxes.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_mailboxes_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mailboxes.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/mailboxes.list ## mailboxes.scan Check your own connected inbox for receipts and invoices now instead of waiting for the daily check; nobody can check another member's inbox, a paused one must be resumed first, and integrations need the mailboxes:search scope. Only financial emails are imported. Returns right away with the check's runId (status started, or busy with the running check's runId when one is already reading the inbox); follow it with runs.get for its steps and counts. `POST /api/v1/accounting/mailboxes.scan` Permissions: `accounting:read`, `accounting:write`, `mailboxes:search` · Roles: admin, finance, contributor · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_mailboxes_scan` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mailboxes.scan \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/mailboxes.scan ## mailboxes.update Change a connected inbox with id, expectedRevision and idempotencyKey: scanEnabled pauses (false) or resumes (true) all reading, dailyChecks turns daily checks on (reading back 90 days) or off, searchForOthers lets Autopilot search it for other members' receipts, and scanFrom changes how far back it reads. Only the member who connected it can resume it or widen what is read, and only from the dashboard; an administrator can pause it or turn those off, and integrations can only narrow. `POST /api/v1/accounting/mailboxes.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` Not available over MCP: Widening what an inbox shares is the owner's choice. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `scanEnabled` | boolean | | | | `dailyChecks` | boolean | | | | `searchForOthers` | boolean | | | | `scanFrom` | string | | Date as YYYY-MM-DD. | | `scanIntervalMinutes` | 60 or 360 or 1440 | | One of: `60`, `360`, `1440`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/mailboxes.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "scanEnabled": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/mailboxes.update ## submissions.get Get a submission by submissionId, including its evidence, jobs, and resulting entry references even when extraction failed. `GET | POST /api/v1/accounting/submissions.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_get_submission` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `submissionId` | string (ID) | Yes | The ID of an upload, returned by uploads.prepare or uploads.inline. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/submissions.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'submissionId=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/submissions.get ## submissions.release Release quarantined accounting intake after administrator review. Restricted mail also requires security consent. `POST /api/v1/accounting/submissions.release` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_submissions_release` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `submissionId` | string (ID) | Yes | The ID of an upload, returned by uploads.prepare or uploads.inline. | | `revision` | integer | Yes | at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/submissions.release \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "submissionId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "revision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/submissions.release ## submissions.retry Retry preserved receipt processing using submissionId, revision and idempotencyKey. `POST /api/v1/accounting/submissions.retry` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_retry_processing` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `submissionId` | string (ID) | Yes | The ID of an upload, returned by uploads.prepare or uploads.inline. | | `revision` | integer | Yes | at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/submissions.retry \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "submissionId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "revision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/submissions.retry ## submissions.tripSuggestions Suggest visible trips for one receipt using submissionId and sourceFactIndex. Compares the receipt date and printed location even when currency is unknown; returns confidence and a reason without linking anything. `GET | POST /api/v1/accounting/submissions.tripSuggestions` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_submission_trip_suggestions` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `submissionId` | string (ID) | Yes | The ID of an upload, returned by uploads.prepare or uploads.inline. | | `sourceFactIndex` | integer | Yes | 0 to 19. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/submissions.tripSuggestions \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'submissionId=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' \ --data-urlencode 'sourceFactIndex=1' ``` Reference page: https://app.getoatmilk.com/docs/api/submissions.tripSuggestions ## uploads.confirm Confirm uploaded evidence using submissionId and idempotencyKey. Returns a durable processing job ID; completion must be checked separately. `POST /api/v1/accounting/uploads.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_confirm_upload` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `submissionId` | string (ID) | Yes | The ID of an upload, returned by uploads.prepare or uploads.inline. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/uploads.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "submissionId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` ### Example response ```json { "data": { "submissionId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "jobId": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e", "generation": 1, "status": "queued" } } ``` Reference page: https://app.getoatmilk.com/docs/api/uploads.confirm ## uploads.inline Add a receipt, invoice or original email in one call, for clients that can't upload to a link: filename, mimeType, contentBase64 (the file's bytes in base64, at most 2 MB), idempotencyKey, and optional receiptFor (the purchase it belongs to, as in uploads.prepare) and paymentAccountId. Oatmilk checks the bytes, stores them privately and starts processing; it returns submissionId and jobId. Retrying with the same key and file is safe. Larger files use uploads.prepare and uploads.confirm. `POST /api/v1/accounting/uploads.inline` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_uploads_inline` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | string | Yes | The file's name, including its extension. 1–255 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `image/jpeg`, `image/png`, `image/webp`, `image/heic`, `image/heif`, `application/pdf`, `message/rfc822`. | | `contentBase64` | string | Yes | The file's exact bytes, encoded as base64. 4–2796204 characters. | | `claimedMetadata` | map | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | | `paymentAccountId` | string (ID) | | The card or account the purchase was paid with, from accounts.list. | | `receiptFor` | string (ID) | | The ID of the purchase this receipt is for, from attention.mine or entries.list. The upload is refused unless that purchase still needs a receipt. | | `receiptBatchId` | string (ID) | | The ID of the related record. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/uploads.inline \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "filename": "receipt.jpg", "mimeType": "image/jpeg", "contentBase64": "SGVsbG8sIE9hdG1pbGsh" }' ``` ### Example response ```json { "data": { "submissionId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "jobId": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e", "generation": 1, "status": "queued", "sizeBytes": 182734 } } ``` Reference page: https://app.getoatmilk.com/docs/api/uploads.inline ## uploads.prepare Prepare a private receipt or original email upload. Supply filename, mimeType, sizeBytes, sha256, idempotencyKey. If alreadyUploaded is true, the immutable original was verified: skip PUT and still confirm the returned submissionId. Otherwise upload unchanged bytes to uploadUrl, then confirm. Reuse the same idempotency key and payload on retry; changed payloads conflict. Authenticated identity is retained regardless of email headers. To add the receipt for one purchase, pass receiptFor with that entry id: it must still need a receipt or invoice (contributors: a purchase on their own card), the upload goes on its account, and matching compares the receipt with that purchase only. `POST /api/v1/accounting/uploads.prepare` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_prepare_upload` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | string | Yes | The file's name, including its extension. 1–255 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `image/jpeg`, `image/png`, `image/webp`, `image/heic`, `image/heif`, `application/pdf`, `message/rfc822`. | | `sizeBytes` | integer | Yes | The file's size in bytes. 1 to 52428800. | | `claimedMetadata` | map | | | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | | `paymentAccountId` | string (ID) | | The card or account the purchase was paid with, from accounts.list. | | `receiptFor` | string (ID) | | The ID of the purchase this receipt is for, from attention.mine or entries.list. The upload is refused unless that purchase still needs a receipt. | | `receiptBatchId` | string (ID) | | The ID of the related record. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/uploads.prepare \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "filename": "receipt.jpg", "mimeType": "image/jpeg", "sizeBytes": 1, "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` ### Example response ```json { "data": { "submissionId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "uploadUrl": "https://storage.example.com/upload/sign/accounting/receipt.jpg?token=synthetic", "uploadToken": "synthetic-upload-token", "storagePath": "org_synthetic/4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a/upload/9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c/receipt.jpg", "expiresIn": 7200, "alreadyUploaded": false } } ``` Reference page: https://app.getoatmilk.com/docs/api/uploads.prepare # Group: Matching > Match receipts to bank transactions and review the decisions. MCP toolset: `matching` (https://app.getoatmilk.com/api/mcp?toolset=matching) ## matching - [`matching.candidates`](https://app.getoatmilk.com/docs/api/matching.candidates.md) — List what an entry could be matched with, best first. For a receipt: unmatched bank or card lines in the same direction across available history, ranked by amount (including a charge in another currency that names the receipt amount), vendor name, what people matched before, and how usual the date gap is. For a bank entry: unlinked receipts with the same amount across available history. Each candidate has its amount, date, a name and why it was suggested. Confirm with accounting_match_transaction or accounting_attach_receipt. - [`matching.confirm`](https://app.getoatmilk.com/docs/api/matching.confirm.md) — Confirm a proposed exact-amount receipt match after finance review. Requires decision, receipt and transaction revisions, proposed target revision when attaching to an existing bank entry, reason and idempotency key. Canonical provider amounts and original evidence are preserved. - [`matching.list`](https://app.getoatmilk.com/docs/api/matching.list.md) — List the latest receipt-to-transaction matching decisions with source candidates, separate match probability and confidence, and review reasons. Finance access required. - [`matching.reject`](https://app.getoatmilk.com/docs/api/matching.reject.md) — Say a candidate bank transaction is not the payment for a receipt, using decisionId, expectedRevision, entryId, entryRevision, transactionId, an optional reason and idempotencyKey. That transaction is not suggested for the receipt again and the receipt goes to a person; the vendor pairing counts against future suggestions only when the line names a different vendor; matching reruns. Send undo: true to lift a rejection. Finance access required. - [`matching.request`](https://app.getoatmilk.com/docs/api/matching.request.md) — Queue a new durable matching pass over stored receipts and bank transactions. Uses the matching evaluator through AI Gateway and returns a job generation. Never creates bank transactions from model output. Finance access and idempotency key required. - [`matching.rerunEntry`](https://app.getoatmilk.com/docs/api/matching.rerunEntry.md) — Check one receipt's match again with its current details, without queueing a pass over every receipt. Supply entryId and idempotencyKey. Saves a fresh matching decision for the receipt, never confirms a match, and returns the receipt's latest decision. Use it when a receipt was edited after matching looked at it. Finance access required. - [`matching.status`](https://app.getoatmilk.com/docs/api/matching.status.md) — Read durable matching progress. Queued or processing status does not mean reconciliation has completed. ## matching.receiptConsolidation - [`matching.receiptConsolidation.candidates`](https://app.getoatmilk.com/docs/api/matching.receiptConsolidation.candidates.md) — Find current source-backed existing purchases and complete Wise card payment groups for one receipt. Candidates require original-source review and do not prove business use or tax eligibility. Returns explicit limits and source blockers; never confirms a match. - [`matching.receiptConsolidation.confirm`](https://app.getoatmilk.com/docs/api/matching.receiptConsolidation.confirm.md) — Dashboard-only confirmation after a finance member reviews the current original documents and source-backed payment identity. Requires exact preview fingerprint, revisions and original IDs. Adds a receipt association and alias without changing bank amounts, allocations, financial fields, tax or review status. API, MCP and automatic actors cannot approve. - [`matching.receiptConsolidation.preview`](https://app.getoatmilk.com/docs/api/matching.receiptConsolidation.preview.md) — Inspect current originals, source identities, exact signed payment pieces and revisions before manually linking a receipt to an existing bank purchase. No financial fields or allocations are changed. - [`matching.receiptConsolidation.undo`](https://app.getoatmilk.com/docs/api/matching.receiptConsolidation.undo.md) — Dashboard-only History rollback of a receipt consolidation whose exact revisions and source state remain unchanged. Restores only its receipt alias and added association; existing purchases, bank pieces, allocations, tax and review status remain intact. ## matching.candidates List what an entry could be matched with, best first. For a receipt: unmatched bank or card lines in the same direction across available history, ranked by amount (including a charge in another currency that names the receipt amount), vendor name, what people matched before, and how usual the date gap is. For a bank entry: unlinked receipts with the same amount across available history. Each candidate has its amount, date, a name and why it was suggested. Confirm with accounting_match_transaction or accounting_attach_receipt. `GET | POST /api/v1/accounting/matching.candidates` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_matching_candidates` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `limit` | integer | | How many results to return at most. 1 to 20. Default `10`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/matching.candidates \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'entryId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/matching.candidates ## matching.confirm Confirm a proposed exact-amount receipt match after finance review. Requires decision, receipt and transaction revisions, proposed target revision when attaching to an existing bank entry, reason and idempotency key. Canonical provider amounts and original evidence are preserved. `POST /api/v1/accounting/matching.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_confirm_matching` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `decisionId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `entryRevision` | integer | Yes | The entry's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `transactionRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `targetEntryId` | string (ID) | | The ID of the related record. | | `targetRevision` | integer | | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `bankAmountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `exchangeRate` | string | | Matches ^(?:0\|[1-9]\d{0,8})(?:\.\d{1,18})?$. | | `exchangeRateDate` | string | | A date, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `exchangeRateSource` | string | | 3–1000 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/matching.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "decisionId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "entryRevision": 3, "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "transactionRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/matching.confirm ## matching.list List the latest receipt-to-transaction matching decisions with source candidates, separate match probability and confidence, and review reasons. Finance access required. `GET | POST /api/v1/accounting/matching.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_matching_review` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | | `outcome` | enum | | One of: `review`, `unmatched`, `matched`. | | `submissionId` | string (ID) | | Return matching decisions for this receipt submission only. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/matching.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/matching.list ## matching.receiptConsolidation.candidates Find current source-backed existing purchases and complete Wise card payment groups for one receipt. Candidates require original-source review and do not prove business use or tax eligibility. Returns explicit limits and source blockers; never confirms a match. `GET | POST /api/v1/accounting/matching.receiptConsolidation.candidates` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_matching_receipt_consolidation_candidates` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `receiptEntryId` | string (ID) | Yes | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/matching.receiptConsolidation.candidates \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'receiptEntryId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/matching.receiptConsolidation.candidates ## matching.receiptConsolidation.confirm Dashboard-only confirmation after a finance member reviews the current original documents and source-backed payment identity. Requires exact preview fingerprint, revisions and original IDs. Adds a receipt association and alias without changing bank amounts, allocations, financial fields, tax or review status. API, MCP and automatic actors cannot approve. `POST /api/v1/accounting/matching.receiptConsolidation.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required Not available over MCP: A person must review the current originals and exact payment group in Oatmilk before changing a receipt association. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `receiptEntryId` | string (ID) | Yes | The ID of the related record. | | `expectedReceiptRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `targetEntryId` | string (ID) | Yes | The ID of the related record. | | `expectedTargetRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `mode` | enum | Yes | One of: `same_order`, `wise_card_settlement`. | | `transactions` | array of objects | Yes | 1–20 items. | | `transactions[].id` | string (ID) | Yes | The record's ID. | | `transactions[].expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `expectedSourceFingerprint` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `reviewedOriginalEvidenceIds` | array of strings (ID) | Yes | A list of record IDs. 1–60 items. | | `originalsReviewed` | true | Yes | | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/matching.receiptConsolidation.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "receiptEntryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedReceiptRevision": 3, "targetEntryId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "expectedTargetRevision": 3, "mode": "same_order", "transactions": [ { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 } ], "expectedSourceFingerprint": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "reviewedOriginalEvidenceIds": [ "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" ], "originalsReviewed": true, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/matching.receiptConsolidation.confirm ## matching.receiptConsolidation.preview Inspect current originals, source identities, exact signed payment pieces and revisions before manually linking a receipt to an existing bank purchase. No financial fields or allocations are changed. `GET | POST /api/v1/accounting/matching.receiptConsolidation.preview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_matching_receipt_consolidation_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `receiptEntryId` | string (ID) | Yes | The ID of the related record. | | `targetEntryId` | string (ID) | Yes | The ID of the related record. | | `mode` | enum | Yes | One of: `same_order`, `wise_card_settlement`. | | `transactionIds` | array of strings (ID) | Yes | A list of record IDs. 1–20 items. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/matching.receiptConsolidation.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "receiptEntryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "targetEntryId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "mode": "same_order", "transactionIds": [ "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/matching.receiptConsolidation.preview ## matching.receiptConsolidation.undo Dashboard-only History rollback of a receipt consolidation whose exact revisions and source state remain unchanged. Restores only its receipt alias and added association; existing purchases, bank pieces, allocations, tax and review status remain intact. `POST /api/v1/accounting/matching.receiptConsolidation.undo` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: A person must review the current originals and exact payment group in Oatmilk before changing a receipt association. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `receiptRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `targetRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/matching.receiptConsolidation.undo \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "receiptRevision": 3, "targetRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/matching.receiptConsolidation.undo ## matching.reject Say a candidate bank transaction is not the payment for a receipt, using decisionId, expectedRevision, entryId, entryRevision, transactionId, an optional reason and idempotencyKey. That transaction is not suggested for the receipt again and the receipt goes to a person; the vendor pairing counts against future suggestions only when the line names a different vendor; matching reruns. Send undo: true to lift a rejection. Finance access required. `POST /api/v1/accounting/matching.reject` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_matching_reject` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `decisionId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `entryRevision` | integer | Yes | The entry's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `reason` | string | | A short note saying why, kept in the record's history. at most 1000 characters. | | `undo` | boolean | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/matching.reject \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "decisionId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "entryRevision": 3, "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/matching.reject ## matching.request Queue a new durable matching pass over stored receipts and bank transactions. Uses the matching evaluator through AI Gateway and returns a job generation. Never creates bank transactions from model output. Finance access and idempotency key required. `POST /api/v1/accounting/matching.request` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_retry_matching` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/matching.request \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/matching.request ## matching.rerunEntry Check one receipt's match again with its current details, without queueing a pass over every receipt. Supply entryId and idempotencyKey. Saves a fresh matching decision for the receipt, never confirms a match, and returns the receipt's latest decision. Use it when a receipt was edited after matching looked at it. Finance access required. `POST /api/v1/accounting/matching.rerunEntry` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_matching_rerun_entry` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/matching.rerunEntry \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/matching.rerunEntry ## matching.status Read durable matching progress. Queued or processing status does not mean reconciliation has completed. `GET | POST /api/v1/accounting/matching.status` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_matching_status` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/matching.status \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/matching.status # Group: Autopilot > What needs a person, Autopilot runs, receipt reminders and the investigator. MCP toolset: `autopilot` (https://app.getoatmilk.com/api/mcp?toolset=autopilot) ## attention - [`attention.list`](https://app.getoatmilk.com/docs/api/attention.list.md) — List what needs a person, grouped by the one action required (find a receipt, choose a category, confirm business use, confirm income or tax, match a payout, approve), with the newest items and the reason for each. - [`attention.mine`](https://app.getoatmilk.com/docs/api/attention.mine.md) — List purchases on the caller's own cards that still need a receipt, newest first, with the card's last four digits, a page at a time: pass the returned nextCursor as cursor for the next page. total counts them all. A purchase someone already added a receipt for is left out while that receipt is matched. Finance assigns cardholders on each card; any member can read their own list and upload the receipts, with uploads.prepare receiptFor set to the purchase. ## autopilot - [`autopilot.classify`](https://app.getoatmilk.com/docs/api/autopilot.classify.md) — Run the Autopilot rules, classifier, evidence and tax checks for up to 25 entries now. With apply false it returns what Oatmilk would do without writing anything; with apply true it saves the result like a normal Autopilot pass. - [`autopilot.explain`](https://app.getoatmilk.com/docs/api/autopilot.explain.md) — Explain an entry's Autopilot state: the classification and its source, evidence and tax status, the single action a person needs to take, and every step Oatmilk already tried. - [`autopilot.status`](https://app.getoatmilk.com/docs/api/autopilot.status.md) — Summarize Autopilot for the organization: bank lines not yet in the books, transactions waiting for AI, how many need a person, and the most recent jobs. - [`autopilot.summary`](https://app.getoatmilk.com/docs/api/autopilot.summary.md) — A light Autopilot summary for Home: whether Autopilot is on, and how many transactions it finished in total and this week. Cheap to call; use autopilot.status for the full backlog. ## autopilot.jobs - [`autopilot.jobs.cancel`](https://app.getoatmilk.com/docs/api/autopilot.jobs.cancel.md) — Cancel a queued or running Autopilot job. Work already applied stays applied. - [`autopilot.jobs.get`](https://app.getoatmilk.com/docs/api/autopilot.jobs.get.md) — Read one Autopilot job with its progress: processed, finished automatically, needing a person, and failures. - [`autopilot.jobs.list`](https://app.getoatmilk.com/docs/api/autopilot.jobs.list.md) — List recent Autopilot jobs with status, progress counts and timing. - [`autopilot.jobs.start`](https://app.getoatmilk.com/docs/api/autopilot.jobs.start.md) — Start an Autopilot job. kind autopilot processes everything new or changed; kind rerun re-evaluates every transaction in an optional date range, account list or entry list, and onlyOpen skips finished ones. Transactions a person already finished or corrected are never overwritten. Returns the queued job. ## autopilot.rules - [`autopilot.rules.list`](https://app.getoatmilk.com/docs/api/autopilot.rules.list.md) — List the merchant category rules Oatmilk learned from people's corrections, with the category, how many times a person confirmed it, and whether it is on. - [`autopilot.rules.update`](https://app.getoatmilk.com/docs/api/autopilot.rules.update.md) — Turn a learned merchant rule on or off, or change its category, with id, expectedRevision and idempotencyKey. Future Autopilot passes use the change; finished transactions stay as they are. ## autopilot.settings - [`autopilot.settings.get`](https://app.getoatmilk.com/docs/api/autopilot.settings.get.md) — Read the organization's Autopilot policy: whether AI runs after each sync, whether confident transactions are finished automatically, the smallest purchase that gets a receipt request, and how many days Oatmilk searches before asking. - [`autopilot.settings.update`](https://app.getoatmilk.com/docs/api/autopilot.settings.update.md) — Change the Autopilot policy with expectedRevision and idempotencyKey: enabled, autoComplete, runAfterSync, receiptRequestMinimumMinor (minor units), evidenceGraceDays, and stripeMissingTax (ask, or no_tax to treat Stripe payments without any tax record as having no GST/HST). Changes that let Oatmilk finish more re-check open transactions. ## chase.preferences - [`chase.preferences.get`](https://app.getoatmilk.com/docs/api/chase.preferences.get.md) — Read your own receipt reminders in this organization: status (active, paused or stopped) and pausedUntil. They're the emails listing purchases on your cards that still need a receipt. A pause that has run out reads as active. - [`chase.preferences.update`](https://app.getoatmilk.com/docs/api/chase.preferences.update.md) — Change your own receipt reminders, like the links in every reminder: action pause (with days, 1 to 90, default 30), stop to turn them off, or resume to turn them back on, with idempotencyKey. It changes only your reminders, never anyone else's, and never the organization's reminder settings. ## chase - [`chase.preview`](https://app.getoatmilk.com/docs/api/chase.preview.md) — Preview receipt reminders to cardholders without sending anything: the settings, each cardholder with purchases on their cards still missing receipts (how many, the oldest, the next tax deadline those purchases count toward, how often they're reminded right now, whether one is due today or when the next one goes out, and whether they paused or turned reminders off), the last reminders sent and the last replies with what the agent found in them. - [`chase.run`](https://app.getoatmilk.com/docs/api/chase.run.md) — Send receipt reminders that are due now, or send one cardholder theirs now with userId. Each person gets at most one reminder a day, and never while they've paused or turned reminders off. Returns how many were sent and the refreshed preview. ## investigator - [`investigator.accept`](https://app.getoatmilk.com/docs/api/investigator.accept.md) — Accept an investigator proposal with id, expectedRevision and idempotencyKey: applies its category, if any, and adds a note with the answer and its source to the entry through the normal entry update, then lets Autopilot recheck the entry. - [`investigator.dismiss`](https://app.getoatmilk.com/docs/api/investigator.dismiss.md) — Dismiss an investigator proposal or open investigation with id, expectedRevision and idempotencyKey. The entry keeps its normal ask for a person. - [`investigator.financialIssue`](https://app.getoatmilk.com/docs/api/investigator.financialIssue.md) — Investigate one current financial-report component using its exact period, componentKey, expectedSourceFingerprint and idempotencyKey. Reads scoped originals and completed source facts before one proposal-only AI call. Existing settings, tenant AI access and limits apply; connected-inbox searches additionally require existing caller consent and mailboxes:search access. Returns evidence, attempted reads, a purpose explanation if supported, and the precise fact a person must confirm. Never changes financial fields, accepts proposals, sends email, imports receipts or approves accounting treatment. Same-key replay rechecks current report and source generations. - [`investigator.forEntry`](https://app.getoatmilk.com/docs/api/investigator.forEntry.md) — Read the investigation for one entry (entryId), if any: its status (awaiting the member, queued, running, proposed, no answer, accepted, dismissed or failed), what was tried, and the proposal with its answer, confidence, summary and evidence references (sender, subject, date and a short quote). - [`investigator.overview`](https://app.getoatmilk.com/docs/api/investigator.overview.md) — Read the investigator: the caller's own consent to have their connected inbox searched and whether they have one connected, and for finance and administrators the organization's settings (on or off, email first or automatic, sources, lookback window in days, confidence needed to propose, maximum investigations a day, which asks it works on) and the 20 most recent investigations with their status and proposals. - [`investigator.run`](https://app.getoatmilk.com/docs/api/investigator.run.md) — Run the investigator now for the organization, or investigate one entry (entryId) immediately. Members' consent and the daily limit still apply. ## investigator.consent - [`investigator.consent.update`](https://app.getoatmilk.com/docs/api/investigator.consent.update.md) — Allow or stop the investigator searching the caller's own connected inbox (inboxAllowed), with idempotencyKey and optional expectedRevision. Only ever changes the caller's own consent. ## investigator.settings - [`investigator.settings.update`](https://app.getoatmilk.com/docs/api/investigator.settings.update.md) — Change the investigator's settings with expectedRevision and idempotencyKey: enabled, mode (email_first or automatic), sources (inbox only for now), lookbackDays (1-365), proposeThreshold (0.5-0.99), maxPerDay (1-100) and askKinds (forward_receipt, contractor_invoice, confirm_business, choose_category, confirm_income). ## requests - [`requests.create`](https://app.getoatmilk.com/docs/api/requests.create.md) — Ask an eligible person to resolve a record, optionally with a personal message. Find the eligible people and their assigneeUserId with requests.people first. Creates their personal task immediately and batches their notification email after ten minutes. Requires an idempotency key; one open request per record and recipient. - [`requests.list`](https://app.getoatmilk.com/docs/api/requests.list.md) — List personal action requests, by default your open tasks. With mine false, list visible requests you sent or finance-visible requests for one record. - [`requests.people`](https://app.getoatmilk.com/docs/api/requests.people.md) — List the eligible people (possible assignees) for one record: active people who can access it and help resolve its next action, each with the userId that requests.create takes as assigneeUserId. Includes the bound contractor for their own timesheet or profile. - [`requests.update`](https://app.getoatmilk.com/docs/api/requests.update.md) — Complete your assigned request or cancel a request you sent. Administrators may resolve or cancel visible requests. Requires the current revision and an idempotency key. ## attention.list List what needs a person, grouped by the one action required (find a receipt, choose a category, confirm business use, confirm income or tax, match a payout, approve), with the newest items and the reason for each. `GET | POST /api/v1/accounting/attention.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_attention_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `20`. | | `kind` | enum | | Which kind of record or job this is. One of: `forward_receipt`, `review_document`, `contractor_invoice`, `choose_contractor`, `choose_category`, `confirm_business`, `confirm_income`, `confirm_tax`, `match_payout`, `approve`, `import_statement`, `reimbursement_receipts`. | | `questionOffset` | integer | | 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/attention.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/attention.list ## attention.mine List purchases on the caller's own cards that still need a receipt, newest first, with the card's last four digits, a page at a time: pass the returned nextCursor as cursor for the next page. total counts them all. A purchase someone already added a receipt for is left out while that receipt is matched. Finance assigns cardholders on each card; any member can read their own list and upload the receipts, with uploads.prepare receiptFor set to the purchase. `GET | POST /api/v1/accounting/attention.mine` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_attention_mine` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `20`. | | `cursor` | string | | Where the next page starts: the nextCursor value from the previous response. 1–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/attention.mine \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` ### Example response ```json { "data": { "total": 1, "cards": [ "4242" ], "items": [ { "id": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "merchant": "Synthetic Office Supply", "date": "2026-09-18", "currency": "CAD", "amountMinor": "4520", "lastFour": "4242" } ], "nextCursor": null } } ``` Reference page: https://app.getoatmilk.com/docs/api/attention.mine ## autopilot.classify Run the Autopilot rules, classifier, evidence and tax checks for up to 25 entries now. With apply false it returns what Oatmilk would do without writing anything; with apply true it saves the result like a normal Autopilot pass. `POST /api/v1/accounting/autopilot.classify` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_autopilot_classify` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryIds` | array of strings (ID) | | IDs of accounting entries, from entries.list or attention.mine. 1–25 items. | | `transactionId` | string (ID) | | The ID of a bank or card transaction, from transactions.list. | | `explanation` | string | | 3–2000 characters. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `apply` | boolean | | Default `false`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/autopilot.classify \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryIds": [ "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/autopilot.classify ## autopilot.explain Explain an entry's Autopilot state: the classification and its source, evidence and tax status, the single action a person needs to take, and every step Oatmilk already tried. `GET | POST /api/v1/accounting/autopilot.explain` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_autopilot_explain` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/autopilot.explain \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'entryId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/autopilot.explain ## autopilot.jobs.cancel Cancel a queued or running Autopilot job. Work already applied stays applied. `POST /api/v1/accounting/autopilot.jobs.cancel` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_autopilot_jobs_cancel` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/autopilot.jobs.cancel \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/autopilot.jobs.cancel ## autopilot.jobs.get Read one Autopilot job with its progress: processed, finished automatically, needing a person, and failures. `GET | POST /api/v1/accounting/autopilot.jobs.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_autopilot_jobs_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/autopilot.jobs.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/autopilot.jobs.get ## autopilot.jobs.list List recent Autopilot jobs with status, progress counts and timing. `GET | POST /api/v1/accounting/autopilot.jobs.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_autopilot_jobs_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `20`. | | `status` | enum | | Only include records with this status. One of: `queued`, `running`, `completed`, `failed`, `canceled`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/autopilot.jobs.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/autopilot.jobs.list ## autopilot.jobs.start Start an Autopilot job. kind autopilot processes everything new or changed; kind rerun re-evaluates every transaction in an optional date range, account list or entry list, and onlyOpen skips finished ones. Transactions a person already finished or corrected are never overwritten. Returns the queued job. `POST /api/v1/accounting/autopilot.jobs.start` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_autopilot_jobs_start` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | enum | | Which kind of record or job this is. One of: `autopilot`, `rerun`. Default `"autopilot"`. | | `from` | string | | The first date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `accountIds` | array of strings (ID) | | A list of record IDs. at most 50 items. | | `entryIds` | array of strings (ID) | | IDs of accounting entries, from entries.list or attention.mine. at most 500 items. | | `onlyOpen` | boolean | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/autopilot.jobs.start \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/autopilot.jobs.start ## autopilot.rules.list List the merchant category rules Oatmilk learned from people's corrections, with the category, how many times a person confirmed it, and whether it is on. `GET | POST /api/v1/accounting/autopilot.rules.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_autopilot_rules_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 200. Default `100`. | | `active` | boolean | | Whether the record is turned on. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/autopilot.rules.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/autopilot.rules.list ## autopilot.rules.update Turn a learned merchant rule on or off, or change its category, with id, expectedRevision and idempotencyKey. Future Autopilot passes use the change; finished transactions stay as they are. `POST /api/v1/accounting/autopilot.rules.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_autopilot_rules_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `active` | boolean | | Whether the record is turned on. | | `categoryId` | string (ID) | | The ID of a category, from categories.list. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/autopilot.rules.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "active": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/autopilot.rules.update ## autopilot.settings.get Read the organization's Autopilot policy: whether AI runs after each sync, whether confident transactions are finished automatically, the smallest purchase that gets a receipt request, and how many days Oatmilk searches before asking. `GET | POST /api/v1/accounting/autopilot.settings.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_autopilot_settings_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/autopilot.settings.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/autopilot.settings.get ## autopilot.settings.update Change the Autopilot policy with expectedRevision and idempotencyKey: enabled, autoComplete, runAfterSync, receiptRequestMinimumMinor (minor units), evidenceGraceDays, and stripeMissingTax (ask, or no_tax to treat Stripe payments without any tax record as having no GST/HST). Changes that let Oatmilk finish more re-check open transactions. `POST /api/v1/accounting/autopilot.settings.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_autopilot_settings_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `enabled` | boolean | | | | `autoComplete` | boolean | | | | `runAfterSync` | boolean | | | | `receiptRequestMinimumMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,12}$. | | `evidenceGraceDays` | integer | | 0 to 60. | | `stripeMissingTax` | enum | | One of: `ask`, `no_tax`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/autopilot.settings.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/autopilot.settings.update ## autopilot.status Summarize Autopilot for the organization: bank lines not yet in the books, transactions waiting for AI, how many need a person, and the most recent jobs. `GET | POST /api/v1/accounting/autopilot.status` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_autopilot_status` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/autopilot.status \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/autopilot.status ## autopilot.summary A light Autopilot summary for Home: whether Autopilot is on, and how many transactions it finished in total and this week. Cheap to call; use autopilot.status for the full backlog. `GET | POST /api/v1/accounting/autopilot.summary` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_autopilot_summary` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/autopilot.summary \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/autopilot.summary ## chase.preferences.get Read your own receipt reminders in this organization: status (active, paused or stopped) and pausedUntil. They're the emails listing purchases on your cards that still need a receipt. A pause that has run out reads as active. `GET | POST /api/v1/accounting/chase.preferences.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_chase_preferences_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chase.preferences.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/chase.preferences.get ## chase.preferences.update Change your own receipt reminders, like the links in every reminder: action pause (with days, 1 to 90, default 30), stop to turn them off, or resume to turn them back on, with idempotencyKey. It changes only your reminders, never anyone else's, and never the organization's reminder settings. `POST /api/v1/accounting/chase.preferences.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_chase_preferences_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `action` | enum | Yes | One of: `pause`, `stop`, `resume`. | | `days` | integer | | 1 to 90. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chase.preferences.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "action": "pause" }' ``` Reference page: https://app.getoatmilk.com/docs/api/chase.preferences.update ## chase.preview Preview receipt reminders to cardholders without sending anything: the settings, each cardholder with purchases on their cards still missing receipts (how many, the oldest, the next tax deadline those purchases count toward, how often they're reminded right now, whether one is due today or when the next one goes out, and whether they paused or turned reminders off), the last reminders sent and the last replies with what the agent found in them. `GET | POST /api/v1/accounting/chase.preview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_chase_preview` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chase.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/chase.preview ## chase.run Send receipt reminders that are due now, or send one cardholder theirs now with userId. Each person gets at most one reminder a day, and never while they've paused or turned reminders off. Returns how many were sent and the refreshed preview. `POST /api/v1/accounting/chase.run` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_chase_run` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `userId` | string | | The ID of a person in your company. 1–200 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chase.run \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/chase.run ## investigator.accept Accept an investigator proposal with id, expectedRevision and idempotencyKey: applies its category, if any, and adds a note with the answer and its source to the entry through the normal entry update, then lets Autopilot recheck the entry. `POST /api/v1/accounting/investigator.accept` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_investigator_accept` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/investigator.accept \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/investigator.accept ## investigator.consent.update Allow or stop the investigator searching the caller's own connected inbox (inboxAllowed), with idempotencyKey and optional expectedRevision. Only ever changes the caller's own consent. `POST /api/v1/accounting/investigator.consent.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_investigator_consent_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `inboxAllowed` | boolean | Yes | | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/investigator.consent.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "inboxAllowed": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/investigator.consent.update ## investigator.dismiss Dismiss an investigator proposal or open investigation with id, expectedRevision and idempotencyKey. The entry keeps its normal ask for a person. `POST /api/v1/accounting/investigator.dismiss` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_investigator_dismiss` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/investigator.dismiss \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/investigator.dismiss ## investigator.financialIssue Investigate one current financial-report component using its exact period, componentKey, expectedSourceFingerprint and idempotencyKey. Reads scoped originals and completed source facts before one proposal-only AI call. Existing settings, tenant AI access and limits apply; connected-inbox searches additionally require existing caller consent and mailboxes:search access. Returns evidence, attempted reads, a purpose explanation if supported, and the precise fact a person must confirm. Never changes financial fields, accepts proposals, sends email, imports receipts or approves accounting treatment. Same-key replay rechecks current report and source generations. `POST /api/v1/accounting/investigator.financialIssue` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_investigator_financial_issue` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `componentKey` | string | Yes | 1–200 characters. | | `expectedSourceFingerprint` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/investigator.financialIssue \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" }, "componentKey": "example", "expectedSourceFingerprint": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/investigator.financialIssue ## investigator.forEntry Read the investigation for one entry (entryId), if any: its status (awaiting the member, queued, running, proposed, no answer, accepted, dismissed or failed), what was tried, and the proposal with its answer, confidence, summary and evidence references (sender, subject, date and a short quote). `GET | POST /api/v1/accounting/investigator.forEntry` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_investigator_for_entry` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/investigator.forEntry \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'entryId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/investigator.forEntry ## investigator.overview Read the investigator: the caller's own consent to have their connected inbox searched and whether they have one connected, and for finance and administrators the organization's settings (on or off, email first or automatic, sources, lookback window in days, confidence needed to propose, maximum investigations a day, which asks it works on) and the 20 most recent investigations with their status and proposals. `GET | POST /api/v1/accounting/investigator.overview` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_investigator_overview` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/investigator.overview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/investigator.overview ## investigator.run Run the investigator now for the organization, or investigate one entry (entryId) immediately. Members' consent and the daily limit still apply. `POST /api/v1/accounting/investigator.run` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_investigator_run` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | | The ID of an accounting entry, from entries.list or attention.mine. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/investigator.run \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/investigator.run ## investigator.settings.update Change the investigator's settings with expectedRevision and idempotencyKey: enabled, mode (email_first or automatic), sources (inbox only for now), lookbackDays (1-365), proposeThreshold (0.5-0.99), maxPerDay (1-100) and askKinds (forward_receipt, contractor_invoice, confirm_business, choose_category, confirm_income). `POST /api/v1/accounting/investigator.settings.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_investigator_settings_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `enabled` | boolean | | | | `mode` | enum | | One of: `email_first`, `automatic`. | | `sources` | array of enum values | | One of: `inbox`, `discord`, `slack`. 1–3 items. | | `lookbackDays` | integer | | 1 to 365. | | `proposeThreshold` | number | | 0.5 to 0.99. | | `maxPerDay` | integer | | 1 to 100. | | `askKinds` | array of enum values | | One of: `forward_receipt`, `contractor_invoice`, `confirm_business`, `choose_category`, `confirm_income`. 1–5 items. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/investigator.settings.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/investigator.settings.update ## requests.create Ask an eligible person to resolve a record, optionally with a personal message. Find the eligible people and their assigneeUserId with requests.people first. Creates their personal task immediately and batches their notification email after ten minutes. Requires an idempotency key; one open request per record and recipient. `POST /api/v1/accounting/requests.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_requests_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `subjectType` | enum | Yes | The kind of record this is about, such as entry, invoice or mail. One of: `entry`, `submission`, `mail`, `statement`, `contractor`, `timesheet`, `compliance_item`, `intake_item`. | | `subjectId` | string (ID) | Yes | The ID of the record this is about. | | `assigneeUserId` | string | Yes | 1–200 characters. | | `message` | string | | at most 2000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/requests.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "subjectType": "entry", "subjectId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "assigneeUserId": "example" }' ``` Reference page: https://app.getoatmilk.com/docs/api/requests.create ## requests.list List personal action requests, by default your open tasks. With mine false, list visible requests you sent or finance-visible requests for one record. `GET | POST /api/v1/accounting/requests.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_requests_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `mine` | boolean | | Default `true`. | | `status` | enum | | Only include records with this status. One of: `open`, `done`, `cancelled`, `all`. Default `"open"`. | | `subjectType` | enum | | The kind of record this is about, such as entry, invoice or mail. One of: `entry`, `submission`, `mail`, `statement`, `contractor`, `timesheet`, `compliance_item`, `intake_item`. | | `subjectId` | string (ID) | | The ID of the record this is about. | | `limit` | integer | | How many results to return at most. 1 to 200. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/requests.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/requests.list ## requests.people List the eligible people (possible assignees) for one record: active people who can access it and help resolve its next action, each with the userId that requests.create takes as assigneeUserId. Includes the bound contractor for their own timesheet or profile. `GET | POST /api/v1/accounting/requests.people` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_requests_people` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `subjectType` | enum | Yes | The kind of record this is about, such as entry, invoice or mail. One of: `entry`, `submission`, `mail`, `statement`, `contractor`, `timesheet`, `compliance_item`, `intake_item`. | | `subjectId` | string (ID) | Yes | The ID of the record this is about. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/requests.people \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'subjectType=entry' \ --data-urlencode 'subjectId=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/requests.people ## requests.update Complete your assigned request or cancel a request you sent. Administrators may resolve or cancel visible requests. Requires the current revision and an idempotency key. `POST /api/v1/accounting/requests.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_requests_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `status` | enum | Yes | Only include records with this status. One of: `done`, `cancelled`. | | `response` | string | | at most 2000 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/requests.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "status": "done", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/requests.update # Group: Invoicing > Invoices, customers and vendors, and how they pay. MCP toolset: `invoicing` (https://app.getoatmilk.com/api/mcp?toolset=invoicing) ## invoices - [`invoices.approve`](https://app.getoatmilk.com/docs/api/invoices.approve.md) — Approve a prepared invoice so it can be sent on its scheduled date. Invoices prepared by schedules and payment plans are never sent without approval. - [`invoices.archive`](https://app.getoatmilk.com/docs/api/invoices.archive.md) — Archive or restore up to 100 invoices at once with ids and archived (true to archive, false to restore). Archived invoices leave the invoice lists and counts but stay in the customer's history, reports and payments. Drafts and prepared invoices without a send date, paid, written-off and cancelled invoices can be archived; an invoice that is still owed or set to go out on a date is skipped with a reason (record the payment or write off the balance, or clear the send date, first). An archived invoice can't be approved, sent, paid, edited or cancelled until it is restored. Returns the invoices changed and, for each one left alone, why. - [`invoices.correctTax`](https://app.getoatmilk.com/docs/api/invoices.correctTax.md) — Correct how an issued invoice imported from outside Oatmilk splits its unchanged total into subtotal and sales tax, without voiding it: pass id, expectedRevision, taxes (up to 5 lines of label, rate in percent, and amountMinor; an empty list means no sales tax), a reason of 10 to 500 characters, and an idempotency key. The tax becomes the sum of the lines and the subtotal the total minus that tax, which moves onto the invoice's single line. The total, payments, status, number, dates, and PDF don't change, and the reason and the split before and after are kept in the audit history. Refused for invoices Oatmilk issued (void and duplicate those instead), void or uncollectible invoices, invoices with more than one line or a quantity other than 1, and taxes above the total. Administrator only. - [`invoices.create`](https://app.getoatmilk.com/docs/api/invoices.create.md) — Create a draft invoice for a customer. By default it starts from the customer's last invoice (lines, currency, notes, payment methods, terms); pass startFrom blank to start empty. Totals and sales tax are calculated on the server. Pass projectId to tie it to a project. - [`invoices.duplicate`](https://app.getoatmilk.com/docs/api/invoices.duplicate.md) — Create a new draft from an existing invoice, or from a customer's last invoice when partyId is given. The new draft keeps the source invoice's project. - [`invoices.filesCommit`](https://app.getoatmilk.com/docs/api/invoices.filesCommit.md) — Import reviewed invoice files with their original numbers, dates, lines, taxes, and status. Paid imports record a matching payment and PDFs become the invoice's permanent file. Nothing is emailed. - [`invoices.filesExtract`](https://app.getoatmilk.com/docs/api/invoices.filesExtract.md) — Read an uploaded invoice file and return extracted invoice candidates with suggested customers. For CSV files, first call without csvMapping to get headers, then call again with the confirmed column mapping. - [`invoices.filesPrepare`](https://app.getoatmilk.com/docs/api/invoices.filesPrepare.md) — Prepare an invoice file upload (PDF, Word, image, CSV, or email up to 20 MB). Supply filename, mimeType, sizeBytes, sha256, and idempotencyKey. Upload unchanged bytes to the returned uploadUrl unless alreadyUploaded is true. - [`invoices.get`](https://app.getoatmilk.com/docs/api/invoices.get.md) — Read one invoice with its lines, taxes, totals, payments, balance due, printed customer and sender details, schedule, and approval and delivery history. - [`invoices.import`](https://app.getoatmilk.com/docs/api/invoices.import.md) — Record an invoice issued outside the platform with its original number, dates, lines, taxes, and status (sent, paid, or void) so it appears in customer history. Paid imports record a matching payment. - [`invoices.issue`](https://app.getoatmilk.com/docs/api/invoices.issue.md) — Issue a draft: assign the next invoice number, freeze the customer, sender, and payment details, and store the final PDF with its SHA-256 hash. Issuing does not email anyone. - [`invoices.lastForParty`](https://app.getoatmilk.com/docs/api/invoices.lastForParty.md) — Get the most recent invoice for a customer and a starting template (lines, currency, notes, payment methods, terms, tax) for creating a similar invoice. - [`invoices.list`](https://app.getoatmilk.com/docs/api/invoices.list.md) — List invoices with number, customer, project, dates, totals, balance due, and status. Filter by status (including open, unpaid, upcoming and needs_action: awaiting approval, ready to send by hand, or late), overdue, customer, project (or none), currency, source (manual, duplicate, recurring, plan, import), schedule, issue or due date range, or search text. Archived invoices are left out unless archived is only or include. Sort by issued, due, amount, number, updated or created. Overdue is derived from the due date and remaining balance. - [`invoices.markUncollectible`](https://app.getoatmilk.com/docs/api/invoices.markUncollectible.md) — Mark an issued, unpaid invoice as uncollectible with a reason, or reopen it with uncollectible false. - [`invoices.pdf`](https://app.getoatmilk.com/docs/api/invoices.pdf.md) — Get a short-lived link to an issued invoice PDF and its SHA-256 hash. - [`invoices.preview`](https://app.getoatmilk.com/docs/api/invoices.preview.md) — Render a PDF preview of a saved invoice or an unsaved draft without changing anything. Returns base64 PDF bytes, the number it would receive, and computed totals. - [`invoices.projectSuggestions`](https://app.getoatmilk.com/docs/api/invoices.projectSuggestions.md) — Suggest which project (a project tag) an invoice belongs to. With id, the top suggestions for that invoice with their reasons; without, recent invoices that have no project and the best suggestion for each. Reasons are the customer's other invoices, invoices of the same recurring schedule or payment plan, the invoice it was duplicated from, the project tag on the bank deposit that paid it, the project's name, aliases or keywords in the title, lines or memo, and the issue date falling inside the project's dates. Suggestions are never applied by themselves; use invoices.setProject. - [`invoices.projectTotals`](https://app.getoatmilk.com/docs/api/invoices.projectTotals.md) — Invoiced revenue per project and currency: how much was invoiced, collected and is still owed, and how many invoices, for issued invoices that aren't cancelled. Filter by projectIds and an issue-date range. Pair with tags.report for the project's spend and income from transactions. - [`invoices.recordPayment`](https://app.getoatmilk.com/docs/api/invoices.recordPayment.md) — Record a full or partial payment on an issued invoice with amountMinor in the invoice currency, paid date, method, reference, and an optional bank transaction. Optionally write off a small remaining balance such as wire fees. Updates the balance and paid status. - [`invoices.remind`](https://app.getoatmilk.com/docs/api/invoices.remind.md) — Email the customer a payment reminder for an issued, unpaid invoice with the PDF attached. - [`invoices.removePayment`](https://app.getoatmilk.com/docs/api/invoices.removePayment.md) — Remove a recorded payment with a reason. The payment stays in history as reversed and the invoice balance and status are recalculated. - [`invoices.schedule`](https://app.getoatmilk.com/docs/api/invoices.schedule.md) — Set or clear the date an invoice is sent. Scheduling approves it and it is sent automatically on that date unless autoSend is false; pass review true to have administrators asked to review it a few days before instead. - [`invoices.send`](https://app.getoatmilk.com/docs/api/invoices.send.md) — Send an invoice to the customer's billing contacts from the invoices sender identity with the PDF attached, issuing it first if needed. The email is queued and the invoice marked sent together, and an invoice's first email goes out only once; sending an already-sent invoice again is a deliberate resend. Use method manual to record that you delivered it yourself. - [`invoices.setProject`](https://app.getoatmilk.com/docs/api/invoices.setProject.md) — Tie up to 100 invoices to a project (a project tag's id from tags.list) or clear it with projectId null. Works on issued invoices too, because the project is internal and never printed. Archived projects can't be chosen. Returns the invoices changed and any left alone. - [`invoices.summary`](https://app.getoatmilk.com/docs/api/invoices.summary.md) — What the invoice list adds up to right now: what customers owe per currency and how much of it is overdue, what was paid in the last 30 days, and how many invoices need action, are overdue, are drafts, wait for approval, or are archived. Archived invoices are counted only as archived. - [`invoices.taxPlace`](https://app.getoatmilk.com/docs/api/invoices.taxPlace.md) — Work out where a customer is for sales tax. With query, the places a typed city, town, province, postal code or country can mean (Toronto, Gimli MB, M5V 2T6, London UK), each with its jurisdiction such as CA-ON; a name Oatmilk doesn't know is read by AI that has no tools and never picks the tax. With partyId, a web search for the customer's head office by its name and website, returning the place only when a quote on the page it came from says it, with that page and quote. Only the name, the website or billing email's domain, and the city leave Oatmilk. Nothing is saved; set the customer's address or the invoice's tax to use the place. - [`invoices.update`](https://app.getoatmilk.com/docs/api/invoices.update.md) — Edit a draft, scheduled, or awaiting-approval invoice with its expectedRevision. Editing an approved invoice returns it for approval. Issued invoices can't be edited; void and duplicate them instead. Their memo and project (projectId) can still change. - [`invoices.void`](https://app.getoatmilk.com/docs/api/invoices.void.md) — Void an invoice with a reason. Invoices with recorded payments must have those payments removed first. Voided invoices stay on record. ## invoices.automation - [`invoices.automation.run`](https://app.getoatmilk.com/docs/api/invoices.automation.run.md) — Run invoice automation now for this organization: prepare due recurring invoices, request reviews, send approved invoices due today, and remind administrators about approvals. Administrator only. ## invoices.numbering - [`invoices.numbering.preview`](https://app.getoatmilk.com/docs/api/invoices.numbering.preview.md) — Preview the next invoice number using the saved or a proposed number pattern and next-number setting. ## invoices.plans - [`invoices.plans.create`](https://app.getoatmilk.com/docs/api/invoices.plans.create.md) — Create a payment plan that splits a project into 2 to 12 installment invoices on set dates, by percentages or exact pre-tax amounts that add up to the subtotal. Each installment is prepared ahead of time and needs approval before it is sent. ## invoices.schedules - [`invoices.schedules.cancel`](https://app.getoatmilk.com/docs/api/invoices.schedules.cancel.md) — Cancel a recurring invoice or payment plan. Its unissued upcoming invoices are voided; issued invoices that weren't sent keep their number but are no longer sent automatically. - [`invoices.schedules.create`](https://app.getoatmilk.com/docs/api/invoices.schedules.create.md) — Create a recurring invoice for a customer from a template with a weekly, monthly, or yearly cadence, start date, optional end date or number of occurrences, review lead days, and auto-send after approval. - [`invoices.schedules.generate`](https://app.getoatmilk.com/docs/api/invoices.schedules.generate.md) — Prepare the next invoice of a recurring schedule now, ahead of its review window. It still needs approval before it is sent. - [`invoices.schedules.list`](https://app.getoatmilk.com/docs/api/invoices.schedules.list.md) — List recurring invoices and payment plans with their cadence, next date, review lead time, auto-send setting, status, and generated invoices. - [`invoices.schedules.pause`](https://app.getoatmilk.com/docs/api/invoices.schedules.pause.md) — Pause a recurring invoice or payment plan. Nothing new is prepared or sent from it while paused. - [`invoices.schedules.resume`](https://app.getoatmilk.com/docs/api/invoices.schedules.resume.md) — Resume a paused recurring invoice or payment plan. Missed dates are skipped, not back-filled. The customer must not be archived. - [`invoices.schedules.update`](https://app.getoatmilk.com/docs/api/invoices.schedules.update.md) — Update a recurring invoice's name, template, cadence, next date, end, review lead days, or auto-send setting with its expectedRevision. Already prepared invoices are not changed. ## parties - [`parties.archive`](https://app.getoatmilk.com/docs/api/parties.archive.md) — Archive or restore a customer or vendor with its expectedRevision. Archived accounts keep their history but can't receive new invoices. Archiving is refused while a recurring invoice, payment plan, or scheduled invoice would still go out to them automatically; pause or cancel those schedules and clear or void those invoices first. - [`parties.create`](https://app.getoatmilk.com/docs/api/parties.create.md) — Create a customer or vendor account with displayName, optional legal name, contacts, address, jurisdiction (for example CA-ON), tax treatment, currency, payment terms, preferred payment methods, invoice prefix, metadata, and an idempotency key. - [`parties.enrich`](https://app.getoatmilk.com/docs/api/parties.enrich.md) — Look a customer or vendor up on the web and save what their own website says: a one-line description, their industry and website, each backed by a quote from a page. Only the name, the website or billing email's domain, and the city leave Oatmilk; never amounts, invoices or contacts. Runs in the background and returns a run id; the result appears on the customer as profile. Nothing a person entered is overwritten. Skips a lookup made in the last 7 days unless force is true. - [`parties.get`](https://app.getoatmilk.com/docs/api/parties.get.md) — Read one customer or vendor with contacts, address, jurisdiction, tax treatment, default currency and payment terms, preferred payment methods, metadata, external links, invoice history, and schedules. - [`parties.list`](https://app.getoatmilk.com/docs/api/parties.list.md) — List customers and vendors (accounts) with their jurisdiction, tax treatment, open balance by currency, overdue count, and last invoice. Filter by search text or kind. - [`parties.preview`](https://app.getoatmilk.com/docs/api/parties.preview.md) — A short snapshot of one customer or vendor for a hover card or a quick answer: name, kind, location, billing contact, what Oatmilk found about them online (description, industry, website and the pages it came from), what they owe by currency and how much of it is overdue, their last invoice, how many invoices they have had, what they were billed and paid over the last 12 months, and how many days they usually take to pay. - [`parties.search`](https://app.getoatmilk.com/docs/api/parties.search.md) — Quickly find customers and vendors by name, legal name, or email for pickers. Returns names, contact email, currency, and the last invoice date. - [`parties.update`](https://app.getoatmilk.com/docs/api/parties.update.md) — Update a customer or vendor account with its expectedRevision and idempotency key. Existing invoices keep the details that were printed on them. ## paymentMethods - [`paymentMethods.archive`](https://app.getoatmilk.com/docs/api/paymentMethods.archive.md) — Deactivate an organization payment method with its expectedRevision so it is no longer offered on new invoices. Issued invoices keep their printed details. Administrator only. - [`paymentMethods.list`](https://app.getoatmilk.com/docs/api/paymentMethods.list.md) — List the organization's receiving payment methods (bank transfer in CAD or USD, international wire, Interac e-Transfer, pay-online links, other instructions) that can be printed on invoices. - [`paymentMethods.save`](https://app.getoatmilk.com/docs/api/paymentMethods.save.md) — Create or update an organization payment method with kind, label, currency, validated banking details, active flag, and whether it is a default for its currency. Updates need expectedRevision. Administrator only. - [`paymentMethods.wiseOptions`](https://app.getoatmilk.com/docs/api/paymentMethods.wiseOptions.md) — List issued, active Wise Business receiving details for an administrator to review before adding them to invoice payment methods. Never returns unissued or deprecated details. ## invoices.approve Approve a prepared invoice so it can be sent on its scheduled date. Invoices prepared by schedules and payment plans are never sent without approval. `POST /api/v1/accounting/invoices.approve` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_invoices_approve` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.approve \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.approve ## invoices.archive Archive or restore up to 100 invoices at once with ids and archived (true to archive, false to restore). Archived invoices leave the invoice lists and counts but stay in the customer's history, reports and payments. Drafts and prepared invoices without a send date, paid, written-off and cancelled invoices can be archived; an invoice that is still owed or set to go out on a date is skipped with a reason (record the payment or write off the balance, or clear the send date, first). An archived invoice can't be approved, sent, paid, edited or cancelled until it is restored. Returns the invoices changed and, for each one left alone, why. `POST /api/v1/accounting/invoices.archive` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_invoices_archive` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `ids` | array of strings (ID) | Yes | 1–100 items. | | `archived` | boolean | | Whether the record is archived. Default `true`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.archive \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "ids": [ "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.archive ## invoices.automation.run Run invoice automation now for this organization: prepare due recurring invoices, request reviews, send approved invoices due today, and remind administrators about approvals. Administrator only. `POST /api/v1/accounting/invoices.automation.run` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_invoices_automation_run` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.automation.run \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.automation.run ## invoices.correctTax Correct how an issued invoice imported from outside Oatmilk splits its unchanged total into subtotal and sales tax, without voiding it: pass id, expectedRevision, taxes (up to 5 lines of label, rate in percent, and amountMinor; an empty list means no sales tax), a reason of 10 to 500 characters, and an idempotency key. The tax becomes the sum of the lines and the subtotal the total minus that tax, which moves onto the invoice's single line. The total, payments, status, number, dates, and PDF don't change, and the reason and the split before and after are kept in the audit history. Refused for invoices Oatmilk issued (void and duplicate those instead), void or uncollectible invoices, invoices with more than one line or a quantity other than 1, and taxes above the total. Administrator only. `POST /api/v1/accounting/invoices.correctTax` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_invoices_correct_tax` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `taxes` | array of objects | Yes | at most 5 items. | | `taxes[].label` | string | Yes | 1–80 characters. | | `taxes[].rate` | string | Yes | Matches ^\d{1,3}(?:\.\d{1,4})?$. | | `taxes[].amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,18}$. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.correctTax \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "taxes": [ { "label": "example", "rate": "13", "amountMinor": "1250" } ], "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.correctTax ## invoices.create Create a draft invoice for a customer. By default it starts from the customer's last invoice (lines, currency, notes, payment methods, terms); pass startFrom blank to start empty. Totals and sales tax are calculated on the server. Pass projectId to tie it to a project. `POST /api/v1/accounting/invoices.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_invoices_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `partyId` | string (ID) | Yes | The ID of a customer or vendor account, from parties.list. | | `startFrom` | enum | | One of: `last`, `blank`. Default `"last"`. | | `title` | string | | A short title. at most 200 characters. | | `issueDate` | string or null | | The date the document was issued, as YYYY-MM-DD. | | `dueDate` | string or null | | The date payment is due, as YYYY-MM-DD. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `lines` | array of objects | | at most 100 items. | | `lines[].id` | string | | The record's ID. at most 64 characters. | | `lines[].description` | string | Yes | A short description. 1–500 characters. | | `lines[].quantity` | string | | | | `lines[].unitAmountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string, may be negative. | | `lines[].isHeader` | boolean | | | | `lines[].taxCode` | enum | | One of: `standard`, `exempt`, `zero_rated`. | | `taxTreatment` | enum or null | | One of: `auto`, `hst`, `gst`, `gst_qst`, `zero_rated`, `exempt`, `none`, `custom`. | | `taxRateCode` | enum or string or null | | | | `paymentMethodIds` | array of strings (ID) | | A list of record IDs. at most 10 items. | | `payUrl` | string (uri) or null | | | | `notes` | string | | Notes kept with the record. at most 4000 characters. | | `memo` | string | | at most 4000 characters. | | `terms` | object | | No other fields. | | `terms.code` | enum | Yes | One of: `due_on_receipt`, `net_7`, `net_15`, `net_30`, `net_45`, `net_60`, `custom`, `split_50_50`, `monthly`. | | `terms.days` | integer or null | | | | `billToContactId` | string or null | | | | `projectId` | string (ID) or null | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "partyId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.create ## invoices.duplicate Create a new draft from an existing invoice, or from a customer's last invoice when partyId is given. The new draft keeps the source invoice's project. `POST /api/v1/accounting/invoices.duplicate` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_invoices_duplicate` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `partyId` | string (ID) | | The ID of a customer or vendor account, from parties.list. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.duplicate \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.duplicate ## invoices.filesCommit Import reviewed invoice files with their original numbers, dates, lines, taxes, and status. Paid imports record a matching payment and PDFs become the invoice's permanent file. Nothing is emailed. `POST /api/v1/accounting/invoices.filesCommit` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_invoices_files_commit` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `items` | array of objects | Yes | 1–50 items. | | `items[].sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `items[].filename` | string | Yes | The file's name, including its extension. 1–200 characters. | | `items[].mimeType` | string | Yes | The file's type, such as image/jpeg or application/pdf. at most 150 characters. | | `items[].partyId` | string (ID) | | The ID of a customer or vendor account, from parties.list. | | `items[].newPartyName` | string | | 1–200 characters. | | `items[].number` | string | Yes | 1–60 characters. | | `items[].issueDate` | string | Yes | The date the document was issued, as YYYY-MM-DD. | | `items[].dueDate` | string | | The date payment is due, as YYYY-MM-DD. | | `items[].currency` | string | | Three-letter currency code, such as CAD or USD. | | `items[].title` | string | | A short title. at most 200 characters. | | `items[].notes` | string | | Notes kept with the record. at most 4000 characters. | | `items[].lines` | array of objects | Yes | 1–100 items. | | `items[].lines[].id` | string | | The record's ID. at most 64 characters. | | `items[].lines[].description` | string | Yes | A short description. 1–500 characters. | | `items[].lines[].quantity` | string | | | | `items[].lines[].unitAmountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string, may be negative. | | `items[].lines[].isHeader` | boolean | | | | `items[].lines[].taxCode` | enum | | One of: `standard`, `exempt`, `zero_rated`. | | `items[].taxes` | array of objects | | at most 5 items. | | `items[].taxes[].label` | string | Yes | 1–80 characters. | | `items[].taxes[].rate` | string | | Matches ^\d{1,3}(?:\.\d{1,4})?$. Default `"0"`. | | `items[].taxes[].amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,18}$. | | `items[].status` | enum | | Only include records with this status. One of: `sent`, `paid`, `void`. Default `"sent"`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.filesCommit \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "items": [ { "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "filename": "receipt.pdf", "mimeType": "example", "number": "example", "issueDate": "2026-09-30", "lines": [ { "description": "Synthetic example from the docs", "unitAmountMinor": "1250" } ], "partyId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.filesCommit ## invoices.filesExtract Read an uploaded invoice file and return extracted invoice candidates with suggested customers. For CSV files, first call without csvMapping to get headers, then call again with the confirmed column mapping. `GET | POST /api/v1/accounting/invoices.filesExtract` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_invoices_files_extract` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `filename` | string | Yes | The file's name, including its extension. 1–200 characters. | | `mimeType` | string | Yes | The file's type, such as image/jpeg or application/pdf. at most 150 characters. | | `csvMapping` | object or null | | | | `csvMapping.number` | string | Yes | 1–200 characters. | | `csvMapping.issueDate` | string | Yes | The date the document was issued, as YYYY-MM-DD. 1–200 characters. | | `csvMapping.dueDate` | string or null | Yes | The date payment is due, as YYYY-MM-DD. | | `csvMapping.customer` | string | Yes | 1–200 characters. | | `csvMapping.total` | string | Yes | 1–200 characters. | | `csvMapping.currency` | string or null | Yes | Three-letter currency code, such as CAD or USD. | | `csvMapping.status` | string or null | Yes | Only include records with this status. | | `csvMapping.description` | string or null | Yes | A short description. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/invoices.filesExtract \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'sha256=9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c' \ --data-urlencode 'filename=receipt.pdf' \ --data-urlencode 'mimeType=example' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.filesExtract ## invoices.filesPrepare Prepare an invoice file upload (PDF, Word, image, CSV, or email up to 20 MB). Supply filename, mimeType, sizeBytes, sha256, and idempotencyKey. Upload unchanged bytes to the returned uploadUrl unless alreadyUploaded is true. `POST /api/v1/accounting/invoices.filesPrepare` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_invoices_files_prepare` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | string | Yes | The file's name, including its extension. 1–200 characters. | | `mimeType` | string | Yes | The file's type, such as image/jpeg or application/pdf. at most 150 characters. | | `sizeBytes` | integer | Yes | The file's size in bytes. 1 to 20971520. | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.filesPrepare \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "filename": "receipt.pdf", "mimeType": "example", "sizeBytes": 1, "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.filesPrepare ## invoices.get Read one invoice with its lines, taxes, totals, payments, balance due, printed customer and sender details, schedule, and approval and delivery history. `GET | POST /api/v1/accounting/invoices.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_invoices_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/invoices.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.get ## invoices.import Record an invoice issued outside the platform with its original number, dates, lines, taxes, and status (sent, paid, or void) so it appears in customer history. Paid imports record a matching payment. `POST /api/v1/accounting/invoices.import` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_invoices_import` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `partyId` | string (ID) | Yes | The ID of a customer or vendor account, from parties.list. | | `number` | string | Yes | 1–60 characters. | | `issueDate` | string | Yes | The date the document was issued, as YYYY-MM-DD. | | `dueDate` | string | | The date payment is due, as YYYY-MM-DD. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `title` | string | | A short title. at most 200 characters. | | `lines` | array of objects | Yes | 1–100 items. | | `lines[].id` | string | | The record's ID. at most 64 characters. | | `lines[].description` | string | Yes | A short description. 1–500 characters. | | `lines[].quantity` | string | | | | `lines[].unitAmountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string, may be negative. | | `lines[].isHeader` | boolean | | | | `lines[].taxCode` | enum | | One of: `standard`, `exempt`, `zero_rated`. | | `taxTreatment` | enum or null | | One of: `auto`, `hst`, `gst`, `gst_qst`, `zero_rated`, `exempt`, `none`. | | `taxRateCode` | enum or null | | One of: `HST_13`, `HST_14`, `HST_15`. | | `taxes` | array of objects | | at most 5 items. | | `taxes[].label` | string | Yes | 1–80 characters. | | `taxes[].rate` | string | | Matches ^\d{1,3}(?:\.\d{1,4})?$. Default `"0"`. | | `taxes[].amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,18}$. | | `notes` | string | | Notes kept with the record. at most 4000 characters. | | `status` | enum | | Only include records with this status. One of: `sent`, `paid`, `void`. Default `"sent"`. | | `sentOn` | string | | | | `paidOn` | string | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.import \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "partyId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "number": "example", "issueDate": "2026-09-30", "lines": [ { "description": "Synthetic example from the docs", "unitAmountMinor": "1250" } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.import ## invoices.issue Issue a draft: assign the next invoice number, freeze the customer, sender, and payment details, and store the final PDF with its SHA-256 hash. Issuing does not email anyone. `POST /api/v1/accounting/invoices.issue` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_invoices_issue` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `issueDate` | string | | The date the document was issued, as YYYY-MM-DD. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.issue \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.issue ## invoices.lastForParty Get the most recent invoice for a customer and a starting template (lines, currency, notes, payment methods, terms, tax) for creating a similar invoice. `GET | POST /api/v1/accounting/invoices.lastForParty` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_invoices_last_for_party` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `partyId` | string (ID) | Yes | The ID of a customer or vendor account, from parties.list. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/invoices.lastForParty \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'partyId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.lastForParty ## invoices.list List invoices with number, customer, project, dates, totals, balance due, and status. Filter by status (including open, unpaid, upcoming and needs_action: awaiting approval, ready to send by hand, or late), overdue, customer, project (or none), currency, source (manual, duplicate, recurring, plan, import), schedule, issue or due date range, or search text. Archived invoices are left out unless archived is only or include. Sort by issued, due, amount, number, updated or created. Overdue is derived from the due date and remaining balance. `GET | POST /api/v1/accounting/invoices.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_invoices_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | enum | | Only include records with this status. One of: `draft`, `scheduled`, `awaiting_approval`, `approved`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `uncollectible`, `open`, `upcoming`, `unpaid`, `needs_action`. | | `partyId` | string (ID) | | The ID of a customer or vendor account, from parties.list. | | `scheduleId` | string (ID) | | The ID of the related record. | | `projectId` | string (ID) or "none" | | One of: `none`. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `source` | enum | | Where the record came from. One of: `manual`, `duplicate`, `recurring`, `plan`, `import`. | | `archived` | enum | | Whether the record is archived. One of: `exclude`, `only`, `include`. Default `"exclude"`. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `dueFrom` | string | | | | `dueTo` | string | | | | `search` | string | | Text to search for. at most 200 characters. | | `overdue` | boolean | | | | `sort` | enum | | One of: `issued`, `due`, `amount`, `number`, `updated`, `created`. | | `direction` | enum | | One of: `asc`, `desc`. | | `limit` | integer | | How many results to return at most. 1 to 200. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.list ## invoices.markUncollectible Mark an issued, unpaid invoice as uncollectible with a reason, or reopen it with uncollectible false. `POST /api/v1/accounting/invoices.markUncollectible` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_invoices_mark_uncollectible` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `uncollectible` | boolean | | Default `true`. | | `reason` | string | | A short note saying why, kept in the record's history. at most 500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.markUncollectible \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.markUncollectible ## invoices.numbering.preview Preview the next invoice number using the saved or a proposed number pattern and next-number setting. `GET | POST /api/v1/accounting/invoices.numbering.preview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_invoices_numbering_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `pattern` | string | | at most 60 characters. | | `nextNumber` | integer | | 1 to 999999999. | | `issueDate` | string | | The date the document was issued, as YYYY-MM-DD. | | `partyId` | string (ID) | | The ID of a customer or vendor account, from parties.list. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.numbering.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.numbering.preview ## invoices.pdf Get a short-lived link to an issued invoice PDF and its SHA-256 hash. `GET | POST /api/v1/accounting/invoices.pdf` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_invoices_pdf` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/invoices.pdf \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.pdf ## invoices.plans.create Create a payment plan that splits a project into 2 to 12 installment invoices on set dates, by percentages or exact pre-tax amounts that add up to the subtotal. Each installment is prepared ahead of time and needs approval before it is sent. `POST /api/v1/accounting/invoices.plans.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_invoices_plans_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `partyId` | string (ID) | Yes | The ID of a customer or vendor account, from parties.list. | | `name` | string | Yes | A display name. 1–200 characters. | | `template` | object | Yes | No other fields. | | `template.title` | string | | A short title. at most 200 characters. | | `template.currency` | string | | Three-letter currency code, such as CAD or USD. | | `template.lines` | array of objects | Yes | 1–100 items. | | `template.lines[].id` | string | | The record's ID. at most 64 characters. | | `template.lines[].description` | string | Yes | A short description. 1–500 characters. | | `template.lines[].quantity` | string | | | | `template.lines[].unitAmountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string, may be negative. | | `template.lines[].isHeader` | boolean | | | | `template.lines[].taxCode` | enum | | One of: `standard`, `exempt`, `zero_rated`. | | `template.notes` | string | | Notes kept with the record. at most 4000 characters. | | `template.memo` | string | | at most 4000 characters. | | `template.paymentMethodIds` | array of strings (ID) | | A list of record IDs. at most 10 items. | | `template.taxTreatment` | enum or null | | One of: `auto`, `hst`, `gst`, `gst_qst`, `zero_rated`, `exempt`, `none`, `custom`. | | `template.taxRateCode` | enum or string or null | | | | `template.terms` | object | | No other fields. | | `template.terms.code` | enum | Yes | One of: `due_on_receipt`, `net_7`, `net_15`, `net_30`, `net_45`, `net_60`, `custom`, `split_50_50`, `monthly`. | | `template.terms.days` | integer or null | | | | `template.payUrl` | string (uri) or null | | | | `template.projectId` | string (ID) or null | | | | `installments` | array of objects | Yes | 2–12 items. | | `installments[].date` | string | Yes | A date, as YYYY-MM-DD. | | `installments[].percent` | string | | at most 9 characters. | | `installments[].amountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,18}$. | | `installments[].label` | string | | at most 120 characters. | | `reviewLeadDays` | integer | | 0 to 60. | | `autoSendAfterApproval` | boolean | | Default `true`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.plans.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "partyId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "name": "Synthetic Ventures Inc.", "template": { "lines": [ { "description": "Synthetic example from the docs", "unitAmountMinor": "1250" } ] }, "installments": [ { "date": "2026-09-30" }, { "date": "2026-09-30" } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.plans.create ## invoices.preview Render a PDF preview of a saved invoice or an unsaved draft without changing anything. Returns base64 PDF bytes, the number it would receive, and computed totals. `GET | POST /api/v1/accounting/invoices.preview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_invoices_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `draft` | object | | No other fields. | | `draft.partyId` | string (ID) | Yes | The ID of a customer or vendor account, from parties.list. | | `draft.title` | string | | A short title. at most 200 characters. | | `draft.issueDate` | string or null | | The date the document was issued, as YYYY-MM-DD. | | `draft.dueDate` | string or null | | The date payment is due, as YYYY-MM-DD. | | `draft.currency` | string | | Three-letter currency code, such as CAD or USD. | | `draft.lines` | array of objects | | at most 100 items. | | `draft.lines[].id` | string | | The record's ID. at most 64 characters. | | `draft.lines[].description` | string | Yes | A short description. 1–500 characters. | | `draft.lines[].quantity` | string | | | | `draft.lines[].unitAmountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string, may be negative. | | `draft.lines[].isHeader` | boolean | | | | `draft.lines[].taxCode` | enum | | One of: `standard`, `exempt`, `zero_rated`. | | `draft.taxTreatment` | enum or null | | One of: `auto`, `hst`, `gst`, `gst_qst`, `zero_rated`, `exempt`, `none`, `custom`. | | `draft.taxRateCode` | enum or string or null | | | | `draft.paymentMethodIds` | array of strings (ID) | | A list of record IDs. at most 10 items. | | `draft.payUrl` | string (uri) or null | | | | `draft.notes` | string | | Notes kept with the record. at most 4000 characters. | | `draft.memo` | string | | at most 4000 characters. | | `draft.terms` | object | | No other fields. | | `draft.terms.code` | enum | Yes | One of: `due_on_receipt`, `net_7`, `net_15`, `net_30`, `net_45`, `net_60`, `custom`, `split_50_50`, `monthly`. | | `draft.terms.days` | integer or null | | | | `draft.billToContactId` | string or null | | | | `draft.projectId` | string (ID) or null | | | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/invoices.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.preview ## invoices.projectSuggestions Suggest which project (a project tag) an invoice belongs to. With id, the top suggestions for that invoice with their reasons; without, recent invoices that have no project and the best suggestion for each. Reasons are the customer's other invoices, invoices of the same recurring schedule or payment plan, the invoice it was duplicated from, the project tag on the bank deposit that paid it, the project's name, aliases or keywords in the title, lines or memo, and the issue date falling inside the project's dates. Suggestions are never applied by themselves; use invoices.setProject. `GET | POST /api/v1/accounting/invoices.projectSuggestions` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_invoices_project_suggestions` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `25`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.projectSuggestions \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.projectSuggestions ## invoices.projectTotals Invoiced revenue per project and currency: how much was invoiced, collected and is still owed, and how many invoices, for issued invoices that aren't cancelled. Filter by projectIds and an issue-date range. Pair with tags.report for the project's spend and income from transactions. `GET | POST /api/v1/accounting/invoices.projectTotals` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_invoices_project_totals` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `projectIds` | array of strings (ID) | | A list of record IDs. at most 100 items. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.projectTotals \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.projectTotals ## invoices.recordPayment Record a full or partial payment on an issued invoice with amountMinor in the invoice currency, paid date, method, reference, and an optional bank transaction. Optionally write off a small remaining balance such as wire fees. Updates the balance and paid status. `POST /api/v1/accounting/invoices.recordPayment` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_invoices_record_payment` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `invoiceId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,18}$. | | `paidOn` | string | Yes | | | `method` | enum | | One of: `bank_transfer`, `wire`, `interac`, `card`, `cheque`, `cash`, `pay_link`, `other`. Default `"bank_transfer"`. | | `reference` | string | | at most 300 characters. | | `note` | string | | A short note, kept with the record. at most 1000 characters. | | `transactionId` | string (ID) | | The ID of a bank or card transaction, from transactions.list. | | `writeOffRemainder` | boolean | | Default `false`. | | `writeOffReason` | string | | at most 300 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.recordPayment \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "invoiceId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "expectedRevision": 3, "amountMinor": "1250", "paidOn": "2026-09-30" }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.recordPayment ## invoices.remind Email the customer a payment reminder for an issued, unpaid invoice with the PDF attached. `POST /api/v1/accounting/invoices.remind` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_invoices_remind` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `to` | array of strings (email) | | The last date to include, as YYYY-MM-DD. 1–10 items; each at most 320 characters. | | `cc` | array of strings (email) | | at most 10 items; each at most 320 characters. | | `message` | string | | at most 2000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.remind \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.remind ## invoices.removePayment Remove a recorded payment with a reason. The payment stays in history as reversed and the invoice balance and status are recalculated. `POST /api/v1/accounting/invoices.removePayment` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_invoices_remove_payment` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `invoiceId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `paymentId` | string (ID) | Yes | The ID of the related record. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.removePayment \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "invoiceId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "expectedRevision": 3, "paymentId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.removePayment ## invoices.schedule Set or clear the date an invoice is sent. Scheduling approves it and it is sent automatically on that date unless autoSend is false; pass review true to have administrators asked to review it a few days before instead. `POST /api/v1/accounting/invoices.schedule` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_invoices_schedule` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `sendDate` | string or null | Yes | | | `autoSend` | boolean | | Default `true`. | | `review` | boolean | | Default `false`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.schedule \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "sendDate": "2026-09-30" }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.schedule ## invoices.schedules.cancel Cancel a recurring invoice or payment plan. Its unissued upcoming invoices are voided; issued invoices that weren't sent keep their number but are no longer sent automatically. `POST /api/v1/accounting/invoices.schedules.cancel` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_invoices_schedules_cancel` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.schedules.cancel \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.schedules.cancel ## invoices.schedules.create Create a recurring invoice for a customer from a template with a weekly, monthly, or yearly cadence, start date, optional end date or number of occurrences, review lead days, and auto-send after approval. `POST /api/v1/accounting/invoices.schedules.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_invoices_schedules_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `partyId` | string (ID) | Yes | The ID of a customer or vendor account, from parties.list. | | `name` | string | Yes | A display name. 1–200 characters. | | `template` | object | Yes | No other fields. | | `template.title` | string | | A short title. at most 200 characters. | | `template.currency` | string | | Three-letter currency code, such as CAD or USD. | | `template.lines` | array of objects | Yes | 1–100 items. | | `template.lines[].id` | string | | The record's ID. at most 64 characters. | | `template.lines[].description` | string | Yes | A short description. 1–500 characters. | | `template.lines[].quantity` | string | | | | `template.lines[].unitAmountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string, may be negative. | | `template.lines[].isHeader` | boolean | | | | `template.lines[].taxCode` | enum | | One of: `standard`, `exempt`, `zero_rated`. | | `template.notes` | string | | Notes kept with the record. at most 4000 characters. | | `template.memo` | string | | at most 4000 characters. | | `template.paymentMethodIds` | array of strings (ID) | | A list of record IDs. at most 10 items. | | `template.taxTreatment` | enum or null | | One of: `auto`, `hst`, `gst`, `gst_qst`, `zero_rated`, `exempt`, `none`, `custom`. | | `template.taxRateCode` | enum or string or null | | | | `template.terms` | object | | No other fields. | | `template.terms.code` | enum | Yes | One of: `due_on_receipt`, `net_7`, `net_15`, `net_30`, `net_45`, `net_60`, `custom`, `split_50_50`, `monthly`. | | `template.terms.days` | integer or null | | | | `template.payUrl` | string (uri) or null | | | | `template.projectId` | string (ID) or null | | | | `cadence` | object | Yes | No other fields. | | `cadence.unit` | enum | Yes | One of: `week`, `month`, `year`. | | `cadence.interval` | integer | | 1 to 24. Default `1`. | | `cadence.dayOfMonth` | integer or null | | | | `startDate` | string | Yes | | | `endDate` | string or null | | | | `occurrences` | integer or null | | | | `reviewLeadDays` | integer | | 0 to 60. | | `autoSendAfterApproval` | boolean | | Default `true`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.schedules.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "partyId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "name": "Synthetic Ventures Inc.", "template": { "lines": [ { "description": "Synthetic example from the docs", "unitAmountMinor": "1250" } ] }, "cadence": { "unit": "week" }, "startDate": "2026-09-30" }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.schedules.create ## invoices.schedules.generate Prepare the next invoice of a recurring schedule now, ahead of its review window. It still needs approval before it is sent. `POST /api/v1/accounting/invoices.schedules.generate` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_invoices_schedules_generate` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.schedules.generate \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.schedules.generate ## invoices.schedules.list List recurring invoices and payment plans with their cadence, next date, review lead time, auto-send setting, status, and generated invoices. `GET | POST /api/v1/accounting/invoices.schedules.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_invoices_schedules_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | enum | | Only include records with this status. One of: `active`, `paused`, `completed`, `cancelled`. | | `kind` | enum | | Which kind of record or job this is. One of: `recurring`, `plan`. | | `partyId` | string (ID) | | The ID of a customer or vendor account, from parties.list. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.schedules.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.schedules.list ## invoices.schedules.pause Pause a recurring invoice or payment plan. Nothing new is prepared or sent from it while paused. `POST /api/v1/accounting/invoices.schedules.pause` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_invoices_schedules_pause` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.schedules.pause \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.schedules.pause ## invoices.schedules.resume Resume a paused recurring invoice or payment plan. Missed dates are skipped, not back-filled. The customer must not be archived. `POST /api/v1/accounting/invoices.schedules.resume` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_invoices_schedules_resume` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.schedules.resume \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.schedules.resume ## invoices.schedules.update Update a recurring invoice's name, template, cadence, next date, end, review lead days, or auto-send setting with its expectedRevision. Already prepared invoices are not changed. `POST /api/v1/accounting/invoices.schedules.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_invoices_schedules_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `name` | string | | A display name. 1–200 characters. | | `template` | object | | No other fields. | | `template.title` | string | | A short title. at most 200 characters. | | `template.currency` | string | | Three-letter currency code, such as CAD or USD. | | `template.lines` | array of objects | Yes | 1–100 items. | | `template.lines[].id` | string | | The record's ID. at most 64 characters. | | `template.lines[].description` | string | Yes | A short description. 1–500 characters. | | `template.lines[].quantity` | string | | | | `template.lines[].unitAmountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string, may be negative. | | `template.lines[].isHeader` | boolean | | | | `template.lines[].taxCode` | enum | | One of: `standard`, `exempt`, `zero_rated`. | | `template.notes` | string | | Notes kept with the record. at most 4000 characters. | | `template.memo` | string | | at most 4000 characters. | | `template.paymentMethodIds` | array of strings (ID) | | A list of record IDs. at most 10 items. | | `template.taxTreatment` | enum or null | | One of: `auto`, `hst`, `gst`, `gst_qst`, `zero_rated`, `exempt`, `none`, `custom`. | | `template.taxRateCode` | enum or string or null | | | | `template.terms` | object | | No other fields. | | `template.terms.code` | enum | Yes | One of: `due_on_receipt`, `net_7`, `net_15`, `net_30`, `net_45`, `net_60`, `custom`, `split_50_50`, `monthly`. | | `template.terms.days` | integer or null | | | | `template.payUrl` | string (uri) or null | | | | `template.projectId` | string (ID) or null | | | | `cadence` | object | | No other fields. | | `cadence.unit` | enum | Yes | One of: `week`, `month`, `year`. | | `cadence.interval` | integer | | 1 to 24. Default `1`. | | `cadence.dayOfMonth` | integer or null | | | | `nextRunDate` | string | | | | `endDate` | string or null | | | | `occurrencesRemaining` | integer or null | | | | `reviewLeadDays` | integer | | 0 to 60. | | `autoSendAfterApproval` | boolean | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.schedules.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.schedules.update ## invoices.send Send an invoice to the customer's billing contacts from the invoices sender identity with the PDF attached, issuing it first if needed. The email is queued and the invoice marked sent together, and an invoice's first email goes out only once; sending an already-sent invoice again is a deliberate resend. Use method manual to record that you delivered it yourself. `POST /api/v1/accounting/invoices.send` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_invoices_send` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `method` | enum | | One of: `email`, `manual`. Default `"email"`. | | `issueDate` | string | | The date the document was issued, as YYYY-MM-DD. | | `to` | array of strings (email) | | The last date to include, as YYYY-MM-DD. 1–10 items; each at most 320 characters. | | `cc` | array of strings (email) | | at most 10 items; each at most 320 characters. | | `message` | string | | at most 2000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.send \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.send ## invoices.setProject Tie up to 100 invoices to a project (a project tag's id from tags.list) or clear it with projectId null. Works on issued invoices too, because the project is internal and never printed. Archived projects can't be chosen. Returns the invoices changed and any left alone. `POST /api/v1/accounting/invoices.setProject` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_invoices_set_project` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `ids` | array of strings (ID) | Yes | 1–100 items. | | `projectId` | string (ID) or null | Yes | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.setProject \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "ids": [ "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" ], "projectId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.setProject ## invoices.summary What the invoice list adds up to right now: what customers owe per currency and how much of it is overdue, what was paid in the last 30 days, and how many invoices need action, are overdue, are drafts, wait for approval, or are archived. Archived invoices are counted only as archived. `GET | POST /api/v1/accounting/invoices.summary` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_invoices_summary` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.summary \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.summary ## invoices.taxPlace Work out where a customer is for sales tax. With query, the places a typed city, town, province, postal code or country can mean (Toronto, Gimli MB, M5V 2T6, London UK), each with its jurisdiction such as CA-ON; a name Oatmilk doesn't know is read by AI that has no tools and never picks the tax. With partyId, a web search for the customer's head office by its name and website, returning the place only when a quote on the page it came from says it, with that page and quote. Only the name, the website or billing email's domain, and the city leave Oatmilk. Nothing is saved; set the customer's address or the invoice's tax to use the place. `GET | POST /api/v1/accounting/invoices.taxPlace` Permissions: `accounting:read` · Roles: admin, finance · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_invoices_tax_place` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `partyId` | string (ID) | | The ID of a customer or vendor account, from parties.list. | | `query` | string | | Text to search for. 2–120 characters. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/invoices.taxPlace \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'partyId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.taxPlace ## invoices.update Edit a draft, scheduled, or awaiting-approval invoice with its expectedRevision. Editing an approved invoice returns it for approval. Issued invoices can't be edited; void and duplicate them instead. Their memo and project (projectId) can still change. `POST /api/v1/accounting/invoices.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_invoices_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `partyId` | string (ID) | | The ID of a customer or vendor account, from parties.list. | | `title` | string | | A short title. at most 200 characters. | | `issueDate` | string or null | | The date the document was issued, as YYYY-MM-DD. | | `dueDate` | string or null | | The date payment is due, as YYYY-MM-DD. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `lines` | array of objects | | at most 100 items. | | `lines[].id` | string | | The record's ID. at most 64 characters. | | `lines[].description` | string | Yes | A short description. 1–500 characters. | | `lines[].quantity` | string | | | | `lines[].unitAmountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string, may be negative. | | `lines[].isHeader` | boolean | | | | `lines[].taxCode` | enum | | One of: `standard`, `exempt`, `zero_rated`. | | `taxTreatment` | enum or null | | One of: `auto`, `hst`, `gst`, `gst_qst`, `zero_rated`, `exempt`, `none`, `custom`. | | `taxRateCode` | enum or string or null | | | | `paymentMethodIds` | array of strings (ID) | | A list of record IDs. at most 10 items. | | `payUrl` | string (uri) or null | | | | `notes` | string | | Notes kept with the record. at most 4000 characters. | | `memo` | string | | at most 4000 characters. | | `terms` | object | | No other fields. | | `terms.code` | enum | Yes | One of: `due_on_receipt`, `net_7`, `net_15`, `net_30`, `net_45`, `net_60`, `custom`, `split_50_50`, `monthly`. | | `terms.days` | integer or null | | | | `billToContactId` | string or null | | | | `projectId` | string (ID) or null | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.update ## invoices.void Void an invoice with a reason. Invoices with recorded payments must have those payments removed first. Voided invoices stay on record. `POST /api/v1/accounting/invoices.void` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_invoices_void` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/invoices.void \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/invoices.void ## parties.archive Archive or restore a customer or vendor with its expectedRevision. Archived accounts keep their history but can't receive new invoices. Archiving is refused while a recurring invoice, payment plan, or scheduled invoice would still go out to them automatically; pause or cancel those schedules and clear or void those invoices first. `POST /api/v1/accounting/parties.archive` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_parties_archive` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `archived` | boolean | | Whether the record is archived. Default `true`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/parties.archive \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/parties.archive ## parties.create Create a customer or vendor account with displayName, optional legal name, contacts, address, jurisdiction (for example CA-ON), tax treatment, currency, payment terms, preferred payment methods, invoice prefix, metadata, and an idempotency key. `POST /api/v1/accounting/parties.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_parties_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | enum | | Which kind of record or job this is. One of: `customer`, `vendor`, `both`. | | `displayName` | string | Yes | 1–200 characters. | | `legalName` | string or null | | | | `email` | string (email) or null | | An email address. | | `phone` | string or null | | | | `website` | string (uri) or null | | | | `contacts` | array of objects | | at most 25 items. | | `contacts[].id` | string | | The record's ID. at most 64 characters. | | `contacts[].name` | string | | A display name. at most 200 characters. Default `""`. | | `contacts[].email` | string (email) or null | | An email address. | | `contacts[].phone` | string or null | | | | `contacts[].title` | string or null | | A short title. | | `contacts[].primary` | boolean | | Default `false`. | | `contacts[].billing` | boolean | | Default `false`. | | `address` | object | | No other fields. | | `address.line1` | string | | at most 200 characters. | | `address.line2` | string | | at most 200 characters. | | `address.city` | string | | at most 120 characters. | | `address.region` | string | | at most 120 characters. | | `address.postalCode` | string | | at most 20 characters. | | `address.country` | string | | at most 60 characters. | | `jurisdiction` | string or null | | | | `taxTreatment` | enum | | One of: `auto`, `hst`, `gst`, `gst_qst`, `zero_rated`, `exempt`, `none`. | | `taxRateCode` | enum or null | | One of: `HST_13`, `HST_14`, `HST_15`. | | `taxId` | string or null | | | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `paymentTerms` | object or null | | | | `paymentTerms.code` | enum | Yes | One of: `due_on_receipt`, `net_7`, `net_15`, `net_30`, `net_45`, `net_60`, `custom`, `split_50_50`, `monthly`. | | `paymentTerms.days` | integer or null | | | | `preferredPaymentMethodIds` | array of strings (ID) | | A list of record IDs. at most 10 items. | | `invoicePrefix` | string or null | | | | `notes` | string | | Notes kept with the record. at most 5000 characters. | | `metadata` | map | | | | `external` | map | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/parties.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "displayName": "Synthetic Ventures Inc." }' ``` Reference page: https://app.getoatmilk.com/docs/api/parties.create ## parties.enrich Look a customer or vendor up on the web and save what their own website says: a one-line description, their industry and website, each backed by a quote from a page. Only the name, the website or billing email's domain, and the city leave Oatmilk; never amounts, invoices or contacts. Runs in the background and returns a run id; the result appears on the customer as profile. Nothing a person entered is overwritten. Skips a lookup made in the last 7 days unless force is true. `POST /api/v1/accounting/parties.enrich` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_parties_enrich` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `force` | boolean | | Default `false`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/parties.enrich \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/parties.enrich ## parties.get Read one customer or vendor with contacts, address, jurisdiction, tax treatment, default currency and payment terms, preferred payment methods, metadata, external links, invoice history, and schedules. `GET | POST /api/v1/accounting/parties.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_parties_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/parties.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/parties.get ## parties.list List customers and vendors (accounts) with their jurisdiction, tax treatment, open balance by currency, overdue count, and last invoice. Filter by search text or kind. `GET | POST /api/v1/accounting/parties.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_parties_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `search` | string | | Text to search for. at most 200 characters. | | `kind` | enum | | Which kind of record or job this is. One of: `customer`, `vendor`, `both`. | | `includeArchived` | boolean | | Also include archived records. | | `archivedOnly` | boolean | | | | `limit` | integer | | How many results to return at most. 1 to 200. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/parties.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/parties.list ## parties.preview A short snapshot of one customer or vendor for a hover card or a quick answer: name, kind, location, billing contact, what Oatmilk found about them online (description, industry, website and the pages it came from), what they owe by currency and how much of it is overdue, their last invoice, how many invoices they have had, what they were billed and paid over the last 12 months, and how many days they usually take to pay. `GET | POST /api/v1/accounting/parties.preview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_parties_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/parties.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/parties.preview ## parties.search Quickly find customers and vendors by name, legal name, or email for pickers. Returns names, contact email, currency, and the last invoice date. `GET | POST /api/v1/accounting/parties.search` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_parties_search` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `query` | string | | Text to search for. at most 200 characters. Default `""`. | | `includeArchived` | boolean | | Also include archived records. | | `limit` | integer | | How many results to return at most. 1 to 25. Default `10`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/parties.search \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/parties.search ## parties.update Update a customer or vendor account with its expectedRevision and idempotency key. Existing invoices keep the details that were printed on them. `POST /api/v1/accounting/parties.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_parties_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | enum | | Which kind of record or job this is. One of: `customer`, `vendor`, `both`. | | `displayName` | string | | 1–200 characters. | | `legalName` | string or null | | | | `email` | string (email) or null | | An email address. | | `phone` | string or null | | | | `website` | string (uri) or null | | | | `contacts` | array of objects | | at most 25 items. | | `contacts[].id` | string | | The record's ID. at most 64 characters. | | `contacts[].name` | string | | A display name. at most 200 characters. Default `""`. | | `contacts[].email` | string (email) or null | | An email address. | | `contacts[].phone` | string or null | | | | `contacts[].title` | string or null | | A short title. | | `contacts[].primary` | boolean | | Default `false`. | | `contacts[].billing` | boolean | | Default `false`. | | `address` | object | | No other fields. | | `address.line1` | string | | at most 200 characters. | | `address.line2` | string | | at most 200 characters. | | `address.city` | string | | at most 120 characters. | | `address.region` | string | | at most 120 characters. | | `address.postalCode` | string | | at most 20 characters. | | `address.country` | string | | at most 60 characters. | | `jurisdiction` | string or null | | | | `taxTreatment` | enum | | One of: `auto`, `hst`, `gst`, `gst_qst`, `zero_rated`, `exempt`, `none`. | | `taxRateCode` | enum or null | | One of: `HST_13`, `HST_14`, `HST_15`. | | `taxId` | string or null | | | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `paymentTerms` | object or null | | | | `paymentTerms.code` | enum | Yes | One of: `due_on_receipt`, `net_7`, `net_15`, `net_30`, `net_45`, `net_60`, `custom`, `split_50_50`, `monthly`. | | `paymentTerms.days` | integer or null | | | | `preferredPaymentMethodIds` | array of strings (ID) | | A list of record IDs. at most 10 items. | | `invoicePrefix` | string or null | | | | `notes` | string | | Notes kept with the record. at most 5000 characters. | | `metadata` | map | | | | `external` | map | | | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/parties.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/parties.update ## paymentMethods.archive Deactivate an organization payment method with its expectedRevision so it is no longer offered on new invoices. Issued invoices keep their printed details. Administrator only. `POST /api/v1/accounting/paymentMethods.archive` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_payment_methods_archive` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/paymentMethods.archive \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/paymentMethods.archive ## paymentMethods.list List the organization's receiving payment methods (bank transfer in CAD or USD, international wire, Interac e-Transfer, pay-online links, other instructions) that can be printed on invoices. `GET | POST /api/v1/accounting/paymentMethods.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_payment_methods_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `includeInactive` | boolean | | Also include inactive. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/paymentMethods.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/paymentMethods.list ## paymentMethods.save Create or update an organization payment method with kind, label, currency, validated banking details, active flag, and whether it is a default for its currency. Updates need expectedRevision. Administrator only. `POST /api/v1/accounting/paymentMethods.save` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_payment_methods_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `kind` | enum | Yes | Which kind of record or job this is. One of: `bank_transfer_cad`, `bank_transfer_usd`, `wire`, `interac`, `pay_link`, `other`. | | `label` | string | Yes | 1–120 characters. | | `currency` | string or null | | Three-letter currency code, such as CAD or USD. | | `details` | map | Yes | | | `active` | boolean | | Whether the record is turned on. | | `defaultForCurrency` | boolean | | | | `sortOrder` | integer | | 0 to 1000. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/paymentMethods.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "kind": "bank_transfer_cad", "label": "example", "details": {} }' ``` Reference page: https://app.getoatmilk.com/docs/api/paymentMethods.save ## paymentMethods.wiseOptions List issued, active Wise Business receiving details for an administrator to review before adding them to invoice payment methods. Never returns unissued or deprecated details. `GET | POST /api/v1/accounting/paymentMethods.wiseOptions` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_payment_methods_wise_options` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/paymentMethods.wiseOptions \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/paymentMethods.wiseOptions # Group: Documents and signing > Agreements, e-signatures and kept records such as tax returns. MCP toolset: `documents` (https://app.getoatmilk.com/api/mcp?toolset=documents) ## company - [`company.applyRecord`](https://app.getoatmilk.com/docs/api/company.applyRecord.md) — Save chosen facts of a kept company record into the company profile, replacing what is there, with documentId, fields (legalName, operatingName, businessNumber, corporationNumber, incorporationDate, jurisdiction, registration, gstHstAccount, gstHstEffectiveDate, gstHstFrequency, fiscalYearEnd, registeredOffice, directors, officers, shareholders), the profile's expectedRevision and idempotencyKey. Each saved fact notes the record as its source. - [`company.recordChanges`](https://app.getoatmilk.com/docs/api/company.recordChanges.md) — Read what a kept company record (documentId of a company_document: articles, a certificate, an Ownr or registry package, an annual return) says about the company compared with the company profile: describesCompany (true when its name or corporation or business number matches, null when the profile has none yet) and changes, each { key, label, current, value, kind }: fill for an empty profile field, conflict for a different value. Oatmilk never saves them on its own: an admin chooses which to use (company.applyRecord, or company.facts.review and company.facts.apply for every record at once). ## company.facts - [`company.facts.apply`](https://app.getoatmilk.com/docs/api/company.facts.apply.md) — Save the details a person chose from company.facts.review into the company profile, in one change: picks ([{ documentId, key }], one record per detail, from the options shown), checked (the ids of the records whose findings the person saw, so the ones they left aren't asked about again), expectedRevision (the review's revision) and idempotencyKey. Only call it after the person confirmed the values. Each saved detail notes its record as the source. A GST/HST number, registration date or filing frequency also marks the company as registered for GST/HST; a GST/HST number has to start with the business number. A changed profile is refused with CONFLICT: read the review again. Returns the saved profile and revision, applied and checked. Run accounting_compliance_refresh afterwards to rebuild the tax deadlines from the new details. - [`company.facts.review`](https://app.getoatmilk.com/docs/api/company.facts.review.md) — Read what the company's own records say about the company, for a person to check before anything is saved. Oatmilk reads every company record (articles, certificates, registry and annual returns, CRA business number and GST/HST registration letters) and every CRA tax record that prints a GST/HST (RT) account, and never saves what it found on its own. Input: optional documentIds to look at only those records (for example the files just uploaded); otherwise the newest 50 company and tax records. Returns revision (the company profile's, for company.facts.apply), documents (each record's id, title, filename, kind, createdAt, status: reading, to_check, nothing_new, other_company, unreadable or failed, and details, how many it has to check) and fields: one per profile detail with slot, key (legalName, operatingName, businessNumber, corporationNumber, incorporationDate, jurisdiction, entityCountry, formationJurisdiction, legalForm, usFederalTaxClassification, registration, gstHstAccount, gstHstEffectiveDate, gstHstFrequency, fiscalYearEnd, registeredOffice, directors, officers or shareholders), label, current (what the profile has now, or null), kind (fill when the profile is empty, conflict when it differs) and options (each value with the records that say it; two options mean the records disagree). pending is how many details wait to be checked. A finding a person already checked and left isn't listed again until the record says something new. Show the fields to the person and let them choose before calling company.facts.apply. ## documents.assets - [`documents.assets.confirm`](https://app.getoatmilk.com/docs/api/documents.assets.confirm.md) — Finish an image upload: checks the uploaded bytes against the declared type and SHA-256 and that the image can be printed, records its size, and returns it with a private link that shows it for about an hour. - [`documents.assets.list`](https://app.getoatmilk.com/docs/api/documents.assets.list.md) — List the images kept in the agreement library (logos, diagrams, stamps) with their titles, sizes and private links that show them for about an hour. Pass ids to get links for specific images, such as the ones a template places with asset:. - [`documents.assets.prepare`](https://app.getoatmilk.com/docs/api/documents.assets.prepare.md) — Start uploading an image for agreements, such as a logo, a diagram or a signature stamp: PNG or JPEG up to 10 MB and 6,000 pixels on a side, with its SHA-256. Pass saved: true to keep it in the library to reuse. Returns the image and, unless the organization already has this exact image, a private upload link: PUT the bytes there with their content type, then call documents.assets.confirm. Place a confirmed image in a template with a line ![Alt text](asset:?width=50&align=center); width is 10–100 percent of the text width (default 100) and align is left, center or right (default center). - [`documents.assets.update`](https://app.getoatmilk.com/docs/api/documents.assets.update.md) — Rename an image, keep it in or take it out of the library (saved), or archive it, with id, expectedRevision and idempotencyKey. Archived images still print where they are already placed. ## documents.comments - [`documents.comments.create`](https://app.getoatmilk.com/docs/api/documents.comments.create.md) — Add an auditable comment thread to an agreement template. A text selection may be anchored; mention IDs must belong to active members of this organization. Requires an idempotency key. - [`documents.comments.list`](https://app.getoatmilk.com/docs/api/documents.comments.list.md) — Read comment threads and replies for an agreement template in this organization, including resolved threads and their anchored text. - [`documents.comments.members`](https://app.getoatmilk.com/docs/api/documents.comments.members.md) — List active finance and administrator members in this organization who can be tagged in document comments. - [`documents.comments.reply`](https://app.getoatmilk.com/docs/api/documents.comments.reply.md) — Add an auditable reply to an open agreement-template comment thread. Participants and newly mentioned active organization members receive in-app notifications. Requires an idempotency key. - [`documents.comments.resolve`](https://app.getoatmilk.com/docs/api/documents.comments.resolve.md) — Resolve or reopen an agreement-template comment thread with its expected revision. The change is recorded in the audit trail. Requires an idempotency key. ## documents.content - [`documents.content.edit`](https://app.getoatmilk.com/docs/api/documents.content.edit.md) — Edit an agreement template or contractor template in place with 1 to 50 operations applied together, all or nothing: replace_text, delete_text, insert_text (Markdown at the start or end, or before or after the block holding anchor text), place_signature_section (moves it if already placed), insert_page_break, replace_document, set_title, set_description, set_letterhead, set_field (label, required, default), remove_field, set_signers, and for agreements set_footer (up to two lines with merge fields and {{page}} / {{pages}}; null for the default footer), set_logo (a library image, or null for the company logo), insert_image (a library image with alt, width 10–100 percent and align), insert_saved_block (a saved block from documents.snippets.list) and insert_record_details (a record's details from documents.records.search as plain text: lines, values or table, optionally only some fields by key). Text is found by quoting it exactly, though spacing may differ; quote enough to be unique or pass occurrence. Pass expectedRevision to refuse if it changed since you read it; without it, quoted edits apply to the latest version. Anyone with it open in the agreement writer sees the change at once. Starter templates and uploaded files are read-only. Returns the new revision and a line per change. - [`documents.content.get`](https://app.getoatmilk.com/docs/api/documents.content.get.md) — Read a document's text to review or edit it: an agreement template written in the agreement writer (documentType agreement_template) or a contractor agreement template written in Oatmilk (contractor_template). Returns the title, revision, status (draft, published, archived, starter), the Markdown text in pages of up to 20,000 characters (offset and nextOffset), its headings, and for agreements the letterhead setting, merge fields, signers, footer, logo and the library images it places. Agreement Markdown uses {{field.key}} merge fields, a {{signatures}} line for the signature section and a \pagebreak line for a page break. ## documents - [`documents.download`](https://app.getoatmilk.com/docs/api/documents.download.md) — Get a short-lived private link to a kept record's unchanged original file. Pass inline: true to open it in the browser instead of downloading it. - [`documents.extract`](https://app.getoatmilk.com/docs/api/documents.extract.md) — Read a kept record's details again with the extraction model, keeping every field a person edited. Returns the record with extraction pending; poll documents.get for the result. - [`documents.get`](https://app.getoatmilk.com/docs/api/documents.get.md) — Read one kept record by id with its fields, what the rules and the model extracted, the classifier's result and the upload it came from. - [`documents.list`](https://app.getoatmilk.com/docs/api/documents.list.md) — List kept records: tax documents (filed returns and schedules, GST/HST returns, notices of assessment and reassessment, CRA letters, payment confirmations, T4, T4A and T5 slips and summaries), company documents, bank statement PDFs and other records. Filter by kind (tax_document, company_document, bank_statement, other, company, contractor for any record filed with a contractor, or all), contractorId (records filed with one contractor, such as their tax forms and IDs, and invoices they attached to a pay period in the portal), contractorPeriodId (the invoices attached to one pay period), taxYear and search. Each record has its extracted fields, extraction status, contractorId and contractorPeriodId; with kind contractor or contractorId, contractorNames maps each contractor to their name. - [`documents.update`](https://app.getoatmilk.com/docs/api/documents.update.md) — Correct a kept record with id, expectedRevision and idempotencyKey: title, subtype, taxYear, periodStart, periodEnd, documentDate, contractorId, archived, and fields such as form, filedOn, assessedOn, businessNumber, programAccount, balanceOwingMinor, refundMinor, dueDate and referenceNumber. Edited fields are never overwritten by a later extraction. ## documents.history - [`documents.history.get`](https://app.getoatmilk.com/docs/api/documents.history.get.md) — Rebuild one saved version of an agreement template from its history (id from documents.history.list, and firstId to compare with the version before that session): its name, text, footer, letterhead, logo and signers, and the same for the version before. - [`documents.history.list`](https://app.getoatmilk.com/docs/api/documents.history.list.md) — Read an agreement template's history, newest first: sessions of saves (who, when, through the writer, Ask AI, an AI app, the API, an accepted suggestion or a restore, and what changed: text, name, fields, signers, letterhead, footer or logo), publishing and archiving, and suggestions made, accepted or rejected. Pass before (the nextBefore it returned) for older history. - [`documents.history.restore`](https://app.getoatmilk.com/docs/api/documents.history.restore.md) — Restore a saved version of an agreement template as a new change (id from documents.history.list and an idempotencyKey): its text, name, letterhead, footer, logo and signers go back, checked and audited like any edit. Earlier versions stay in the history. ## documents.records - [`documents.records.get`](https://app.getoatmilk.com/docs/api/documents.records.get.md) — Read one record's details as labelled values to put into a document, such as a contractor's or customer's legal name, email, phone and address, a merchant's website, a teammate's name and role, or the company's legal name, address and business number. Pass type and id from documents.records.search (id is "company" for the company). - [`documents.records.search`](https://app.getoatmilk.com/docs/api/documents.records.search.md) — Find a record whose details can go into a document: contractors, customers and vendors (party), merchants, teammates (member) and the company itself. Filter by type and search by name or email. Returns each record's type, id, name and a short description; read its details with documents.records.get. ## documents.snippets - [`documents.snippets.create`](https://app.getoatmilk.com/docs/api/documents.snippets.create.md) — Save a block of Markdown (kind block) or a footer (kind footer, up to 500 characters and two lines) to reuse in agreements, with a title and an idempotencyKey. Insert a saved block into a template with documents.content.edit insert_text. - [`documents.snippets.list`](https://app.getoatmilk.com/docs/api/documents.snippets.list.md) — List saved blocks (reusable clauses and passages in Markdown, which may use {{field.key}} merge fields) and saved footers (up to two lines, which may use merge fields and {{page}} / {{pages}}). Filter by kind block or footer and search titles. - [`documents.snippets.update`](https://app.getoatmilk.com/docs/api/documents.snippets.update.md) — Change a saved block's or footer's title or text, or archive it, with id, expectedRevision and idempotencyKey. Templates that already used it keep their own copy. ## documents.suggestions - [`documents.suggestions.create`](https://app.getoatmilk.com/docs/api/documents.suggestions.create.md) — Suggest changes to an agreement template for a person to review instead of making them: the same operations as documents.content.edit, with a one-line summary of what they do and why, and an idempotencyKey. Nothing changes until a person accepts it in the agreement writer; they can reject it instead. The operations must apply to the template as it is now. Use this when the person asks for suggestions or reviews changes first. - [`documents.suggestions.decide`](https://app.getoatmilk.com/docs/api/documents.suggestions.decide.md) — Accept or reject a suggested change with id, expectedRevision, decision (accept or reject), an optional note and an idempotencyKey. Accepting applies the suggestion to the template as it is now, in the same step as the decision, and is recorded in the audit trail; one that no longer applies can only be rejected. Ask AI can't decide; the person does in the writer. - [`documents.suggestions.list`](https://app.getoatmilk.com/docs/api/documents.suggestions.list.md) — List suggested changes to an agreement template (open ones by default, or all with status all). Each open suggestion includes a preview of what accepting it would do to the template as it is now: the blocks before and after and a line per change, or why it no longer applies. ## signing.envelopes - [`signing.envelopes.certificate`](https://app.getoatmilk.com/docs/api/signing.envelopes.certificate.md) — Render the current certificate and audit trail for an envelope as a PDF (pdfBase64): recipients, verification and signing times, IP addresses, devices, document hashes, and consent text. - [`signing.envelopes.createDraft`](https://app.getoatmilk.com/docs/api/signing.envelopes.createDraft.md) — Create a draft envelope from a template (templateId or templateKey) or an uploaded file (fileId) with title, message, recipients (signer or cc, signerRole, order, userId for a company signer who signs in Oatmilk), mergeValues, signingOrder (parallel or sequential), deadline, and reminder interval. A template that states pay (like the contractor agreement) is made from a job: jobRoleId and jobRoleVersion (a version from contractorOps.roles.versions), and engagement: the pay as rate {amountMinor (minor units, as a string of digits), currency, unit: hour, day, week, month, year or fixed}, basis job (the version's pay: its currency and unit, within its band) or one_off (a rate for this agreement only, with an optional note), and startDate, endDate and noticeDays. The pay, its currency, the role and the dates are then filled in from these and can't be set through mergeValues. Nothing is sent. - [`signing.envelopes.defaults`](https://app.getoatmilk.com/docs/api/signing.envelopes.defaults.md) — Suggested merge values and recipients for a new document from the company profile, the requesting member, and an optional linked contractor or customer. For a contractor it also says what their agreement in effect sets (title, job role and rate) and whether an update to it is waiting (contractor). - [`signing.envelopes.download`](https://app.getoatmilk.com/docs/api/signing.envelopes.download.md) — Get a short-lived private download for an envelope document: the original upload, the signable PDF, or the completed PDF with its certificate of completion. Downloads are recorded in the audit trail. - [`signing.envelopes.get`](https://app.getoatmilk.com/docs/api/signing.envelopes.get.md) — Read one envelope with recipients and their signing status (including links locked after too many incorrect codes), documents (original, signable, completed) with SHA-256 hashes, the most recent audit events newest first (hasOlderEvents tells you to page back with signing.events.list), the email log, the actions available now, and for an agreement made from a job, the job version it used (job.version: what it said and paid then) and where that job is now (job.current: its current version and pay, and whether it's archived), with the agreement's pay in envelope.engagement. - [`signing.envelopes.importExecuted`](https://app.getoatmilk.com/docs/api/signing.envelopes.importExecuted.md) — Record an agreement signed outside Oatmilk: fileId from signing.files.confirm (or googleDocUrl), title, executedOn, parties, optional endsOn and linked record (for example subjectType contractor). The original is kept unchanged with its hash and the envelope is marked completed as the agreement on file. - [`signing.envelopes.keep`](https://app.getoatmilk.com/docs/api/signing.envelopes.keep.md) — Keep an envelope that is waiting on suggested changes as it is: every suggested change is declined with the reply (and optional replies per change), signing resumes where it was, and each person who suggested changes gets the reply with a fresh link. - [`signing.envelopes.list`](https://app.getoatmilk.com/docs/api/signing.envelopes.list.md) — List signature envelopes with their status, linked record, recipients' progress, and key dates. Filter by status (including open and attention), templateKey, subjectType and subjectId (for example contractor), or a title/recipient search. Each envelope says whether it is waiting for your own signature (awaitingMe) and, when it is, gives signUrl: the page where you sign it yourself. Pass awaitingMe: true to list only those. Signing only happens there; agents can't sign for you. An envelope made from a job gives the job's title and version and its pay as the agreement states it (pay, rateBasis job or one_off). counts: true also returns how many envelopes each status filter holds. - [`signing.envelopes.prepare`](https://app.getoatmilk.com/docs/api/signing.envelopes.prepare.md) — Prepare the exact document to be signed: render the template on letterhead, or convert an uploaded Word document, image, text file, or public Google Docs link. Uploaded PDF pages stay intact; senders place signature, initials, date, name, title, or email fields directly on the prepared pages before sending. Stores the PDF privately with its SHA-256 hash and field positions. - [`signing.envelopes.remind`](https://app.getoatmilk.com/docs/api/signing.envelopes.remind.md) — Email a reminder with a fresh link to signers who can sign now. Limited to one manual reminder per 24 hours; automatic reminders follow the envelope's reminder interval until the deadline. - [`signing.envelopes.resend`](https://app.getoatmilk.com/docs/api/signing.envelopes.resend.md) — Send a new link to one waiting signer, revoking earlier links and sessions and resetting their verification-code limits. This also unlocks a link that was locked after too many incorrect codes. Optionally correct the signer's name or email, or extend the deadline with expiresAt. - [`signing.envelopes.retryCompletion`](https://app.getoatmilk.com/docs/api/signing.envelopes.retryCompletion.md) — Retry finalizing an envelope whose completed PDF could not be prepared after 20 automatic attempts, with expectedRevision. Starts a fresh set of attempts right away; the envelope is completed and the completion emails are sent at most once. Recorded in the audit trail. - [`signing.envelopes.revise`](https://app.getoatmilk.com/docs/api/signing.envelopes.revise.md) — Answer the changes signers suggested to an envelope that is waiting on them by sending a revised version: decisions has, for each open request id, a decision per suggested change (accepted, rejected, or noted for a comment), optionally with the wording the sender accepts instead (text) and a reply. Accepted changes go into the agreement's text, a new envelope is prepared and sent to every signer again, and the old one is cancelled as replaced; signatures on it stay in its record. Optional reply to all signers and a new deadline (expiresAt). Requires at least one accepted change. - [`signing.envelopes.send`](https://app.getoatmilk.com/docs/api/signing.envelopes.send.md) — Send a prepared draft for signature. Each signer receives a private link by email (company signers are asked to sign in Oatmilk); copied recipients are notified. Sequential envelopes notify the next signers only when earlier signers finish. A document that states pay needs its structured pay first, and a job it names must still be in use. A contractor agreement sent to a linked contractor records its job, pay and dates as an update to their terms that takes effect once everyone signs; only one update can wait at a time, and a new title needs a title change first. - [`signing.envelopes.signAsMember`](https://app.getoatmilk.com/docs/api/signing.envelopes.signAsMember.md) — Sign as the assigned company signer from a signed-in Oatmilk session (dashboard only, never API keys or agents): consent, typed or drawn signature, name, and title are recorded with the document hash, time, IP address, and device. - [`signing.envelopes.updateDraft`](https://app.getoatmilk.com/docs/api/signing.envelopes.updateDraft.md) — Edit a draft envelope with expectedRevision, including uploaded-document field positions, and its job (jobRoleId with jobRoleVersion, or both null) and engagement (the structured pay and dates, or null). Changing document content, the job or the pay clears the prepared PDF and its field positions; field placement alone keeps the PDF. - [`signing.envelopes.void`](https://app.getoatmilk.com/docs/api/signing.envelopes.void.md) — Void a draft, an envelope that is still collecting signatures, an envelope whose finalizing stopped after its automatic attempts, or an imported executed agreement, with a reason. Signing links stop working and people who were notified receive a short notice. Records are kept. ## signing.events - [`signing.events.list`](https://app.getoatmilk.com/docs/api/signing.events.list.md) — List the audit events of one envelope: created, prepared, sent, email accepted/delivered, viewed, code sent, incorrect code, link locked or unlocked, verified, consented, signed, declined, reminded, resent, voided, expired, finalizing stopped or restarted, completed, downloaded. order oldest (default) pages forward with afterId; order newest pages back from the latest event with beforeId. Returns hasMore. ## signing.files - [`signing.files.confirm`](https://app.getoatmilk.com/docs/api/signing.files.confirm.md) — Confirm an uploaded original by fileId after verifying its size, SHA-256 hash, and file signature. Returns the verified fileId, filename, mimeType, sha256, and sizeBytes for use as an envelope source or executed agreement. - [`signing.files.fromGoogleDocs`](https://app.getoatmilk.com/docs/api/signing.files.fromGoogleDocs.md) — Import a Google Docs document by its docs.google.com link as a verified PDF original. Only documents shared publicly (anyone with the link) can be fetched; otherwise download the document as PDF or Word from Google Docs and upload it. - [`signing.files.prepare`](https://app.getoatmilk.com/docs/api/signing.files.prepare.md) — Prepare a private upload for an agreement original: filename, mimeType (PDF, Word .docx, PNG, JPEG, text, Markdown; legacy .doc, .odt and .rtf are kept as records only), sizeBytes up to 25 MB, sha256, idempotencyKey. Returns fileId and uploadUrl; if alreadyUploaded is true skip the upload and confirm. ## signing.subjects - [`signing.subjects.search`](https://app.getoatmilk.com/docs/api/signing.subjects.search.md) — Search contractors and customers to link to an agreement and prefill the other party's details. ## signing.templates - [`signing.templates.archive`](https://app.getoatmilk.com/docs/api/signing.templates.archive.md) — Archive (archived=true) or restore (archived=false) a template with expectedRevision. Archived templates are hidden from new documents but kept for records. - [`signing.templates.create`](https://app.getoatmilk.com/docs/api/signing.templates.create.md) — Create a custom agreement template from Markdown (body with {{field.key}} merge fields and an optional {{signatures}} block), fields, signerRoles, and letterhead. Set duplicateOf to copy an existing or starter template. It is published and ready to send unless publish is false, which keeps it a draft until signing.templates.publish. Requires an idempotency key. - [`signing.templates.get`](https://app.getoatmilk.com/docs/api/signing.templates.get.md) — Read one agreement template with its Markdown body, merge fields, signer roles, letterhead setting, and revision. - [`signing.templates.list`](https://app.getoatmilk.com/docs/api/signing.templates.list.md) — List agreement templates: the Contractor Agreement, Non-Disclosure Agreement, Signature Request, and Document with Header starters on company letterhead plus custom templates, with merge fields and signer roles. Set includeArchived to include archived templates. - [`signing.templates.preview`](https://app.getoatmilk.com/docs/api/signing.templates.preview.md) — Render a PDF preview of a template (templateId) or unsaved template text (body, fields, signerRoles) with merge values and recipients on the organization letterhead. Returns pdfBase64, the page count, and the labels of required fields that are still empty. Nothing is stored. - [`signing.templates.publish`](https://app.getoatmilk.com/docs/api/signing.templates.publish.md) — Publish a draft template with id and expectedRevision so it can be used to send agreements. Templates saved from the agreement writer stay drafts until someone publishes them, and drafts can't be sent. Publishing a template that is already published changes nothing. - [`signing.templates.update`](https://app.getoatmilk.com/docs/api/signing.templates.update.md) — Edit a custom template with expectedRevision. Starter templates are read-only; duplicate them to customize. Documents already sent keep the exact version they were sent with. ## company.applyRecord Save chosen facts of a kept company record into the company profile, replacing what is there, with documentId, fields (legalName, operatingName, businessNumber, corporationNumber, incorporationDate, jurisdiction, registration, gstHstAccount, gstHstEffectiveDate, gstHstFrequency, fiscalYearEnd, registeredOffice, directors, officers, shareholders), the profile's expectedRevision and idempotencyKey. Each saved fact notes the record as its source. `POST /api/v1/accounting/company.applyRecord` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_company_apply_record` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `documentId` | string (ID) | Yes | The ID of the related record. | | `fields` | array of enum values | Yes | One of: `legalName`, `operatingName`, `businessNumber`, `corporationNumber`, `incorporationDate`, `jurisdiction`, `entityCountry`, `formationJurisdiction`, `legalForm`, `usFederalTaxClassification`, `registration`, `gstHstAccount`, `gstHstEffectiveDate`, `gstHstFrequency`, `fiscalYearEnd`, `registeredOffice`, `directors`, `officers`, `shareholders`. 1–19 items. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/company.applyRecord \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3, "documentId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "fields": [ "legalName" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/company.applyRecord ## company.facts.apply Save the details a person chose from company.facts.review into the company profile, in one change: picks ([{ documentId, key }], one record per detail, from the options shown), checked (the ids of the records whose findings the person saw, so the ones they left aren't asked about again), expectedRevision (the review's revision) and idempotencyKey. Only call it after the person confirmed the values. Each saved detail notes its record as the source. A GST/HST number, registration date or filing frequency also marks the company as registered for GST/HST; a GST/HST number has to start with the business number. A changed profile is refused with CONFLICT: read the review again. Returns the saved profile and revision, applied and checked. Run accounting_compliance_refresh afterwards to rebuild the tax deadlines from the new details. `POST /api/v1/accounting/company.facts.apply` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_company_facts_apply` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `picks` | array of objects | Yes | at most 60 items. | | `picks[].documentId` | string (ID) | Yes | The ID of the related record. | | `picks[].key` | enum | Yes | One of: `legalName`, `operatingName`, `businessNumber`, `corporationNumber`, `incorporationDate`, `jurisdiction`, `entityCountry`, `formationJurisdiction`, `legalForm`, `usFederalTaxClassification`, `registration`, `gstHstAccount`, `gstHstEffectiveDate`, `gstHstFrequency`, `fiscalYearEnd`, `registeredOffice`, `directors`, `officers`, `shareholders`. | | `checked` | array of strings (ID) | | at most 50 items. Default `[]`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 1000000000. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–180 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/company.facts.apply \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "picks": [ { "documentId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "key": "legalName" } ], "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/company.facts.apply ## company.facts.review Read what the company's own records say about the company, for a person to check before anything is saved. Oatmilk reads every company record (articles, certificates, registry and annual returns, CRA business number and GST/HST registration letters) and every CRA tax record that prints a GST/HST (RT) account, and never saves what it found on its own. Input: optional documentIds to look at only those records (for example the files just uploaded); otherwise the newest 50 company and tax records. Returns revision (the company profile's, for company.facts.apply), documents (each record's id, title, filename, kind, createdAt, status: reading, to_check, nothing_new, other_company, unreadable or failed, and details, how many it has to check) and fields: one per profile detail with slot, key (legalName, operatingName, businessNumber, corporationNumber, incorporationDate, jurisdiction, entityCountry, formationJurisdiction, legalForm, usFederalTaxClassification, registration, gstHstAccount, gstHstEffectiveDate, gstHstFrequency, fiscalYearEnd, registeredOffice, directors, officers or shareholders), label, current (what the profile has now, or null), kind (fill when the profile is empty, conflict when it differs) and options (each value with the records that say it; two options mean the records disagree). pending is how many details wait to be checked. A finding a person already checked and left isn't listed again until the record says something new. Show the fields to the person and let them choose before calling company.facts.apply. `GET | POST /api/v1/accounting/company.facts.review` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_company_facts_review` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `documentIds` | array of strings (ID) | | A list of record IDs. 1–50 items. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/company.facts.review \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/company.facts.review ## company.recordChanges Read what a kept company record (documentId of a company_document: articles, a certificate, an Ownr or registry package, an annual return) says about the company compared with the company profile: describesCompany (true when its name or corporation or business number matches, null when the profile has none yet) and changes, each { key, label, current, value, kind }: fill for an empty profile field, conflict for a different value. Oatmilk never saves them on its own: an admin chooses which to use (company.applyRecord, or company.facts.review and company.facts.apply for every record at once). `GET | POST /api/v1/accounting/company.recordChanges` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_company_record_changes` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `documentId` | string (ID) | Yes | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/company.recordChanges \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'documentId=1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d' ``` Reference page: https://app.getoatmilk.com/docs/api/company.recordChanges ## documents.assets.confirm Finish an image upload: checks the uploaded bytes against the declared type and SHA-256 and that the image can be printed, records its size, and returns it with a private link that shows it for about an hour. `POST /api/v1/accounting/documents.assets.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_documents_assets_confirm` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.assets.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.assets.confirm ## documents.assets.list List the images kept in the agreement library (logos, diagrams, stamps) with their titles, sizes and private links that show them for about an hour. Pass ids to get links for specific images, such as the ones a template places with asset:. `GET | POST /api/v1/accounting/documents.assets.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_documents_assets_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `ids` | array of strings (ID) | | 1–100 items. | | `search` | string | | Text to search for. at most 120 characters. | | `includeArchived` | boolean | | Also include archived records. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.assets.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/documents.assets.list ## documents.assets.prepare Start uploading an image for agreements, such as a logo, a diagram or a signature stamp: PNG or JPEG up to 10 MB and 6,000 pixels on a side, with its SHA-256. Pass saved: true to keep it in the library to reuse. Returns the image and, unless the organization already has this exact image, a private upload link: PUT the bytes there with their content type, then call documents.assets.confirm. Place a confirmed image in a template with a line ![Alt text](asset:?width=50&align=center); width is 10–100 percent of the text width (default 100) and align is left, center or right (default center). `POST /api/v1/accounting/documents.assets.prepare` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_documents_assets_prepare` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | string | Yes | The file's name, including its extension. 1–200 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `image/png`, `image/jpeg`. | | `sizeBytes` | integer | Yes | The file's size in bytes. 1 to 10485760. | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `title` | string | | A short title. at most 120 characters. | | `saved` | boolean | | Default `false`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.assets.prepare \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "filename": "receipt.png", "mimeType": "image/png", "sizeBytes": 1, "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.assets.prepare ## documents.assets.update Rename an image, keep it in or take it out of the library (saved), or archive it, with id, expectedRevision and idempotencyKey. Archived images still print where they are already placed. `POST /api/v1/accounting/documents.assets.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_documents_assets_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `title` | string | | A short title. at most 120 characters. | | `saved` | boolean | | | | `archived` | boolean | | Whether the record is archived. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.assets.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "title": "Synthetic services agreement" }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.assets.update ## documents.comments.create Add an auditable comment thread to an agreement template. A text selection may be anchored; mention IDs must belong to active members of this organization. Requires an idempotency key. `POST /api/v1/accounting/documents.comments.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_documents_comments_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `documentType` | "agreement_template" | Yes | | | `documentId` | string (ID) | Yes | The ID of the related record. | | `anchor` | object or null | Yes | | | `anchor.from` | integer | Yes | The first date to include, as YYYY-MM-DD. 0 to 1000000. | | `anchor.to` | integer | Yes | The last date to include, as YYYY-MM-DD. 0 to 1000000. | | `anchor.quote` | string | Yes | at most 2000 characters. | | `anchor.before` | string | | at most 100 characters. Default `""`. | | `anchor.after` | string | | at most 100 characters. Default `""`. | | `body` | string | Yes | 1–10000 characters. | | `mentions` | array of strings | | at most 20 items; each 1–200 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.comments.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "documentType": "agreement_template", "documentId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "anchor": { "from": 1, "to": 1, "quote": "USD" }, "body": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.comments.create ## documents.comments.list Read comment threads and replies for an agreement template in this organization, including resolved threads and their anchored text. `GET | POST /api/v1/accounting/documents.comments.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_documents_comments_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `documentType` | "agreement_template" | Yes | | | `documentId` | string (ID) | Yes | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/documents.comments.list \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'documentType=agreement_template' \ --data-urlencode 'documentId=1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.comments.list ## documents.comments.members List active finance and administrator members in this organization who can be tagged in document comments. `GET | POST /api/v1/accounting/documents.comments.members` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_documents_comments_members` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.comments.members \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/documents.comments.members ## documents.comments.reply Add an auditable reply to an open agreement-template comment thread. Participants and newly mentioned active organization members receive in-app notifications. Requires an idempotency key. `POST /api/v1/accounting/documents.comments.reply` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_documents_comments_reply` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `threadId` | string (ID) | Yes | The ID of the related record. | | `body` | string | Yes | 1–10000 characters. | | `mentions` | array of strings | | at most 20 items; each 1–200 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.comments.reply \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "threadId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "body": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.comments.reply ## documents.comments.resolve Resolve or reopen an agreement-template comment thread with its expected revision. The change is recorded in the audit trail. Requires an idempotency key. `POST /api/v1/accounting/documents.comments.resolve` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_documents_comments_resolve` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `threadId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `resolved` | boolean | Yes | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.comments.resolve \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "threadId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedRevision": 3, "resolved": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.comments.resolve ## documents.content.edit Edit an agreement template or contractor template in place with 1 to 50 operations applied together, all or nothing: replace_text, delete_text, insert_text (Markdown at the start or end, or before or after the block holding anchor text), place_signature_section (moves it if already placed), insert_page_break, replace_document, set_title, set_description, set_letterhead, set_field (label, required, default), remove_field, set_signers, and for agreements set_footer (up to two lines with merge fields and {{page}} / {{pages}}; null for the default footer), set_logo (a library image, or null for the company logo), insert_image (a library image with alt, width 10–100 percent and align), insert_saved_block (a saved block from documents.snippets.list) and insert_record_details (a record's details from documents.records.search as plain text: lines, values or table, optionally only some fields by key). Text is found by quoting it exactly, though spacing may differ; quote enough to be unique or pass occurrence. Pass expectedRevision to refuse if it changed since you read it; without it, quoted edits apply to the latest version. Anyone with it open in the agreement writer sees the change at once. Starter templates and uploaded files are read-only. Returns the new revision and a line per change. `POST /api/v1/accounting/documents.content.edit` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_documents_content_edit` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `documentType` | enum | Yes | One of: `agreement_template`, `contractor_template`. | | `documentId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `operations` | array of objects | Yes | 1–50 items. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.content.edit \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "documentType": "agreement_template", "documentId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "operations": [ { "type": "replace_text", "find": "example", "replace": "example" } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.content.edit ## documents.content.get Read a document's text to review or edit it: an agreement template written in the agreement writer (documentType agreement_template) or a contractor agreement template written in Oatmilk (contractor_template). Returns the title, revision, status (draft, published, archived, starter), the Markdown text in pages of up to 20,000 characters (offset and nextOffset), its headings, and for agreements the letterhead setting, merge fields, signers, footer, logo and the library images it places. Agreement Markdown uses {{field.key}} merge fields, a {{signatures}} line for the signature section and a \pagebreak line for a page break. `GET | POST /api/v1/accounting/documents.content.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_documents_content_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `documentType` | enum | Yes | One of: `agreement_template`, `contractor_template`. | | `documentId` | string (ID) | Yes | The ID of the related record. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 500000. Default `0`. | | `limit` | integer | | How many results to return at most. 500 to 20000. Default `20000`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/documents.content.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'documentType=agreement_template' \ --data-urlencode 'documentId=1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.content.get ## documents.download Get a short-lived private link to a kept record's unchanged original file. Pass inline: true to open it in the browser instead of downloading it. `GET | POST /api/v1/accounting/documents.download` Permissions: `accounting:read` · Roles: admin, finance Not available over MCP: Accountants' supporting originals grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `inline` | boolean | | | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/documents.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.download ## documents.extract Read a kept record's details again with the extraction model, keeping every field a person edited. Returns the record with extraction pending; poll documents.get for the result. `POST /api/v1/accounting/documents.extract` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_documents_extract` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.extract \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.extract ## documents.get Read one kept record by id with its fields, what the rules and the model extracted, the classifier's result and the upload it came from. `GET | POST /api/v1/accounting/documents.get` Permissions: `accounting:read` · Roles: admin, finance Not available over MCP: Accountants' supporting originals grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/documents.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.get ## documents.history.get Rebuild one saved version of an agreement template from its history (id from documents.history.list, and firstId to compare with the version before that session): its name, text, footer, letterhead, logo and signers, and the same for the version before. `GET | POST /api/v1/accounting/documents.history.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_documents_history_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `documentType` | "agreement_template" | Yes | | | `documentId` | string (ID) | Yes | The ID of the related record. | | `id` | string (ID) | Yes | The record's ID. | | `firstId` | string (ID) | | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/documents.history.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'documentType=agreement_template' \ --data-urlencode 'documentId=1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d' \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.history.get ## documents.history.list Read an agreement template's history, newest first: sessions of saves (who, when, through the writer, Ask AI, an AI app, the API, an accepted suggestion or a restore, and what changed: text, name, fields, signers, letterhead, footer or logo), publishing and archiving, and suggestions made, accepted or rejected. Pass before (the nextBefore it returned) for older history. `GET | POST /api/v1/accounting/documents.history.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_documents_history_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `documentType` | "agreement_template" | Yes | | | `documentId` | string (ID) | Yes | The ID of the related record. | | `before` | string (date-time) | | | | `limit` | integer | | How many results to return at most. 1 to 200. Default `100`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/documents.history.list \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'documentType=agreement_template' \ --data-urlencode 'documentId=1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.history.list ## documents.history.restore Restore a saved version of an agreement template as a new change (id from documents.history.list and an idempotencyKey): its text, name, letterhead, footer, logo and signers go back, checked and audited like any edit. Earlier versions stay in the history. `POST /api/v1/accounting/documents.history.restore` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_documents_history_restore` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `documentType` | "agreement_template" | Yes | | | `documentId` | string (ID) | Yes | The ID of the related record. | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.history.restore \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "documentType": "agreement_template", "documentId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.history.restore ## documents.list List kept records: tax documents (filed returns and schedules, GST/HST returns, notices of assessment and reassessment, CRA letters, payment confirmations, T4, T4A and T5 slips and summaries), company documents, bank statement PDFs and other records. Filter by kind (tax_document, company_document, bank_statement, other, company, contractor for any record filed with a contractor, or all), contractorId (records filed with one contractor, such as their tax forms and IDs, and invoices they attached to a pay period in the portal), contractorPeriodId (the invoices attached to one pay period), taxYear and search. Each record has its extracted fields, extraction status, contractorId and contractorPeriodId; with kind contractor or contractorId, contractorNames maps each contractor to their name. `GET | POST /api/v1/accounting/documents.list` Permissions: `accounting:read` · Roles: admin, finance Not available over MCP: Accountants' supporting originals grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | enum | | Which kind of record or job this is. One of: `tax_document`, `company_document`, `bank_statement`, `other`, `all`, `company`, `contractor`. Default `"all"`. | | `taxYear` | integer | | 1990 to 2100. | | `search` | string | | Text to search for. at most 200 characters. | | `includeArchived` | boolean | | Also include archived records. | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `contractorPeriodId` | string (ID) | | The ID of the related record. | | `limit` | integer | | How many results to return at most. 1 to 200. Default `100`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/documents.list ## documents.records.get Read one record's details as labelled values to put into a document, such as a contractor's or customer's legal name, email, phone and address, a merchant's website, a teammate's name and role, or the company's legal name, address and business number. Pass type and id from documents.records.search (id is "company" for the company). `GET | POST /api/v1/accounting/documents.records.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_documents_records_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `type` | enum | Yes | One of: `contractor`, `party`, `merchant`, `member`, `company`. | | `id` | string | Yes | The record's ID. 1–200 characters. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/documents.records.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'type=contractor' \ --data-urlencode 'id=example' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.records.get ## documents.records.search Find a record whose details can go into a document: contractors, customers and vendors (party), merchants, teammates (member) and the company itself. Filter by type and search by name or email. Returns each record's type, id, name and a short description; read its details with documents.records.get. `GET | POST /api/v1/accounting/documents.records.search` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_documents_records_search` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `type` | enum | | One of: `contractor`, `party`, `merchant`, `member`, `company`, `all`. Default `"all"`. | | `query` | string | | Text to search for. at most 200 characters. Default `""`. | | `limit` | integer | | How many results to return at most. 1 to 25. Default `10`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.records.search \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/documents.records.search ## documents.snippets.create Save a block of Markdown (kind block) or a footer (kind footer, up to 500 characters and two lines) to reuse in agreements, with a title and an idempotencyKey. Insert a saved block into a template with documents.content.edit insert_text. `POST /api/v1/accounting/documents.snippets.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_documents_snippets_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | enum | Yes | Which kind of record or job this is. One of: `block`, `footer`. | | `title` | string | Yes | A short title. 1–120 characters. | | `body` | string | Yes | 1–20000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.snippets.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "kind": "block", "title": "Synthetic services agreement", "body": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.snippets.create ## documents.snippets.list List saved blocks (reusable clauses and passages in Markdown, which may use {{field.key}} merge fields) and saved footers (up to two lines, which may use merge fields and {{page}} / {{pages}}). Filter by kind block or footer and search titles. `GET | POST /api/v1/accounting/documents.snippets.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_documents_snippets_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | enum | | Which kind of record or job this is. One of: `block`, `footer`, `all`. Default `"all"`. | | `search` | string | | Text to search for. at most 120 characters. | | `includeArchived` | boolean | | Also include archived records. | | `limit` | integer | | How many results to return at most. 1 to 200. Default `100`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.snippets.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/documents.snippets.list ## documents.snippets.update Change a saved block's or footer's title or text, or archive it, with id, expectedRevision and idempotencyKey. Templates that already used it keep their own copy. `POST /api/v1/accounting/documents.snippets.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_documents_snippets_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `title` | string | | A short title. 1–120 characters. | | `body` | string | | 1–20000 characters. | | `archived` | boolean | | Whether the record is archived. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.snippets.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "title": "Synthetic services agreement" }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.snippets.update ## documents.suggestions.create Suggest changes to an agreement template for a person to review instead of making them: the same operations as documents.content.edit, with a one-line summary of what they do and why, and an idempotencyKey. Nothing changes until a person accepts it in the agreement writer; they can reject it instead. The operations must apply to the template as it is now. Use this when the person asks for suggestions or reviews changes first. `POST /api/v1/accounting/documents.suggestions.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_documents_suggestions_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `documentType` | "agreement_template" | Yes | | | `documentId` | string (ID) | Yes | The ID of the related record. | | `summary` | string | Yes | 1–500 characters. | | `operations` | array of objects | Yes | 1–50 items. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.suggestions.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "documentType": "agreement_template", "documentId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "summary": "Synthetic example from the docs", "operations": [ { "type": "replace_text", "find": "example", "replace": "example" } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.suggestions.create ## documents.suggestions.decide Accept or reject a suggested change with id, expectedRevision, decision (accept or reject), an optional note and an idempotencyKey. Accepting applies the suggestion to the template as it is now, in the same step as the decision, and is recorded in the audit trail; one that no longer applies can only be rejected. Ask AI can't decide; the person does in the writer. `POST /api/v1/accounting/documents.suggestions.decide` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_documents_suggestions_decide` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `decision` | enum | Yes | What you decided. One of: `accept`, `reject`. | | `note` | string | | A short note, kept with the record. at most 1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.suggestions.decide \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "decision": "accept" }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.suggestions.decide ## documents.suggestions.list List suggested changes to an agreement template (open ones by default, or all with status all). Each open suggestion includes a preview of what accepting it would do to the template as it is now: the blocks before and after and a line per change, or why it no longer applies. `GET | POST /api/v1/accounting/documents.suggestions.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_documents_suggestions_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `documentType` | "agreement_template" | Yes | | | `documentId` | string (ID) | Yes | The ID of the related record. | | `status` | enum | | Only include records with this status. One of: `open`, `all`. Default `"open"`. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/documents.suggestions.list \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'documentType=agreement_template' \ --data-urlencode 'documentId=1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.suggestions.list ## documents.update Correct a kept record with id, expectedRevision and idempotencyKey: title, subtype, taxYear, periodStart, periodEnd, documentDate, contractorId, archived, and fields such as form, filedOn, assessedOn, businessNumber, programAccount, balanceOwingMinor, refundMinor, dueDate and referenceNumber. Edited fields are never overwritten by a later extraction. `POST /api/v1/accounting/documents.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_documents_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `title` | string | | A short title. 1–200 characters. | | `subtype` | string or null | | | | `taxYear` | integer or null | | | | `periodStart` | string (date) or null | | | | `periodEnd` | string (date) or null | | | | `documentDate` | string (date) or null | | | | `contractorId` | string (ID) or null | | The ID of a contractor, from contractors.list. | | `fields` | map | | | | `archived` | boolean | | Whether the record is archived. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/documents.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/documents.update ## signing.envelopes.certificate Render the current certificate and audit trail for an envelope as a PDF (pdfBase64): recipients, verification and signing times, IP addresses, devices, document hashes, and consent text. `GET | POST /api/v1/accounting/signing.envelopes.certificate` Permissions: `accounting:read` · Roles: admin, finance Not available over MCP: Accountants' supporting originals grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.certificate \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.certificate ## signing.envelopes.createDraft Create a draft envelope from a template (templateId or templateKey) or an uploaded file (fileId) with title, message, recipients (signer or cc, signerRole, order, userId for a company signer who signs in Oatmilk), mergeValues, signingOrder (parallel or sequential), deadline, and reminder interval. A template that states pay (like the contractor agreement) is made from a job: jobRoleId and jobRoleVersion (a version from contractorOps.roles.versions), and engagement: the pay as rate {amountMinor (minor units, as a string of digits), currency, unit: hour, day, week, month, year or fixed}, basis job (the version's pay: its currency and unit, within its band) or one_off (a rate for this agreement only, with an optional note), and startDate, endDate and noticeDays. The pay, its currency, the role and the dates are then filled in from these and can't be set through mergeValues. Nothing is sent. `POST /api/v1/accounting/signing.envelopes.createDraft` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_signing_envelopes_create_draft` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `title` | string | Yes | A short title. 1–200 characters. | | `message` | string | | at most 4000 characters. Default `""`. | | `templateId` | string (ID) | | The ID of the related record. | | `templateKey` | string | | Matches ^[a-z][a-z0-9-]{1,60}$. | | `fileId` | string (ID) | | The ID of the related record. | | `subjectType` | enum | | The kind of record this is about, such as entry, invoice or mail. One of: `contractor`, `party`, `invoice`. | | `subjectId` | string | | The ID of the record this is about. 1–200 characters. | | `mergeValues` | map | | Default `{}`. | | `signingOrder` | enum | | One of: `sequential`, `parallel`. Default `"parallel"`. | | `recipients` | array of objects | | at most 20 items. | | `recipients[].kind` | enum | Yes | Which kind of record or job this is. One of: `signer`, `cc`. | | `recipients[].signerRole` | string or null | | | | `recipients[].name` | string | Yes | A display name. 1–200 characters. | | `recipients[].email` | string (email) | Yes | An email address. at most 320 characters. | | `recipients[].title` | string or null | | A short title. | | `recipients[].company` | string or null | | | | `recipients[].order` | integer | | 1 to 20. | | `recipients[].userId` | string or null | | The ID of a person in your company. | | `fields` | array of objects | | at most 500 items. Default `[]`. | | `fields[].role` | string | Yes | A person's access level in the company. Matches ^[a-z][a-z0-9_]{0,40}$. | | `fields[].kind` | enum | Yes | Which kind of record or job this is. One of: `signature`, `initials`, `name`, `title`, `date`, `email`. | | `fields[].page` | integer | Yes | 0 to 499. | | `fields[].x` | number | Yes | 0 to 5000. | | `fields[].y` | number | Yes | 0 to 5000. | | `fields[].width` | number | Yes | at most 5000; greater than 0. | | `fields[].height` | number | Yes | at most 5000; greater than 0. | | `expiresAt` | string (date-time) | | When this stops working, as an ISO 8601 date and time. | | `expiresInDays` | integer | | 1 to 365. | | `reminderIntervalDays` | integer | | 0 to 30. Default `3`. | | `allowChanges` | boolean | | | | `jobRoleId` | string (ID) | | The ID of the related record. | | `jobRoleVersion` | integer | | 1 to 100000. | | `engagement` | object | | No other fields. | | `engagement.rate` | object | Yes | No other fields. | | `engagement.rate.amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^[1-9]\d{0,14}$. | | `engagement.rate.currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `engagement.rate.unit` | enum | Yes | One of: `hour`, `day`, `week`, `month`, `year`, `fixed`. | | `engagement.basis` | enum | Yes | One of: `job`, `one_off`. | | `engagement.note` | string or null | | A short note, kept with the record. | | `engagement.startDate` | string (date) or null | | | | `engagement.endDate` | string (date) or null | | | | `engagement.noticeDays` | integer or null | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.createDraft \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "title": "Synthetic services agreement" }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.createDraft ## signing.envelopes.defaults Suggested merge values and recipients for a new document from the company profile, the requesting member, and an optional linked contractor or customer. For a contractor it also says what their agreement in effect sets (title, job role and rate) and whether an update to it is waiting (contractor). `GET | POST /api/v1/accounting/signing.envelopes.defaults` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_signing_envelopes_defaults` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `templateId` | string (ID) | | The ID of the related record. | | `subjectType` | enum | | The kind of record this is about, such as entry, invoice or mail. One of: `contractor`, `party`, `invoice`. | | `subjectId` | string | | The ID of the record this is about. 1–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.defaults \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.defaults ## signing.envelopes.download Get a short-lived private download for an envelope document: the original upload, the signable PDF, or the completed PDF with its certificate of completion. Downloads are recorded in the audit trail. `GET | POST /api/v1/accounting/signing.envelopes.download` Permissions: `accounting:read` · Roles: admin, finance Not available over MCP: Accountants' supporting originals grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `document` | enum | | One of: `original`, `signable`, `completed`. Default `"completed"`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.download ## signing.envelopes.get Read one envelope with recipients and their signing status (including links locked after too many incorrect codes), documents (original, signable, completed) with SHA-256 hashes, the most recent audit events newest first (hasOlderEvents tells you to page back with signing.events.list), the email log, the actions available now, and for an agreement made from a job, the job version it used (job.version: what it said and paid then) and where that job is now (job.current: its current version and pay, and whether it's archived), with the agreement's pay in envelope.engagement. `GET | POST /api/v1/accounting/signing.envelopes.get` Permissions: `accounting:read` · Roles: admin, finance Not available over MCP: Accountants' supporting originals grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.get ## signing.envelopes.importExecuted Record an agreement signed outside Oatmilk: fileId from signing.files.confirm (or googleDocUrl), title, executedOn, parties, optional endsOn and linked record (for example subjectType contractor). The original is kept unchanged with its hash and the envelope is marked completed as the agreement on file. `POST /api/v1/accounting/signing.envelopes.importExecuted` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_signing_envelopes_import_executed` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `title` | string | Yes | A short title. 1–200 characters. | | `fileId` | string (ID) | | The ID of the related record. | | `googleDocUrl` | string (uri) | | at most 2000 characters. | | `executedOn` | string (date) | Yes | A date, as YYYY-MM-DD. | | `endsOn` | string (date) | | A date, as YYYY-MM-DD. | | `parties` | array of objects | Yes | 1–10 items. | | `parties[].name` | string | Yes | A display name. 1–200 characters. | | `parties[].email` | string (email) or null | | An email address. | | `parties[].title` | string or null | | A short title. | | `parties[].company` | string or null | | | | `subjectType` | enum | | The kind of record this is about, such as entry, invoice or mail. One of: `contractor`, `party`, `invoice`. | | `subjectId` | string | | The ID of the record this is about. 1–200 characters. | | `notes` | string | | Notes kept with the record. at most 4000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.importExecuted \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "title": "Synthetic services agreement", "executedOn": "2026-09-01", "parties": [ { "name": "Synthetic Ventures Inc." } ], "fileId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.importExecuted ## signing.envelopes.keep Keep an envelope that is waiting on suggested changes as it is: every suggested change is declined with the reply (and optional replies per change), signing resumes where it was, and each person who suggested changes gets the reply with a fresh link. `POST /api/v1/accounting/signing.envelopes.keep` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_signing_envelopes_keep` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `requestIds` | array of strings (ID) | Yes | A list of record IDs. 1–20 items. | | `reply` | string | Yes | 3–2000 characters. | | `replies` | map | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.keep \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "requestIds": [ "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" ], "reply": "example" }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.keep ## signing.envelopes.list List signature envelopes with their status, linked record, recipients' progress, and key dates. Filter by status (including open and attention), templateKey, subjectType and subjectId (for example contractor), or a title/recipient search. Each envelope says whether it is waiting for your own signature (awaitingMe) and, when it is, gives signUrl: the page where you sign it yourself. Pass awaitingMe: true to list only those. Signing only happens there; agents can't sign for you. An envelope made from a job gives the job's title and version and its pay as the agreement states it (pay, rateBasis job or one_off). counts: true also returns how many envelopes each status filter holds. `GET | POST /api/v1/accounting/signing.envelopes.list` Permissions: `accounting:read` · Roles: admin, finance Not available over MCP: Accountants' supporting originals grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | enum | | Only include records with this status. One of: `draft`, `sent`, `in_progress`, `changes_requested`, `completing`, `completed`, `declined`, `voided`, `expired`, `open`, `attention`. | | `templateKey` | string | | Matches ^[a-z][a-z0-9-]{1,60}$. | | `subjectType` | enum | | The kind of record this is about, such as entry, invoice or mail. One of: `contractor`, `party`, `invoice`. | | `subjectId` | string | | The ID of the record this is about. 1–200 characters. | | `query` | string | | Text to search for. at most 200 characters. | | `awaitingMe` | boolean | | | | `counts` | boolean | | | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.list ## signing.envelopes.prepare Prepare the exact document to be signed: render the template on letterhead, or convert an uploaded Word document, image, text file, or public Google Docs link. Uploaded PDF pages stay intact; senders place signature, initials, date, name, title, or email fields directly on the prepared pages before sending. Stores the PDF privately with its SHA-256 hash and field positions. `POST /api/v1/accounting/signing.envelopes.prepare` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_signing_envelopes_prepare` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `fileId` | string (ID) | | The ID of the related record. | | `googleDocUrl` | string (uri) | | at most 2000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.prepare \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.prepare ## signing.envelopes.remind Email a reminder with a fresh link to signers who can sign now. Limited to one manual reminder per 24 hours; automatic reminders follow the envelope's reminder interval until the deadline. `POST /api/v1/accounting/signing.envelopes.remind` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_signing_envelopes_remind` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `recipientId` | string (ID) | | The ID of one signer on a document. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.remind \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.remind ## signing.envelopes.resend Send a new link to one waiting signer, revoking earlier links and sessions and resetting their verification-code limits. This also unlocks a link that was locked after too many incorrect codes. Optionally correct the signer's name or email, or extend the deadline with expiresAt. `POST /api/v1/accounting/signing.envelopes.resend` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_signing_envelopes_resend` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `recipientId` | string (ID) | Yes | The ID of one signer on a document. | | `name` | string | | A display name. 1–200 characters. | | `email` | string (email) | | An email address. at most 320 characters. | | `expiresAt` | string (date-time) | | When this stops working, as an ISO 8601 date and time. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.resend \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "recipientId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.resend ## signing.envelopes.retryCompletion Retry finalizing an envelope whose completed PDF could not be prepared after 20 automatic attempts, with expectedRevision. Starts a fresh set of attempts right away; the envelope is completed and the completion emails are sent at most once. Recorded in the audit trail. `POST /api/v1/accounting/signing.envelopes.retryCompletion` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_signing_envelopes_retry_completion` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.retryCompletion \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.retryCompletion ## signing.envelopes.revise Answer the changes signers suggested to an envelope that is waiting on them by sending a revised version: decisions has, for each open request id, a decision per suggested change (accepted, rejected, or noted for a comment), optionally with the wording the sender accepts instead (text) and a reply. Accepted changes go into the agreement's text, a new envelope is prepared and sent to every signer again, and the old one is cancelled as replaced; signatures on it stay in its record. Optional reply to all signers and a new deadline (expiresAt). Requires at least one accepted change. `POST /api/v1/accounting/signing.envelopes.revise` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_signing_envelopes_revise` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `decisions` | map | Yes | | | `reply` | string | | at most 2000 characters. | | `expiresAt` | string (date-time) | | When this stops working, as an ISO 8601 date and time. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.revise \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "decisions": {} }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.revise ## signing.envelopes.send Send a prepared draft for signature. Each signer receives a private link by email (company signers are asked to sign in Oatmilk); copied recipients are notified. Sequential envelopes notify the next signers only when earlier signers finish. A document that states pay needs its structured pay first, and a job it names must still be in use. A contractor agreement sent to a linked contractor records its job, pay and dates as an update to their terms that takes effect once everyone signs; only one update can wait at a time, and a new title needs a title change first. `POST /api/v1/accounting/signing.envelopes.send` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_signing_envelopes_send` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.send \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.send ## signing.envelopes.signAsMember Sign as the assigned company signer from a signed-in Oatmilk session (dashboard only, never API keys or agents): consent, typed or drawn signature, name, and title are recorded with the document hash, time, IP address, and device. `POST /api/v1/accounting/signing.envelopes.signAsMember` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required Not available over MCP: A person's own e-signature, with their signed-in session. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `recipientId` | string (ID) | Yes | The ID of one signer on a document. | | `documentSha256` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `consent` | true | Yes | | | `consentVersion` | string | Yes | Matches ^[0-9a-z.-]{1,40}$. | | `signature` | object | Yes | | | `signature.method` | "typed" | Yes | | | `signature.text` | string | Yes | 1–100 characters. | | `signature.font` | string | | at most 60 characters. | | `signature.method` | "drawn" | Yes | | | `signature.image` | string | Yes | 40–400000 characters; Matches ^data:image\/png;base64,[A-Za-z0-9+/]+=*$. | | `signedName` | string | Yes | 1–200 characters. | | `signedTitle` | string | | at most 200 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.signAsMember \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "recipientId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "documentSha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "consent": true, "consentVersion": "example", "signature": { "method": "typed", "text": "example" }, "signedName": "Synthetic Ventures Inc." }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.signAsMember ## signing.envelopes.updateDraft Edit a draft envelope with expectedRevision, including uploaded-document field positions, and its job (jobRoleId with jobRoleVersion, or both null) and engagement (the structured pay and dates, or null). Changing document content, the job or the pay clears the prepared PDF and its field positions; field placement alone keeps the PDF. `POST /api/v1/accounting/signing.envelopes.updateDraft` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_signing_envelopes_update_draft` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `title` | string | | A short title. 1–200 characters. | | `message` | string | | at most 4000 characters. | | `templateId` | string (ID) or null | | | | `fileId` | string (ID) | | The ID of the related record. | | `subjectType` | enum or null | | The kind of record this is about, such as entry, invoice or mail. One of: `contractor`, `party`, `invoice`. | | `subjectId` | string or null | | The ID of the record this is about. | | `mergeValues` | map | | | | `signingOrder` | enum | | One of: `sequential`, `parallel`. | | `recipients` | array of objects | | at most 20 items. | | `recipients[].kind` | enum | Yes | Which kind of record or job this is. One of: `signer`, `cc`. | | `recipients[].signerRole` | string or null | | | | `recipients[].name` | string | Yes | A display name. 1–200 characters. | | `recipients[].email` | string (email) | Yes | An email address. at most 320 characters. | | `recipients[].title` | string or null | | A short title. | | `recipients[].company` | string or null | | | | `recipients[].order` | integer | | 1 to 20. | | `recipients[].userId` | string or null | | The ID of a person in your company. | | `fields` | array of objects | | at most 500 items. | | `fields[].role` | string | Yes | A person's access level in the company. Matches ^[a-z][a-z0-9_]{0,40}$. | | `fields[].kind` | enum | Yes | Which kind of record or job this is. One of: `signature`, `initials`, `name`, `title`, `date`, `email`. | | `fields[].page` | integer | Yes | 0 to 499. | | `fields[].x` | number | Yes | 0 to 5000. | | `fields[].y` | number | Yes | 0 to 5000. | | `fields[].width` | number | Yes | at most 5000; greater than 0. | | `fields[].height` | number | Yes | at most 5000; greater than 0. | | `expiresAt` | string (date-time) | | When this stops working, as an ISO 8601 date and time. | | `expiresInDays` | integer | | 1 to 365. | | `reminderIntervalDays` | integer | | 0 to 30. | | `allowChanges` | boolean | | | | `jobRoleId` | string (ID) or null | | | | `jobRoleVersion` | integer or null | | | | `engagement` | object or null | | | | `engagement.rate` | object | Yes | No other fields. | | `engagement.rate.amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^[1-9]\d{0,14}$. | | `engagement.rate.currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `engagement.rate.unit` | enum | Yes | One of: `hour`, `day`, `week`, `month`, `year`, `fixed`. | | `engagement.basis` | enum | Yes | One of: `job`, `one_off`. | | `engagement.note` | string or null | | A short note, kept with the record. | | `engagement.startDate` | string (date) or null | | | | `engagement.endDate` | string (date) or null | | | | `engagement.noticeDays` | integer or null | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.updateDraft \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.updateDraft ## signing.envelopes.void Void a draft, an envelope that is still collecting signatures, an envelope whose finalizing stopped after its automatic attempts, or an imported executed agreement, with a reason. Signing links stop working and people who were notified receive a short notice. Records are kept. `POST /api/v1/accounting/signing.envelopes.void` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_signing_envelopes_void` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.envelopes.void \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.envelopes.void ## signing.events.list List the audit events of one envelope: created, prepared, sent, email accepted/delivered, viewed, code sent, incorrect code, link locked or unlocked, verified, consented, signed, declined, reminded, resent, voided, expired, finalizing stopped or restarted, completed, downloaded. order oldest (default) pages forward with afterId; order newest pages back from the latest event with beforeId. Returns hasMore. `GET | POST /api/v1/accounting/signing.events.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_signing_events_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `envelopeId` | string (ID) | Yes | The ID of a document sent for signature, from signing.envelopes.list. | | `order` | enum | | One of: `oldest`, `newest`. Default `"oldest"`. | | `afterId` | integer | | 0 to 9007199254740991. Default `0`. | | `beforeId` | integer | | 1 to 9007199254740991. | | `limit` | integer | | How many results to return at most. 1 to 500. Default `200`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/signing.events.list \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'envelopeId=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.events.list ## signing.files.confirm Confirm an uploaded original by fileId after verifying its size, SHA-256 hash, and file signature. Returns the verified fileId, filename, mimeType, sha256, and sizeBytes for use as an envelope source or executed agreement. `POST /api/v1/accounting/signing.files.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_signing_files_confirm` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `fileId` | string (ID) | Yes | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.files.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "fileId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.files.confirm ## signing.files.fromGoogleDocs Import a Google Docs document by its docs.google.com link as a verified PDF original. Only documents shared publicly (anyone with the link) can be fetched; otherwise download the document as PDF or Word from Google Docs and upload it. `POST /api/v1/accounting/signing.files.fromGoogleDocs` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_signing_files_from_google_docs` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `url` | string (uri) | Yes | A full web address, starting with https://. at most 2000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.files.fromGoogleDocs \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "url": "https://example.com/webhooks/oatmilk" }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.files.fromGoogleDocs ## signing.files.prepare Prepare a private upload for an agreement original: filename, mimeType (PDF, Word .docx, PNG, JPEG, text, Markdown; legacy .doc, .odt and .rtf are kept as records only), sizeBytes up to 25 MB, sha256, idempotencyKey. Returns fileId and uploadUrl; if alreadyUploaded is true skip the upload and confirm. `POST /api/v1/accounting/signing.files.prepare` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_signing_files_prepare` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | string | Yes | The file's name, including its extension. 1–255 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `application/msword`, `application/vnd.oasis.opendocument.text`, `application/rtf`, `image/png`, `image/jpeg`, `text/plain`, `text/markdown`. | | `sizeBytes` | integer | Yes | The file's size in bytes. 1 to 26214400. | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.files.prepare \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "filename": "receipt.pdf", "mimeType": "application/pdf", "sizeBytes": 1, "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.files.prepare ## signing.subjects.search Search contractors and customers to link to an agreement and prefill the other party's details. `GET | POST /api/v1/accounting/signing.subjects.search` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_signing_subjects_search` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `query` | string | | Text to search for. at most 200 characters. | | `type` | enum | | One of: `contractor`, `party`. | | `limit` | integer | | How many results to return at most. 1 to 50. Default `20`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.subjects.search \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/signing.subjects.search ## signing.templates.archive Archive (archived=true) or restore (archived=false) a template with expectedRevision. Archived templates are hidden from new documents but kept for records. `POST /api/v1/accounting/signing.templates.archive` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_signing_templates_archive` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `archived` | boolean | Yes | Whether the record is archived. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.templates.archive \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "archived": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.templates.archive ## signing.templates.create Create a custom agreement template from Markdown (body with {{field.key}} merge fields and an optional {{signatures}} block), fields, signerRoles, and letterhead. Set duplicateOf to copy an existing or starter template. It is published and ready to send unless publish is false, which keeps it a draft until signing.templates.publish. Requires an idempotency key. `POST /api/v1/accounting/signing.templates.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_signing_templates_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `title` | string | Yes | A short title. 1–200 characters. | | `description` | string | | A short description. at most 1000 characters. Default `""`. | | `kind` | enum | | Which kind of record or job this is. One of: `nda`, `contractor_agreement`, `signature_request`, `letterhead`, `custom`. Default `"custom"`. | | `body` | string | | 1–200000 characters. | | `editorJson` | map or null | | | | `fields` | array of objects | | at most 100 items. | | `fields[].key` | string | Yes | Matches ^[a-zA-Z][a-zA-Z0-9_.]{0,80}$. | | `fields[].label` | string | Yes | 1–120 characters. | | `fields[].required` | boolean | | Default `false`. | | `fields[].defaultValue` | string | | at most 5000 characters. | | `fields[].multiline` | boolean | | | | `fields[].markdown` | boolean | | | | `fields[].help` | string | | at most 300 characters. | | `fields[].source` | enum | | Where the record came from. One of: `company`, `counterparty`, `contractor`, `agreement`. | | `fields[].type` | enum | | One of: `date`, `choice`, `email`, `phone`, `address`, `job`. | | `fields[].options` | array of strings | | 1–40 items; each 1–120 characters. | | `fields[].allowOther` | boolean | | | | `signerRoles` | array of objects | | at most 10 items. | | `signerRoles[].role` | string | Yes | A person's access level in the company. Matches ^[a-z][a-z0-9_]{0,40}$. | | `signerRoles[].label` | string | Yes | 1–80 characters. | | `signerRoles[].partyLabelField` | string | Yes | Matches ^[a-zA-Z][a-zA-Z0-9_.]{0,80}$. | | `signerRoles[].order` | integer | Yes | 1 to 20. | | `signerRoles[].defaultInternal` | boolean | | Default `false`. | | `letterhead` | boolean | | | | `footer` | string or null | | | | `logoAssetId` | string (ID) or null | | | | `duplicateOf` | string (ID) | | | | `publish` | boolean | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.templates.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "title": "Synthetic services agreement", "body": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.templates.create ## signing.templates.get Read one agreement template with its Markdown body, merge fields, signer roles, letterhead setting, and revision. `GET | POST /api/v1/accounting/signing.templates.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_signing_templates_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/signing.templates.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.templates.get ## signing.templates.list List agreement templates: the Contractor Agreement, Non-Disclosure Agreement, Signature Request, and Document with Header starters on company letterhead plus custom templates, with merge fields and signer roles. Set includeArchived to include archived templates. `GET | POST /api/v1/accounting/signing.templates.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_signing_templates_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `includeArchived` | boolean | | Also include archived records. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.templates.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/signing.templates.list ## signing.templates.preview Render a PDF preview of a template (templateId) or unsaved template text (body, fields, signerRoles) with merge values and recipients on the organization letterhead. Returns pdfBase64, the page count, and the labels of required fields that are still empty. Nothing is stored. `GET | POST /api/v1/accounting/signing.templates.preview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_signing_templates_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `templateId` | string (ID) | | The ID of the related record. | | `title` | string | | A short title. 1–200 characters. | | `body` | string | | 1–200000 characters. | | `fields` | array of objects | | at most 100 items. | | `fields[].key` | string | Yes | Matches ^[a-zA-Z][a-zA-Z0-9_.]{0,80}$. | | `fields[].label` | string | Yes | 1–120 characters. | | `fields[].required` | boolean | | Default `false`. | | `fields[].defaultValue` | string | | at most 5000 characters. | | `fields[].multiline` | boolean | | | | `fields[].markdown` | boolean | | | | `fields[].help` | string | | at most 300 characters. | | `fields[].source` | enum | | Where the record came from. One of: `company`, `counterparty`, `contractor`, `agreement`. | | `fields[].type` | enum | | One of: `date`, `choice`, `email`, `phone`, `address`, `job`. | | `fields[].options` | array of strings | | 1–40 items; each 1–120 characters. | | `fields[].allowOther` | boolean | | | | `signerRoles` | array of objects | | at most 10 items. | | `signerRoles[].role` | string | Yes | A person's access level in the company. Matches ^[a-z][a-z0-9_]{0,40}$. | | `signerRoles[].label` | string | Yes | 1–80 characters. | | `signerRoles[].partyLabelField` | string | Yes | Matches ^[a-zA-Z][a-zA-Z0-9_.]{0,80}$. | | `signerRoles[].order` | integer | Yes | 1 to 20. | | `signerRoles[].defaultInternal` | boolean | | Default `false`. | | `letterhead` | boolean | | | | `footer` | string or null | | | | `logoAssetId` | string (ID) or null | | | | `values` | map | | Default `{}`. | | `recipients` | array of objects | | at most 20 items. | | `recipients[].kind` | enum | Yes | Which kind of record or job this is. One of: `signer`, `cc`. | | `recipients[].signerRole` | string or null | | | | `recipients[].name` | string | Yes | A display name. 1–200 characters. | | `recipients[].email` | string (email) | Yes | An email address. at most 320 characters. | | `recipients[].title` | string or null | | A short title. | | `recipients[].company` | string or null | | | | `recipients[].order` | integer | | 1 to 20. | | `recipients[].userId` | string or null | | The ID of a person in your company. | | `subjectType` | enum | | The kind of record this is about, such as entry, invoice or mail. One of: `contractor`, `party`, `invoice`. | | `subjectId` | string | | The ID of the record this is about. 1–200 characters. | | `jobRoleId` | string (ID) | | The ID of the related record. | | `jobRoleVersion` | integer | | 1 to 100000. | | `engagement` | object | | No other fields. | | `engagement.rate` | object | Yes | No other fields. | | `engagement.rate.amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^[1-9]\d{0,14}$. | | `engagement.rate.currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `engagement.rate.unit` | enum | Yes | One of: `hour`, `day`, `week`, `month`, `year`, `fixed`. | | `engagement.basis` | enum | Yes | One of: `job`, `one_off`. | | `engagement.note` | string or null | | A short note, kept with the record. | | `engagement.startDate` | string (date) or null | | | | `engagement.endDate` | string (date) or null | | | | `engagement.noticeDays` | integer or null | | | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/signing.templates.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'templateId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.templates.preview ## signing.templates.publish Publish a draft template with id and expectedRevision so it can be used to send agreements. Templates saved from the agreement writer stay drafts until someone publishes them, and drafts can't be sent. Publishing a template that is already published changes nothing. `POST /api/v1/accounting/signing.templates.publish` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_signing_templates_publish` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.templates.publish \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.templates.publish ## signing.templates.update Edit a custom template with expectedRevision. Starter templates are read-only; duplicate them to customize. Documents already sent keep the exact version they were sent with. `POST /api/v1/accounting/signing.templates.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_signing_templates_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `title` | string | | A short title. 1–200 characters. | | `description` | string | | A short description. at most 1000 characters. | | `kind` | enum | | Which kind of record or job this is. One of: `nda`, `contractor_agreement`, `signature_request`, `letterhead`, `custom`. | | `body` | string | | 1–200000 characters. | | `editorJson` | map or null | | | | `fields` | array of objects | | at most 100 items. | | `fields[].key` | string | Yes | Matches ^[a-zA-Z][a-zA-Z0-9_.]{0,80}$. | | `fields[].label` | string | Yes | 1–120 characters. | | `fields[].required` | boolean | | Default `false`. | | `fields[].defaultValue` | string | | at most 5000 characters. | | `fields[].multiline` | boolean | | | | `fields[].markdown` | boolean | | | | `fields[].help` | string | | at most 300 characters. | | `fields[].source` | enum | | Where the record came from. One of: `company`, `counterparty`, `contractor`, `agreement`. | | `fields[].type` | enum | | One of: `date`, `choice`, `email`, `phone`, `address`, `job`. | | `fields[].options` | array of strings | | 1–40 items; each 1–120 characters. | | `fields[].allowOther` | boolean | | | | `signerRoles` | array of objects | | at most 10 items. | | `signerRoles[].role` | string | Yes | A person's access level in the company. Matches ^[a-z][a-z0-9_]{0,40}$. | | `signerRoles[].label` | string | Yes | 1–80 characters. | | `signerRoles[].partyLabelField` | string | Yes | Matches ^[a-zA-Z][a-zA-Z0-9_.]{0,80}$. | | `signerRoles[].order` | integer | Yes | 1 to 20. | | `signerRoles[].defaultInternal` | boolean | | Default `false`. | | `letterhead` | boolean | | | | `footer` | string or null | | | | `logoAssetId` | string (ID) or null | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/signing.templates.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/signing.templates.update # Group: Contractors > The finance side of contractors: directory, timesheets, payouts and recruiting. MCP toolset: `contractors` (https://app.getoatmilk.com/api/mcp?toolset=contractors) ## contractorOps.activity - [`contractorOps.activity.list`](https://app.getoatmilk.com/docs/api/contractorOps.activity.list.md) — Read a human-readable activity log for one contractor or all contractors: profile and payment changes, hours, reviews, agreements, reminders sent, and payouts, newest first. ## contractorOps.agreements - [`contractorOps.agreements.addContractor`](https://app.getoatmilk.com/docs/api/contractorOps.agreements.addContractor.md) — Add the other party of a signed agreement or NDA that has no contractor as a new contractor and file the document with them: envelopeId, contractor (displayName, legalName, email, optional otherEmails, country, region, jobRoleId, title, rate, startDate, endDate) and idempotencyKey. Refused when an address already belongs to a contractor (use contractorOps.agreements.linkContractor), or when someone has the same or a close name unless notDuplicateOf lists them. An agreement's role, rate and dates become its terms when its title has a matching jobRoleId; otherwise the terms wait for review and role selection. With no terms given, the document is read for its terms to check. An NDA stays apart from the agreement. No invitation is sent. A document is never filed twice, whoever asks. - [`contractorOps.agreements.contractorDraft`](https://app.getoatmilk.com/docs/api/contractorOps.agreements.contractorDraft.md) — Prefill for filing a signed agreement or NDA that has no contractor (envelopeId, or intakeItemId of the filed upload): the other party's name, email addresses, role and the job role and level it matches, rate, start and end dates and where they live, as the document states them with the words each came from, plus existing contractors it may be (any address Oatmilk knows for them, then the same name, then a close name) with which of the document's addresses each uses and whether the document is newer than their terms. Pass the name and email being added (name, email) to look those up too. Nothing is saved. - [`contractorOps.agreements.ended`](https://app.getoatmilk.com/docs/api/contractorOps.agreements.ended.md) — List agreements that have ended for active contractors with no decision yet (not renewed, no renewal waiting for signatures, and no end decision recorded): the end date, what happened since (minutes logged, timesheet entries, pay periods, payouts and payments after the end date), a one-line summary, and the renewal to offer (the same terms from the day after it ended). An ended agreement blocks nothing. Contractors who kept working come first. Filter by contractorId. Renew with contractorOps.terms.propose, or record a decision with contractorOps.terms.endDecision (continuing keeps them working without renewing, ending lets it end); either decision removes it from this list. - [`contractorOps.agreements.importExecuted`](https://app.getoatmilk.com/docs/api/contractorOps.agreements.importExecuted.md) — Mark an already-signed agreement as this contractor's executed agreement using a fileId from the signing upload flow, a title, the execution date, and idempotencyKey. - [`contractorOps.agreements.keepAsHistory`](https://app.getoatmilk.com/docs/api/contractorOps.agreements.keepAsHistory.md) — Keep an agreement filed with a contractor as history instead of reading its terms (contractorId, envelopeId, idempotencyKey), such as one that couldn't be read, isn't a contractor agreement, or whose terms were entered by hand. Its terms are never read or applied, and it leaves the checklist. Refused while its terms are being read. - [`contractorOps.agreements.linkContractor`](https://app.getoatmilk.com/docs/api/contractorOps.agreements.linkContractor.md) — File a signed agreement or NDA that has no contractor with an existing contractor (envelopeId, contractorId, idempotencyKey). An agreement newer than their terms is read for its terms to check and replaces them once confirmed (a draft read from an older agreement is replaced; while an update is out for signature it waits); an older or undated one is kept as history and never changes newer terms. An NDA stays apart from the agreement. - [`contractorOps.agreements.list`](https://app.getoatmilk.com/docs/api/contractorOps.agreements.list.md) — List contractor agreements from the versions of each contractor's terms, for a view: all, active (in effect), ending (in effect with an end date within 45 days), expired (the one in effect has passed its end date) or waiting (an update waiting for a signature or a check), with counts for every view. Each has the contractor, role, rate, start and end dates, days left, state (active, ending, expired, upcoming, replaced or waiting), finance's end decision for an expired one (continuing, ending, or null when undecided), when a renewal starts, whether new terms are waiting (renewalPending), for an undecided expired one whether they kept working and what happened since (the same as contractorOps.agreements.ended), and href, the contractor's Agreement tab. Each version also says where its signed copy is (copy: a document in Oatmilk, or a link when it was signed elsewhere). Without contractorId it covers the organization, lists active contractors with no agreement recorded, and lists signed agreements imported with the dates not stated (undated), which aren't in the views by date. With contractorId it covers that contractor and adds their documents on file from contractor agreements, signing envelopes and imports (items, each an agreement or an nda, with signedElsewhere for a copy kept at its link and datesNotStated for an undated one), whether an agreement is on file (status on_file, signed_elsewhere, terms_on_file, requested, draft or agreement_missing), and whether a signed NDA is. - [`contractorOps.agreements.sendTemplate`](https://app.getoatmilk.com/docs/api/contractorOps.agreements.sendTemplate.md) — Create a contractor agreement envelope from the contractor agreement template, prefilled from the profile (legal name, address, role, rate, cadence, term), optionally with a company signer who is an administrator or finance member (found by email), and send it for signature. values overrides template fields by their keys, like contractor.estimatedEngagement. Prefer contractorOps.terms.propose, which also records the terms Oatmilk enforces. - [`contractorOps.agreements.unlinked`](https://app.getoatmilk.com/docs/api/contractorOps.agreements.unlinked.md) — List signed agreements and NDAs that aren't filed with any contractor or other record, newest first, with the other party as recorded and a link to each. ## contractorOps.directory - [`contractorOps.directory.list`](https://app.getoatmilk.com/docs/api/contractorOps.directory.list.md) — List every contractor with their roles, department, status, rate and pay cadence, payment profile completeness (masked), agreement on file, pending hours, next hours due date, last activity, their recent pay periods and live payouts with whether payouts are prepared for them automatically (pay, the same as contractorOps.profiles.get returns, for when they are paid next), and for anyone who has not joined the portal where their invitation stands (invite: state and label, such as not_invited, sent, delivered, bounced or expired; null once they have joined). Filter by query, status, department, or role. ## contractorOps.encryption - [`contractorOps.encryption.resetFingerprintKey`](https://app.getoatmilk.com/docs/api/contractorOps.encryption.resetFingerprintKey.md) — Only while the organization's fingerprint key can't be read any more (the encryption key it was sealed with is lost), replace it with a new one, with a reason, so tax numbers and payment details can be saved and paid out again. Fingerprints are worked out again for every tax number and payment email that can still be read; ones that can't be read lose theirs and are flagged until they're entered again, and saved Wise recipients are created again on their next payout. Refused while the fingerprint key can still be read. Audited with counts and the reason only. Only an administrator in the dashboard can; not available to API keys or MCP clients. - [`contractorOps.encryption.status`](https://app.getoatmilk.com/docs/api/contractorOps.encryption.status.md) — Read how contractor tax numbers and payment details are encrypted here: whether the encryption key and the previous key are set and usable (never their values), how many values the scheduled run still has to encrypt or re-encrypt, how many can't be read with the keys set now, recorded failures by error code, when a value was last tried, whether the previous key is safe to remove (only once nothing in any organization uses it), whether the organization's fingerprint key can be read (fingerprintKey) and can be reset (canResetFingerprintKey), and warnings. Counts only. ## contractorOps.forms - [`contractorOps.forms.list`](https://app.getoatmilk.com/docs/api/contractorOps.forms.list.md) — List the custom hours forms contractors fill in for each time entry, including which one is the default. - [`contractorOps.forms.save`](https://app.getoatmilk.com/docs/api/contractorOps.forms.save.md) — Create or update a custom hours form with name, ordered fields (key, label, type text/textarea/number/select/multiselect/date/checkbox/url, required, options, help), optional isDefault or archived, expectedRevision for updates, and idempotencyKey. ## contractorOps.history - [`contractorOps.history.allocations.get`](https://app.getoatmilk.com/docs/api/contractorOps.history.allocations.get.md) — What one contractor's payments and imported timesheets look like for sharing a payment across timesheets (contractorId; transactionId or payoutId to include that payment even when it isn't linked to them yet). payments lists each outgoing bank payment linked to them and each payment recorded in Oatmilk with no pay period, newest first, with its date, amount, kind (wise, bank or recorded), what it already gave which timesheets (allocations), and the timesheets an earlier Mark paid tied to it without amounts (legacyHistoryIds). timesheets lists their imported timesheets with the period, hours, amount (null for a fixed fee with no amount), what payments gave each, and whether it's unpaid, partly paid or paid and how. Nothing changes. - [`contractorOps.history.allocations.save`](https://app.getoatmilk.com/docs/api/contractorOps.history.allocations.save.md) — Share one payment across several imported timesheets (weeks, two-week periods or parts of a month), or pay one timesheet from several payments. Send contractorId and either transactionId (an outgoing bank payment) or payoutId (a payment recorded in Oatmilk with no pay period) with the allocations of that payment, or historyId with the allocations of that timesheet. Each allocation is historyId, transactionId or payoutId, amountMinor, and closes (true counts a small shortfall, such as a fee, as paid in full). The list replaces what that payment (or timesheet) had; an empty list removes it, which is how a match is undone. A payment never gives more than it paid, a timesheet never takes more than it pays, and one payment pays one contractor. A timesheet its allocations cover (any allocation, when it has no amount) is marked paid on the latest payment's date; one no longer covered goes back to unpaid. Timesheets paid in their table or marked paid by hand keep that. Automatic matching leaves the payment alone afterwards. Optional note and idempotencyKey. Audited. - [`contractorOps.history.list`](https://app.getoatmilk.com/docs/api/contractorOps.history.list.md) — List every contractor timesheet as history: rows imported from a Notion database or CSV file, Oatmilk pay periods with hours or a payout, and milestone payouts. Each has the contractor, period, hours, rate, payout in minor units (the imported amount, or hours × rate plus GST/HST), GST/HST, status paid or unpaid with how (paid in the imported table, marked paid in Oatmilk with the date and bank payment, or the payout's state), when it was submitted and how many days late, its source (imported or oatmilk), and historyId for imported rows. An imported row whose dates also have hours logged in Oatmilk is marked alsoInOatmilk and left out of totals so nothing counts twice. Filter by contractorId, status (all, paid, unpaid), source (all, imported, oatmilk), and from and to (periods overlapping those dates). Unpaid is sorted by contractor, oldest first, with each contractor's totals in groups; the other views are newest first. Totals cover every match, by currency. - [`contractorOps.history.markPaid`](https://app.getoatmilk.com/docs/api/contractorOps.history.markPaid.md) — Record imported timesheets as paid, for example once the payroll that settled them went out: ids (the historyId of each imported row, up to 500), paidOn (today when omitted; never in the future), an optional transactionId of the bank payment that paid them (money out of an account, and then every row must be the same contractor's; it counts once as money sent even if it's linked to the contractor later), an optional note, and idempotencyKey. Rows already paid are left as they are and counted in alreadyPaid. Oatmilk pay periods aren't marked here; they're paid through their payouts. Importing the table again never marks them unpaid. Audited for each contractor. - [`contractorOps.history.paymentAsks.answer`](https://app.getoatmilk.com/docs/api/contractorOps.history.paymentAsks.answer.md) — Answer a payment question (id, decision, idempotencyKey). paid marks the question's timesheets that are still unpaid paid, on the payment's date and with the payment (audited like contractorOps.history.markPaid); other records that the payment was for something else, and it is never matched to timesheets or asked about again. - [`contractorOps.history.paymentAsks.list`](https://app.getoatmilk.com/docs/api/contractorOps.history.paymentAsks.list.md) — The open questions about a bank payment to a contractor that is close to, or covers some of, their unpaid imported timesheets but isn't an exact match: who was paid, the payment (date, amount, Wise or bank), the timesheets it would settle with their periods, hours and amounts, and why Oatmilk isn't sure. Answer each with contractorOps.history.paymentAsks.answer. - [`contractorOps.history.rateBackfill.apply`](https://app.getoatmilk.com/docs/api/contractorOps.history.rateBackfill.apply.md) — Save agreement-derived hourly rate snapshots for explicitly selected imported timesheets after reviewing the preview. Supply each history row id and expectedRevision plus idempotencyKey. Rechecks the agreement and source row atomically; a changed, split-rate or ambiguous week fails without saving any row. Does not mark paid, prepare, approve or send a payout. - [`contractorOps.history.rateBackfill.preview`](https://app.getoatmilk.com/docs/api/contractorOps.history.rateBackfill.preview.md) — Preview imported timesheets missing a source amount and rate, with the active hourly agreement covering each complete period. Shows which weeks are ready to save and which need review because an agreement changes, is missing, or has a currency or rate conflict. No data changes and no payout is prepared. ## contractorOps.hours - [`contractorOps.hours.history`](https://app.getoatmilk.com/docs/api/contractorOps.hours.history.md) — Read one time entry's history (hoursId): every change from logging to approval and any correction, oldest first, with the entry's dates, who acted (contractor, a finance member by email, or the platform), the old and new values, the reason given, whether it's on hold for an open correction, and where it stands in a payout (unpaid, scheduled or paid). ## contractorOps.import - [`contractorOps.import.agreements`](https://app.getoatmilk.com/docs/api/contractorOps.import.agreements.md) — Bring agreements and NDAs in from a contracts table such as the Notion Contractor Contracts database, or the NDAs from a documents table such as Contractors Documents (source notion with databaseId, the database's link or ID, or csv with rows). mapping names the column for any fields whose column names don't match; the rest are matched by name. Fields: contractor (or email), title, kind (a type column: NDA, Contract…), signed (a signed column), role, rate (hourly), currency, startDate, endDate, file (a Notion files column with the signed copy), fileUrl (a link to a copy kept elsewhere) and nda (an NDA link or file on a contract row). A signed agreement with a start date becomes a version of the contractor's terms from that date, unless terms starting that day are already on file; one without dates is recorded as signed with the dates not stated and never becomes terms, so it can't replace dated ones; an unsigned, draft or stale row is kept as an unsigned draft. The signed copy is kept: a file attached to a Notion row is brought in with the Notion file import and filed as the agreement's signed document (linked to its terms), and a copy that's only a link is recorded as signed elsewhere. NDAs are kept apart from the contractor agreement. The role is linked to a job role by title and, for a title with several levels, the level whose pay band holds the rate; otherwise jobRoleSuggestion says what to do, and a contractor with no job role gets the one their current agreement matches. A table without start dates only brings in its NDAs. Every row is kept by the row it came from, so importing again fills in what's missing (a signed copy, a job role) instead of adding it twice. Rows for someone not in Oatmilk are left out with the reason; a rate of $1 or less is read as none. Nothing is sent to sign and nobody is emailed. A column whose name says it holds payment or tax details is never read, and problems says so when mapping names one. With preview true nothing is saved, and each row says whether it's already on file (onFile) and where the table disagrees with the terms on file (conflict). - [`contractorOps.import.contractors`](https://app.getoatmilk.com/docs/api/contractorOps.import.contractors.md) — Bring contractors in from a table: source notion with databaseId (the database's link or ID), or source csv with rows. mapping names the column for any fields whose column names don't match (null for none); the others are matched by name, and a column that isn't in the table is reported in problems. A required field no column matches is refused with the table's columns listed. Fields: name (required), legalName, workEmail, personalEmail, wiseEmail, wiseLink, phone, roles, jobRole, department, jurisdiction, address (split into street, city, province or state, postal code and country), city, postalCode, chargesSalesTax, salesTaxRate, discord, github, portfolio, startDate, notes, currency, and the bank columns accountHolderName, bankName, bankAddress, accountNumber, institutionNumber, transitNumber, routingNumber, iban and swiftBic. People already in Oatmilk are matched by email and then by name, and only have blanks filled in; new people are added without an invitation, and no one is emailed. jobRole is matched to a job role by title and level; a title with several levels takes the level whose pay band holds the person's current rate, and otherwise jobRoleLevels lists the levels to choose from. The address used to reach each person is work email, then personal, then Wise, unless emails (row reference to address) chooses another; a row with no email is left out until emails gives it one. Payment details are saved only when an administrator imports and only for someone with none yet, in the currency column's currency, else the bank's country's, else the person's country's, else CAD. They are validated, sealed and audited, and saving them doesn't email the contractor; a row whose details don't validate is still imported and listed in paymentProblems. Payment details are never returned, in a preview, a result or a message: each row's plan has paymentDetails with present, method (bank_transfer or wise_email), masked (at most the last four characters of the account number or IBAN, or the Wise email masked) and fields (the names of the details the row has), a Wise email is left out of emails and masked when it's the address used, and a column whose name says it holds payment or tax details is only read for the bank and Wise fields. With preview true nothing is saved and the plan for every row comes back. - [`contractorOps.import.hours`](https://app.getoatmilk.com/docs/api/contractorOps.import.hours.md) — Bring past timesheets in as history from a table (source notion with databaseId, the database's link or ID, or csv with rows; mapping names the column for any fields whose column names don't match, and the others are matched by name). Fields: contractor (or firstName and lastName, or email), period (a date or a range, with optional periodEnd), hours, and optional rate, amount, tax, currency, paid, notes and submitted. Rows are matched to contractors by email, then name. Imported hours appear in each contractor's history, spending and on-time insights, but never become pay periods or payouts. A row that is a timesheet already here (the same Notion page or CSV row, or the same contractor's for the same hours starting or ending the same day) updates it instead of adding a duplicate: edited dates, hours, rate, amount, GST/HST and notes are taken, a blank cell erases nothing, and Paid marks an unpaid timesheet paid but an unpaid row never marks a paid one unpaid. The result lists what was added, corrected and marked paid in saved (counts and rows) and in summaryText, and afterwards new unpaid timesheets are settled against bank payments already linked to the contractor (payments). A column whose name says it holds payment or tax details is never read, and problems says so when mapping names one. With preview true nothing is saved and changes says what would happen. - [`contractorOps.import.hoursSync`](https://app.getoatmilk.com/docs/api/contractorOps.import.hoursSync.md) — Sync past timesheets from the Notion Contractors Hours database in one step (databaseId once, its link or ID; later syncs leave it out and use the remembered one): checks the database, then adds new timesheets, corrects edited ones (dates, hours, rate, amount, GST/HST, notes) and marks unpaid ones paid when Notion says Paid (never the reverse), without duplicating any, then settles new unpaid timesheets against bank payments already linked to the contractor. Returns counts, the change for each row, the payments that settled timesheets or now need a person's OK, and a plain-words summary. Bank and tax columns are never read. Send a new idempotencyKey for each sync. - [`contractorOps.import.hoursSyncStatus`](https://app.getoatmilk.com/docs/api/contractorOps.import.hoursSyncStatus.md) — Whether Notion is connected, the remembered Contractors Hours database (id and title), whether it is synced automatically about every 6 hours, and how the last sync went. ## contractorOps.insights - [`contractorOps.insights.get`](https://app.getoatmilk.com/docs/api/contractorOps.insights.get.md) — Contractor spending and timesheet insights from imported timesheets and Oatmilk pay periods and payouts: money sent (linked bank payments, payouts paid outside Wise, and bank payments recorded when past timesheets were marked paid), money still owed (unpaid imported timesheets and payouts not yet paid), paid plus owed, what the work cost by the month it was done (payouts including GST/HST) next to money sent by payment month, GST/HST, hours, monthly and weekly (Monday) series with empty ones as zero, monthly and weekly averages, each contractor's totals, cost in each of the last twelve months, hours a week over the last four weeks against the four before, on-time and late timesheets with average days late, the most and least punctual (at least three timesheets), and a few plain observations and trends. Amounts are in minor units of one currency (CAD unless currency is given); other currencies are listed. ## contractorOps.payments - [`contractorOps.payments.reveal`](https://app.getoatmilk.com/docs/api/contractorOps.payments.reveal.md) — Show a contractor's full payment details to an administrator in the dashboard with an audited reason. Not available to API keys or MCP clients. - [`contractorOps.payments.update`](https://app.getoatmilk.com/docs/api/contractorOps.payments.update.md) — Administrators set a contractor's payment method (Wise email, bank transfer, Interac, or other), currency, and payment details. Omitted detail fields keep their saved values; null clears them. Responses are masked, the audit log records only which fields changed, the saved Wise recipient is reset, and approved payouts must be approved again. When the saved details can't be read (the profile's paymentDetailsState isn't readable), nothing changes unless the details sent are a complete set for the method; with an encryption key that isn't set any more, that also needs replaceUnreadable: true. - [`contractorOps.payments.verify`](https://app.getoatmilk.com/docs/api/contractorOps.payments.verify.md) — Record that an administrator confirmed a contractor's current payment details out of band, using the payment profile revision they reviewed and a note. ## contractorOps.payouts - [`contractorOps.payouts.approve`](https://app.getoatmilk.com/docs/api/contractorOps.payouts.approve.md) — Approve a prepared payout with expectedRevision and idempotencyKey. Approval records the contractor's current payment details revision; if those details change later, the payout must be approved again before sending. - [`contractorOps.payouts.cancel`](https://app.getoatmilk.com/docs/api/contractorOps.payouts.cancel.md) — Cancel a payout that has not been funded, releasing its time entries for a future payout. An unfunded Wise transfer is cancelled first. Requires a reason, expectedRevision, and idempotencyKey. - [`contractorOps.payouts.get`](https://app.getoatmilk.com/docs/api/contractorOps.payouts.get.md) — Read one contractor payout with its time entries, calculation, approval, Wise quote, recipient and transfer identifiers, and the related activity run. - [`contractorOps.payouts.list`](https://app.getoatmilk.com/docs/api/contractorOps.payouts.list.md) — List contractor payouts with amounts, tax, status, approvals, Wise transfer progress, and errors. Filter by contractor, period, or status. - [`contractorOps.payouts.markPaid`](https://app.getoatmilk.com/docs/api/contractorOps.payouts.markPaid.md) — Record a payout as paid outside Wise with a payment reference, the payment date, expectedRevision, and idempotencyKey. - [`contractorOps.payouts.prepare`](https://app.getoatmilk.com/docs/api/contractorOps.payouts.prepare.md) — Prepare a payout from approved hours for a pay period (periodId), or a milestone payout for a contractor (contractorId with hoursIds or amountMinor). Amounts use exact minor units: approved minutes × hourly rate, days × day rate, or fixed and monthly rules, plus GST/HST only at a rate finance confirmed (salesTaxStatus shows when the contractor's declared tax isn't confirmed yet). Hours can never be included in two active payouts. - [`contractorOps.payouts.send`](https://app.getoatmilk.com/docs/api/contractorOps.payouts.send.md) — Send an approved payout through Wise (quote, recipient, transfer, fund from balance). Requires an administrator in the dashboard, the confirmed total and currency, expectedRevision, and idempotencyKey. Refused while an hour in the payout has a correction waiting, and while an hour is still inside the wait after approval unless overrideReason says why (audited). Retries never create a second transfer. Not available to API keys or MCP clients. ## contractorOps.periods - [`contractorOps.periods.list`](https://app.getoatmilk.com/docs/api/contractorOps.periods.list.md) — List contractor pay periods with period dates, hours due date, pay date, status (open, submitted, approved, paid, skipped), and submitted and approved minutes. - [`contractorOps.periods.update`](https://app.getoatmilk.com/docs/api/contractorOps.periods.update.md) — Mark a pay period as skipped (no hours expected) or reopen it, with expectedRevision, an optional note, and idempotencyKey. Skipped periods receive no reminders and don't accept hours. A period with submitted or approved hours or a payout can't be skipped. ## contractorOps.profiles - [`contractorOps.profiles.get`](https://app.getoatmilk.com/docs/api/contractorOps.profiles.get.md) — Read one contractor's operations profile: roles, department, dates, contact details, address, rate, pay cadence, sales tax and corporation details, hours form, completeness, and a masked payment profile. Also returns their recent pay periods and payouts, and whether Oatmilk prepares their payouts automatically, so a caller can say when they are paid next. Full bank numbers are never returned. - [`contractorOps.profiles.update`](https://app.getoatmilk.com/docs/api/contractorOps.profiles.update.md) — Create or update a contractor's operations profile with contractorId, profile fields, expectedRevision (omit only when no profile exists yet), and idempotencyKey. Changing the pay cadence regenerates future empty pay periods. Changing the declared GST/HST answers stops finance's confirmation from applying and removes the GST/HST it added from payouts that haven't been paid and have no Wise transfer (approved ones go back for approval). ## contractorOps.reminders - [`contractorOps.reminders.run`](https://app.getoatmilk.com/docs/api/contractorOps.reminders.run.md) — Run contractor operations now: create pay periods, send due, late, profile, and approval reminders that are due, and prepare payouts for approved periods. Safe to repeat; reminders are never sent twice. ## contractorOps.roles - [`contractorOps.roles.archive`](https://app.getoatmilk.com/docs/api/contractorOps.roles.archive.md) — Archive a job role so it isn't offered for new contractors, or restore it (archived false), with expectedRevision and idempotencyKey. Contractors keep a role that was archived. - [`contractorOps.roles.archiveJob`](https://app.getoatmilk.com/docs/api/contractorOps.roles.archiveJob.md) — Archive, or restore (archived false), several levels of a job at once: levels with each id and expectedRevision, archived and idempotencyKey. Contractors keep a role that was archived. - [`contractorOps.roles.assign`](https://app.getoatmilk.com/docs/api/contractorOps.roles.assign.md) — Give a contractor a job role, or remove it with roleId null. - [`contractorOps.roles.duplicateJob`](https://app.getoatmilk.com/docs/api/contractorOps.roles.duplicateJob.md) — Copy a job under a new title: levels lists the ids to copy, title is the new job's name, with idempotencyKey. Each copy keeps the level, department, type, description, pay band and ladder order, and has nobody in it. - [`contractorOps.roles.get`](https://app.getoatmilk.com/docs/api/contractorOps.roles.get.md) — Read one job role with its full description, pay band and current version. - [`contractorOps.roles.import`](https://app.getoatmilk.com/docs/api/contractorOps.roles.import.md) — Create or update job roles from a table: source notion with databaseId (the database's link or ID), or source csv with rows (column name to value). mapping names the column for any fields whose column names don't match (null for none); the others are matched by name, and a column that isn't in the table is reported in problems. A required field no column matches is refused with the table's columns listed. Fields: title (required), level, department, employmentType, summary, responsibilities, requirements, and either payBand text ("CAD 60–80/hour", "$90k–110k per year") or payMin, payMax, payCurrency, payUnit. Roles are matched by title and level, so importing again updates instead of duplicating, and a blank cell never erases what's written. A column whose name says it holds payment or tax details is never read, and problems says so when mapping names one. With preview true nothing is saved and the parsed roles and problems come back. - [`contractorOps.roles.list`](https://app.getoatmilk.com/docs/api/contractorOps.roles.list.md) — List the company's job roles (job descriptions for contractors and employees): title, level, department, type, summary, responsibilities, requirements, pay band (currency, lowest and highest amount in minor units, paid per hour, day, week, month or year), and the active contractors who have each role. A job is every role with the same title, and its levels form a ladder in levelOrder (1 is the first rung; null until someone reorders them). Archived roles are left out unless includeArchived is true. - [`contractorOps.roles.reorder`](https://app.getoatmilk.com/docs/api/contractorOps.roles.reorder.md) — Put a job's levels in order, first rung first: levels lists each level's id and expectedRevision in the order wanted, with idempotencyKey. - [`contractorOps.roles.save`](https://app.getoatmilk.com/docs/api/contractorOps.roles.save.md) — Create a job role, or update one with id and expectedRevision: title, level, department, employmentType (contractor, employee or either), summary, responsibilities, requirements, an optional pay band (payCurrency, payMinMinor, payMaxMinor, payUnit) and an optional levelOrder (its place on the job's ladder). Title and level together are unique. - [`contractorOps.roles.updateJob`](https://app.getoatmilk.com/docs/api/contractorOps.roles.updateJob.md) — Rename a job, and optionally set its department and type, on every level at once. levels lists each level's id and expectedRevision (include archived levels so the whole job changes together), with title and idempotencyKey. It changes nothing if any level changed since it was read, or if the new title and a level's name are already a role. A level held through an agreement can't be renamed here. - [`contractorOps.roles.versions`](https://app.getoatmilk.com/docs/api/contractorOps.roles.versions.md) — List a job role's versions, newest first, with roleId: what the role said and paid at each version (title, level, department, type, summary, responsibilities, requirements and pay band), which parts changed from the version before (title, level, department, type, description or pay), who changed it and when, and how many sent agreements were made on that version. Every change to those parts is a new version, and an agreement keeps the version and pay it was made with. ## contractorOps.salesTax - [`contractorOps.salesTax.confirm`](https://app.getoatmilk.com/docs/api/contractorOps.salesTax.confirm.md) — Confirm or correct the GST/HST a contractor declared, with contractorId, expectedRevision (the profile revision reviewed), chargesSalesTax, salesTaxRateBps (1 to 10000 when charged), a note, and idempotencyKey. Payouts add sales tax only at a confirmed rate, and a confirmation stops applying when the declared answers change. Payouts that aren't paid and have no Wise transfer are updated to the confirmed rate; approved ones go back for approval. ## contractorOps.settings - [`contractorOps.settings.get`](https://app.getoatmilk.com/docs/api/contractorOps.settings.get.md) — Read contractor operations preferences: reminder timing and limits, approvers, automatic payout preparation, default cadence, time zone, and Wise source currency. - [`contractorOps.settings.update`](https://app.getoatmilk.com/docs/api/contractorOps.settings.update.md) — Update contractor operations preferences with idempotencyKey and optional expectedRevision. Only supplied settings change. ## contractorOps.taxForms - [`contractorOps.taxForms.export`](https://app.getoatmilk.com/docs/api/contractorOps.taxForms.export.md) — Download the T4A and T4A-NR worksheet for a calendarYear with full tax numbers, to type into the CRA's web forms, with a reason. Only an administrator in the dashboard can; the download is audited with the year and the number of slips, never the numbers. Not available to API keys or MCP clients. - [`contractorOps.taxForms.get`](https://app.getoatmilk.com/docs/api/contractorOps.taxForms.get.md) — What to issue or review for each contractor paid in a calendar year (calendarYear), based on the organization's formation country. For a Canadian organization: CRA administrative policy calls for a T4A when annual service fees exceed $500 before sales tax or any tax was deducted; non-residents who worked in Canada generally get a T4A-NR at any amount with 15% Regulation 105 withholding unless waived. For a U.S. organization: shows a review checklist and never labels payments as Canadian T4A slips; the accountant confirms W-9/W-8, payer and payee status, service source, payment type and channel, and applicable reporting. Certain 1099-NEC and 1099-MISC payments after 2025 use a $2,000 threshold. Foreign formations require local accountant review. Numbers are masked; addresses are not shown to accountants; no U.S. forms are filed. ## contractorOps.taxInfo - [`contractorOps.taxInfo.get`](https://app.getoatmilk.com/docs/api/contractorOps.taxInfo.get.md) — Read one contractor's tax info for slips (contractorId): masked tax numbers, the structured mailing address and its check, both legal names, identity confirmation, the last request, and the revisions an edit needs. - [`contractorOps.taxInfo.list`](https://app.getoatmilk.com/docs/api/contractorOps.taxInfo.list.md) — List every contractor with what their CRA slip still needs: the slip their residency calls for (T4A, T4A-NR, none, or unknown), tax numbers on file masked like •••-•••-286 (never in full), the mailing address check (missing or invalid parts by name), a legal name that differs between the contractor record and their profile, identity confirmation, and when their tax details were last requested. Filter with missingOnly or query. Fix a legal name with contractors.update (the record) or contractorOps.profiles.update (the profile). - [`contractorOps.taxInfo.request`](https://app.getoatmilk.com/docs/api/contractorOps.taxInfo.request.md) — Email a contractor who uses the portal to add what their slip still needs (their tax number and mailing address), naming the fields and asking them to use the portal, never email. Nothing is sent unless this is called. The request is recorded with its date, the record of a reasonable effort to get the SIN. - [`contractorOps.taxInfo.update`](https://app.getoatmilk.com/docs/api/contractorOps.taxInfo.update.md) — Add or replace a contractor's tax number (taxNumber: kind sin, bn, itn or foreign, the value, and country for a foreign number) or remove one (removeTaxNumber: the kind), with expectedRevision once that kind was saved before; and/or set the mailing address their slips go to (mailingAddress: line1 street, line2 unit, city, region province or state, postalCode, country) with expectedProfileRevision. A SIN must pass the check-digit test, a business number has 9 digits and an optional program account like RT0001, and a Canadian postal code looks like K1A 0B6. Numbers are stored encrypted and only ever returned masked; the audit log records that one changed, never the value. Finance can change a contractor's tax numbers at most 5 times an hour, and 50 times an hour across the organization (429 with Retry-After); a contractor's own portal saves have a separate budget, so neither can use up the other's. Nothing is returned about other contractors' numbers, except that an administrator in the dashboard saving a SIN or ITN is told who else has the same one (sameNumberAs). ## contractorOps.terms - [`contractorOps.terms.cancel`](https://app.getoatmilk.com/docs/api/contractorOps.terms.cancel.md) — Withdraw an update that is waiting for signature or confirmation, with id, expectedRevision, a reason and idempotencyKey. A document already sent is voided so it can't be signed. - [`contractorOps.terms.confirm`](https://app.getoatmilk.com/docs/api/contractorOps.terms.confirm.md) — Confirm a waiting version, such as terms Oatmilk read from an uploaded signed agreement, optionally correcting its terms or effective date and selecting a matching jobRoleId first, with id, expectedRevision and idempotencyKey. - [`contractorOps.terms.endDecision`](https://app.getoatmilk.com/docs/api/contractorOps.terms.endDecision.md) — Decide about an agreement at or past its end date, with the active terms version id, its expectedRevision, decision, and idempotencyKey. 'continuing' keeps them working and paid without renewing and stops the end-date reminders; 'ending' lets it end, so no new pay periods start after the end date (it does not stop the contractor logging hours or their portal access); null takes the decision back. An end date alone never stops work. To extend an agreement on paper instead, send new terms with a later end date using contractorOps.terms.propose. - [`contractorOps.terms.get`](https://app.getoatmilk.com/docs/api/contractorOps.terms.get.md) — Read a contractor's agreement terms: the version in effect today (role, rate, pay schedule, hour limits per day, week, month or in total, dates, notice, where the work is done, notable clauses), versions starting later, an update waiting for signature or confirmation, the full history with what each version changed, how many of today's, this week's, month's and the agreement's hours are used, the agreement that ended with no decision yet and what happened since (ended), what happened to each filed agreement's terms (reads: reading, read, queued behind an update out for signature, kept as history, or failed, with the reason), and the renewal to suggest (renewal: the same terms for another stretch, from the day after the end, or from the day after it ended when they kept working, with effectiveFrom, the new dates in terms, its length, endsIn days, and due when it ends within 45 days or already has; null with no end date, when later terms are set, or while an update is waiting). Send it with contractorOps.terms.propose, changing anything first. - [`contractorOps.terms.linkRole`](https://app.getoatmilk.com/docs/api/contractorOps.terms.linkRole.md) — Link the current agreement's existing title to a matching active contractor job role, without changing the title or agreement terms. Requires terms id, roleId, expectedRevision and idempotencyKey. Only available when the current agreement has no role link; the change is audited. - [`contractorOps.terms.propose`](https://app.getoatmilk.com/docs/api/contractorOps.terms.propose.md) — Send new agreement terms for signature, with contractorId, terms, effectiveFrom, optional jobRoleId, send, an optional company signer (an administrator or finance member who signs in Oatmilk) and idempotencyKey. A titled first agreement must link to a matching active job role; a unique match is resolved when jobRoleId is omitted. With no agreement in effect it sends the full contractor agreement; otherwise a one-page amendment listing changes to pay, hours or other terms. Use contractorOps.titleChange.propose to change a current title. The terms take effect once everyone has signed. Only one update can wait at a time. A blank rate keeps the rate already agreed, and basisTermsId (the version in effect when you read it, or null for none) refuses the change when someone else changed the agreement since. - [`contractorOps.terms.read`](https://app.getoatmilk.com/docs/api/contractorOps.terms.read.md) — Have the agreement reader agent read a signed agreement already on file for a contractor (envelopeId) and propose its terms as a draft to confirm: role, rate, pay schedule, hour limits, dates, notice, where the work is done and notable clauses, each with the words it came from. Nothing takes effect until a person confirms it. - [`contractorOps.terms.set`](https://app.getoatmilk.com/docs/api/contractorOps.terms.set.md) — Set a contractor's agreement terms without a signature (for example a daily or weekly hour limit, or terms of an agreement signed elsewhere), with contractorId, terms, effectiveFrom, optional jobRoleId and idempotencyKey. A titled agreement must link to an active matching job role; a unique match is resolved when jobRoleId is omitted. They take effect from effectiveFrom: hour limits apply to every timesheet entry from then on, and role, rate, pay schedule and dates update the profile. A blank rate keeps the rate already agreed (the previous version's, or the profile's). Pass basisTermsId (the version in effect when you read it, or null for none) to be refused when someone else changed the agreement since. A first version can't start after hours that are waiting to be paid. ## contractorOps.timesheets - [`contractorOps.timesheets.list`](https://app.getoatmilk.com/docs/api/contractorOps.timesheets.list.md) — List timesheets awaiting approval, approved, or open by pay period. Hours submitted for dates no pay period covers (a manual cadence, or before the first period) are listed per contractor under unscheduled. With periodId, returns every time entry with its custom form values, missing required fields, and an estimated payout; with contractorId and unscheduled=true, returns that contractor's hours outside a pay period to review and the approved ones ready to pay (prepare them with contractorOps.payouts.prepare and hoursIds). Each listed period carries its live payout, if any, and pipeline says whether payouts are prepared automatically and whether contractors get reminder emails. - [`contractorOps.timesheets.review`](https://app.getoatmilk.com/docs/api/contractorOps.timesheets.review.md) — Approve or return submitted time entries in a single atomic review, for one pay period (periodId) or for one contractor's hours outside a pay period (contractorId). Supply decisions with hoursId, expectedRevision and decision, a review reason, and idempotencyKey. A decision on a mistake the contractor reported in approved hours also carries the correctionId and decides approved or declined; approving it applies the corrected values, and declining leaves the entry as approved. Approval never sends money. ## contractorOps.titleChange - [`contractorOps.titleChange.override`](https://app.getoatmilk.com/docs/api/contractorOps.titleChange.override.md) — Administrator override of a contractor title change with a reason, job role, date and idempotency key. A pending title amendment is voided before the override is recorded; its audit and document history remain. - [`contractorOps.titleChange.plan`](https://app.getoatmilk.com/docs/api/contractorOps.titleChange.plan.md) — Plan a contractor title change from the current agreement. Returns the current title, organization-local today, pending or later agreement, and every signer carried forward with whether an inactive member needs replacement; no document is sent or saved. - [`contractorOps.titleChange.propose`](https://app.getoatmilk.com/docs/api/contractorOps.titleChange.propose.md) — Create a title-change amendment tied to an active organization job role with a different title, copying every other current agreement term and all required signer roles. The contractor uses their current email; company signers must be active finance or admin members. Optional companySigner and signerReplacements select current recipients before send. The title stays pending until every signer signs and its effective date arrives. Requires contractorId, roleId, effectiveFrom, send and idempotencyKey. ## contractorOps.wise - [`contractorOps.wise.status`](https://app.getoatmilk.com/docs/api/contractorOps.wise.status.md) — Read whether Wise payouts are enabled in this environment: sandbox or production, write token and signing key presence (never their values), business profile, and optionally available balances (amountMinor in minor units). ## contractors.access - [`contractors.access.reset`](https://app.getoatmilk.com/docs/api/contractors.access.reset.md) — Administrators only: end a contractor's portal link with id, expectedRevision and a written reason (at least 10 characters), so they can be invited again. Use it when the wrong account accepted an invitation or their sign-in email changed. Open invitations are cancelled. Their hours, agreements and payments stay. The reason is audited. Send a new invitation afterwards. ## contractors.agreements - [`contractors.agreements.create`](https://app.getoatmilk.com/docs/api/contractors.agreements.create.md) — Create a draft agreement document for a contractor from a confirmed file (fileId), with a title and the contractor's expectedContractorRevision. Nothing is sent until contractors.agreements.send. - [`contractors.agreements.download`](https://app.getoatmilk.com/docs/api/contractors.agreements.download.md) — Get a one-minute download link for an agreement document, optionally for one version. - [`contractors.agreements.get`](https://app.getoatmilk.com/docs/api/contractors.agreements.get.md) — Read one portal agreement document with its status, version and the contractor's signature events. - [`contractors.agreements.list`](https://app.getoatmilk.com/docs/api/contractors.agreements.list.md) — List agreement documents sent through the contractor portal for signature (draft, requested, signed, declined or void), optionally for one contractor. For the agreement on file, signed copies, NDAs and terms, use contractorOps.agreements.list and contractorOps.terms.get. - [`contractors.agreements.remind`](https://app.getoatmilk.com/docs/api/contractors.agreements.remind.md) — Email a contractor a reminder to sign the agreement document waiting for them, at most once a day, with expectedRevision. - [`contractors.agreements.revise`](https://app.getoatmilk.com/docs/api/contractors.agreements.revise.md) — Replace an unsigned agreement document with a new confirmed file (fileId) and a reason, with expectedRevision. It goes back to draft as a new version; a signed one can't be revised (send an amendment with contractorOps.terms.propose). - [`contractors.agreements.send`](https://app.getoatmilk.com/docs/api/contractors.agreements.send.md) — Ask a contractor who has joined the portal to sign a draft agreement document, with expectedRevision. Only they can sign it, in person, in their portal. - [`contractors.agreements.void`](https://app.getoatmilk.com/docs/api/contractors.agreements.void.md) — Void an unsigned agreement document with a reason and expectedRevision, so it can't be signed. The record is kept; a signed one can't be voided. ## contractors - [`contractors.create`](https://app.getoatmilk.com/docs/api/contractors.create.md) — Add a contractor with displayName, legalName, email (unique in the organization), an optional two-letter country, an optional jobRoleId (an active role from contractorOps.roles.list; add one with contractorOps.roles.save) and idempotencyKey. No invitation is sent: invite them with contractors.invite, or share the link from contractors.invitations.link. - [`contractors.get`](https://app.getoatmilk.com/docs/api/contractors.get.md) — Read one contractor by id: display and legal name, email, country, whether portal access is on (active), job role (job_role_id), reviewed tax profile (residency, services in Canada, identity confirmed) and revision. - [`contractors.invite`](https://app.getoatmilk.com/docs/api/contractors.invite.md) — Email a contractor an invitation to the contractor portal, with id and expectedRevision. It replaces any open invitation and lasts 7 days, at most 3 a day. The portal only shows them their own hours, agreements and payments. Refused once they've joined. - [`contractors.list`](https://app.getoatmilk.com/docs/api/contractors.list.md) — List contractors, newest first: display and legal name, email, country, whether portal access is on (active), their job role (job_role_id) and revision. Filter by query (display or legal name); page with limit and offset. For roles, rates, pay cadence, agreement on file and pending hours use contractorOps.directory.list. - [`contractors.taxReport`](https://app.getoatmilk.com/docs/api/contractors.taxReport.md) — Report a calendar year's contractor payments for T4A and T4A-NR review (calendarYear): reviewed payments for services and returns in CAD per contractor, payouts paid outside Wise, bank debits still to attribute, and open items. It holds no tax numbers. - [`contractors.update`](https://app.getoatmilk.com/docs/api/contractors.update.md) — Change a contractor's displayName, legalName or country with id and expectedRevision. Only an administrator can turn portal access on or off (active). Change their email with contractors.email.update and their job role with contractorOps.roles.assign. ## contractors.email - [`contractors.email.update`](https://app.getoatmilk.com/docs/api/contractors.email.update.md) — Change the email a contractor is invited at, until they join the portal, with id, expectedRevision and email. It must be unique in the organization, and open invitations to the old address are revoked. Once they've joined, the email is their sign-in and can't change here. The audit log records that it changed, never the addresses. ## contractors.files - [`contractors.files.confirm`](https://app.getoatmilk.com/docs/api/contractors.files.confirm.md) — Confirm an uploaded contractor document (fileId) once its bytes are uploaded. Its size, SHA-256 and file type are checked against what was prepared. - [`contractors.files.prepare`](https://app.getoatmilk.com/docs/api/contractors.files.prepare.md) — Prepare a private upload for a contractor document (PDF, Word, Markdown or text, up to 20 MB) with filename, mimeType, sizeBytes and sha256. PUT the unchanged bytes to uploadUrl, then call contractors.files.confirm; alreadyUploaded means the same file is already stored and only needs confirming. ## contractors.hours - [`contractors.hours.list`](https://app.getoatmilk.com/docs/api/contractors.hours.list.md) — List time entries logged by contractors (date, minutes, description, status draft, submitted, approved or rejected, review reason), filtered by contractorId, status and from and to dates. For timesheets by pay period use contractorOps.timesheets.list. - [`contractors.hours.review`](https://app.getoatmilk.com/docs/api/contractors.hours.review.md) — Approve or return one submitted time entry with id, expectedRevision, decision (approved or rejected) and a reason. Approving never sends money. To review a whole pay period at once use contractorOps.timesheets.review. ## contractors.invitations - [`contractors.invitations.fixEmail`](https://app.getoatmilk.com/docs/api/contractors.invitations.fixEmail.md) — Fix the email a contractor was invited at and send a new invitation in one step, until they join the portal, with id, expectedRevision and email. Open invitations to the old address stop working and their link says it was replaced, and a new 7-day invitation is queued to the corrected address. The email must be unique in the organization and different from the current one; at most 6 invitations a day. Refused once they've joined. - [`contractors.invitations.link`](https://app.getoatmilk.com/docs/api/contractors.invitations.link.md) — Get a portal invitation link to send a contractor yourself: an open invitation is reused, or a 7-day one is created, and nothing is emailed. The same link keeps working until it expires or is revoked. - [`contractors.invitations.list`](https://app.getoatmilk.com/docs/api/contractors.invitations.list.md) — List a contractor's portal invitations (contractorId): the email each was sent to, when it expires, and whether it was accepted or revoked. - [`contractors.invitations.revoke`](https://app.getoatmilk.com/docs/api/contractors.invitations.revoke.md) — Revoke an open portal invitation with id and expectedRevision, so its link stops working. ## contractors.notifications - [`contractors.notifications.list`](https://app.getoatmilk.com/docs/api/contractors.notifications.list.md) — List contractor emails (portal invitations and signature requests and reminders) with their status: queued, sent (the provider accepted it), failed or cancelled. Queued doesn't mean delivered. ## contractors.pastPayments - [`contractors.pastPayments.link`](https://app.getoatmilk.com/docs/api/contractors.pastPayments.link.md) — Confirm a Wise recipient (profileId, recipientId) is this contractor and link every past outgoing transfer to it, as services by default (treatment), with expectedContractorRevision. Transfers in a closed period are skipped and counted. Never sends money. - [`contractors.pastPayments.suggest`](https://app.getoatmilk.com/docs/api/contractors.pastPayments.suggest.md) — Suggest the Wise recipients whose past transfers were likely to this contractor (the family name and a given name match), with each one's number of transfers, total and dates, to link with contractors.pastPayments.link. ## contractors.payments - [`contractors.payments.bind`](https://app.getoatmilk.com/docs/api/contractors.payments.bind.md) — Link one bank payment (transactionId with expectedTransactionRevision) to a contractor through a confirmed Wise recipient (recipientBindingId), as services, reimbursement or unknown, with a reason. - [`contractors.payments.get`](https://app.getoatmilk.com/docs/api/contractors.payments.get.md) — Read one outgoing bank transaction's contractor link or open identification question, with contractor choices. No payment credentials are returned. - [`contractors.payments.identify`](https://app.getoatmilk.com/docs/api/contractors.payments.identify.md) — Preview or apply contractor identification for one outgoing bank transaction, or queue a bounded backfill for all eligible transactions. Applying requires an idempotency key and never sends money. - [`contractors.payments.jobs`](https://app.getoatmilk.com/docs/api/contractors.payments.jobs.md) — List recent contractor payment identification backfill jobs with progress counts and status, without bank or recipient details. - [`contractors.payments.link`](https://app.getoatmilk.com/docs/api/contractors.payments.link.md) — Link an outgoing bank transaction to a contractor, with its expected revision, treatment, reason and idempotency key. Remembering its payee can identify later payments; no payout is sent. - [`contractors.payments.list`](https://app.getoatmilk.com/docs/api/contractors.payments.list.md) — List all payments to a contractor from linked bank transfers, Oatmilk payouts and imported history, with timesheet coverage and calendar-year totals for review. - [`contractors.payments.unbind`](https://app.getoatmilk.com/docs/api/contractors.payments.unbind.md) — Unlink a bank payment from a contractor with id, expectedRevision and a reason. The history is kept. - [`contractors.payments.unlink`](https://app.getoatmilk.com/docs/api/contractors.payments.unlink.md) — Remove a contractor link from a bank transaction, or mark it as unrelated to contractors, with its expected revision, reason and idempotency key. The audit history remains. ## contractors.recipients - [`contractors.recipients.bind`](https://app.getoatmilk.com/docs/api/contractors.recipients.bind.md) — Confirm that a Wise recipient (profileId, recipientId) is this contractor, with a reason and the contractor's expectedContractorRevision, so transfers to it are attributed to them. A recipient can belong to one contractor. - [`contractors.recipients.list`](https://app.getoatmilk.com/docs/api/contractors.recipients.list.md) — List the Wise recipients confirmed as a contractor's (contractorId), with who confirmed each and why. ## contractors.taxProfile - [`contractors.taxProfile.update`](https://app.getoatmilk.com/docs/api/contractors.taxProfile.update.md) — Record a contractor's reviewed tax residency (canadian, nonresident or unknown), whether their services were performed in Canada (servicesInCanada), and whether their identity was verified, with a reason and expectedRevision. A Wise currency or bank country alone doesn't establish residency. ## contractors.templates - [`contractors.templates.create`](https://app.getoatmilk.com/docs/api/contractors.templates.create.md) — Add a contractor agreement template from an uploaded original (fileId from contractors.files.confirm) with a title. - [`contractors.templates.download`](https://app.getoatmilk.com/docs/api/contractors.templates.download.md) — Get a template's Markdown, or a one-minute download link for its uploaded original. - [`contractors.templates.list`](https://app.getoatmilk.com/docs/api/contractors.templates.list.md) — List contractor agreement templates: drafts written in Markdown and uploaded originals (PDF, Word or text). - [`contractors.templates.preview`](https://app.getoatmilk.com/docs/api/contractors.templates.preview.md) — Read a template to review it: Markdown, text or Word as text (up to 500,000 characters), or a one-minute link to a PDF. - [`contractors.templates.update`](https://app.getoatmilk.com/docs/api/contractors.templates.update.md) — Replace a Markdown template's title and content (contentMarkdown) with id and expectedRevision. ## recruiting.applications - [`recruiting.applications.assign`](https://app.getoatmilk.com/docs/api/recruiting.applications.assign.md) — Set who interviews a candidate (interviewerUserIds, active members only) with applicationId, expectedRevision and idempotencyKey. Interviewers can then see the application and submit a scorecard. - [`recruiting.applications.convert`](https://app.getoatmilk.com/docs/api/recruiting.applications.convert.md) — Add a hired candidate as a contractor with applicationId and idempotencyKey: the contractor is created from their name and email (or an existing contractor with that email is linked) and given the posting's job role. No invitation is sent; invite them from Contractors when ready. - [`recruiting.applications.create`](https://app.getoatmilk.com/docs/api/recruiting.applications.create.md) — Add a candidate to a posting yourself (a referral or someone you sourced): postingId, candidate name, email, optional phone, location and links, source (referral, sourced, other), an optional note, and idempotencyKey. A person already in recruiting with that email is reused. Only administrators and the posting's hiring manager can do this. - [`recruiting.applications.get`](https://app.getoatmilk.com/docs/api/recruiting.applications.get.md) — Read one application: the candidate, answers, cover letter, resume details, interviews, scorecards, timeline with notes, the candidate's other applications and possible duplicates (same name, different email). Interviewers see other people's scorecards only after submitting their own. Candidate details are visible to administrators and the posting's hiring manager; interviewers see only the applications they're assigned to, without email or phone. - [`recruiting.applications.list`](https://app.getoatmilk.com/docs/api/recruiting.applications.list.md) — List applications for one posting (postingId) or every posting you can see, filtered by status (active, rejected, hired) or a name search, with stage, source, resume presence, interviewers and a count of yes and no scorecards. Candidate details are visible to administrators and the posting's hiring manager; interviewers see only the applications they're assigned to, without email or phone. - [`recruiting.applications.move`](https://app.getoatmilk.com/docs/api/recruiting.applications.move.md) — Move an application to another stage of its posting with applicationId, stage, expectedRevision and idempotencyKey. Moving to Hired marks the candidate hired. Every move is recorded on the timeline. - [`recruiting.applications.reject`](https://app.getoatmilk.com/docs/api/recruiting.applications.reject.md) — Reject an application with applicationId, reason (not_qualified, experience, not_a_fit, compensation, location, withdrew, no_response, position_filled, duplicate, spam, other), an optional note, expectedRevision and idempotencyKey. Nothing is sent to the candidate. - [`recruiting.applications.restore`](https://app.getoatmilk.com/docs/api/recruiting.applications.restore.md) — Return a rejected application to its stage with applicationId, expectedRevision and idempotencyKey. - [`recruiting.applications.resume`](https://app.getoatmilk.com/docs/api/recruiting.applications.resume.md) — Get a link, valid for one minute, to download an application's resume. Available to administrators, the hiring manager and assigned interviewers. - [`recruiting.applications.summarize`](https://app.getoatmilk.com/docs/api/recruiting.applications.summarize.md) — Have AI summarize an application against the posting (a short summary, strengths, gaps and questions to ask), when the posting has aiAssist on, with applicationId and idempotencyKey. It never scores, ranks or decides; people make every decision. Administrators and the hiring manager only. ## recruiting.board - [`recruiting.board.get`](https://app.getoatmilk.com/docs/api/recruiting.board.get.md) — Read the organization's public job board: its address, the name shown and the introduction. - [`recruiting.board.save`](https://app.getoatmilk.com/docs/api/recruiting.board.save.md) — Set up or change the public job board: slug (its address, 3 to 60 lowercase letters, digits and dashes), displayName, intro, and expectedRevision after the first save. ## recruiting.interviews - [`recruiting.interviews.mine`](https://app.getoatmilk.com/docs/api/recruiting.interviews.mine.md) — The candidates you're assigned to interview: their posting and stage, your upcoming interviews, and whether you've submitted a scorecard. - [`recruiting.interviews.save`](https://app.getoatmilk.com/docs/api/recruiting.interviews.save.md) — Add an interview to an application, or update one with id and expectedRevision: title, scheduledAt, durationMinutes, location (a room or a video link), interviewerUserIds, notes, and status (scheduled, completed, cancelled). No calendar invitation is sent. Interviewers on the panel are added to the application. ## recruiting.notes - [`recruiting.notes.add`](https://app.getoatmilk.com/docs/api/recruiting.notes.add.md) — Add a note to an application's timeline with applicationId, body and idempotencyKey. Everyone who can see the application can read it. ## recruiting.postings - [`recruiting.postings.get`](https://app.getoatmilk.com/docs/api/recruiting.postings.get.md) — Read one job posting with its description, pay, application questions, stages, hiring manager, the public job board address, and for private postings the private link to share with chosen candidates. - [`recruiting.postings.list`](https://app.getoatmilk.com/docs/api/recruiting.postings.list.md) — List job postings with status (draft, open, closed), visibility (public on the job board, or private by link), hiring manager, stages, and how many applications are in each stage. Administrators and finance see every posting; others see postings whose hiring team they're on. canViewCandidates says whether candidate details can be opened. - [`recruiting.postings.rotateLink`](https://app.getoatmilk.com/docs/api/recruiting.postings.rotateLink.md) — Replace a posting's private link so the old link stops working, with id, expectedRevision and idempotencyKey. Returns the new link. - [`recruiting.postings.save`](https://app.getoatmilk.com/docs/api/recruiting.postings.save.md) — Create a job posting (often from a job role with jobRoleId), or update one with id and expectedRevision: title, department, location, workplace (remote, hybrid, onsite), employmentType, description, payText, vacancyExists, aiAssist (disclosed on the posting), visibility (public or private), questions (key, label, type text/textarea/yes_no/select/url, required, options) and stages (Applied first, Hired last). The creator becomes hiring manager unless an administrator chooses someone; only an administrator or the current hiring manager can change it. New postings start as drafts. - [`recruiting.postings.status`](https://app.getoatmilk.com/docs/api/recruiting.postings.status.md) — Open a posting to take applications, close it, or return it to draft, with id, status, expectedRevision and idempotencyKey. Administrators, finance and the hiring manager can do this. Public postings appear on the job board only while open. ## recruiting.scorecards - [`recruiting.scorecards.submit`](https://app.getoatmilk.com/docs/api/recruiting.scorecards.submit.md) — Submit or update your scorecard for an application (optionally for one interview): recommendation (strong_no, no, yes, strong_yes), ratings (label and score 1 to 4), summary, and idempotencyKey. Administrators, the hiring manager and assigned interviewers can submit. ## recruiting.team - [`recruiting.team.list`](https://app.getoatmilk.com/docs/api/recruiting.team.list.md) — List the members who can be hiring managers or interviewers (administrators, finance and contributors, never accountants), with their email. ## contractorOps.activity.list Read a human-readable activity log for one contractor or all contractors: profile and payment changes, hours, reviews, agreements, reminders sent, and payouts, newest first. `GET | POST /api/v1/accounting/contractorOps.activity.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_activity_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | | `before` | string (date-time) | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.activity.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.activity.list ## contractorOps.agreements.addContractor Add the other party of a signed agreement or NDA that has no contractor as a new contractor and file the document with them: envelopeId, contractor (displayName, legalName, email, optional otherEmails, country, region, jobRoleId, title, rate, startDate, endDate) and idempotencyKey. Refused when an address already belongs to a contractor (use contractorOps.agreements.linkContractor), or when someone has the same or a close name unless notDuplicateOf lists them. An agreement's role, rate and dates become its terms when its title has a matching jobRoleId; otherwise the terms wait for review and role selection. With no terms given, the document is read for its terms to check. An NDA stays apart from the agreement. No invitation is sent. A document is never filed twice, whoever asks. `POST /api/v1/accounting/contractorOps.agreements.addContractor` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_agreements_add_contractor` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `envelopeId` | string (ID) | Yes | The ID of a document sent for signature, from signing.envelopes.list. | | `contractor` | object | Yes | No other fields. | | `contractor.displayName` | string | Yes | 1–200 characters. | | `contractor.legalName` | string | Yes | 1–300 characters. | | `contractor.email` | string (email) | Yes | An email address. at most 320 characters. | | `contractor.otherEmails` | array of strings (email) | | at most 10 items; each at most 320 characters. | | `contractor.country` | string or null | | | | `contractor.region` | string or null | | | | `contractor.jobRoleId` | string (ID) or null | | | | `contractor.title` | string or null | | A short title. | | `contractor.rate` | object or null | | | | `contractor.rate.amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,15}$. | | `contractor.rate.currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `contractor.rate.unit` | enum | Yes | One of: `hour`, `day`, `month`. | | `contractor.startDate` | string (date) or null | | | | `contractor.endDate` | string (date) or null | | | | `notDuplicateOf` | array of strings (ID) | | at most 20 items. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.agreements.addContractor \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "envelopeId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "contractor": { "displayName": "Synthetic Ventures Inc.", "legalName": "Synthetic Ventures Inc.", "email": "finance@example.com" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.agreements.addContractor ## contractorOps.agreements.contractorDraft Prefill for filing a signed agreement or NDA that has no contractor (envelopeId, or intakeItemId of the filed upload): the other party's name, email addresses, role and the job role and level it matches, rate, start and end dates and where they live, as the document states them with the words each came from, plus existing contractors it may be (any address Oatmilk knows for them, then the same name, then a close name) with which of the document's addresses each uses and whether the document is newer than their terms. Pass the name and email being added (name, email) to look those up too. Nothing is saved. `GET | POST /api/v1/accounting/contractorOps.agreements.contractorDraft` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_agreements_contractor_draft` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `envelopeId` | string (ID) | | The ID of a document sent for signature, from signing.envelopes.list. | | `intakeItemId` | string (ID) | | The ID of the related record. | | `name` | string | | A display name. at most 300 characters. | | `email` | string (email) | | An email address. at most 320 characters. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractorOps.agreements.contractorDraft \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'envelopeId=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.agreements.contractorDraft ## contractorOps.agreements.ended List agreements that have ended for active contractors with no decision yet (not renewed, no renewal waiting for signatures, and no end decision recorded): the end date, what happened since (minutes logged, timesheet entries, pay periods, payouts and payments after the end date), a one-line summary, and the renewal to offer (the same terms from the day after it ended). An ended agreement blocks nothing. Contractors who kept working come first. Filter by contractorId. Renew with contractorOps.terms.propose, or record a decision with contractorOps.terms.endDecision (continuing keeps them working without renewing, ending lets it end); either decision removes it from this list. `GET | POST /api/v1/accounting/contractorOps.agreements.ended` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_agreements_ended` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.agreements.ended \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.agreements.ended ## contractorOps.agreements.importExecuted Mark an already-signed agreement as this contractor's executed agreement using a fileId from the signing upload flow, a title, the execution date, and idempotencyKey. `POST /api/v1/accounting/contractorOps.agreements.importExecuted` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_agreements_import_executed` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `fileId` | string | Yes | 1–200 characters. | | `title` | string | Yes | A short title. 1–200 characters. | | `executedOn` | string (date) | Yes | A date, as YYYY-MM-DD. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.agreements.importExecuted \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "fileId": "example", "title": "Synthetic services agreement", "executedOn": "2026-09-01" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.agreements.importExecuted ## contractorOps.agreements.keepAsHistory Keep an agreement filed with a contractor as history instead of reading its terms (contractorId, envelopeId, idempotencyKey), such as one that couldn't be read, isn't a contractor agreement, or whose terms were entered by hand. Its terms are never read or applied, and it leaves the checklist. Refused while its terms are being read. `POST /api/v1/accounting/contractorOps.agreements.keepAsHistory` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_agreements_keep_as_history` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `envelopeId` | string (ID) | Yes | The ID of a document sent for signature, from signing.envelopes.list. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.agreements.keepAsHistory \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "envelopeId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.agreements.keepAsHistory ## contractorOps.agreements.linkContractor File a signed agreement or NDA that has no contractor with an existing contractor (envelopeId, contractorId, idempotencyKey). An agreement newer than their terms is read for its terms to check and replaces them once confirmed (a draft read from an older agreement is replaced; while an update is out for signature it waits); an older or undated one is kept as history and never changes newer terms. An NDA stays apart from the agreement. `POST /api/v1/accounting/contractorOps.agreements.linkContractor` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_agreements_link_contractor` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `envelopeId` | string (ID) | Yes | The ID of a document sent for signature, from signing.envelopes.list. | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.agreements.linkContractor \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "envelopeId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.agreements.linkContractor ## contractorOps.agreements.list List contractor agreements from the versions of each contractor's terms, for a view: all, active (in effect), ending (in effect with an end date within 45 days), expired (the one in effect has passed its end date) or waiting (an update waiting for a signature or a check), with counts for every view. Each has the contractor, role, rate, start and end dates, days left, state (active, ending, expired, upcoming, replaced or waiting), finance's end decision for an expired one (continuing, ending, or null when undecided), when a renewal starts, whether new terms are waiting (renewalPending), for an undecided expired one whether they kept working and what happened since (the same as contractorOps.agreements.ended), and href, the contractor's Agreement tab. Each version also says where its signed copy is (copy: a document in Oatmilk, or a link when it was signed elsewhere). Without contractorId it covers the organization, lists active contractors with no agreement recorded, and lists signed agreements imported with the dates not stated (undated), which aren't in the views by date. With contractorId it covers that contractor and adds their documents on file from contractor agreements, signing envelopes and imports (items, each an agreement or an nda, with signedElsewhere for a copy kept at its link and datesNotStated for an undated one), whether an agreement is on file (status on_file, signed_elsewhere, terms_on_file, requested, draft or agreement_missing), and whether a signed NDA is. `GET | POST /api/v1/accounting/contractorOps.agreements.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_agreements_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `view` | enum | | One of: `all`, `active`, `ending`, `expired`, `waiting`. Default `"all"`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.agreements.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.agreements.list ## contractorOps.agreements.sendTemplate Create a contractor agreement envelope from the contractor agreement template, prefilled from the profile (legal name, address, role, rate, cadence, term), optionally with a company signer who is an administrator or finance member (found by email), and send it for signature. values overrides template fields by their keys, like contractor.estimatedEngagement. Prefer contractorOps.terms.propose, which also records the terms Oatmilk enforces. `POST /api/v1/accounting/contractorOps.agreements.sendTemplate` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_contractor_ops_agreements_send_template` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `title` | string | | A short title. 1–200 characters. | | `message` | string | | at most 2000 characters. | | `send` | boolean | | Default `true`. | | `companySigner` | object | | No other fields. | | `companySigner.name` | string | Yes | A display name. 1–200 characters. | | `companySigner.email` | string (email) | Yes | An email address. at most 320 characters. | | `companySigner.title` | string | | A short title. at most 160 characters. | | `values` | map | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.agreements.sendTemplate \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.agreements.sendTemplate ## contractorOps.agreements.unlinked List signed agreements and NDAs that aren't filed with any contractor or other record, newest first, with the other party as recorded and a link to each. `GET | POST /api/v1/accounting/contractorOps.agreements.unlinked` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_agreements_unlinked` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.agreements.unlinked \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.agreements.unlinked ## contractorOps.directory.list List every contractor with their roles, department, status, rate and pay cadence, payment profile completeness (masked), agreement on file, pending hours, next hours due date, last activity, their recent pay periods and live payouts with whether payouts are prepared for them automatically (pay, the same as contractorOps.profiles.get returns, for when they are paid next), and for anyone who has not joined the portal where their invitation stands (invite: state and label, such as not_invited, sent, delivered, bounced or expired; null once they have joined). Filter by query, status, department, or role. `GET | POST /api/v1/accounting/contractorOps.directory.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_directory_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | | `query` | string | | Text to search for. at most 200 characters. | | `status` | enum | | Only include records with this status. One of: `onboarding`, `active`, `inactive`. | | `department` | string | | at most 120 characters. | | `role` | string | | A person's access level in the company. at most 80 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.directory.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.directory.list ## contractorOps.encryption.resetFingerprintKey Only while the organization's fingerprint key can't be read any more (the encryption key it was sealed with is lost), replace it with a new one, with a reason, so tax numbers and payment details can be saved and paid out again. Fingerprints are worked out again for every tax number and payment email that can still be read; ones that can't be read lose theirs and are flagged until they're entered again, and saved Wise recipients are created again on their next payout. Refused while the fingerprint key can still be read. Audited with counts and the reason only. Only an administrator in the dashboard can; not available to API keys or MCP clients. `POST /api/v1/accounting/contractorOps.encryption.resetFingerprintKey` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Destructive: confirm with a person first Not available over MCP: Destructive key management. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 5–500 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.encryption.resetFingerprintKey \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.encryption.resetFingerprintKey ## contractorOps.encryption.status Read how contractor tax numbers and payment details are encrypted here: whether the encryption key and the previous key are set and usable (never their values), how many values the scheduled run still has to encrypt or re-encrypt, how many can't be read with the keys set now, recorded failures by error code, when a value was last tried, whether the previous key is safe to remove (only once nothing in any organization uses it), whether the organization's fingerprint key can be read (fingerprintKey) and can be reset (canResetFingerprintKey), and warnings. Counts only. `GET | POST /api/v1/accounting/contractorOps.encryption.status` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_contractor_ops_encryption_status` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.encryption.status \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.encryption.status ## contractorOps.forms.list List the custom hours forms contractors fill in for each time entry, including which one is the default. `GET | POST /api/v1/accounting/contractorOps.forms.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_forms_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `includeArchived` | boolean | | Also include archived records. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.forms.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.forms.list ## contractorOps.forms.save Create or update a custom hours form with name, ordered fields (key, label, type text/textarea/number/select/multiselect/date/checkbox/url, required, options, help), optional isDefault or archived, expectedRevision for updates, and idempotencyKey. `POST /api/v1/accounting/contractorOps.forms.save` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractor_ops_forms_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `name` | string | Yes | A display name. 1–120 characters. | | `description` | string | | A short description. at most 1000 characters. Default `""`. | | `fields` | array of objects | Yes | at most 40 items. | | `fields[].key` | string | Yes | Matches ^[a-z][a-zA-Z0-9_]{0,39}$. | | `fields[].label` | string | Yes | 1–120 characters. | | `fields[].type` | enum | Yes | One of: `text`, `textarea`, `number`, `select`, `multiselect`, `date`, `checkbox`, `url`. | | `fields[].required` | boolean | | Default `false`. | | `fields[].options` | array of strings | | at most 50 items; each 1–100 characters. | | `fields[].help` | string | | at most 300 characters. | | `isDefault` | boolean | | | | `archived` | boolean | | Whether the record is archived. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.forms.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Synthetic Ventures Inc.", "fields": [ { "key": "key", "label": "example", "type": "text" } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.forms.save ## contractorOps.history.allocations.get What one contractor's payments and imported timesheets look like for sharing a payment across timesheets (contractorId; transactionId or payoutId to include that payment even when it isn't linked to them yet). payments lists each outgoing bank payment linked to them and each payment recorded in Oatmilk with no pay period, newest first, with its date, amount, kind (wise, bank or recorded), what it already gave which timesheets (allocations), and the timesheets an earlier Mark paid tied to it without amounts (legacyHistoryIds). timesheets lists their imported timesheets with the period, hours, amount (null for a fixed fee with no amount), what payments gave each, and whether it's unpaid, partly paid or paid and how. Nothing changes. `GET | POST /api/v1/accounting/contractorOps.history.allocations.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_history_allocations_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `transactionId` | string (ID) | | The ID of a bank or card transaction, from transactions.list. | | `payoutId` | string (ID) | | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractorOps.history.allocations.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'contractorId=4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.history.allocations.get ## contractorOps.history.allocations.save Share one payment across several imported timesheets (weeks, two-week periods or parts of a month), or pay one timesheet from several payments. Send contractorId and either transactionId (an outgoing bank payment) or payoutId (a payment recorded in Oatmilk with no pay period) with the allocations of that payment, or historyId with the allocations of that timesheet. Each allocation is historyId, transactionId or payoutId, amountMinor, and closes (true counts a small shortfall, such as a fee, as paid in full). The list replaces what that payment (or timesheet) had; an empty list removes it, which is how a match is undone. A payment never gives more than it paid, a timesheet never takes more than it pays, and one payment pays one contractor. A timesheet its allocations cover (any allocation, when it has no amount) is marked paid on the latest payment's date; one no longer covered goes back to unpaid. Timesheets paid in their table or marked paid by hand keep that. Automatic matching leaves the payment alone afterwards. Optional note and idempotencyKey. Audited. `POST /api/v1/accounting/contractorOps.history.allocations.save` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_history_allocations_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `transactionId` | string (ID) | | The ID of a bank or card transaction, from transactions.list. | | `payoutId` | string (ID) | | The ID of the related record. | | `historyId` | string (ID) | | The ID of the related record. | | `allocations` | array of objects | Yes | at most 200 items. | | `allocations[].historyId` | string (ID) | Yes | The ID of the related record. | | `allocations[].transactionId` | string (ID) | | The ID of a bank or card transaction, from transactions.list. | | `allocations[].payoutId` | string (ID) | | The ID of the related record. | | `allocations[].amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,15}$. | | `allocations[].closes` | boolean | | | | `note` | string | | A short note, kept with the record. at most 500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.history.allocations.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "allocations": [ { "historyId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "amountMinor": "1250", "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" } ], "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.history.allocations.save ## contractorOps.history.list List every contractor timesheet as history: rows imported from a Notion database or CSV file, Oatmilk pay periods with hours or a payout, and milestone payouts. Each has the contractor, period, hours, rate, payout in minor units (the imported amount, or hours × rate plus GST/HST), GST/HST, status paid or unpaid with how (paid in the imported table, marked paid in Oatmilk with the date and bank payment, or the payout's state), when it was submitted and how many days late, its source (imported or oatmilk), and historyId for imported rows. An imported row whose dates also have hours logged in Oatmilk is marked alsoInOatmilk and left out of totals so nothing counts twice. Filter by contractorId, status (all, paid, unpaid), source (all, imported, oatmilk), and from and to (periods overlapping those dates). Unpaid is sorted by contractor, oldest first, with each contractor's totals in groups; the other views are newest first. Totals cover every match, by currency. `GET | POST /api/v1/accounting/contractorOps.history.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_history_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `status` | enum | | Only include records with this status. One of: `all`, `paid`, `unpaid`. Default `"all"`. | | `source` | enum | | Where the record came from. One of: `all`, `imported`, `oatmilk`. Default `"all"`. | | `from` | string (date) | | The first date to include, as YYYY-MM-DD. | | `to` | string (date) | | The last date to include, as YYYY-MM-DD. | | `limit` | integer | | How many results to return at most. 1 to 500. Default `200`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.history.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.history.list ## contractorOps.history.markPaid Record imported timesheets as paid, for example once the payroll that settled them went out: ids (the historyId of each imported row, up to 500), paidOn (today when omitted; never in the future), an optional transactionId of the bank payment that paid them (money out of an account, and then every row must be the same contractor's; it counts once as money sent even if it's linked to the contractor later), an optional note, and idempotencyKey. Rows already paid are left as they are and counted in alreadyPaid. Oatmilk pay periods aren't marked here; they're paid through their payouts. Importing the table again never marks them unpaid. Audited for each contractor. `POST /api/v1/accounting/contractorOps.history.markPaid` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_history_mark_paid` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `ids` | array of strings (ID) | Yes | 1–500 items. | | `paidOn` | string (date) | | A date, as YYYY-MM-DD. | | `transactionId` | string (ID) or null | | The ID of a bank or card transaction, from transactions.list. | | `note` | string | | A short note, kept with the record. at most 500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.history.markPaid \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "ids": [ "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.history.markPaid ## contractorOps.history.paymentAsks.answer Answer a payment question (id, decision, idempotencyKey). paid marks the question's timesheets that are still unpaid paid, on the payment's date and with the payment (audited like contractorOps.history.markPaid); other records that the payment was for something else, and it is never matched to timesheets or asked about again. `POST /api/v1/accounting/contractorOps.history.paymentAsks.answer` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_history_payment_asks_answer` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `decision` | enum | Yes | paid marks the question's unpaid timesheets paid, matched to the bank payment; other says the payment was for something else and it is never asked about again. One of: `paid`, `other`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.history.paymentAsks.answer \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "decision": "paid" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.history.paymentAsks.answer ## contractorOps.history.paymentAsks.list The open questions about a bank payment to a contractor that is close to, or covers some of, their unpaid imported timesheets but isn't an exact match: who was paid, the payment (date, amount, Wise or bank), the timesheets it would settle with their periods, hours and amounts, and why Oatmilk isn't sure. Answer each with contractorOps.history.paymentAsks.answer. `GET | POST /api/v1/accounting/contractorOps.history.paymentAsks.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_history_payment_asks_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.history.paymentAsks.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.history.paymentAsks.list ## contractorOps.history.rateBackfill.apply Save agreement-derived hourly rate snapshots for explicitly selected imported timesheets after reviewing the preview. Supply each history row id and expectedRevision plus idempotencyKey. Rechecks the agreement and source row atomically; a changed, split-rate or ambiguous week fails without saving any row. Does not mark paid, prepare, approve or send a payout. `POST /api/v1/accounting/contractorOps.history.rateBackfill.apply` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_contractor_ops_history_rate_backfill_apply` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `rows` | array of objects | Yes | 1–500 items. | | `rows[].id` | string (ID) | Yes | The record's ID. | | `rows[].expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.history.rateBackfill.apply \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "rows": [ { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.history.rateBackfill.apply ## contractorOps.history.rateBackfill.preview Preview imported timesheets missing a source amount and rate, with the active hourly agreement covering each complete period. Shows which weeks are ready to save and which need review because an agreement changes, is missing, or has a currency or rate conflict. No data changes and no payout is prepared. `GET | POST /api/v1/accounting/contractorOps.history.rateBackfill.preview` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_contractor_ops_history_rate_backfill_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `from` | string (date) | | The first date to include, as YYYY-MM-DD. | | `to` | string (date) | | The last date to include, as YYYY-MM-DD. | | `limit` | integer | | How many results to return at most. 1 to 500. Default `100`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.history.rateBackfill.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.history.rateBackfill.preview ## contractorOps.hours.history Read one time entry's history (hoursId): every change from logging to approval and any correction, oldest first, with the entry's dates, who acted (contractor, a finance member by email, or the platform), the old and new values, the reason given, whether it's on hold for an open correction, and where it stands in a payout (unpaid, scheduled or paid). `GET | POST /api/v1/accounting/contractorOps.hours.history` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_hours_history` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `hoursId` | string (ID) | Yes | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractorOps.hours.history \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'hoursId=1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.hours.history ## contractorOps.import.agreements Bring agreements and NDAs in from a contracts table such as the Notion Contractor Contracts database, or the NDAs from a documents table such as Contractors Documents (source notion with databaseId, the database's link or ID, or csv with rows). mapping names the column for any fields whose column names don't match; the rest are matched by name. Fields: contractor (or email), title, kind (a type column: NDA, Contract…), signed (a signed column), role, rate (hourly), currency, startDate, endDate, file (a Notion files column with the signed copy), fileUrl (a link to a copy kept elsewhere) and nda (an NDA link or file on a contract row). A signed agreement with a start date becomes a version of the contractor's terms from that date, unless terms starting that day are already on file; one without dates is recorded as signed with the dates not stated and never becomes terms, so it can't replace dated ones; an unsigned, draft or stale row is kept as an unsigned draft. The signed copy is kept: a file attached to a Notion row is brought in with the Notion file import and filed as the agreement's signed document (linked to its terms), and a copy that's only a link is recorded as signed elsewhere. NDAs are kept apart from the contractor agreement. The role is linked to a job role by title and, for a title with several levels, the level whose pay band holds the rate; otherwise jobRoleSuggestion says what to do, and a contractor with no job role gets the one their current agreement matches. A table without start dates only brings in its NDAs. Every row is kept by the row it came from, so importing again fills in what's missing (a signed copy, a job role) instead of adding it twice. Rows for someone not in Oatmilk are left out with the reason; a rate of $1 or less is read as none. Nothing is sent to sign and nobody is emailed. A column whose name says it holds payment or tax details is never read, and problems says so when mapping names one. With preview true nothing is saved, and each row says whether it's already on file (onFile) and where the table disagrees with the terms on file (conflict). `POST /api/v1/accounting/contractorOps.import.agreements` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_import_agreements` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | enum | Yes | notion reads a Notion database on the server; csv takes the rows of a parsed CSV file. One of: `csv`, `notion`. | | `rows` | array of maps | | For csv: each row as column name to value, at most 500. 1–500 items. | | `databaseId` | string | | For notion: the database's link or ID. The database must be shared with the Oatmilk connection. 1–2000 characters. | | `mapping` | object | | The column for any fields whose column names don't match (null for none). Fields left out, or every field when it's omitted, are matched by column name; the result returns the mapping it used, so a preview shows the suggested one. | | `mapping.contractor` | string or null | | The column holding Contractor (required), or null when the table has none. | | `mapping.email` | string or null | | The column holding Email, or null when the table has none. | | `mapping.title` | string or null | | The column holding Agreement, or null when the table has none. | | `mapping.kind` | string or null | | The column holding Type, or null when the table has none. | | `mapping.signed` | string or null | | The column holding Signed, or null when the table has none. | | `mapping.role` | string or null | | The column holding Role, or null when the table has none. | | `mapping.rate` | string or null | | The column holding Rate, or null when the table has none. | | `mapping.rateUnit` | string or null | | The column holding Paid per, or null when the table has none. | | `mapping.currency` | string or null | | The column holding Currency, or null when the table has none. | | `mapping.startDate` | string or null | | The column holding Start date, or null when the table has none. | | `mapping.endDate` | string or null | | The column holding End date, or null when the table has none. | | `mapping.file` | string or null | | The column holding Signed copy (file), or null when the table has none. | | `mapping.fileUrl` | string or null | | The column holding Signed copy (link), or null when the table has none. | | `mapping.nda` | string or null | | The column holding NDA, or null when the table has none. | | `preview` | boolean | | true checks every row and saves nothing. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.import.agreements \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "source": "csv", "rows": [ {} ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.import.agreements ## contractorOps.import.contractors Bring contractors in from a table: source notion with databaseId (the database's link or ID), or source csv with rows. mapping names the column for any fields whose column names don't match (null for none); the others are matched by name, and a column that isn't in the table is reported in problems. A required field no column matches is refused with the table's columns listed. Fields: name (required), legalName, workEmail, personalEmail, wiseEmail, wiseLink, phone, roles, jobRole, department, jurisdiction, address (split into street, city, province or state, postal code and country), city, postalCode, chargesSalesTax, salesTaxRate, discord, github, portfolio, startDate, notes, currency, and the bank columns accountHolderName, bankName, bankAddress, accountNumber, institutionNumber, transitNumber, routingNumber, iban and swiftBic. People already in Oatmilk are matched by email and then by name, and only have blanks filled in; new people are added without an invitation, and no one is emailed. jobRole is matched to a job role by title and level; a title with several levels takes the level whose pay band holds the person's current rate, and otherwise jobRoleLevels lists the levels to choose from. The address used to reach each person is work email, then personal, then Wise, unless emails (row reference to address) chooses another; a row with no email is left out until emails gives it one. Payment details are saved only when an administrator imports and only for someone with none yet, in the currency column's currency, else the bank's country's, else the person's country's, else CAD. They are validated, sealed and audited, and saving them doesn't email the contractor; a row whose details don't validate is still imported and listed in paymentProblems. Payment details are never returned, in a preview, a result or a message: each row's plan has paymentDetails with present, method (bank_transfer or wise_email), masked (at most the last four characters of the account number or IBAN, or the Wise email masked) and fields (the names of the details the row has), a Wise email is left out of emails and masked when it's the address used, and a column whose name says it holds payment or tax details is only read for the bank and Wise fields. With preview true nothing is saved and the plan for every row comes back. `POST /api/v1/accounting/contractorOps.import.contractors` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_import_contractors` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | enum | Yes | notion reads a Notion database on the server; csv takes the rows of a parsed CSV file. One of: `csv`, `notion`. | | `rows` | array of maps | | For csv: each row as column name to value, at most 500. 1–500 items. | | `databaseId` | string | | For notion: the database's link or ID. The database must be shared with the Oatmilk connection. 1–2000 characters. | | `mapping` | object | | The column for any fields whose column names don't match (null for none). Fields left out, or every field when it's omitted, are matched by column name; the result returns the mapping it used, so a preview shows the suggested one. | | `mapping.name` | string or null | | The column holding Name (required), or null when the table has none. | | `mapping.legalName` | string or null | | The column holding Legal name, or null when the table has none. | | `mapping.workEmail` | string or null | | The column holding Work email, or null when the table has none. | | `mapping.personalEmail` | string or null | | The column holding Personal email, or null when the table has none. | | `mapping.wiseEmail` | string or null | | The column holding Wise email, or null when the table has none. | | `mapping.wiseLink` | string or null | | The column holding Wise link, or null when the table has none. | | `mapping.phone` | string or null | | The column holding Phone, or null when the table has none. | | `mapping.roles` | string or null | | The column holding Roles, or null when the table has none. | | `mapping.jobRole` | string or null | | The column holding Job role, or null when the table has none. | | `mapping.department` | string or null | | The column holding Department, or null when the table has none. | | `mapping.jurisdiction` | string or null | | The column holding Province or country, or null when the table has none. | | `mapping.address` | string or null | | The column holding Address, or null when the table has none. | | `mapping.city` | string or null | | The column holding City, or null when the table has none. | | `mapping.postalCode` | string or null | | The column holding Postal code, or null when the table has none. | | `mapping.chargesSalesTax` | string or null | | The column holding Charges GST/HST, or null when the table has none. | | `mapping.salesTaxRate` | string or null | | The column holding GST/HST rate, or null when the table has none. | | `mapping.discord` | string or null | | The column holding Discord, or null when the table has none. | | `mapping.github` | string or null | | The column holding GitHub, or null when the table has none. | | `mapping.linkedin` | string or null | | The column holding LinkedIn, or null when the table has none. | | `mapping.portfolio` | string or null | | The column holding Portfolio, or null when the table has none. | | `mapping.startDate` | string or null | | The column holding Start date, or null when the table has none. | | `mapping.notes` | string or null | | The column holding Notes, or null when the table has none. | | `mapping.currency` | string or null | | The column holding Payment currency, or null when the table has none. | | `mapping.accountHolderName` | string or null | | The column holding Account holder, or null when the table has none. | | `mapping.bankName` | string or null | | The column holding Bank name, or null when the table has none. | | `mapping.bankAddress` | string or null | | The column holding Bank address, or null when the table has none. | | `mapping.accountNumber` | string or null | | The column holding Account number, or null when the table has none. | | `mapping.institutionNumber` | string or null | | The column holding Institution number, or null when the table has none. | | `mapping.transitNumber` | string or null | | The column holding Transit number, or null when the table has none. | | `mapping.routingNumber` | string or null | | The column holding Routing number, or null when the table has none. | | `mapping.iban` | string or null | | The column holding IBAN, or null when the table has none. | | `mapping.swiftBic` | string or null | | The column holding SWIFT/BIC, or null when the table has none. | | `preview` | boolean | | true checks every row and saves nothing. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `emails` | map | | The address to use for a row, by the row's reference (its Notion page link, or Row 2, Row 3 for a CSV file), instead of work, personal, then Wise email. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.import.contractors \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "source": "csv", "rows": [ {} ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.import.contractors ## contractorOps.import.hours Bring past timesheets in as history from a table (source notion with databaseId, the database's link or ID, or csv with rows; mapping names the column for any fields whose column names don't match, and the others are matched by name). Fields: contractor (or firstName and lastName, or email), period (a date or a range, with optional periodEnd), hours, and optional rate, amount, tax, currency, paid, notes and submitted. Rows are matched to contractors by email, then name. Imported hours appear in each contractor's history, spending and on-time insights, but never become pay periods or payouts. A row that is a timesheet already here (the same Notion page or CSV row, or the same contractor's for the same hours starting or ending the same day) updates it instead of adding a duplicate: edited dates, hours, rate, amount, GST/HST and notes are taken, a blank cell erases nothing, and Paid marks an unpaid timesheet paid but an unpaid row never marks a paid one unpaid. The result lists what was added, corrected and marked paid in saved (counts and rows) and in summaryText, and afterwards new unpaid timesheets are settled against bank payments already linked to the contractor (payments). A column whose name says it holds payment or tax details is never read, and problems says so when mapping names one. With preview true nothing is saved and changes says what would happen. `POST /api/v1/accounting/contractorOps.import.hours` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_import_hours` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | enum | Yes | notion reads a Notion database on the server; csv takes the rows of a parsed CSV file. One of: `csv`, `notion`. | | `rows` | array of maps | | For csv: each row as column name to value, at most 500. 1–500 items. | | `databaseId` | string | | For notion: the database's link or ID. The database must be shared with the Oatmilk connection. 1–2000 characters. | | `mapping` | object | | The column for any fields whose column names don't match (null for none). Fields left out, or every field when it's omitted, are matched by column name; the result returns the mapping it used, so a preview shows the suggested one. | | `mapping.contractor` | string or null | | The column holding Contractor (required), or null when the table has none. | | `mapping.firstName` | string or null | | The column holding First name, or null when the table has none. | | `mapping.lastName` | string or null | | The column holding Last name, or null when the table has none. | | `mapping.email` | string or null | | The column holding Email, or null when the table has none. | | `mapping.period` | string or null | | The column holding Period (required), or null when the table has none. | | `mapping.periodEnd` | string or null | | The column holding Period end, or null when the table has none. | | `mapping.hours` | string or null | | The column holding Hours (required), or null when the table has none. | | `mapping.rate` | string or null | | The column holding Rate, or null when the table has none. | | `mapping.amount` | string or null | | The column holding Amount, or null when the table has none. | | `mapping.tax` | string or null | | The column holding GST/HST, or null when the table has none. | | `mapping.currency` | string or null | | The column holding Currency, or null when the table has none. | | `mapping.paid` | string or null | | The column holding Paid, or null when the table has none. | | `mapping.notes` | string or null | | The column holding Notes, or null when the table has none. | | `mapping.submitted` | string or null | | The column holding Submitted, or null when the table has none. | | `preview` | boolean | | true checks every row and saves nothing. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.import.hours \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "source": "csv", "rows": [ {} ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.import.hours ## contractorOps.import.hoursSync Sync past timesheets from the Notion Contractors Hours database in one step (databaseId once, its link or ID; later syncs leave it out and use the remembered one): checks the database, then adds new timesheets, corrects edited ones (dates, hours, rate, amount, GST/HST, notes) and marks unpaid ones paid when Notion says Paid (never the reverse), without duplicating any, then settles new unpaid timesheets against bank payments already linked to the contractor. Returns counts, the change for each row, the payments that settled timesheets or now need a person's OK, and a plain-words summary. Bank and tax columns are never read. Send a new idempotencyKey for each sync. `POST /api/v1/accounting/contractorOps.import.hoursSync` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_contractor_ops_import_hours_sync` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `databaseId` | string | | The Contractors Hours database's link or ID. Needed once; Oatmilk remembers it, and later syncs leave it out. 1–2000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.import.hoursSync \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.import.hoursSync ## contractorOps.import.hoursSyncStatus Whether Notion is connected, the remembered Contractors Hours database (id and title), whether it is synced automatically about every 6 hours, and how the last sync went. `GET | POST /api/v1/accounting/contractorOps.import.hoursSyncStatus` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_import_hours_sync_status` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.import.hoursSyncStatus \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.import.hoursSyncStatus ## contractorOps.insights.get Contractor spending and timesheet insights from imported timesheets and Oatmilk pay periods and payouts: money sent (linked bank payments, payouts paid outside Wise, and bank payments recorded when past timesheets were marked paid), money still owed (unpaid imported timesheets and payouts not yet paid), paid plus owed, what the work cost by the month it was done (payouts including GST/HST) next to money sent by payment month, GST/HST, hours, monthly and weekly (Monday) series with empty ones as zero, monthly and weekly averages, each contractor's totals, cost in each of the last twelve months, hours a week over the last four weeks against the four before, on-time and late timesheets with average days late, the most and least punctual (at least three timesheets), and a few plain observations and trends. Amounts are in minor units of one currency (CAD unless currency is given); other currencies are listed. `GET | POST /api/v1/accounting/contractorOps.insights.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_insights_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `currency` | string | | Three-letter currency code, such as CAD or USD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.insights.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.insights.get ## contractorOps.payments.reveal Show a contractor's full payment details to an administrator in the dashboard with an audited reason. Not available to API keys or MCP clients. `POST /api/v1/accounting/contractorOps.payments.reveal` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required Not available over MCP: Full bank details would pass through the agent. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 5–500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.payments.reveal \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.payments.reveal ## contractorOps.payments.update Administrators set a contractor's payment method (Wise email, bank transfer, Interac, or other), currency, and payment details. Omitted detail fields keep their saved values; null clears them. Responses are masked, the audit log records only which fields changed, the saved Wise recipient is reset, and approved payouts must be approved again. When the saved details can't be read (the profile's paymentDetailsState isn't readable), nothing changes unless the details sent are a complete set for the method; with an encryption key that isn't set any more, that also needs replaceUnreadable: true. `POST /api/v1/accounting/contractorOps.payments.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` Not available over MCP: Replacing unreadable saved bank details needs an administrator in the dashboard. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `method` | enum | Yes | One of: `wise_email`, `bank_transfer`, `interac`, `other`. | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `details` | object | Yes | No other fields. | | `details.wiseEmail` | string (email) or null | | | | `details.wiseLink` | string or null | | | | `details.accountHolderName` | string or null | | | | `details.bankName` | string or null | | | | `details.bankAddress` | string or null | | | | `details.accountNumber` | string or null | | | | `details.swiftBic` | string or null | | | | `details.iban` | string or null | | | | `details.institutionNumber` | string or null | | | | `details.transitNumber` | string or null | | | | `details.routingNumber` | string or null | | | | `details.accountType` | enum or null | | One of: `checking`, `savings`. | | `details.country` | string or null | | | | `details.interacEmail` | string (email) or null | | | | `details.otherInstructions` | string or null | | | | `replaceUnreadable` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.payments.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "method": "wise_email", "currency": "CAD", "details": {} }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.payments.update ## contractorOps.payments.verify Record that an administrator confirmed a contractor's current payment details out of band, using the payment profile revision they reviewed and a note. `POST /api/v1/accounting/contractorOps.payments.verify` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractor_ops_payments_verify` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `note` | string | Yes | A short note, kept with the record. 3–500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.payments.verify \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "expectedRevision": 3, "note": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.payments.verify ## contractorOps.payouts.approve Approve a prepared payout with expectedRevision and idempotencyKey. Approval records the contractor's current payment details revision; if those details change later, the payout must be approved again before sending. `POST /api/v1/accounting/contractorOps.payouts.approve` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractor_ops_payouts_approve` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `note` | string | | A short note, kept with the record. at most 500 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.payouts.approve \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.payouts.approve ## contractorOps.payouts.cancel Cancel a payout that has not been funded, releasing its time entries for a future payout. An unfunded Wise transfer is cancelled first. Requires a reason, expectedRevision, and idempotencyKey. `POST /api/v1/accounting/contractorOps.payouts.cancel` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_contractor_ops_payouts_cancel` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.payouts.cancel \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.payouts.cancel ## contractorOps.payouts.get Read one contractor payout with its time entries, calculation, approval, Wise quote, recipient and transfer identifiers, and the related activity run. `GET | POST /api/v1/accounting/contractorOps.payouts.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_payouts_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractorOps.payouts.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.payouts.get ## contractorOps.payouts.list List contractor payouts with amounts, tax, status, approvals, Wise transfer progress, and errors. Filter by contractor, period, or status. `GET | POST /api/v1/accounting/contractorOps.payouts.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_payouts_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `periodId` | string (ID) | | The ID of a contractor pay period. | | `status` | enum | | Only include records with this status. One of: `draft`, `pending_approval`, `approved`, `processing`, `sent`, `failed`, `cancelled`, `paid_manually`, `attention`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.payouts.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.payouts.list ## contractorOps.payouts.markPaid Record a payout as paid outside Wise with a payment reference, the payment date, expectedRevision, and idempotencyKey. `POST /api/v1/accounting/contractorOps.payouts.markPaid` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractor_ops_payouts_mark_paid` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `reference` | string | Yes | 3–200 characters. | | `paidOn` | string (date) | Yes | A date, as YYYY-MM-DD. | | `note` | string | | A short note, kept with the record. at most 500 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.payouts.markPaid \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reference": "example", "paidOn": "2026-09-01" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.payouts.markPaid ## contractorOps.payouts.prepare Prepare a payout from approved hours for a pay period (periodId), or a milestone payout for a contractor (contractorId with hoursIds or amountMinor). Amounts use exact minor units: approved minutes × hourly rate, days × day rate, or fixed and monthly rules, plus GST/HST only at a rate finance confirmed (salesTaxStatus shows when the contractor's declared tax isn't confirmed yet). Hours can never be included in two active payouts. `POST /api/v1/accounting/contractorOps.payouts.prepare` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_payouts_prepare` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `periodId` | string (ID) | | The ID of a contractor pay period. | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `hoursIds` | array of strings (ID) | | A list of record IDs. at most 500 items. | | `amountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,15}$. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `description` | string | | A short description. at most 500 characters. | | `includeEarlierUnpaid` | boolean | | Also include earlier unpaid. Default `true`. | | `applyTax` | boolean | | Default `true`. | | `submitForApproval` | boolean | | Default `true`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.payouts.prepare \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "periodId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.payouts.prepare ## contractorOps.payouts.send Send an approved payout through Wise (quote, recipient, transfer, fund from balance). Requires an administrator in the dashboard, the confirmed total and currency, expectedRevision, and idempotencyKey. Refused while an hour in the payout has a correction waiting, and while an hour is still inside the wait after approval unless overrideReason says why (audited). Retries never create a second transfer. Not available to API keys or MCP clients. `POST /api/v1/accounting/contractorOps.payouts.send` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) Not available over MCP: Moves money. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `confirmTotalMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,15}$. | | `confirmCurrency` | string | Yes | Three-letter currency code, such as CAD. | | `overrideReason` | string | | 3–500 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.payouts.send \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "confirmTotalMinor": "1250", "confirmCurrency": "CAD" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.payouts.send ## contractorOps.periods.list List contractor pay periods with period dates, hours due date, pay date, status (open, submitted, approved, paid, skipped), and submitted and approved minutes. `GET | POST /api/v1/accounting/contractorOps.periods.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_periods_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `status` | enum | | Only include records with this status. One of: `open`, `submitted`, `approved`, `paid`, `skipped`. | | `from` | string (date) | | The first date to include, as YYYY-MM-DD. | | `to` | string (date) | | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.periods.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.periods.list ## contractorOps.periods.update Mark a pay period as skipped (no hours expected) or reopen it, with expectedRevision, an optional note, and idempotencyKey. Skipped periods receive no reminders and don't accept hours. A period with submitted or approved hours or a payout can't be skipped. `POST /api/v1/accounting/contractorOps.periods.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractor_ops_periods_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `status` | enum | Yes | Only include records with this status. One of: `open`, `skipped`. | | `note` | string | | A short note, kept with the record. at most 1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.periods.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "status": "open" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.periods.update ## contractorOps.profiles.get Read one contractor's operations profile: roles, department, dates, contact details, address, rate, pay cadence, sales tax and corporation details, hours form, completeness, and a masked payment profile. Also returns their recent pay periods and payouts, and whether Oatmilk prepares their payouts automatically, so a caller can say when they are paid next. Full bank numbers are never returned. `GET | POST /api/v1/accounting/contractorOps.profiles.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_profiles_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractorOps.profiles.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'contractorId=4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.profiles.get ## contractorOps.profiles.update Create or update a contractor's operations profile with contractorId, profile fields, expectedRevision (omit only when no profile exists yet), and idempotencyKey. Changing the pay cadence regenerates future empty pay periods. Changing the declared GST/HST answers stops finance's confirmation from applying and removes the GST/HST it added from payouts that haven't been paid and have no Wise transfer (approved ones go back for approval). `POST /api/v1/accounting/contractorOps.profiles.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractor_ops_profiles_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `profile` | object | Yes | No other fields. | | `profile.legalName` | string or null | | | | `profile.roles` | array of strings | | at most 12 items; each 1–80 characters. | | `profile.phone` | string or null | | | | `profile.discordUsername` | string or null | | | | `profile.githubUsername` | string or null | | | | `profile.linkedinUrl` | string or null | | | | `profile.portfolioUrl` | string or null | | | | `profile.customContacts` | array of objects | | at most 10 items. | | `profile.customContacts[].label` | string | Yes | 1–60 characters. | | `profile.customContacts[].value` | string | Yes | 1–500 characters. | | `profile.address` | object | | No other fields. | | `profile.address.line1` | string | | at most 200 characters. | | `profile.address.line2` | string | | at most 200 characters. | | `profile.address.city` | string | | at most 120 characters. | | `profile.address.region` | string | | at most 120 characters. | | `profile.address.postalCode` | string | | at most 20 characters. | | `profile.address.country` | string | | Matches ^([A-Za-z]{2})?$. | | `profile.notes` | string or null | | Notes kept with the record. | | `profile.tax` | object | | No other fields. | | `profile.tax.chargesSalesTax` | boolean | | Default `false`. | | `profile.tax.salesTaxRateBps` | integer or null | | | | `profile.tax.salesTaxNumber` | string or null | | | | `profile.tax.province` | enum or null | | One of: `AB`, `BC`, `MB`, `NB`, `NL`, `NS`, `NT`, `NU`, `ON`, `PE`, `QC`, `SK`, `YT`. | | `profile.tax.operatesAsCorporation` | boolean | | Default `false`. | | `profile.tax.corporationName` | string or null | | | | `profile.tax.businessNumber` | string or null | | | | `profile.tax.corporationAddress` | object or null | | | | `profile.tax.corporationAddress.line1` | string | | at most 200 characters. | | `profile.tax.corporationAddress.line2` | string | | at most 200 characters. | | `profile.tax.corporationAddress.city` | string | | at most 120 characters. | | `profile.tax.corporationAddress.region` | string | | at most 120 characters. | | `profile.tax.corporationAddress.postalCode` | string | | at most 20 characters. | | `profile.tax.corporationAddress.country` | string | | Matches ^([A-Za-z]{2})?$. | | `profile.tax.signerName` | string or null | | | | `profile.tax.signerTitle` | string or null | | | | `profile.tax.usTaxForm` | enum or null | | One of: `w9`, `w8ben`, `w8bene`, `unsure`. | | `profile.tax.usTaxClassification` | enum or null | | One of: `individual`, `c_corporation`, `s_corporation`, `partnership`, `trust_estate`, `llc_c`, `llc_s`, `llc_p`, `other`. | | `profile.department` | string or null | | | | `profile.title` | string or null | | A short title. | | `profile.startDate` | string (date) or null | | | | `profile.endDate` | string (date) or null | | | | `profile.status` | enum | | Only include records with this status. One of: `onboarding`, `active`, `inactive`. | | `profile.rate` | object or null | | | | `profile.rate.amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,15}$. | | `profile.rate.currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `profile.rate.unit` | enum | Yes | One of: `hour`, `day`, `month`, `fixed`. | | `profile.cadence` | object | | | | `profile.cadence.kind` | "weekly" | Yes | Which kind of record or job this is. | | `profile.cadence.weekStartsOn` | integer | | 0 to 6. Default `1`. | | `profile.cadence.dueDaysAfterPeriod` | integer | | 0 to 30. Default `1`. | | `profile.cadence.payWithinBusinessDays` | integer | | 0 to 30. Default `5`. | | `profile.cadence.kind` | "biweekly" | Yes | Which kind of record or job this is. | | `profile.cadence.anchorDate` | string (date) | Yes | A date, as YYYY-MM-DD. | | `profile.cadence.dueDaysAfterPeriod` | integer | | 0 to 30. Default `1`. | | `profile.cadence.payWithinBusinessDays` | integer | | 0 to 30. Default `5`. | | `profile.cadence.kind` | "semi_monthly" | Yes | Which kind of record or job this is. | | `profile.cadence.days` | array of values | Yes | | | `profile.cadence.dueDaysAfterPeriod` | integer | | 0 to 30. Default `1`. | | `profile.cadence.payWithinBusinessDays` | integer | | 0 to 30. Default `5`. | | `profile.cadence.kind` | "monthly" | Yes | Which kind of record or job this is. | | `profile.cadence.day` | integer | | 1 to 28. Default `1`. | | `profile.cadence.dueDaysAfterPeriod` | integer | | 0 to 30. Default `1`. | | `profile.cadence.payWithinBusinessDays` | integer | | 0 to 30. Default `5`. | | `profile.cadence.kind` | "manual" | Yes | Which kind of record or job this is. | | `profile.hoursFormId` | string (ID) or null | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.profiles.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "profile": {} }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.profiles.update ## contractorOps.reminders.run Run contractor operations now: create pay periods, send due, late, profile, and approval reminders that are due, and prepare payouts for approved periods. Safe to repeat; reminders are never sent twice. `POST /api/v1/accounting/contractorOps.reminders.run` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_contractor_ops_reminders_run` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.reminders.run \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.reminders.run ## contractorOps.roles.archive Archive a job role so it isn't offered for new contractors, or restore it (archived false), with expectedRevision and idempotencyKey. Contractors keep a role that was archived. `POST /api/v1/accounting/contractorOps.roles.archive` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_contractor_ops_roles_archive` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `archived` | boolean | Yes | Whether the record is archived. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.roles.archive \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "archived": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.roles.archive ## contractorOps.roles.archiveJob Archive, or restore (archived false), several levels of a job at once: levels with each id and expectedRevision, archived and idempotencyKey. Contractors keep a role that was archived. `POST /api/v1/accounting/contractorOps.roles.archiveJob` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Destructive: confirm with a person first MCP tool: `accounting_contractor_ops_roles_archive_job` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `levels` | array of objects | Yes | 1–100 items. | | `levels[].id` | string (ID) | Yes | The record's ID. | | `levels[].expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `archived` | boolean | Yes | Whether the record is archived. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.roles.archiveJob \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "levels": [ { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 } ], "archived": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.roles.archiveJob ## contractorOps.roles.assign Give a contractor a job role, or remove it with roleId null. `POST /api/v1/accounting/contractorOps.roles.assign` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_roles_assign` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `roleId` | string (ID) or null | Yes | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.roles.assign \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "roleId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.roles.assign ## contractorOps.roles.duplicateJob Copy a job under a new title: levels lists the ids to copy, title is the new job's name, with idempotencyKey. Each copy keeps the level, department, type, description, pay band and ladder order, and has nobody in it. `POST /api/v1/accounting/contractorOps.roles.duplicateJob` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_roles_duplicate_job` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `levels` | array of objects | Yes | 1–100 items. | | `levels[].id` | string (ID) | Yes | The record's ID. | | `title` | string | Yes | A short title. 1–200 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.roles.duplicateJob \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "levels": [ { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" } ], "title": "Synthetic services agreement" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.roles.duplicateJob ## contractorOps.roles.get Read one job role with its full description, pay band and current version. `GET | POST /api/v1/accounting/contractorOps.roles.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_roles_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractorOps.roles.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.roles.get ## contractorOps.roles.import Create or update job roles from a table: source notion with databaseId (the database's link or ID), or source csv with rows (column name to value). mapping names the column for any fields whose column names don't match (null for none); the others are matched by name, and a column that isn't in the table is reported in problems. A required field no column matches is refused with the table's columns listed. Fields: title (required), level, department, employmentType, summary, responsibilities, requirements, and either payBand text ("CAD 60–80/hour", "$90k–110k per year") or payMin, payMax, payCurrency, payUnit. Roles are matched by title and level, so importing again updates instead of duplicating, and a blank cell never erases what's written. A column whose name says it holds payment or tax details is never read, and problems says so when mapping names one. With preview true nothing is saved and the parsed roles and problems come back. `POST /api/v1/accounting/contractorOps.roles.import` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_roles_import` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | enum | Yes | notion reads a Notion database on the server; csv takes the rows of a parsed CSV file. One of: `csv`, `notion`. | | `rows` | array of maps | | For csv: each row as column name to value, at most 500. 1–500 items. | | `databaseId` | string | | For notion: the database's link or ID. The database must be shared with the Oatmilk connection. 1–2000 characters. | | `mapping` | object | | The column for any fields whose column names don't match (null for none). Fields left out, or every field when it's omitted, are matched by column name; the result returns the mapping it used, so a preview shows the suggested one. | | `mapping.title` | string or null | | The column holding Title (required), or null when the table has none. | | `mapping.level` | string or null | | The column holding Level, or null when the table has none. | | `mapping.department` | string or null | | The column holding Department, or null when the table has none. | | `mapping.employmentType` | string or null | | The column holding Type, or null when the table has none. | | `mapping.summary` | string or null | | The column holding Summary, or null when the table has none. | | `mapping.responsibilities` | string or null | | The column holding Responsibilities, or null when the table has none. | | `mapping.requirements` | string or null | | The column holding Requirements, or null when the table has none. | | `mapping.payBand` | string or null | | The column holding Pay band, or null when the table has none. | | `mapping.payMin` | string or null | | The column holding Pay from, or null when the table has none. | | `mapping.payMax` | string or null | | The column holding Pay up to, or null when the table has none. | | `mapping.payCurrency` | string or null | | The column holding Currency, or null when the table has none. | | `mapping.payUnit` | string or null | | The column holding Paid per, or null when the table has none. | | `preview` | boolean | | true checks every row and saves nothing. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.roles.import \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "source": "csv", "rows": [ {} ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.roles.import ## contractorOps.roles.list List the company's job roles (job descriptions for contractors and employees): title, level, department, type, summary, responsibilities, requirements, pay band (currency, lowest and highest amount in minor units, paid per hour, day, week, month or year), and the active contractors who have each role. A job is every role with the same title, and its levels form a ladder in levelOrder (1 is the first rung; null until someone reorders them). Archived roles are left out unless includeArchived is true. `GET | POST /api/v1/accounting/contractorOps.roles.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_roles_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `includeArchived` | boolean | | Also include archived records. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.roles.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.roles.list ## contractorOps.roles.reorder Put a job's levels in order, first rung first: levels lists each level's id and expectedRevision in the order wanted, with idempotencyKey. `POST /api/v1/accounting/contractorOps.roles.reorder` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_roles_reorder` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `levels` | array of objects | Yes | 1–100 items. | | `levels[].id` | string (ID) | Yes | The record's ID. | | `levels[].expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.roles.reorder \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "levels": [ { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.roles.reorder ## contractorOps.roles.save Create a job role, or update one with id and expectedRevision: title, level, department, employmentType (contractor, employee or either), summary, responsibilities, requirements, an optional pay band (payCurrency, payMinMinor, payMaxMinor, payUnit) and an optional levelOrder (its place on the job's ladder). Title and level together are unique. `POST /api/v1/accounting/contractorOps.roles.save` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractor_ops_roles_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `role` | object | Yes | A person's access level in the company. No other fields. | | `role.title` | string | Yes | A short title. 1–200 characters. | | `role.level` | string | | at most 80 characters. Default `""`. | | `role.department` | string | | at most 120 characters. Default `""`. | | `role.employmentType` | enum | | One of: `contractor`, `employee`, `either`. Default `"contractor"`. | | `role.summary` | string | | at most 2000 characters. Default `""`. | | `role.responsibilities` | string | | at most 20000 characters. Default `""`. | | `role.requirements` | string | | at most 20000 characters. Default `""`. | | `role.payCurrency` | string or null | | Default `null`. | | `role.payMinMinor` | string or null | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Default `null`. | | `role.payMaxMinor` | string or null | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Default `null`. | | `role.payUnit` | enum or null | | One of: `hour`, `day`, `week`, `month`, `year`. Default `null`. | | `role.levelOrder` | integer or null | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.roles.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "role": { "title": "Synthetic services agreement" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.roles.save ## contractorOps.roles.updateJob Rename a job, and optionally set its department and type, on every level at once. levels lists each level's id and expectedRevision (include archived levels so the whole job changes together), with title and idempotencyKey. It changes nothing if any level changed since it was read, or if the new title and a level's name are already a role. A level held through an agreement can't be renamed here. `POST /api/v1/accounting/contractorOps.roles.updateJob` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_roles_update_job` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `levels` | array of objects | Yes | 1–100 items. | | `levels[].id` | string (ID) | Yes | The record's ID. | | `levels[].expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `title` | string | Yes | A short title. 1–200 characters. | | `department` | string | | at most 120 characters. | | `employmentType` | enum | | One of: `contractor`, `employee`, `either`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.roles.updateJob \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "levels": [ { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 } ], "title": "Synthetic services agreement" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.roles.updateJob ## contractorOps.roles.versions List a job role's versions, newest first, with roleId: what the role said and paid at each version (title, level, department, type, summary, responsibilities, requirements and pay band), which parts changed from the version before (title, level, department, type, description or pay), who changed it and when, and how many sent agreements were made on that version. Every change to those parts is a new version, and an agreement keeps the version and pay it was made with. `GET | POST /api/v1/accounting/contractorOps.roles.versions` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_roles_versions` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `roleId` | string (ID) | Yes | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractorOps.roles.versions \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'roleId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.roles.versions ## contractorOps.salesTax.confirm Confirm or correct the GST/HST a contractor declared, with contractorId, expectedRevision (the profile revision reviewed), chargesSalesTax, salesTaxRateBps (1 to 10000 when charged), a note, and idempotencyKey. Payouts add sales tax only at a confirmed rate, and a confirmation stops applying when the declared answers change. Payouts that aren't paid and have no Wise transfer are updated to the confirmed rate; approved ones go back for approval. `POST /api/v1/accounting/contractorOps.salesTax.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractor_ops_sales_tax_confirm` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `chargesSalesTax` | boolean | Yes | | | `salesTaxRateBps` | integer or null | | | | `note` | string | Yes | A short note, kept with the record. 3–500 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.salesTax.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "expectedRevision": 3, "chargesSalesTax": true, "note": "Synthetic example from the docs", "salesTaxRateBps": 1 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.salesTax.confirm ## contractorOps.settings.get Read contractor operations preferences: reminder timing and limits, approvers, automatic payout preparation, default cadence, time zone, and Wise source currency. `GET | POST /api/v1/accounting/contractorOps.settings.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_settings_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.settings.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.settings.get ## contractorOps.settings.update Update contractor operations preferences with idempotencyKey and optional expectedRevision. Only supplied settings change. `POST /api/v1/accounting/contractorOps.settings.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractor_ops_settings_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `settings` | object | Yes | No other fields. | | `settings.remindersEnabled` | boolean | | | | `settings.hoursReminderLeadDays` | integer | | 0 to 14. | | `settings.lateReminderEveryDays` | integer | | 1 to 14. | | `settings.lateReminderMax` | integer | | 0 to 10. | | `settings.approvalReminderEveryDays` | integer | | 1 to 14. | | `settings.approvalReminderMax` | integer | | 0 to 10. | | `settings.profileReminderMax` | integer | | 0 to 12. | | `settings.approverEmails` | array of strings (email) | | at most 20 items; each at most 320 characters. | | `settings.autoPreparePayouts` | boolean | | | | `settings.defaultCadence` | object | | | | `settings.defaultCadence.kind` | "weekly" | Yes | Which kind of record or job this is. | | `settings.defaultCadence.weekStartsOn` | integer | | 0 to 6. Default `1`. | | `settings.defaultCadence.dueDaysAfterPeriod` | integer | | 0 to 30. Default `1`. | | `settings.defaultCadence.payWithinBusinessDays` | integer | | 0 to 30. Default `5`. | | `settings.defaultCadence.kind` | "biweekly" | Yes | Which kind of record or job this is. | | `settings.defaultCadence.anchorDate` | string (date) | Yes | A date, as YYYY-MM-DD. | | `settings.defaultCadence.dueDaysAfterPeriod` | integer | | 0 to 30. Default `1`. | | `settings.defaultCadence.payWithinBusinessDays` | integer | | 0 to 30. Default `5`. | | `settings.defaultCadence.kind` | "semi_monthly" | Yes | Which kind of record or job this is. | | `settings.defaultCadence.days` | array of values | Yes | | | `settings.defaultCadence.dueDaysAfterPeriod` | integer | | 0 to 30. Default `1`. | | `settings.defaultCadence.payWithinBusinessDays` | integer | | 0 to 30. Default `5`. | | `settings.defaultCadence.kind` | "monthly" | Yes | Which kind of record or job this is. | | `settings.defaultCadence.day` | integer | | 1 to 28. Default `1`. | | `settings.defaultCadence.dueDaysAfterPeriod` | integer | | 0 to 30. Default `1`. | | `settings.defaultCadence.payWithinBusinessDays` | integer | | 0 to 30. Default `5`. | | `settings.defaultCadence.kind` | "manual" | Yes | Which kind of record or job this is. | | `settings.timezone` | string | | 3–60 characters. | | `settings.wiseSourceCurrency` | string or null | | | | `settings.wiseTransferPurpose` | string or null | | | | `settings.wiseSourceOfFunds` | string or null | | | | `settings.paymentReferencePrefix` | string | | Matches ^[A-Za-z0-9 -]{0,10}$. | | `settings.payOnlyWhenAllApproved` | boolean | | | | `settings.autoApprove` | object | | No other fields. | | `settings.autoApprove.enabled` | boolean | Yes | | | `settings.autoApprove.maxEntryHours` | number or null | Yes | | | `settings.autoApprove.maxWeeklyHours` | number or null | Yes | | | `settings.autoApprove.afterApprovedPeriods` | integer | Yes | 0 to 24. | | `settings.paydayHeadsUpDays` | integer | | 0 to 14. | | `settings.paymentBufferDays` | integer | | 0 to 30. | | `settings.hoursNotionDatabase` | object or null | | | | `settings.hoursNotionDatabase.id` | string | Yes | The record's ID. 1–200 characters. | | `settings.hoursNotionDatabase.title` | string or null | Yes | A short title. | | `settings.hoursNotionSync` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.settings.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "settings": {} }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.settings.update ## contractorOps.taxForms.export Download the T4A and T4A-NR worksheet for a calendarYear with full tax numbers, to type into the CRA's web forms, with a reason. Only an administrator in the dashboard can; the download is audited with the year and the number of slips, never the numbers. Not available to API keys or MCP clients. `POST /api/v1/accounting/contractorOps.taxForms.export` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required Not available over MCP: Slip details with full tax numbers. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `calendarYear` | integer | Yes | A calendar year, such as 2026. 2000 to 2100. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 5–500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.taxForms.export \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "calendarYear": 2026, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.taxForms.export ## contractorOps.taxForms.get What to issue or review for each contractor paid in a calendar year (calendarYear), based on the organization's formation country. For a Canadian organization: CRA administrative policy calls for a T4A when annual service fees exceed $500 before sales tax or any tax was deducted; non-residents who worked in Canada generally get a T4A-NR at any amount with 15% Regulation 105 withholding unless waived. For a U.S. organization: shows a review checklist and never labels payments as Canadian T4A slips; the accountant confirms W-9/W-8, payer and payee status, service source, payment type and channel, and applicable reporting. Certain 1099-NEC and 1099-MISC payments after 2025 use a $2,000 threshold. Foreign formations require local accountant review. Numbers are masked; addresses are not shown to accountants; no U.S. forms are filed. `GET | POST /api/v1/accounting/contractorOps.taxForms.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_tax_forms_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `calendarYear` | integer | Yes | A calendar year, such as 2026. 2000 to 2100. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractorOps.taxForms.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'calendarYear=2026' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.taxForms.get ## contractorOps.taxInfo.get Read one contractor's tax info for slips (contractorId): masked tax numbers, the structured mailing address and its check, both legal names, identity confirmation, the last request, and the revisions an edit needs. `GET | POST /api/v1/accounting/contractorOps.taxInfo.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_tax_info_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractorOps.taxInfo.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'contractorId=4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.taxInfo.get ## contractorOps.taxInfo.list List every contractor with what their CRA slip still needs: the slip their residency calls for (T4A, T4A-NR, none, or unknown), tax numbers on file masked like •••-•••-286 (never in full), the mailing address check (missing or invalid parts by name), a legal name that differs between the contractor record and their profile, identity confirmation, and when their tax details were last requested. Filter with missingOnly or query. Fix a legal name with contractors.update (the record) or contractorOps.profiles.update (the profile). `GET | POST /api/v1/accounting/contractorOps.taxInfo.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_tax_info_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `missingOnly` | boolean | | | | `query` | string | | Text to search for. at most 200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.taxInfo.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.taxInfo.list ## contractorOps.taxInfo.request Email a contractor who uses the portal to add what their slip still needs (their tax number and mailing address), naming the fields and asking them to use the portal, never email. Nothing is sent unless this is called. The request is recorded with its date, the record of a reasonable effort to get the SIN. `POST /api/v1/accounting/contractorOps.taxInfo.request` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_tax_info_request` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.taxInfo.request \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.taxInfo.request ## contractorOps.taxInfo.update Add or replace a contractor's tax number (taxNumber: kind sin, bn, itn or foreign, the value, and country for a foreign number) or remove one (removeTaxNumber: the kind), with expectedRevision once that kind was saved before; and/or set the mailing address their slips go to (mailingAddress: line1 street, line2 unit, city, region province or state, postalCode, country) with expectedProfileRevision. A SIN must pass the check-digit test, a business number has 9 digits and an optional program account like RT0001, and a Canadian postal code looks like K1A 0B6. Numbers are stored encrypted and only ever returned masked; the audit log records that one changed, never the value. Finance can change a contractor's tax numbers at most 5 times an hour, and 50 times an hour across the organization (429 with Retry-After); a contractor's own portal saves have a separate budget, so neither can use up the other's. Nothing is returned about other contractors' numbers, except that an administrator in the dashboard saving a SIN or ITN is told who else has the same one (sameNumberAs). `POST /api/v1/accounting/contractorOps.taxInfo.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: The warning that another contractor already has the same SIN or ITN would let a client test numbers against everyone else's. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `taxNumber` | object | | No other fields. | | `taxNumber.kind` | enum | Yes | Which kind of record or job this is. One of: `sin`, `bn`, `itn`, `foreign`. | | `taxNumber.value` | string | Yes | 1–40 characters. | | `taxNumber.country` | string or null | | | | `removeTaxNumber` | enum | | One of: `sin`, `bn`, `itn`, `foreign`. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `mailingAddress` | object | | No other fields. | | `mailingAddress.line1` | string | | at most 200 characters. | | `mailingAddress.line2` | string | | at most 200 characters. | | `mailingAddress.city` | string | | at most 120 characters. | | `mailingAddress.region` | string | | at most 120 characters. | | `mailingAddress.postalCode` | string | | at most 20 characters. | | `mailingAddress.country` | string | | Matches ^([A-Za-z]{2})?$. | | `usTaxForm` | enum or null | | One of: `w9`, `w8ben`, `w8bene`, `unsure`. | | `usTaxClassification` | enum or null | | One of: `individual`, `c_corporation`, `s_corporation`, `partnership`, `trust_estate`, `llc_c`, `llc_s`, `llc_p`, `other`. | | `expectedProfileRevision` | integer | | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.taxInfo.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "taxNumber": { "kind": "sin", "value": "example" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.taxInfo.update ## contractorOps.terms.cancel Withdraw an update that is waiting for signature or confirmation, with id, expectedRevision, a reason and idempotencyKey. A document already sent is voided so it can't be signed. `POST /api/v1/accounting/contractorOps.terms.cancel` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_contractor_ops_terms_cancel` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.terms.cancel \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.terms.cancel ## contractorOps.terms.confirm Confirm a waiting version, such as terms Oatmilk read from an uploaded signed agreement, optionally correcting its terms or effective date and selecting a matching jobRoleId first, with id, expectedRevision and idempotencyKey. `POST /api/v1/accounting/contractorOps.terms.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractor_ops_terms_confirm` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `terms` | any | | | | `effectiveFrom` | string (date) | | | | `jobRoleId` | string (ID) | | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.terms.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.terms.confirm ## contractorOps.terms.endDecision Decide about an agreement at or past its end date, with the active terms version id, its expectedRevision, decision, and idempotencyKey. 'continuing' keeps them working and paid without renewing and stops the end-date reminders; 'ending' lets it end, so no new pay periods start after the end date (it does not stop the contractor logging hours or their portal access); null takes the decision back. An end date alone never stops work. To extend an agreement on paper instead, send new terms with a later end date using contractorOps.terms.propose. `POST /api/v1/accounting/contractorOps.terms.endDecision` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractor_ops_terms_end_decision` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `decision` | enum or null | Yes | What you decided. One of: `ending`, `continuing`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.terms.endDecision \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "decision": "ending" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.terms.endDecision ## contractorOps.terms.get Read a contractor's agreement terms: the version in effect today (role, rate, pay schedule, hour limits per day, week, month or in total, dates, notice, where the work is done, notable clauses), versions starting later, an update waiting for signature or confirmation, the full history with what each version changed, how many of today's, this week's, month's and the agreement's hours are used, the agreement that ended with no decision yet and what happened since (ended), what happened to each filed agreement's terms (reads: reading, read, queued behind an update out for signature, kept as history, or failed, with the reason), and the renewal to suggest (renewal: the same terms for another stretch, from the day after the end, or from the day after it ended when they kept working, with effectiveFrom, the new dates in terms, its length, endsIn days, and due when it ends within 45 days or already has; null with no end date, when later terms are set, or while an update is waiting). Send it with contractorOps.terms.propose, changing anything first. `GET | POST /api/v1/accounting/contractorOps.terms.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_terms_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractorOps.terms.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'contractorId=4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.terms.get ## contractorOps.terms.linkRole Link the current agreement's existing title to a matching active contractor job role, without changing the title or agreement terms. Requires terms id, roleId, expectedRevision and idempotencyKey. Only available when the current agreement has no role link; the change is audited. `POST /api/v1/accounting/contractorOps.terms.linkRole` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractor_ops_terms_link_role` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `roleId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.terms.linkRole \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "roleId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.terms.linkRole ## contractorOps.terms.propose Send new agreement terms for signature, with contractorId, terms, effectiveFrom, optional jobRoleId, send, an optional company signer (an administrator or finance member who signs in Oatmilk) and idempotencyKey. A titled first agreement must link to a matching active job role; a unique match is resolved when jobRoleId is omitted. With no agreement in effect it sends the full contractor agreement; otherwise a one-page amendment listing changes to pay, hours or other terms. Use contractorOps.titleChange.propose to change a current title. The terms take effect once everyone has signed. Only one update can wait at a time. A blank rate keeps the rate already agreed, and basisTermsId (the version in effect when you read it, or null for none) refuses the change when someone else changed the agreement since. `POST /api/v1/accounting/contractorOps.terms.propose` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_terms_propose` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `terms` | any | Yes | | | `effectiveFrom` | string (date) | Yes | | | `jobRoleId` | string (ID) | | The ID of the related record. | | `note` | string | | A short note, kept with the record. at most 2000 characters. | | `basisTermsId` | string (ID) or null | | | | `send` | boolean | | Default `true`. | | `companySigner` | object or null | | | | `companySigner.userId` | string | Yes | The ID of a person in your company. 1–200 characters. | | `companySigner.name` | string | Yes | A display name. 1–200 characters. | | `companySigner.email` | string (email) | Yes | An email address. at most 320 characters. | | `companySigner.title` | string or null | | A short title. | | `message` | string or null | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.terms.propose \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "terms": null, "effectiveFrom": "2026-09-01" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.terms.propose ## contractorOps.terms.read Have the agreement reader agent read a signed agreement already on file for a contractor (envelopeId) and propose its terms as a draft to confirm: role, rate, pay schedule, hour limits, dates, notice, where the work is done and notable clauses, each with the words it came from. Nothing takes effect until a person confirms it. `POST /api/v1/accounting/contractorOps.terms.read` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_terms_read` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `envelopeId` | string (ID) | Yes | The ID of a document sent for signature, from signing.envelopes.list. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.terms.read \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "envelopeId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.terms.read ## contractorOps.terms.set Set a contractor's agreement terms without a signature (for example a daily or weekly hour limit, or terms of an agreement signed elsewhere), with contractorId, terms, effectiveFrom, optional jobRoleId and idempotencyKey. A titled agreement must link to an active matching job role; a unique match is resolved when jobRoleId is omitted. They take effect from effectiveFrom: hour limits apply to every timesheet entry from then on, and role, rate, pay schedule and dates update the profile. A blank rate keeps the rate already agreed (the previous version's, or the profile's). Pass basisTermsId (the version in effect when you read it, or null for none) to be refused when someone else changed the agreement since. A first version can't start after hours that are waiting to be paid. `POST /api/v1/accounting/contractorOps.terms.set` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_terms_set` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `terms` | any | Yes | | | `effectiveFrom` | string (date) | Yes | | | `jobRoleId` | string (ID) | | The ID of the related record. | | `note` | string | | A short note, kept with the record. at most 2000 characters. | | `basisTermsId` | string (ID) or null | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.terms.set \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "terms": null, "effectiveFrom": "2026-09-01" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.terms.set ## contractorOps.timesheets.list List timesheets awaiting approval, approved, or open by pay period. Hours submitted for dates no pay period covers (a manual cadence, or before the first period) are listed per contractor under unscheduled. With periodId, returns every time entry with its custom form values, missing required fields, and an estimated payout; with contractorId and unscheduled=true, returns that contractor's hours outside a pay period to review and the approved ones ready to pay (prepare them with contractorOps.payouts.prepare and hoursIds). Each listed period carries its live payout, if any, and pipeline says whether payouts are prepared automatically and whether contractors get reminder emails. `GET | POST /api/v1/accounting/contractorOps.timesheets.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_timesheets_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | | `periodId` | string (ID) | | The ID of a contractor pay period. | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `unscheduled` | boolean | | | | `status` | enum | | Only include records with this status. One of: `awaiting`, `approved`, `open`, `all`. Default `"awaiting"`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.timesheets.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.timesheets.list ## contractorOps.timesheets.review Approve or return submitted time entries in a single atomic review, for one pay period (periodId) or for one contractor's hours outside a pay period (contractorId). Supply decisions with hoursId, expectedRevision and decision, a review reason, and idempotencyKey. A decision on a mistake the contractor reported in approved hours also carries the correctionId and decides approved or declined; approving it applies the corrected values, and declining leaves the entry as approved. Approval never sends money. `POST /api/v1/accounting/contractorOps.timesheets.review` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_timesheets_review` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `periodId` | string (ID) | | The ID of a contractor pay period. | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `decisions` | array of objects | Yes | 1–200 items. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.timesheets.review \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "reason": "Synthetic example from the docs", "decisions": [ { "hoursId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "expectedRevision": 3, "decision": "approved" } ], "periodId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.timesheets.review ## contractorOps.titleChange.override Administrator override of a contractor title change with a reason, job role, date and idempotency key. A pending title amendment is voided before the override is recorded; its audit and document history remain. `POST /api/v1/accounting/contractorOps.titleChange.override` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_contractor_ops_title_change_override` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `roleId` | string (ID) | Yes | The ID of the related record. | | `effectiveFrom` | string (date) | Yes | | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.titleChange.override \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "roleId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "effectiveFrom": "2026-09-01", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.titleChange.override ## contractorOps.titleChange.plan Plan a contractor title change from the current agreement. Returns the current title, organization-local today, pending or later agreement, and every signer carried forward with whether an inactive member needs replacement; no document is sent or saved. `GET | POST /api/v1/accounting/contractorOps.titleChange.plan` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_title_change_plan` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractorOps.titleChange.plan \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'contractorId=4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.titleChange.plan ## contractorOps.titleChange.propose Create a title-change amendment tied to an active organization job role with a different title, copying every other current agreement term and all required signer roles. The contractor uses their current email; company signers must be active finance or admin members. Optional companySigner and signerReplacements select current recipients before send. The title stays pending until every signer signs and its effective date arrives. Requires contractorId, roleId, effectiveFrom, send and idempotencyKey. `POST /api/v1/accounting/contractorOps.titleChange.propose` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractor_ops_title_change_propose` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `roleId` | string (ID) | Yes | The ID of the related record. | | `effectiveFrom` | string (date) | Yes | | | `send` | boolean | | Default `true`. | | `companySigner` | object | | No other fields. | | `companySigner.userId` | string | | The ID of a person in your company. 1–200 characters. | | `companySigner.name` | string | Yes | A display name. 1–200 characters. | | `companySigner.email` | string (email) | Yes | An email address. at most 320 characters. | | `companySigner.title` | string or null | | A short title. | | `signerReplacements` | array of objects | | at most 25 items. | | `signerReplacements[].recipientId` | string (ID) | Yes | The ID of one signer on a document. | | `signerReplacements[].name` | string | Yes | A display name. 1–200 characters. | | `signerReplacements[].email` | string (email) | Yes | An email address. at most 320 characters. | | `message` | string or null | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.titleChange.propose \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "roleId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "effectiveFrom": "2026-09-01" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.titleChange.propose ## contractorOps.wise.status Read whether Wise payouts are enabled in this environment: sandbox or production, write token and signing key presence (never their values), business profile, and optionally available balances (amountMinor in minor units). `GET | POST /api/v1/accounting/contractorOps.wise.status` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractor_ops_wise_status` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `includeBalances` | boolean | | Also include balances. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractorOps.wise.status \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractorOps.wise.status ## contractors.access.reset Administrators only: end a contractor's portal link with id, expectedRevision and a written reason (at least 10 characters), so they can be invited again. Use it when the wrong account accepted an invitation or their sign-in email changed. Open invitations are cancelled. Their hours, agreements and payments stay. The reason is audited. Send a new invitation afterwards. `POST /api/v1/accounting/contractors.access.reset` Permissions: `accounting:read`, `accounting:write` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_contractors_access_reset` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.access.reset \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.access.reset ## contractors.agreements.create Create a draft agreement document for a contractor from a confirmed file (fileId), with a title and the contractor's expectedContractorRevision. Nothing is sent until contractors.agreements.send. `POST /api/v1/accounting/contractors.agreements.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractors_agreements_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `title` | string | Yes | A short title. 1–200 characters. | | `fileId` | string (ID) | Yes | The ID of the related record. | | `expectedContractorRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.agreements.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "title": "Synthetic services agreement", "fileId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedContractorRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.agreements.create ## contractors.agreements.download Get a one-minute download link for an agreement document, optionally for one version. `GET | POST /api/v1/accounting/contractors.agreements.download` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_agreements_download` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `version` | integer | | at most 9007199254740991; greater than 0. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractors.agreements.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.agreements.download ## contractors.agreements.get Read one portal agreement document with its status, version and the contractor's signature events. `GET | POST /api/v1/accounting/contractors.agreements.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_agreements_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractors.agreements.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.agreements.get ## contractors.agreements.list List agreement documents sent through the contractor portal for signature (draft, requested, signed, declined or void), optionally for one contractor. For the agreement on file, signed copies, NDAs and terms, use contractorOps.agreements.list and contractorOps.terms.get. `GET | POST /api/v1/accounting/contractors.agreements.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_agreements_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 9007199254740991. Default `0`. | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.agreements.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.agreements.list ## contractors.agreements.remind Email a contractor a reminder to sign the agreement document waiting for them, at most once a day, with expectedRevision. `POST /api/v1/accounting/contractors.agreements.remind` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_contractors_agreements_remind` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.agreements.remind \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.agreements.remind ## contractors.agreements.revise Replace an unsigned agreement document with a new confirmed file (fileId) and a reason, with expectedRevision. It goes back to draft as a new version; a signed one can't be revised (send an amendment with contractorOps.terms.propose). `POST /api/v1/accounting/contractors.agreements.revise` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractors_agreements_revise` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `fileId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.agreements.revise \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "fileId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.agreements.revise ## contractors.agreements.send Ask a contractor who has joined the portal to sign a draft agreement document, with expectedRevision. Only they can sign it, in person, in their portal. `POST /api/v1/accounting/contractors.agreements.send` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_contractors_agreements_send` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.agreements.send \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.agreements.send ## contractors.agreements.void Void an unsigned agreement document with a reason and expectedRevision, so it can't be signed. The record is kept; a signed one can't be voided. `POST /api/v1/accounting/contractors.agreements.void` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_contractors_agreements_void` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.agreements.void \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.agreements.void ## contractors.create Add a contractor with displayName, legalName, email (unique in the organization), an optional two-letter country, an optional jobRoleId (an active role from contractorOps.roles.list; add one with contractorOps.roles.save) and idempotencyKey. No invitation is sent: invite them with contractors.invite, or share the link from contractors.invitations.link. `POST /api/v1/accounting/contractors.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractors_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `displayName` | string | Yes | 1–200 characters. | | `legalName` | string | Yes | 1–300 characters. | | `email` | string (email) | Yes | An email address. at most 320 characters. | | `country` | string | | Two-letter country or province code. | | `jobRoleId` | string (ID) or null | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "displayName": "Synthetic Ventures Inc.", "legalName": "Synthetic Ventures Inc.", "email": "finance@example.com" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.create ## contractors.email.update Change the email a contractor is invited at, until they join the portal, with id, expectedRevision and email. It must be unique in the organization, and open invitations to the old address are revoked. Once they've joined, the email is their sign-in and can't change here. The audit log records that it changed, never the addresses. `POST /api/v1/accounting/contractors.email.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractors_email_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `email` | string (email) | Yes | An email address. at most 320 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.email.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "email": "finance@example.com" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.email.update ## contractors.files.confirm Confirm an uploaded contractor document (fileId) once its bytes are uploaded. Its size, SHA-256 and file type are checked against what was prepared. `POST /api/v1/accounting/contractors.files.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractors_files_confirm` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `fileId` | string (ID) | Yes | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.files.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "fileId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.files.confirm ## contractors.files.prepare Prepare a private upload for a contractor document (PDF, Word, Markdown or text, up to 20 MB) with filename, mimeType, sizeBytes and sha256. PUT the unchanged bytes to uploadUrl, then call contractors.files.confirm; alreadyUploaded means the same file is already stored and only needs confirming. `POST /api/v1/accounting/contractors.files.prepare` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractors_files_prepare` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | string | Yes | The file's name, including its extension. 1–255 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `text/markdown`, `text/plain`. | | `sizeBytes` | integer | Yes | The file's size in bytes. at most 20000000; greater than 0. | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.files.prepare \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "filename": "receipt.pdf", "mimeType": "application/pdf", "sizeBytes": 1, "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.files.prepare ## contractors.get Read one contractor by id: display and legal name, email, country, whether portal access is on (active), job role (job_role_id), reviewed tax profile (residency, services in Canada, identity confirmed) and revision. `GET | POST /api/v1/accounting/contractors.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractors.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.get ## contractors.hours.list List time entries logged by contractors (date, minutes, description, status draft, submitted, approved or rejected, review reason), filtered by contractorId, status and from and to dates. For timesheets by pay period use contractorOps.timesheets.list. `GET | POST /api/v1/accounting/contractors.hours.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_hours_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 9007199254740991. Default `0`. | | `contractorId` | string (ID) | | The ID of a contractor, from contractors.list. | | `status` | enum | | Only include records with this status. One of: `draft`, `submitted`, `approved`, `rejected`. | | `from` | string (date) | | The first date to include, as YYYY-MM-DD. | | `to` | string (date) | | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.hours.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.hours.list ## contractors.hours.review Approve or return one submitted time entry with id, expectedRevision, decision (approved or rejected) and a reason. Approving never sends money. To review a whole pay period at once use contractorOps.timesheets.review. `POST /api/v1/accounting/contractors.hours.review` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractors_hours_review` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | | `decision` | enum | Yes | What you decided. One of: `approved`, `rejected`. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.hours.review \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "decision": "approved", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.hours.review ## contractors.invitations.fixEmail Fix the email a contractor was invited at and send a new invitation in one step, until they join the portal, with id, expectedRevision and email. Open invitations to the old address stop working and their link says it was replaced, and a new 7-day invitation is queued to the corrected address. The email must be unique in the organization and different from the current one; at most 6 invitations a day. Refused once they've joined. `POST /api/v1/accounting/contractors.invitations.fixEmail` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_contractors_invitations_fix_email` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `email` | string (email) | Yes | An email address. at most 320 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.invitations.fixEmail \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "email": "finance@example.com" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.invitations.fixEmail ## contractors.invitations.link Get a portal invitation link to send a contractor yourself: an open invitation is reused, or a 7-day one is created, and nothing is emailed. The same link keeps working until it expires or is revoked. `POST /api/v1/accounting/contractors.invitations.link` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractors_invitations_link` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.invitations.link \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.invitations.link ## contractors.invitations.list List a contractor's portal invitations (contractorId): the email each was sent to, when it expires, and whether it was accepted or revoked. `GET | POST /api/v1/accounting/contractors.invitations.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_invitations_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 9007199254740991. Default `0`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractors.invitations.list \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'contractorId=4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.invitations.list ## contractors.invitations.revoke Revoke an open portal invitation with id and expectedRevision, so its link stops working. `POST /api/v1/accounting/contractors.invitations.revoke` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_contractors_invitations_revoke` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.invitations.revoke \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.invitations.revoke ## contractors.invite Email a contractor an invitation to the contractor portal, with id and expectedRevision. It replaces any open invitation and lasts 7 days, at most 3 a day. The portal only shows them their own hours, agreements and payments. Refused once they've joined. `POST /api/v1/accounting/contractors.invite` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_contractors_invite` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.invite \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.invite ## contractors.list List contractors, newest first: display and legal name, email, country, whether portal access is on (active), their job role (job_role_id) and revision. Filter by query (display or legal name); page with limit and offset. For roles, rates, pay cadence, agreement on file and pending hours use contractorOps.directory.list. `GET | POST /api/v1/accounting/contractors.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 9007199254740991. Default `0`. | | `query` | string | | Text to search for. at most 200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.list ## contractors.notifications.list List contractor emails (portal invitations and signature requests and reminders) with their status: queued, sent (the provider accepted it), failed or cancelled. Queued doesn't mean delivered. `GET | POST /api/v1/accounting/contractors.notifications.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_notifications_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 9007199254740991. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.notifications.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.notifications.list ## contractors.pastPayments.link Confirm a Wise recipient (profileId, recipientId) is this contractor and link every past outgoing transfer to it, as services by default (treatment), with expectedContractorRevision. Transfers in a closed period are skipped and counted. Never sends money. `POST /api/v1/accounting/contractors.pastPayments.link` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractors_past_payments_link` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `expectedContractorRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `profileId` | string | Yes | Matches ^\d{1,30}$. | | `recipientId` | string | Yes | The ID of one signer on a document. Matches ^\d{1,30}$. | | `treatment` | enum | | One of: `services`, `reimbursement`, `unknown`. Default `"services"`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.pastPayments.link \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "expectedContractorRevision": 3, "profileId": "1250", "recipientId": "1250" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.pastPayments.link ## contractors.pastPayments.suggest Suggest the Wise recipients whose past transfers were likely to this contractor (the family name and a given name match), with each one's number of transfers, total and dates, to link with contractors.pastPayments.link. `GET | POST /api/v1/accounting/contractors.pastPayments.suggest` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_past_payments_suggest` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractors.pastPayments.suggest \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'contractorId=4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.pastPayments.suggest ## contractors.payments.bind Link one bank payment (transactionId with expectedTransactionRevision) to a contractor through a confirmed Wise recipient (recipientBindingId), as services, reimbursement or unknown, with a reason. `POST /api/v1/accounting/contractors.payments.bind` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractors_payments_bind` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `recipientBindingId` | string (ID) | Yes | The ID of the related record. | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `expectedTransactionRevision` | integer | Yes | The bank transaction's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `treatment` | enum | Yes | One of: `services`, `reimbursement`, `unknown`. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.payments.bind \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "recipientBindingId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedTransactionRevision": 3, "treatment": "services", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.payments.bind ## contractors.payments.get Read one outgoing bank transaction's contractor link or open identification question, with contractor choices. No payment credentials are returned. `GET | POST /api/v1/accounting/contractors.payments.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_payments_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractors.payments.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'transactionId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.payments.get ## contractors.payments.identify Preview or apply contractor identification for one outgoing bank transaction, or queue a bounded backfill for all eligible transactions. Applying requires an idempotency key and never sends money. `POST /api/v1/accounting/contractors.payments.identify` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance MCP tool: `accounting_contractors_payments_identify` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `transactionId` | string (ID) | | The ID of a bank or card transaction, from transactions.list. | | `dryRun` | boolean | | Default `true`. | | `scope` | object | | No other fields. Default `{}`. | | `scope.from` | string (date) | | The first date to include, as YYYY-MM-DD. | | `scope.to` | string (date) | | The last date to include, as YYYY-MM-DD. | | `idempotencyKey` | string | | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.payments.identify \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.payments.identify ## contractors.payments.jobs List recent contractor payment identification backfill jobs with progress counts and status, without bank or recipient details. `GET | POST /api/v1/accounting/contractors.payments.jobs` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_payments_jobs` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 30. Default `10`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.payments.jobs \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.payments.jobs ## contractors.payments.link Link an outgoing bank transaction to a contractor, with its expected revision, treatment, reason and idempotency key. Remembering its payee can identify later payments; no payout is sent. `POST /api/v1/accounting/contractors.payments.link` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractors_payments_link` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `expectedTransactionRevision` | integer | Yes | The bank transaction's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `treatment` | enum | | One of: `services`, `reimbursement`, `unknown`. Default `"unknown"`. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `remember` | boolean | | Default `true`. | | `respectPersonDecision` | boolean | | Default `false`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.payments.link \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedTransactionRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.payments.link ## contractors.payments.list List all payments to a contractor from linked bank transfers, Oatmilk payouts and imported history, with timesheet coverage and calendar-year totals for review. `GET | POST /api/v1/accounting/contractors.payments.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_payments_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 9007199254740991. Default `0`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractors.payments.list \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'contractorId=4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.payments.list ## contractors.payments.unbind Unlink a bank payment from a contractor with id, expectedRevision and a reason. The history is kept. `POST /api/v1/accounting/contractors.payments.unbind` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_contractors_payments_unbind` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.payments.unbind \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.payments.unbind ## contractors.payments.unlink Remove a contractor link from a bank transaction, or mark it as unrelated to contractors, with its expected revision, reason and idempotency key. The audit history remains. `POST /api/v1/accounting/contractors.payments.unlink` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Destructive: confirm with a person first MCP tool: `accounting_contractors_payments_unlink` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `expectedTransactionRevision` | integer | Yes | The bank transaction's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `notContractor` | boolean | | Default `false`. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.payments.unlink \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedTransactionRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.payments.unlink ## contractors.recipients.bind Confirm that a Wise recipient (profileId, recipientId) is this contractor, with a reason and the contractor's expectedContractorRevision, so transfers to it are attributed to them. A recipient can belong to one contractor. `POST /api/v1/accounting/contractors.recipients.bind` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractors_recipients_bind` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `profileId` | string | Yes | Matches ^\d{1,30}$. | | `recipientId` | string | Yes | The ID of one signer on a document. Matches ^\d{1,30}$. | | `expectedContractorRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.recipients.bind \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "profileId": "1250", "recipientId": "1250", "expectedContractorRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.recipients.bind ## contractors.recipients.list List the Wise recipients confirmed as a contractor's (contractorId), with who confirmed each and why. `GET | POST /api/v1/accounting/contractors.recipients.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_recipients_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractors.recipients.list \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'contractorId=4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.recipients.list ## contractors.taxProfile.update Record a contractor's reviewed tax residency (canadian, nonresident or unknown), whether their services were performed in Canada (servicesInCanada), and whether their identity was verified, with a reason and expectedRevision. A Wise currency or bank country alone doesn't establish residency. `POST /api/v1/accounting/contractors.taxProfile.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractors_tax_profile_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | | `residency` | enum | Yes | One of: `canadian`, `nonresident`, `unknown`. | | `servicesInCanada` | boolean or null | Yes | | | `identityConfirmed` | boolean | Yes | | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.taxProfile.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "residency": "canadian", "servicesInCanada": true, "identityConfirmed": true, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.taxProfile.update ## contractors.taxReport Report a calendar year's contractor payments for T4A and T4A-NR review (calendarYear): reviewed payments for services and returns in CAD per contractor, payouts paid outside Wise, bank debits still to attribute, and open items. It holds no tax numbers. `GET | POST /api/v1/accounting/contractors.taxReport` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_tax_report` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `calendarYear` | integer | Yes | A calendar year, such as 2026. 2000 to 2100. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractors.taxReport \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'calendarYear=2026' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.taxReport ## contractors.templates.create Add a contractor agreement template from an uploaded original (fileId from contractors.files.confirm) with a title. `POST /api/v1/accounting/contractors.templates.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_contractors_templates_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `title` | string | Yes | A short title. 1–200 characters. | | `fileId` | string (ID) | Yes | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.templates.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "title": "Synthetic services agreement", "fileId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.templates.create ## contractors.templates.download Get a template's Markdown, or a one-minute download link for its uploaded original. `GET | POST /api/v1/accounting/contractors.templates.download` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_templates_download` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractors.templates.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.templates.download ## contractors.templates.list List contractor agreement templates: drafts written in Markdown and uploaded originals (PDF, Word or text). `GET | POST /api/v1/accounting/contractors.templates.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_templates_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 9007199254740991. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.templates.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.templates.list ## contractors.templates.preview Read a template to review it: Markdown, text or Word as text (up to 500,000 characters), or a one-minute link to a PDF. `GET | POST /api/v1/accounting/contractors.templates.preview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_contractors_templates_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/contractors.templates.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.templates.preview ## contractors.templates.update Replace a Markdown template's title and content (contentMarkdown) with id and expectedRevision. `POST /api/v1/accounting/contractors.templates.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractors_templates_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `title` | string | Yes | A short title. 1–200 characters. | | `contentMarkdown` | string | Yes | 1–500000 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.templates.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "title": "Synthetic services agreement", "contentMarkdown": "example", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.templates.update ## contractors.update Change a contractor's displayName, legalName or country with id and expectedRevision. Only an administrator can turn portal access on or off (active). Change their email with contractors.email.update and their job role with contractorOps.roles.assign. `POST /api/v1/accounting/contractors.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_contractors_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | | `displayName` | string | | 1–200 characters. | | `legalName` | string | | 1–300 characters. | | `country` | string | | Two-letter country or province code. | | `active` | boolean | | Whether the record is turned on. | | `acknowledgeUnpaid` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/contractors.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractors.update ## recruiting.applications.assign Set who interviews a candidate (interviewerUserIds, active members only) with applicationId, expectedRevision and idempotencyKey. Interviewers can then see the application and submit a scorecard. `POST /api/v1/accounting/recruiting.applications.assign` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_recruiting_applications_assign` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `applicationId` | string (ID) | Yes | The ID of the related record. | | `interviewerUserIds` | array of strings | Yes | A list of record IDs. at most 20 items; each 1–200 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.applications.assign \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "applicationId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "interviewerUserIds": [ "example" ], "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.applications.assign ## recruiting.applications.convert Add a hired candidate as a contractor with applicationId and idempotencyKey: the contractor is created from their name and email (or an existing contractor with that email is linked) and given the posting's job role. No invitation is sent; invite them from Contractors when ready. `POST /api/v1/accounting/recruiting.applications.convert` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_recruiting_applications_convert` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `applicationId` | string (ID) | Yes | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.applications.convert \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "applicationId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.applications.convert ## recruiting.applications.create Add a candidate to a posting yourself (a referral or someone you sourced): postingId, candidate name, email, optional phone, location and links, source (referral, sourced, other), an optional note, and idempotencyKey. A person already in recruiting with that email is reused. Only administrators and the posting's hiring manager can do this. `POST /api/v1/accounting/recruiting.applications.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_recruiting_applications_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `postingId` | string (ID) | Yes | The ID of the related record. | | `candidate` | object | Yes | No other fields. | | `candidate.name` | string | Yes | A display name. 1–200 characters. | | `candidate.email` | string (email) | Yes | An email address. at most 320 characters. | | `candidate.phone` | string | | at most 40 characters. | | `candidate.location` | string | | at most 200 characters. | | `candidate.links` | array of strings | | at most 5 items; each at most 500 characters. | | `source` | enum | | Where the record came from. One of: `referral`, `sourced`, `other`. Default `"sourced"`. | | `note` | string | | A short note, kept with the record. at most 5000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.applications.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "postingId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "candidate": { "name": "Synthetic Ventures Inc.", "email": "finance@example.com" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.applications.create ## recruiting.applications.get Read one application: the candidate, answers, cover letter, resume details, interviews, scorecards, timeline with notes, the candidate's other applications and possible duplicates (same name, different email). Interviewers see other people's scorecards only after submitting their own. Candidate details are visible to administrators and the posting's hiring manager; interviewers see only the applications they're assigned to, without email or phone. `GET | POST /api/v1/accounting/recruiting.applications.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_recruiting_applications_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/recruiting.applications.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.applications.get ## recruiting.applications.list List applications for one posting (postingId) or every posting you can see, filtered by status (active, rejected, hired) or a name search, with stage, source, resume presence, interviewers and a count of yes and no scorecards. Candidate details are visible to administrators and the posting's hiring manager; interviewers see only the applications they're assigned to, without email or phone. `GET | POST /api/v1/accounting/recruiting.applications.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_recruiting_applications_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `postingId` | string (ID) | | The ID of the related record. | | `status` | enum | | Only include records with this status. One of: `active`, `rejected`, `hired`. | | `query` | string | | Text to search for. at most 100 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.applications.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.applications.list ## recruiting.applications.move Move an application to another stage of its posting with applicationId, stage, expectedRevision and idempotencyKey. Moving to Hired marks the candidate hired. Every move is recorded on the timeline. `POST /api/v1/accounting/recruiting.applications.move` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_recruiting_applications_move` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `applicationId` | string (ID) | Yes | The ID of the related record. | | `stage` | string | Yes | Matches ^[a-z][a-z0-9_]{0,39}$. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.applications.move \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "applicationId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "stage": "stage", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.applications.move ## recruiting.applications.reject Reject an application with applicationId, reason (not_qualified, experience, not_a_fit, compensation, location, withdrew, no_response, position_filled, duplicate, spam, other), an optional note, expectedRevision and idempotencyKey. Nothing is sent to the candidate. `POST /api/v1/accounting/recruiting.applications.reject` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_recruiting_applications_reject` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `applicationId` | string (ID) | Yes | The ID of the related record. | | `reason` | enum | Yes | A short note saying why, kept in the record's history. One of: `not_qualified`, `experience`, `not_a_fit`, `compensation`, `location`, `withdrew`, `no_response`, `position_filled`, `duplicate`, `spam`, `other`. | | `note` | string | | A short note, kept with the record. at most 2000 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.applications.reject \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "applicationId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "reason": "not_qualified", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.applications.reject ## recruiting.applications.restore Return a rejected application to its stage with applicationId, expectedRevision and idempotencyKey. `POST /api/v1/accounting/recruiting.applications.restore` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_recruiting_applications_restore` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `applicationId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.applications.restore \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "applicationId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.applications.restore ## recruiting.applications.resume Get a link, valid for one minute, to download an application's resume. Available to administrators, the hiring manager and assigned interviewers. `GET | POST /api/v1/accounting/recruiting.applications.resume` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_recruiting_applications_resume` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `applicationId` | string (ID) | Yes | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/recruiting.applications.resume \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'applicationId=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.applications.resume ## recruiting.applications.summarize Have AI summarize an application against the posting (a short summary, strengths, gaps and questions to ask), when the posting has aiAssist on, with applicationId and idempotencyKey. It never scores, ranks or decides; people make every decision. Administrators and the hiring manager only. `POST /api/v1/accounting/recruiting.applications.summarize` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_recruiting_applications_summarize` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `applicationId` | string (ID) | Yes | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.applications.summarize \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "applicationId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.applications.summarize ## recruiting.board.get Read the organization's public job board: its address, the name shown and the introduction. `GET | POST /api/v1/accounting/recruiting.board.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_recruiting_board_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.board.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.board.get ## recruiting.board.save Set up or change the public job board: slug (its address, 3 to 60 lowercase letters, digits and dashes), displayName, intro, and expectedRevision after the first save. `POST /api/v1/accounting/recruiting.board.save` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_recruiting_board_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `slug` | string | Yes | Matches ^[a-z0-9][a-z0-9-]{1,58}[a-z0-9]$. | | `displayName` | string | Yes | 1–200 characters. | | `intro` | string | | at most 2000 characters. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.board.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "slug": "slug", "displayName": "Synthetic Ventures Inc." }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.board.save ## recruiting.interviews.mine The candidates you're assigned to interview: their posting and stage, your upcoming interviews, and whether you've submitted a scorecard. `GET | POST /api/v1/accounting/recruiting.interviews.mine` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_recruiting_interviews_mine` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.interviews.mine \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.interviews.mine ## recruiting.interviews.save Add an interview to an application, or update one with id and expectedRevision: title, scheduledAt, durationMinutes, location (a room or a video link), interviewerUserIds, notes, and status (scheduled, completed, cancelled). No calendar invitation is sent. Interviewers on the panel are added to the application. `POST /api/v1/accounting/recruiting.interviews.save` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_recruiting_interviews_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `applicationId` | string (ID) | Yes | The ID of the related record. | | `id` | string (ID) | | The record's ID. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `interview` | object | Yes | No other fields. | | `interview.title` | string | Yes | A short title. 1–200 characters. | | `interview.scheduledAt` | string (date-time) or null | Yes | | | `interview.durationMinutes` | integer | Yes | 5 to 600. | | `interview.location` | string | Yes | at most 500 characters. | | `interview.interviewerUserIds` | array of strings | Yes | A list of record IDs. at most 20 items; each 1–200 characters. | | `interview.notes` | string | Yes | Notes kept with the record. at most 5000 characters. | | `interview.status` | enum | | Only include records with this status. One of: `scheduled`, `completed`, `cancelled`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.interviews.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "applicationId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "interview": { "title": "Synthetic services agreement", "scheduledAt": "2026-09-30T14:00:00Z", "durationMinutes": 90, "location": "example", "interviewerUserIds": [ "example" ], "notes": "Synthetic example from the docs" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.interviews.save ## recruiting.notes.add Add a note to an application's timeline with applicationId, body and idempotencyKey. Everyone who can see the application can read it. `POST /api/v1/accounting/recruiting.notes.add` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_recruiting_notes_add` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `applicationId` | string (ID) | Yes | The ID of the related record. | | `body` | string | Yes | 1–5000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.notes.add \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "applicationId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "body": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.notes.add ## recruiting.postings.get Read one job posting with its description, pay, application questions, stages, hiring manager, the public job board address, and for private postings the private link to share with chosen candidates. `GET | POST /api/v1/accounting/recruiting.postings.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_recruiting_postings_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/recruiting.postings.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.postings.get ## recruiting.postings.list List job postings with status (draft, open, closed), visibility (public on the job board, or private by link), hiring manager, stages, and how many applications are in each stage. Administrators and finance see every posting; others see postings whose hiring team they're on. canViewCandidates says whether candidate details can be opened. `GET | POST /api/v1/accounting/recruiting.postings.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_recruiting_postings_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.postings.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.postings.list ## recruiting.postings.rotateLink Replace a posting's private link so the old link stops working, with id, expectedRevision and idempotencyKey. Returns the new link. `POST /api/v1/accounting/recruiting.postings.rotateLink` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_recruiting_postings_rotate_link` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.postings.rotateLink \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.postings.rotateLink ## recruiting.postings.save Create a job posting (often from a job role with jobRoleId), or update one with id and expectedRevision: title, department, location, workplace (remote, hybrid, onsite), employmentType, description, payText, vacancyExists, aiAssist (disclosed on the posting), visibility (public or private), questions (key, label, type text/textarea/yes_no/select/url, required, options) and stages (Applied first, Hired last). The creator becomes hiring manager unless an administrator chooses someone; only an administrator or the current hiring manager can change it. New postings start as drafts. `POST /api/v1/accounting/recruiting.postings.save` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_recruiting_postings_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `posting` | object | Yes | No other fields. | | `posting.jobRoleId` | string (ID) or null | Yes | | | `posting.title` | string | Yes | A short title. 1–200 characters. | | `posting.department` | string | Yes | at most 120 characters. | | `posting.location` | string | Yes | at most 200 characters. | | `posting.workplace` | enum | Yes | One of: `remote`, `hybrid`, `onsite`. | | `posting.employmentType` | enum | Yes | One of: `contractor`, `employee`, `either`. | | `posting.description` | string | Yes | A short description. at most 40000 characters. | | `posting.payText` | string | Yes | at most 200 characters. | | `posting.vacancyExists` | boolean | Yes | | | `posting.aiAssist` | boolean | Yes | | | `posting.visibility` | enum | Yes | One of: `public`, `private`. | | `posting.questions` | array of objects | Yes | at most 20 items. | | `posting.questions[].key` | string | Yes | Matches ^[a-z][a-z0-9_]{0,39}$. | | `posting.questions[].label` | string | Yes | 1–300 characters. | | `posting.questions[].type` | enum | Yes | One of: `text`, `textarea`, `yes_no`, `select`, `url`. | | `posting.questions[].required` | boolean | Yes | | | `posting.questions[].options` | array of strings | | at most 20 items; each 1–200 characters. | | `posting.stages` | array of objects | Yes | 2–12 items. | | `posting.stages[].id` | string | Yes | The record's ID. Matches ^[a-z][a-z0-9_]{0,39}$. | | `posting.stages[].label` | string | Yes | 1–60 characters. | | `posting.hiringManagerUserId` | string or null | Yes | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.postings.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "posting": { "jobRoleId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "title": "Synthetic services agreement", "department": "example", "location": "example", "workplace": "remote", "employmentType": "contractor", "description": "Synthetic example from the docs", "payText": "example", "vacancyExists": true, "aiAssist": true, "visibility": "public", "questions": [ { "key": "key", "label": "example", "type": "text", "required": true } ], "stages": [ { "id": "id", "label": "example" }, { "id": "id", "label": "example" } ], "hiringManagerUserId": "example" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.postings.save ## recruiting.postings.status Open a posting to take applications, close it, or return it to draft, with id, status, expectedRevision and idempotencyKey. Administrators, finance and the hiring manager can do this. Public postings appear on the job board only while open. `POST /api/v1/accounting/recruiting.postings.status` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_recruiting_postings_status` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `status` | enum | Yes | Only include records with this status. One of: `draft`, `open`, `closed`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.postings.status \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "status": "draft", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.postings.status ## recruiting.scorecards.submit Submit or update your scorecard for an application (optionally for one interview): recommendation (strong_no, no, yes, strong_yes), ratings (label and score 1 to 4), summary, and idempotencyKey. Administrators, the hiring manager and assigned interviewers can submit. `POST /api/v1/accounting/recruiting.scorecards.submit` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_recruiting_scorecards_submit` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `applicationId` | string (ID) | Yes | The ID of the related record. | | `interviewId` | string (ID) or null | | | | `recommendation` | enum | Yes | One of: `strong_no`, `no`, `yes`, `strong_yes`. | | `ratings` | array of objects | | at most 12 items. Default `[]`. | | `ratings[].label` | string | Yes | 1–120 characters. | | `ratings[].score` | integer | Yes | 1 to 4. | | `summary` | string | | at most 5000 characters. Default `""`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.scorecards.submit \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "applicationId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "recommendation": "strong_no" }' ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.scorecards.submit ## recruiting.team.list List the members who can be hiring managers or interviewers (administrators, finance and contributors, never accountants), with their email. `GET | POST /api/v1/accounting/recruiting.team.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_recruiting_team_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/recruiting.team.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/recruiting.team.list # Group: Contractor portal > A contractor's own hours, pay, agreements and profile, with a contractor sign-in. MCP toolset: `contractor` (https://app.getoatmilk.com/api/mcp?toolset=contractor) ## agreement - [`agreement.get`](https://app.getoatmilk.com/docs/api/contractor/agreement.get.md) — Read your agreement in effect: your role, when it starts and ends, the notice it asks for to end it and the day that notice is due, and when new terms start if they do. ## agreements - [`agreements.download`](https://app.getoatmilk.com/docs/api/contractor/agreements.download.md) — Get a one-minute download link for an agreement sent to you, optionally one version. - [`agreements.get`](https://app.getoatmilk.com/docs/api/contractor/agreements.get.md) — Read one agreement sent to you, with its status, version and your signature events. - [`agreements.list`](https://app.getoatmilk.com/docs/api/contractor/agreements.list.md) — List the agreements sent to you for signature, with each one's status and version. ## history - [`history.list`](https://app.getoatmilk.com/docs/api/contractor/history.list.md) — Read your own past and current timesheet history, including imported weeks and their agreement-based rate estimates. ## home - [`home.get`](https://app.getoatmilk.com/docs/api/contractor/home.get.md) — Read your own portal summary for one company: your agreement and rate, your next payday, and the one thing to do next (a payment that couldn't be sent, returned hours, an agreement to sign, hours due, or details still needed). ## hours - [`hours.amend`](https://app.getoatmilk.com/docs/api/contractor/hours.amend.md) — Change one of your submitted time entries (date, minutes, description, and optionally the custom field values) with its expectedRevision, as long as nobody has reviewed it yet. It stays submitted, and a reviewer who already opened it is told it changed. - [`hours.create`](https://app.getoatmilk.com/docs/api/contractor/hours.create.md) — Log time: a date (not in the future, and not in a pay period that was already paid), minutes and a description, as a draft to submit with your timesheet. A day can't add up to more than 24 hours. The same work on the same day twice is refused unless confirmDuplicate is true. - [`hours.form`](https://app.getoatmilk.com/docs/api/contractor/hours.form.md) — Read the custom fields you fill in for each time entry. - [`hours.list`](https://app.getoatmilk.com/docs/api/contractor/hours.list.md) — List your own time entries, optionally from and to dates. - [`hours.submit`](https://app.getoatmilk.com/docs/api/contractor/hours.submit.md) — Submit one of your time entries for approval with its expectedRevision, once the hours form's required fields are filled in. - [`hours.update`](https://app.getoatmilk.com/docs/api/contractor/hours.update.md) — Change one of your draft or returned time entries (date, minutes, description) with its expectedRevision. The same rules as logging time apply. - [`hours.withdraw`](https://app.getoatmilk.com/docs/api/contractor/hours.withdraw.md) — Take a submitted time entry back to draft with its expectedRevision, before it's reviewed. ## hours.correction - [`hours.correction.request`](https://app.getoatmilk.com/docs/api/contractor/hours.correction.request.md) — Report a mistake in one of your approved time entries (hoursId, its expectedRevision, the corrected date, minutes and description, and a reason). The approved entry stays as it is and is held out of payouts until finance decides. Hours that were already paid are raised with finance as an adjustment instead. One correction can be open per entry. - [`hours.correction.withdraw`](https://app.getoatmilk.com/docs/api/contractor/hours.correction.withdraw.md) — Take back a correction you reported (correctionId) before finance decides on it, so the entry can be paid as approved. ## hours.details - [`hours.details.list`](https://app.getoatmilk.com/docs/api/contractor/hours.details.list.md) — Read the custom field values for your own time entries. - [`hours.details.save`](https://app.getoatmilk.com/docs/api/contractor/hours.details.save.md) — Save custom field values for one of your own draft or returned time entries. Fill a time entry's required custom fields here before submitting it (hours.form lists them). ## identity - [`identity.get`](https://app.getoatmilk.com/docs/api/contractor/identity.get.md) — Read your own contractor record: your name, legal name, email and country. ## invoice - [`invoice.attach`](https://app.getoatmilk.com/docs/api/contractor/invoice.attach.md) — Attach an uploaded invoice to one of your pay periods. Finance sees it with your timesheet and your documents. - [`invoice.prepare`](https://app.getoatmilk.com/docs/api/contractor/invoice.prepare.md) — Get a private upload link for an invoice (a PDF, PNG or JPEG up to 20 MB, with its size and SHA-256) for one of your pay periods. ## onboarding - [`onboarding.save`](https://app.getoatmilk.com/docs/api/contractor/onboarding.save.md) — Record which setup questions you answered or skipped, whether you finished, and whether you agree to get your tax slips and pay statements by email. It changes nothing else about your profile. Over the API and MCP only answered, skipped and completed are accepted: agreeing to get tax slips by email (eDelivery) is the person's own choice in the portal and is refused with INTERACTIVE_PORTAL_REQUIRED. ## organizations - [`organizations.list`](https://app.getoatmilk.com/docs/api/contractor/organizations.list.md) — List the companies whose contractor portal records are linked to your verified account. Use it first: pass the organizationId you choose with every other contractor call, or send the X-Accounting-Organization header. ## payment - [`payment.get`](https://app.getoatmilk.com/docs/api/contractor/payment.get.md) — Read your own payment method with account numbers masked. - [`payment.save`](https://app.getoatmilk.com/docs/api/contractor/payment.save.md) — Replace your own payment details. Saved numbers are never shown again in full. Change bank details in your contractor portal. Agents and API tokens can't replace payment details. ## payments - [`payments.history`](https://app.getoatmilk.com/docs/api/contractor/payments.history.md) — Read your own linked bank payments, paid Oatmilk payouts and imported paid history for this employer, without bank descriptions or internal matching details. ## payouts - [`payouts.list`](https://app.getoatmilk.com/docs/api/contractor/payouts.list.md) — Read your own payouts and when they were sent. - [`payouts.statement`](https://app.getoatmilk.com/docs/api/contractor/payouts.statement.md) — Download a payment statement (PDF) for one of your paid payouts by id: what it was for, fees, GST/HST, total and date. It isn't a pay stub. - [`payouts.summary`](https://app.getoatmilk.com/docs/api/contractor/payouts.summary.md) — Download a summary (PDF) of your payments in a calendar year (calendarYear), with totals by currency before and after GST/HST, for your own tax return. ## profile - [`profile.get`](https://app.getoatmilk.com/docs/api/contractor/profile.get.md) — Read your own onboarding profile, completeness, pay cadence, and masked payment details. - [`profile.save`](https://app.getoatmilk.com/docs/api/contractor/profile.save.md) — Update your own legal name, roles, contact details, address, sales tax, and corporation details. ## reminders - [`reminders.set`](https://app.getoatmilk.com/docs/api/contractor/reminders.set.md) — Turn off, or back on, the emails that remind you to log, send and finish things. To take a break, turn them off with until, the day they start again by themselves (after today, at most two years away); pay periods inside a break are never reminded about. Emails about payments, returned hours and agreements always keep coming. ## requests - [`requests.list`](https://app.getoatmilk.com/docs/api/contractor/requests.list.md) — Read the personal requests assigned to you for your own contractor profile and timesheets. - [`requests.update`](https://app.getoatmilk.com/docs/api/contractor/requests.update.md) — Complete a personal request assigned to you using its current revision and an idempotency key. ## signatures - [`signatures.accept`](https://app.getoatmilk.com/docs/api/contractor/signatures.accept.md) — Accept and sign an agreement in your own portal session, after reviewing the exact version. Only you can, interactively; agents can't sign for you. - [`signatures.decline`](https://app.getoatmilk.com/docs/api/contractor/signatures.decline.md) — Decline an agreement with a reason, in your own portal session. Only you can, interactively; agents can't decide for you. - [`signatures.prepare`](https://app.getoatmilk.com/docs/api/contractor/signatures.prepare.md) — Start signing an agreement in your own portal session. Only you can, interactively; agents can't sign for you. - [`signatures.status`](https://app.getoatmilk.com/docs/api/contractor/signatures.status.md) — Read where your signature on an agreement stands. ## taxInfo - [`taxInfo.get`](https://app.getoatmilk.com/docs/api/contractor/taxInfo.get.md) — Read your own tax numbers, shown only partly, and what your tax slip still needs: your SIN or business number and a complete mailing address. - [`taxInfo.save`](https://app.getoatmilk.com/docs/api/contractor/taxInfo.save.md) — Add, replace or remove one of your tax numbers (SIN, business number, ITN, or your country's tax number) for your tax slips. It's stored encrypted and only ever shown partly again. Add or replace a tax number in your contractor portal. Agents and API tokens can't handle full tax numbers. ## timesheet - [`timesheet.fill`](https://app.getoatmilk.com/docs/api/contractor/timesheet.fill.md) — Fill one of your own empty current or past pay periods with draft time entries copied from the last period or your usual pattern. Dates, hours, and reusable form answers can be reviewed before submission. - [`timesheet.get`](https://app.getoatmilk.com/docs/api/contractor/timesheet.get.md) — Read one of your pay periods, by id or by a date in it, with its entries, custom fields, due date, pay date, and any hour limits in your agreement with how many hours are left this week, this month and in total. - [`timesheet.noHours`](https://app.getoatmilk.com/docs/api/contractor/timesheet.noHours.md) — Mark your own empty pay period as having no hours, or undo your own report before finance skips it. - [`timesheet.submit`](https://app.getoatmilk.com/docs/api/contractor/timesheet.submit.md) — Submit all of your draft entries in a pay period (by id, or by a date in it when the dates aren't in a scheduled pay period) after required custom fields are filled in, in one step: either all of them go to review or none do. Skipped and paid periods can't take hours. ## agreement.get Read your agreement in effect: your role, when it starts and ends, the notice it asks for to end it and the day that notice is due, and when new terms start if they do. `GET | POST /api/v1/contractor/agreement.get` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_agreement_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/agreement.get \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/agreement.get ## agreements.download Get a one-minute download link for an agreement sent to you, optionally one version. `GET | POST /api/v1/contractor/agreements.download` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_agreements_download` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `version` | integer | | at most 9007199254740991; greater than 0. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/contractor/agreements.download \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/agreements.download ## agreements.get Read one agreement sent to you, with its status, version and your signature events. `GET | POST /api/v1/contractor/agreements.get` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_agreements_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/contractor/agreements.get \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/agreements.get ## agreements.list List the agreements sent to you for signature, with each one's status and version. `GET | POST /api/v1/contractor/agreements.list` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_agreements_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 9007199254740991. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/agreements.list \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/agreements.list ## history.list Read your own past and current timesheet history, including imported weeks and their agreement-based rate estimates. `GET | POST /api/v1/contractor/history.list` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_history_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/history.list \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/history.list ## home.get Read your own portal summary for one company: your agreement and rate, your next payday, and the one thing to do next (a payment that couldn't be sent, returned hours, an agreement to sign, hours due, or details still needed). `GET | POST /api/v1/contractor/home.get` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_home_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/home.get \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/home.get ## hours.amend Change one of your submitted time entries (date, minutes, description, and optionally the custom field values) with its expectedRevision, as long as nobody has reviewed it yet. It stays submitted, and a reviewer who already opened it is told it changed. `POST /api/v1/contractor/hours.amend` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` MCP tool: `contractor_hours_amend` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | | `date` | string (date) | Yes | A date, as YYYY-MM-DD. | | `minutes` | integer | Yes | 1 to 1440. | | `description` | string | Yes | A short description. 1–2000 characters. | | `values` | map | | | | `confirmDuplicate` | boolean | | | | `reason` | string | | A short note saying why, kept in the record's history. at most 1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/hours.amend \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "date": "2026-09-01", "minutes": 90, "description": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/hours.amend ## hours.correction.request Report a mistake in one of your approved time entries (hoursId, its expectedRevision, the corrected date, minutes and description, and a reason). The approved entry stays as it is and is held out of payouts until finance decides. Hours that were already paid are raised with finance as an adjustment instead. One correction can be open per entry. `POST /api/v1/contractor/hours.correction.request` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` MCP tool: `contractor_hours_correction_request` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `hoursId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | | `date` | string (date) | Yes | A date, as YYYY-MM-DD. | | `minutes` | integer | Yes | 1 to 1440. | | `description` | string | Yes | A short description. 1–2000 characters. | | `values` | map | | | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `confirmDuplicate` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/hours.correction.request \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "hoursId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "expectedRevision": 3, "date": "2026-09-01", "minutes": 90, "description": "Synthetic example from the docs", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/hours.correction.request ## hours.correction.withdraw Take back a correction you reported (correctionId) before finance decides on it, so the entry can be paid as approved. `POST /api/v1/contractor/hours.correction.withdraw` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required MCP tool: `contractor_hours_correction_withdraw` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `correctionId` | string (ID) | Yes | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/hours.correction.withdraw \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "correctionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/hours.correction.withdraw ## hours.create Log time: a date (not in the future, and not in a pay period that was already paid), minutes and a description, as a draft to submit with your timesheet. A day can't add up to more than 24 hours. The same work on the same day twice is refused unless confirmDuplicate is true. `POST /api/v1/contractor/hours.create` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required MCP tool: `contractor_hours_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `date` | string (date) | Yes | A date, as YYYY-MM-DD. | | `minutes` | integer | Yes | 1 to 1440. | | `description` | string | Yes | A short description. 1–2000 characters. | | `confirmDuplicate` | boolean | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/hours.create \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "date": "2026-09-01", "minutes": 90, "description": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/hours.create ## hours.details.list Read the custom field values for your own time entries. `GET | POST /api/v1/contractor/hours.details.list` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_hours_details_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string (date) | | The first date to include, as YYYY-MM-DD. | | `to` | string (date) | | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/hours.details.list \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/hours.details.list ## hours.details.save Save custom field values for one of your own draft or returned time entries. Fill a time entry's required custom fields here before submitting it (hours.form lists them). `POST /api/v1/contractor/hours.details.save` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` MCP tool: `contractor_hours_details_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `hoursId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer or null | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `values` | map | Yes | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/hours.details.save \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "hoursId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "values": {} }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/hours.details.save ## hours.form Read the custom fields you fill in for each time entry. `GET | POST /api/v1/contractor/hours.form` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_hours_form` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/hours.form \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/hours.form ## hours.list List your own time entries, optionally from and to dates. `GET | POST /api/v1/contractor/hours.list` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_hours_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 9007199254740991. Default `0`. | | `from` | string (date) | | The first date to include, as YYYY-MM-DD. | | `to` | string (date) | | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/hours.list \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/hours.list ## hours.submit Submit one of your time entries for approval with its expectedRevision, once the hours form's required fields are filled in. `POST /api/v1/contractor/hours.submit` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` MCP tool: `contractor_hours_submit` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/hours.submit \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/hours.submit ## hours.update Change one of your draft or returned time entries (date, minutes, description) with its expectedRevision. The same rules as logging time apply. `POST /api/v1/contractor/hours.update` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` MCP tool: `contractor_hours_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | | `date` | string (date) | Yes | A date, as YYYY-MM-DD. | | `minutes` | integer | Yes | 1 to 1440. | | `description` | string | Yes | A short description. 1–2000 characters. | | `confirmDuplicate` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/hours.update \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "date": "2026-09-01", "minutes": 90, "description": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/hours.update ## hours.withdraw Take a submitted time entry back to draft with its expectedRevision, before it's reviewed. `POST /api/v1/contractor/hours.withdraw` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` MCP tool: `contractor_hours_withdraw` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/hours.withdraw \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/hours.withdraw ## identity.get Read your own contractor record: your name, legal name, email and country. `GET | POST /api/v1/contractor/identity.get` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_identity_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/identity.get \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/identity.get ## invoice.attach Attach an uploaded invoice to one of your pay periods. Finance sees it with your timesheet and your documents. `POST /api/v1/contractor/invoice.attach` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required MCP tool: `contractor_invoice_attach` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `periodId` | string (ID) | Yes | The ID of a contractor pay period. | | `filename` | string | Yes | The file's name, including its extension. 1–255 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `application/pdf`, `image/png`, `image/jpeg`. | | `sizeBytes` | integer | Yes | The file's size in bytes. at most 20000000; greater than 0. | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/invoice.attach \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "periodId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "filename": "receipt.pdf", "mimeType": "application/pdf", "sizeBytes": 1, "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/invoice.attach ## invoice.prepare Get a private upload link for an invoice (a PDF, PNG or JPEG up to 20 MB, with its size and SHA-256) for one of your pay periods. `POST /api/v1/contractor/invoice.prepare` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required MCP tool: `contractor_invoice_prepare` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `periodId` | string (ID) | Yes | The ID of a contractor pay period. | | `filename` | string | Yes | The file's name, including its extension. 1–255 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `application/pdf`, `image/png`, `image/jpeg`. | | `sizeBytes` | integer | Yes | The file's size in bytes. at most 20000000; greater than 0. | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/invoice.prepare \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "periodId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "filename": "receipt.pdf", "mimeType": "application/pdf", "sizeBytes": 1, "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/invoice.prepare ## onboarding.save Record which setup questions you answered or skipped, whether you finished, and whether you agree to get your tax slips and pay statements by email. It changes nothing else about your profile. Over the API and MCP only answered, skipped and completed are accepted: agreeing to get tax slips by email (eDelivery) is the person's own choice in the portal and is refused with INTERACTIVE_PORTAL_REQUIRED. `POST /api/v1/contractor/onboarding.save` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required MCP tool: `contractor_onboarding_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `answered` | array of strings | | at most 40 items; each Matches ^[A-Za-z]{2,30}$. | | `skipped` | array of strings | | at most 40 items; each Matches ^[A-Za-z]{2,30}$. | | `eDelivery` | boolean | | | | `eDeliveryVersion` | string | | Matches ^[0-9A-Za-z._-]{1,20}$. | | `completed` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/onboarding.save \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "answered": [ "answered" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/onboarding.save ## organizations.list List the companies whose contractor portal records are linked to your verified account. Use it first: pass the organizationId you choose with every other contractor call, or send the X-Accounting-Organization header. `GET | POST /api/v1/contractor/organizations.list` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_organizations_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/organizations.list \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/organizations.list ## payment.get Read your own payment method with account numbers masked. `GET | POST /api/v1/contractor/payment.get` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_payment_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/payment.get \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/payment.get ## payment.save Replace your own payment details. Saved numbers are never shown again in full. Change bank details in your contractor portal. Agents and API tokens can't replace payment details. `POST /api/v1/contractor/payment.save` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` **INTERACTIVE_PORTAL_REQUIRED.** Only the contractor does this, in their own portal. API and MCP calls are refused with a portalUrl to the exact step. MCP tool: `contractor_payment_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer or null | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `method` | enum | Yes | One of: `wise_email`, `bank_transfer`, `interac`, `other`. | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `details` | object | Yes | No other fields. | | `details.wiseEmail` | string (email) or null | | | | `details.wiseLink` | string or null | | | | `details.accountHolderName` | string or null | | | | `details.bankName` | string or null | | | | `details.bankAddress` | string or null | | | | `details.accountNumber` | string or null | | | | `details.swiftBic` | string or null | | | | `details.iban` | string or null | | | | `details.institutionNumber` | string or null | | | | `details.transitNumber` | string or null | | | | `details.routingNumber` | string or null | | | | `details.accountType` | enum or null | | One of: `checking`, `savings`. | | `details.country` | string or null | | | | `details.interacEmail` | string (email) or null | | | | `details.otherInstructions` | string or null | | | | `replaceUnreadable` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/payment.save \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "method": "wise_email", "currency": "CAD", "details": {} }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/payment.save ## payments.history Read your own linked bank payments, paid Oatmilk payouts and imported paid history for this employer, without bank descriptions or internal matching details. `GET | POST /api/v1/contractor/payments.history` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_payments_history` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/payments.history \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/payments.history ## payouts.list Read your own payouts and when they were sent. `GET | POST /api/v1/contractor/payouts.list` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_payouts_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/payouts.list \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/payouts.list ## payouts.statement Download a payment statement (PDF) for one of your paid payouts by id: what it was for, fees, GST/HST, total and date. It isn't a pay stub. `GET | POST /api/v1/contractor/payouts.statement` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_payouts_statement` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/contractor/payouts.statement \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/payouts.statement ## payouts.summary Download a summary (PDF) of your payments in a calendar year (calendarYear), with totals by currency before and after GST/HST, for your own tax return. `GET | POST /api/v1/contractor/payouts.summary` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_payouts_summary` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `calendarYear` | integer | Yes | A calendar year, such as 2026. 2000 to 2100. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/contractor/payouts.summary \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ --data-urlencode 'calendarYear=2026' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/payouts.summary ## profile.get Read your own onboarding profile, completeness, pay cadence, and masked payment details. `GET | POST /api/v1/contractor/profile.get` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_profile_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/profile.get \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/profile.get ## profile.save Update your own legal name, roles, contact details, address, sales tax, and corporation details. `POST /api/v1/contractor/profile.save` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` MCP tool: `contractor_profile_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer or null | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `profile` | object | Yes | No other fields. | | `profile.legalName` | string or null | | | | `profile.roles` | array of strings | | at most 12 items; each 1–80 characters. | | `profile.phone` | string or null | | | | `profile.discordUsername` | string or null | | | | `profile.githubUsername` | string or null | | | | `profile.linkedinUrl` | string or null | | | | `profile.portfolioUrl` | string or null | | | | `profile.customContacts` | array of objects | | at most 10 items. | | `profile.customContacts[].label` | string | Yes | 1–60 characters. | | `profile.customContacts[].value` | string | Yes | 1–500 characters. | | `profile.address` | object | | No other fields. | | `profile.address.line1` | string | | at most 200 characters. | | `profile.address.line2` | string | | at most 200 characters. | | `profile.address.city` | string | | at most 120 characters. | | `profile.address.region` | string | | at most 120 characters. | | `profile.address.postalCode` | string | | at most 20 characters. | | `profile.address.country` | string | | Matches ^([A-Za-z]{2})?$. | | `profile.notes` | string or null | | Notes kept with the record. | | `profile.tax` | object | | No other fields. | | `profile.tax.chargesSalesTax` | boolean | | Default `false`. | | `profile.tax.salesTaxRateBps` | integer or null | | | | `profile.tax.salesTaxNumber` | string or null | | | | `profile.tax.province` | enum or null | | One of: `AB`, `BC`, `MB`, `NB`, `NL`, `NS`, `NT`, `NU`, `ON`, `PE`, `QC`, `SK`, `YT`. | | `profile.tax.operatesAsCorporation` | boolean | | Default `false`. | | `profile.tax.corporationName` | string or null | | | | `profile.tax.businessNumber` | string or null | | | | `profile.tax.corporationAddress` | object or null | | | | `profile.tax.corporationAddress.line1` | string | | at most 200 characters. | | `profile.tax.corporationAddress.line2` | string | | at most 200 characters. | | `profile.tax.corporationAddress.city` | string | | at most 120 characters. | | `profile.tax.corporationAddress.region` | string | | at most 120 characters. | | `profile.tax.corporationAddress.postalCode` | string | | at most 20 characters. | | `profile.tax.corporationAddress.country` | string | | Matches ^([A-Za-z]{2})?$. | | `profile.tax.signerName` | string or null | | | | `profile.tax.signerTitle` | string or null | | | | `profile.tax.usTaxForm` | enum or null | | One of: `w9`, `w8ben`, `w8bene`, `unsure`. | | `profile.tax.usTaxClassification` | enum or null | | One of: `individual`, `c_corporation`, `s_corporation`, `partnership`, `trust_estate`, `llc_c`, `llc_s`, `llc_p`, `other`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/profile.save \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "profile": {} }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/profile.save ## reminders.set Turn off, or back on, the emails that remind you to log, send and finish things. To take a break, turn them off with until, the day they start again by themselves (after today, at most two years away); pay periods inside a break are never reminded about. Emails about payments, returned hours and agreements always keep coming. `POST /api/v1/contractor/reminders.set` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required MCP tool: `contractor_reminders_set` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `off` | boolean | Yes | | | `until` | string (date) | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/reminders.set \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "off": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/reminders.set ## requests.list Read the personal requests assigned to you for your own contractor profile and timesheets. `GET | POST /api/v1/contractor/requests.list` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_requests_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `mine` | boolean | | Default `true`. | | `status` | enum | | Only include records with this status. One of: `open`, `done`, `cancelled`, `all`. Default `"open"`. | | `subjectType` | enum | | The kind of record this is about, such as entry, invoice or mail. One of: `entry`, `submission`, `mail`, `statement`, `contractor`, `timesheet`, `compliance_item`, `intake_item`. | | `subjectId` | string (ID) | | The ID of the record this is about. | | `limit` | integer | | How many results to return at most. 1 to 200. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/requests.list \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/requests.list ## requests.update Complete a personal request assigned to you using its current revision and an idempotency key. `POST /api/v1/contractor/requests.update` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` MCP tool: `contractor_requests_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `status` | enum | Yes | Only include records with this status. One of: `done`, `cancelled`. | | `response` | string | | at most 2000 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/requests.update \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "status": "done", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/requests.update ## signatures.accept Accept and sign an agreement in your own portal session, after reviewing the exact version. Only you can, interactively; agents can't sign for you. `POST /api/v1/contractor/signatures.accept` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` **INTERACTIVE_SIGNATURE_REQUIRED.** The contractor signs or declines in their own portal session. API and MCP calls are refused with a portalUrl to the agreement. Not available over MCP: The named contractor signs or declines in person, after verifying again. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | | `challengeId` | string (ID) | Yes | The ID of the related record. | | `challenge` | string | Yes | 40–200 characters. | | `documentSha256` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `version` | integer | Yes | at most 9007199254740991; greater than 0. | | `consent` | true | Yes | | | `legalName` | string | Yes | 1–300 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/signatures.accept \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "challengeId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "challenge": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c", "documentSha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "version": 1, "consent": true, "legalName": "Synthetic Ventures Inc." }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/signatures.accept ## signatures.decline Decline an agreement with a reason, in your own portal session. Only you can, interactively; agents can't decide for you. `POST /api/v1/contractor/signatures.decline` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` **INTERACTIVE_SIGNATURE_REQUIRED.** The contractor signs or declines in their own portal session. API and MCP calls are refused with a portalUrl to the agreement. Not available over MCP: The named contractor signs or declines in person, after verifying again. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/signatures.decline \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/signatures.decline ## signatures.prepare Start signing an agreement in your own portal session. Only you can, interactively; agents can't sign for you. `POST /api/v1/contractor/signatures.prepare` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Send `expectedRevision` **INTERACTIVE_SIGNATURE_REQUIRED.** The contractor signs or declines in their own portal session. API and MCP calls are refused with a portalUrl to the agreement. Not available over MCP: The named contractor signs or declines in person, after verifying again. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/signatures.prepare \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/signatures.prepare ## signatures.status Read where your signature on an agreement stands. `GET | POST /api/v1/contractor/signatures.status` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_signatures_status` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/contractor/signatures.status \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/signatures.status ## taxInfo.get Read your own tax numbers, shown only partly, and what your tax slip still needs: your SIN or business number and a complete mailing address. `GET | POST /api/v1/contractor/taxInfo.get` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_tax_info_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/taxInfo.get \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/taxInfo.get ## taxInfo.save Add, replace or remove one of your tax numbers (SIN, business number, ITN, or your country's tax number) for your tax slips. It's stored encrypted and only ever shown partly again. Add or replace a tax number in your contractor portal. Agents and API tokens can't handle full tax numbers. `POST /api/v1/contractor/taxInfo.save` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` **INTERACTIVE_PORTAL_REQUIRED.** Only the contractor does this, in their own portal. API and MCP calls are refused with a portalUrl to the exact step. MCP tool: `contractor_tax_info_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer or null | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `taxNumber` | object | | No other fields. | | `taxNumber.kind` | enum | Yes | Which kind of record or job this is. One of: `sin`, `bn`, `itn`, `foreign`. | | `taxNumber.value` | string | Yes | 1–40 characters. | | `taxNumber.country` | string or null | | | | `removeTaxNumber` | enum | | One of: `sin`, `bn`, `itn`, `foreign`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/taxInfo.save \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "taxNumber": { "kind": "sin", "value": "example" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/taxInfo.save ## timesheet.fill Fill one of your own empty current or past pay periods with draft time entries copied from the last period or your usual pattern. Dates, hours, and reusable form answers can be reviewed before submission. `POST /api/v1/contractor/timesheet.fill` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` MCP tool: `contractor_timesheet_fill` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `periodId` | string (ID) | Yes | The ID of a contractor pay period. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `source` | enum | Yes | Where the record came from. One of: `lastPeriod`, `usualPattern`. | | `entries` | array of objects | | 1–31 items. | | `entries[].date` | string (date) | Yes | A date, as YYYY-MM-DD. | | `entries[].minutes` | integer | Yes | 1 to 1440. | | `entries[].description` | string | Yes | A short description. 1–2000 characters. | | `entries[].values` | map | Yes | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/timesheet.fill \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "periodId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "expectedRevision": 3, "source": "lastPeriod" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/timesheet.fill ## timesheet.get Read one of your pay periods, by id or by a date in it, with its entries, custom fields, due date, pay date, and any hour limits in your agreement with how many hours are left this week, this month and in total. `GET | POST /api/v1/contractor/timesheet.get` Permissions: `contractor:read` · Roles: contractor MCP tool: `contractor_timesheet_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `periodId` | string (ID) | | The ID of a contractor pay period. | | `date` | string (date) | | A date, as YYYY-MM-DD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/timesheet.get \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/timesheet.get ## timesheet.noHours Mark your own empty pay period as having no hours, or undo your own report before finance skips it. `POST /api/v1/contractor/timesheet.noHours` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required · Send `expectedRevision` MCP tool: `contractor_timesheet_no_hours` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `periodId` | string (ID) | Yes | The ID of a contractor pay period. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `noHours` | boolean | Yes | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/timesheet.noHours \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "periodId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "expectedRevision": 3, "noHours": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/timesheet.noHours ## timesheet.submit Submit all of your draft entries in a pay period (by id, or by a date in it when the dates aren't in a scheduled pay period) after required custom fields are filled in, in one step: either all of them go to review or none do. Skipped and paid periods can't take hours. `POST /api/v1/contractor/timesheet.submit` Permissions: `contractor:read`, `contractor:write` · Roles: contractor · Idempotency key required MCP tool: `contractor_timesheet_submit` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `periodId` | string (ID) | | The ID of a contractor pay period. | | `date` | string (date) | | A date, as YYYY-MM-DD. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/contractor/timesheet.submit \ -H "Authorization: Bearer $OATMILK_OAUTH_TOKEN" \ -H "X-Accounting-Organization: $OATMILK_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "periodId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" }' ``` Reference page: https://app.getoatmilk.com/docs/api/contractor/timesheet.submit # Group: Reports and tax > Reports, exports, insights, subscriptions and tax preparation. MCP toolset: `reports` (https://app.getoatmilk.com/api/mcp?toolset=reports) ## company.shareholders - [`company.shareholders.export`](https://app.getoatmilk.com/docs/api/company.shareholders.export.md) — Download the shareholder register as a spreadsheet with full tax numbers, to file Schedule 50 or T5 slips, with a reason. Only an administrator in the dashboard can; the download is audited with the reason and the number of shareholders, never the numbers. Not available to API keys or MCP clients. - [`company.shareholders.get`](https://app.getoatmilk.com/docs/api/company.shareholders.get.md) — Read the shareholder register: the classes of shares (common or preferred, voting or not), each shareholder (person, corporation or trust; address; resident in Canada or not) with their holdings (number of shares, class, issue date, certificate, what was paid, end date), directors and officers, shares outstanding by class, each holder's share of the common, preferred and voting shares, the T2 Schedule 50 rows (holders of 10% or more, with the tax number masked), how much of the vote Canadians hold, and names found in company documents that aren't in the register yet. Tax numbers are only ever masked. - [`company.shareholders.taxNumber`](https://app.getoatmilk.com/docs/api/company.shareholders.taxNumber.md) — Save or remove a shareholder's tax number for Schedule 50 and T5 slips: { holderId, kind (sin, bn with an optional program account, itn, or foreign with country), value, country? } or value null to remove it. SINs are checked with their check digit. The number is stored encrypted and only ever returned masked; it is never logged or audited. - [`company.shareholders.update`](https://app.getoatmilk.com/docs/api/company.shareholders.update.md) — Replace the shareholder register with { register: { classes, holders, holdings, officers }, expectedRevision, idempotencyKey } (from company.shareholders.get, changed). Ids are 8 to 40 lowercase letters, digits and hyphens; shares are whole numbers; every holding names a holder and class in the register. Removing a shareholder removes their saved tax number. Audited with counts only. ## handoff - [`handoff.export`](https://app.getoatmilk.com/docs/api/handoff.export.md) — Export a versioned professional handoff with stable record identifiers, unresolved items and provider control totals. Employee records require supporting originals access. Generates no ledger postings. - [`handoff.get`](https://app.getoatmilk.com/docs/api/handoff.get.md) — Read professional handoff, separate authorizations, filing confirmations and calendar-year payroll completeness. Prepared records never establish filing or create ledger postings. - [`handoff.update`](https://app.getoatmilk.com/docs/api/handoff.update.md) — Record one handoff item with its owner, source, state, original documents and current revision. Filing acceptance requires retained confirmation. Stores no full payroll identity numbers and creates no postings. ## insights.finance - [`insights.finance.get`](https://app.getoatmilk.com/docs/api/insights.finance.get.md) — Read gross income, expenses, refunds, profit or loss and category totals per currency for an accounting period, with reviewed and unresolved coverage. - [`insights.finance.trends`](https://app.getoatmilk.com/docs/api/insights.finance.trends.md) — Read up to 24 months of ledger income, expenses, profit or loss, net burn proxy and top vendors per currency, with period and review coverage. ## insights.forecast - [`insights.forecast.get`](https://app.getoatmilk.com/docs/api/insights.forecast.get.md) — Read a 12-month finance projection for one currency from reviewed ledger history, with recurring item labels, seasonal assumptions and indicative ranges. No books are changed. ## insights.map - [`insights.map.config.get`](https://app.getoatmilk.com/docs/api/insights.map.config.get.md) — Read availability of the interactive Google map and image export, plus its public referrer-restricted browser key. Private provider keys are never returned. - [`insights.map.drilldown`](https://app.getoatmilk.com/docs/api/insights.map.drilldown.md) — Inspect up to 100 map records with the same filters, an optional returned clusterKey and an opaque nextCursor. Logical records and stable cursor paging preserve source access and missing-location coverage. - [`insights.map.export`](https://app.getoatmilk.com/docs/api/insights.map.export.md) — Generate a private PNG of the permitted Insights Map filters and supplied viewport, with native map attribution. Return a short-lived private download URL; no accounting records change. - [`insights.map.get`](https://app.getoatmilk.com/docs/api/insights.map.get.md) — Read a bounded geographic map of transactions, trips, saved hotel groups and saved event venues. Filter by layers, dates, transaction types, currency, search, explicit country/region/city, bounds and zoom; group by saved city/region or geographic grids. Counts include missing-location coverage, and transaction values stay separate by currency. Only locations and sources the actor can read are included; no geocoding or model calls. - [`insights.map.view`](https://app.getoatmilk.com/docs/api/insights.map.view.md) — Create a safe local Insights Map URL from canonical filters and renderer center/selection. No financial records or saved locations change. ## insights.people - [`insights.people.get`](https://app.getoatmilk.com/docs/api/insights.people.get.md) — Read all-time contractor cost, payments, hours and timesheet punctuality for one currency, with source coverage. ## insights.projects - [`insights.projects.get`](https://app.getoatmilk.com/docs/api/insights.projects.get.md) — Read project-tag income and spending per currency for an accounting period, including deduplicated totals and overlapping tags. ## insights.tax - [`insights.tax.get`](https://app.getoatmilk.com/docs/api/insights.tax.get.md) — Read summarized corporate or GST/HST tax workpaper readiness, issue counts and CAD lines for a period, without company details or transaction rows. This is a draft, not a filed return. ## reports - [`reports.export`](https://app.getoatmilk.com/docs/api/reports.export.md) — Create a CSV or evidence ZIP export. Supply format, date filters and idempotencyKey. Tax exports require completed fiscal settings. Exports identify provisional records and unresolved items. - [`reports.summary`](https://app.getoatmilk.com/docs/api/reports.summary.md) — Return accounting totals separated by currency and provisional or reviewed record counts. ## subscriptions.duplicates - [`subscriptions.duplicates.check`](https://app.getoatmilk.com/docs/api/subscriptions.duplicates.check.md) — On demand, find plans billed by the same vendor in one currency: name rules first, then the Classifier over every plan's normalized merchant descriptor, dates and amounts, then a second opinion on uncertain groups. Never sends raw names, payee names, receipts, accounts or notes. Saves suggestions only; nothing is merged. ## subscriptions - [`subscriptions.list`](https://app.getoatmilk.com/docs/api/subscriptions.list.md) — List software subscription candidates from reviewed ledger charges, saved finance corrections, evidence-backed quantities, actual paid totals by calendar year and annualized estimates. Plans a person merged are combined within one currency, and possible duplicate plans are listed with the classifier's and the second opinion's views. A possibly stopped charge is not proof of cancellation. - [`subscriptions.save`](https://app.getoatmilk.com/docs/api/subscriptions.save.md) — Confirm or correct one subscription's cadence, status, renewal, amount or explicitly evidenced seat or usage quantity. Requires expectedRevision and idempotencyKey. Does not change accounting entries or cancel a provider subscription. - [`subscriptions.suggest`](https://app.getoatmilk.com/docs/api/subscriptions.suggest.md) — On demand, use the Classifier over the dates and amounts of up to 32 software purchase patterns to suggest recurring or one-off, without sending merchant names or receipt contents to the model. Returns uncertainty and coverage; no records change. ## subscriptions.merges - [`subscriptions.merges.create`](https://app.getoatmilk.com/docs/api/subscriptions.merges.create.md) — Merge two or more plans in the same currency by hand, combining their history and yearly estimate into one plan. Requires an idempotencyKey. Changes no accounting entries and can be undone. - [`subscriptions.merges.decide`](https://app.getoatmilk.com/docs/api/subscriptions.merges.decide.md) — Merge suggested duplicate plans, or keep them separate so they are not suggested again, for one group or many at once. Requires each group's expectedRevision and an idempotencyKey. Changes no accounting entries and can be undone. - [`subscriptions.merges.undo`](https://app.getoatmilk.com/docs/api/subscriptions.merges.undo.md) — Undo the last decision on one merge group: a merge returns to a suggestion (or, when made by hand, the plans stay separate), and plans kept separate return to a suggestion. Requires expectedRevision and idempotencyKey. ## tax.adjustments - [`tax.adjustments.update`](https://app.getoatmilk.com/docs/api/tax.adjustments.update.md) — Record reviewed regular-method GST/HST adjustments, instalments, rebates and self-assessments in CAD minor units, with reason, source evidence, revision and the current ledger snapshot. This does not remit tax or file a return. ## tax.checks - [`tax.checks.update`](https://app.getoatmilk.com/docs/api/tax.checks.update.md) — Record tax-preparation review evidence against the current workpaper snapshot, with revision checks. Later ledger changes invalidate completed checks. ## tax.entries - [`tax.entries.review`](https://app.getoatmilk.com/docs/api/tax.entries.review.md) — Confirm recognition date, GST/HST treatment, supporting documentation, input tax credit eligibility and explicit CAD exchange-rate provenance for an entry. For GST/HST dated before the registration date, preRegistration records the decision: no_itc (a purchase with no input tax credit) or accountant. Requires both entry and tax-review revisions. ## tax - [`tax.export`](https://app.getoatmilk.com/docs/api/tax.export.md) — Create a private year-end package for { period, expectedSnapshotHash, idempotencyKey }. The core zip (url) holds a README, a summary PDF, the workpaper JSON and CSV, record listings and a hash manifest; corporate years add a draft income statement by GIFI line, a general ledger, the year-end questionnaire, contractor payments and, when registered, a GST/HST summary, and GST/HST periods add the return lines and the period's records instead. Original receipts, statements, Stripe records and tax documents come as separate originals parts (parts[], each under 40 MB, hashed in the manifest). Repeating the request with the same key returns the same package with fresh links. Requires the current snapshot hash. Nothing is filed. - [`tax.readiness`](https://app.getoatmilk.com/docs/api/tax.readiness.md) — Read draft corporate or GST/HST tax workpapers, reviewed totals, unresolved records and preparation tasks. This does not file a return or calculate final T2 liability. ## tax.filings - [`tax.filings.get`](https://app.getoatmilk.com/docs/api/tax.filings.get.md) — Read a return or slips the company files itself, step by step: { kind: gst_hst (a GST/HST period), t4a (contractor T4A and T4A-NR slips for a calendarYear) or t2 (a corporate year), period? or calendarYear? (defaults to the newest one that ended) }. Returns the due dates (with weekend and holiday shifts), each walkthrough step and whether it's done, what was recorded (filed on, CRA confirmation number, paid on and amount, copies given), the matching compliance checklist items, the business number on file, the other periods to choose from and the official sources. The numbers to enter come from tax.prep.overview (GST/HST lines) and contractorOps.taxForms.get (slips). Oatmilk never files or pays anything. - [`tax.filings.update`](https://app.getoatmilk.com/docs/api/tax.filings.update.md) — Record progress on a return or slips the company files itself: { kind, periodKey (from tax.filings.get), expectedRevision, idempotencyKey, steps? ({ stepKey: true|false }), filedOn?, confirmation? (the CRA confirmation number), paidOn?, amountPaidMinor?, copiesSentOn?, note? }. Once filed (and paid, or the copies given), the matching compliance checklist items are marked done. It only records what the person did on the CRA's site; nothing is sent to the CRA. ## tax.financialCounterparts - [`tax.financialCounterparts.clear`](https://app.getoatmilk.com/docs/api/tax.financialCounterparts.clear.md) — Clear a separate financial counterpart using its current revision, source fingerprint and retry key. Releases versioned repayment allocations. Advances with active repayments cannot be cleared. Preserves imported records, earlier evidence and audit history. Legacy purposes require dashboard confirmation. - [`tax.financialCounterparts.get`](https://app.getoatmilk.com/docs/api/tax.financialCounterparts.get.md) — Read an existing CAD transfer's current original bank evidence, full allocation proof and separately reviewed balance-sheet counterpart. Unsupported or changed sources remain unresolved. Read-only. - [`tax.financialCounterparts.preview`](https://app.getoatmilk.com/docs/api/tax.financialCounterparts.preview.md) — Preview an explicit incoming or outgoing intercompany advance or repayment against its imported cash movement. Validates current source/review/obligation revisions, counterparties, original evidence, accounting dates, partial repayment allocations and remaining balances. No writes. - [`tax.financialCounterparts.review`](https://app.getoatmilk.com/docs/api/tax.financialCounterparts.review.md) — Classify a supported CAD transfer with an explicit incoming advance, outgoing advance, incoming repayment or outgoing repayment. Uses current source/review/obligation revisions, original evidence, owner attestations, a posting preview and a retry key. Repayments allocate existing obligations and cannot overpay. Atomically saves the separate counterpart, balances and audit without duplicating imported cash or touching other companies. Legacy shareholder and share-capital purposes require dashboard confirmation. ## tax.gifi - [`tax.gifi.confirm`](https://app.getoatmilk.com/docs/api/tax.gifi.confirm.md) — Confirm a corporate year's tax lines (GIFI) for { period, snapshotHash, idempotencyKey } once every category with activity has a confirmed GIFI code and every record has a category: saves the financial statements and GIFI mapping checks the way the tax lines step does. Refused (INVALID_STATE) while a category still needs a code (tax.gifi.update) or a record has no category. A changed snapshotHash means records changed: read again. Nothing is filed. - [`tax.gifi.get`](https://app.getoatmilk.com/docs/api/tax.gifi.get.md) — Read the categories with records in a tax period ({ period }), their reviewed CAD totals, suggested and confirmed GIFI codes from the CRA RC4088 index, the supported code list, the mapping revision and a draft income statement by GIFI line. Suggestions are never saved until confirmed. - [`tax.gifi.update`](https://app.getoatmilk.com/docs/api/tax.gifi.update.md) — Confirm GIFI codes for categories with mappings [{ categoryId, code }], the current mapping revision (expectedRevision) and an idempotency key. Codes must be in the supported GIFI list. Changes are audited and do not change any accounting record. ## tax.intercompanyCounterparties - [`tax.intercompanyCounterparties.create`](https://app.getoatmilk.com/docs/api/tax.intercompanyCounterparties.create.md) — Create or reuse a named tenant-local intercompany counterparty with a retry key. Never creates a company, grants access, or writes reciprocal books. - [`tax.intercompanyCounterparties.list`](https://app.getoatmilk.com/docs/api/tax.intercompanyCounterparties.list.md) — List stable counterparty IDs recorded locally in this organization. A counterparty grants no access to another company's books. ## tax.intercompanyObligations - [`tax.intercompanyObligations.list`](https://app.getoatmilk.com/docs/api/tax.intercompanyObligations.list.md) — Inspect current intercompany advance IDs, revisions, allocated amounts and remaining CAD balances, optionally for one tenant-local counterparty. Retains committed allocations when later evidence needs review. ## tax.packages - [`tax.packages.download`](https://app.getoatmilk.com/docs/api/tax.packages.download.md) — Download a ready package in Oatmilk: { packageId }. Returns a link that works for five minutes. Each download is audited. - [`tax.packages.list`](https://app.getoatmilk.com/docs/api/tax.packages.list.md) — List the year-end data packages asked for in the last 60 days, newest first: each one's fiscal year, status (queued and building while it's being put together, then ready for seven days, failed or expired), size, what's inside (reports, bank statements, Stripe, company documents, shareholder register, and anything left out), who it was sent to, whether each person's email went out, and every download (through a link or in Oatmilk, and whether the person was signed in). Read-only; the links themselves are only ever in the emails. - [`tax.packages.request`](https://app.getoatmilk.com/docs/api/tax.packages.request.md) — Ask for one ZIP of everything an outside accountant needs for a fiscal year: { period? (a corporate year; defaults to the newest ended one), recipients? (up to 10 email addresses besides the person asking), note? (up to 500 characters, shown in the email), idempotencyKey }. It's put together in the background, usually within a few minutes; the person asking and each recipient then get an email with their own download link, which works without signing in for seven days. Each download is audited. Tell the person it's being put together and that the email will come when it's ready; check on it with tax.packages.list. - [`tax.packages.revoke`](https://app.getoatmilk.com/docs/api/tax.packages.revoke.md) — Turn off a package's download links: { packageId, linkId? (one person's link; leave it out to turn off every link), idempotencyKey }. The team can still download the package in Oatmilk until it expires. - [`tax.packages.share`](https://app.getoatmilk.com/docs/api/tax.packages.share.md) — Send a package to more people: { packageId, emails (1 to 10 addresses), idempotencyKey }. Each new person gets their own link by email, at once if the package is ready, otherwise as soon as it is. Someone whose link was turned off gets a new one. A package can go to 25 people at most. ## tax.personalExpenses - [`tax.personalExpenses.confirm`](https://app.getoatmilk.com/docs/api/tax.personalExpenses.confirm.md) — Confirm (confirmed: true) or withdraw (false) 'No business expenses were paid personally' for { period }, with an optional note, the current confirmation revision (0 when none) and an idempotency key. Audited. - [`tax.personalExpenses.export`](https://app.getoatmilk.com/docs/api/tax.personalExpenses.export.md) — The personally paid expenses for { period } as a CSV for the accountant: { filename, csv }. - [`tax.personalExpenses.get`](https://app.getoatmilk.com/docs/api/tax.personalExpenses.get.md) — The list the accountant asked for: business expenses paid personally in { period }, with date, amount, currency, category, merchant, who paid, receipt link, and whether each was reimbursed by Wise transfer, is still owed or was not claimed, plus totals and whether the company confirmed that none were paid personally. ## tax.prep - [`tax.prep.overview`](https://app.getoatmilk.com/docs/api/tax.prep.overview.md) — Read the guided year-end checklist for a corporate (T2) fiscal year or a GST/HST period: steps with status, counts and the next action, progress, the periods to choose from and deadlines. Input { kind, year? | from and to?, summary?, includeWorkpaper? }; without a year it opens the newest ended period, even when it is done. summary: true leaves out the record lists (for a period's steps left). includeWorkpaper: true returns the same period's full workpaper alongside the checklist, or null when unsupported. Read-only; nothing is filed or paid. ## tax.prizes - [`tax.prizes.events.save`](https://app.getoatmilk.com/docs/api/tax.prizes.events.save.md) — Create or edit a prize event (hackathon, competition, event) with its date, notes and the event or promo documents kept as evidence. Input { id?, expectedRevision, name, kind, heldOn?, notes, evidenceIds?, venue?, archived?, idempotencyKey }. Optional venue { lat, lon, label, address?, city?, region?, countryCode? } adds its location to Insights Map; countryCode is an explicit two-letter uppercase country code. Omit venue to keep it, or set null to remove it. Never infer a venue from an event name. - [`tax.prizes.overview`](https://app.getoatmilk.com/docs/api/tax.prizes.overview.md) — Prize payouts for a calendar year (default: the latest with payouts): events, winners with masked details and intake link state, confirmed payouts, suggestions detected from outgoing transfers (never applied automatically), the per-winner total against the $500 T4A box 028 threshold with a plain status (under $500, T4A needed, waiting for winner details, ask your accountant), sponsorship invoices shown separately, and the checklist for the CRA RZ account and the filing deadline. Optional eventId includes that active workspace event when opening an older map result, without changing totals or other source counts. No HST applies to prizes. Nothing is sent or filed. - [`tax.prizes.payouts.decide`](https://app.getoatmilk.com/docs/api/tax.prizes.payouts.decide.md) — Decide about one outgoing transfer or record: op record confirms it as a prize (optionally to a winner and event), dismiss says it wasn't a prize, update changes its winner, event, note or returned amount, undo puts it back to a suggestion. Never sends money. - [`tax.prizes.recipients.link`](https://app.getoatmilk.com/docs/api/tax.prizes.recipients.link.md) — Issue a fresh winner details link, send a reminder (at most every 12 hours and six times), or copy the current link ({ recipientId, mode: issue|remind|copy }). Copying and issuing without an email return the private link and work in the signed-in dashboard only. - [`tax.prizes.recipients.save`](https://app.getoatmilk.com/docs/api/tax.prizes.recipients.save.md) — Add a winner (individual or business), edit them, or archive them. With sendLink and an email address Oatmilk emails the winner a private, account-free link for their legal name, SIN or business number and mailing address, before any payment. The email never contains a number. - [`tax.prizes.schedule`](https://app.getoatmilk.com/docs/api/tax.prizes.schedule.md) — The T4A summary schedule for a calendar year { year } as a CSV: winner, legal name, SIN or business number, address, amount, box 028 and payment dates. Numbers are masked. reveal { reason } shows full numbers to an administrator in the dashboard or to the accountant, and is audited with the reason. ## tax.questions - [`tax.questions.answer`](https://app.getoatmilk.com/docs/api/tax.questions.answer.md) — Answer one year-end question of a corporate tax year: { period, question, answer: yes|no|not_sure, choice?, choice2?, text?, amount?, note?, evidenceIds?, snapshotHash, idempotencyKey }. tax.questions.list names the follow-up fields each answer needs; amounts are in dollars ("1250.00") and files are evidence ids, from tax.sources.confirm or an original already kept with a record. Oatmilk checks the answer as the questions step does, words it the same way and saves it as the question's tax checks at their current revisions, exactly like tax.checks.update. not_sure leaves the question open for the accountant. Returns the saved checks. A changed snapshotHash means records changed: read the questions again. Nothing is filed. - [`tax.questions.list`](https://app.getoatmilk.com/docs/api/tax.questions.list.md) — Read the year-end questions of a corporate tax year ({ period }), the questions step of tax preparation: each question's key, title, question and plain hint; the answers it takes (yes, no, not_sure) with what each one means and the follow-up fields it asks for (choice and choice2 with their options, text, amount in dollars, note, files); the saved answer read back from the tax checks, open (left for the accountant) or stale (records changed after it was saved); Oatmilk's suggested answer from the books where it has one; progress; and the snapshotHash to answer with. Read-only. ## tax.reports - [`tax.reports.download`](https://app.getoatmilk.com/docs/api/tax.reports.download.md) — Download those reports for { period, format (xlsx default, or pdf) } and either report (everything, financial_statements, trial_balance, balance_sheet, income_statement, general_ledger, gst_hst, stripe, contractors) or reports, a list of the ones a person picked (balance_sheet, income_statement, trial_balance, general_ledger, gst_hst, stripe, contractors), which come as one file. The period can be any fiscal year from tax.reports.overview or any dates up to a year apart. Excel workbooks have one sheet per report with formulas for the totals; everything is the whole workbook. Returns a short-lived download. Each request builds a fresh file and is audited. - [`tax.reports.overview`](https://app.getoatmilk.com/docs/api/tax.reports.overview.md) — Read the reports an accountant asks for at year-end, for { period? (defaults to the newest ended fiscal year) }: the trial balance with debit and credit columns, the balance sheet and income statement (draft, from every record, in CAD, by account and GIFI line), plus GST/HST collected and claimed and money set aside for it, Stripe charges before fees with the fees and payouts, contractors and the T4A slips they likely need, key dates (balance due, GST/HST, T2, T4A) and the company documents on file. Also lists the fiscal years. Read-only. ## tax.requests - [`tax.requests.add`](https://app.getoatmilk.com/docs/api/tax.requests.add.md) — Add one item to the accountant's checklist by hand, to a new request or to an existing one (requestId). Input { requestId?, kind, title, detail?, idempotencyKey }. - [`tax.requests.extract`](https://app.getoatmilk.com/docs/api/tax.requests.extract.md) — Turn the text of an accountant's email into checklist items with a strict-schema AI extraction. The text is untrusted: instructions inside it are ignored, no tools run, only the accountant's short quotes are kept, and nothing is sent or changed besides the new checklist. Input { text, receivedOn?, from?, to?, idempotencyKey }. - [`tax.requests.list`](https://app.getoatmilk.com/docs/api/tax.requests.list.md) — List what the accountant asked for as a checklist. Each item has a status (open, ready or sent), a link to where Oatmilk fulfils it, and whether Oatmilk already holds the answer (statements, personally paid expenses, payroll answer, accounting access). Also returns the one year-end question when the accountant names a year-end that differs from the company's. Input { from?, to? } picks the period whose facts decide what is ready. - [`tax.requests.update`](https://app.getoatmilk.com/docs/api/tax.requests.update.md) — Change a checklist item ({ op: item, itemId, expectedRevision, status?: open|ready|sent|dismissed, note?, evidenceIds? }) or record how the year-end question was answered ({ op: year_end, requestId, expectedRevision, resolution: kept|changed }). Changing the company's year-end itself uses company.update. ## tax.sources - [`tax.sources.confirm`](https://app.getoatmilk.com/docs/api/tax.sources.confirm.md) — Verify uploaded company/tax source original bytes and preserve evidence without creating accounting entries. Returns an evidence ID for source references. - [`tax.sources.prepare`](https://app.getoatmilk.com/docs/api/tax.sources.prepare.md) — Prepare an immutable private PDF or photo company/tax source upload. Returns a signed upload URL; this does not create an expense or run AI. ## tax.statements - [`tax.statements.acknowledge`](https://app.getoatmilk.com/docs/api/tax.statements.acknowledge.md) — Tell the accountant that an account's statement gap is known and nothing more can be done ({ period, accountId, acknowledged, note (required, plain words such as 'The card wasn't used this year'), expectedRevision (0 when new), idempotencyKey }). The account stops counting as needing attention, the gap stays listed for the accountant with the note, and the answer is audited. acknowledged false withdraws it. - [`tax.statements.overview`](https://app.getoatmilk.com/docs/api/tax.statements.overview.md) — Read every bank, card and payment account's statement bundle for a period { period }: opening and closing balance as of the period end, transaction counts and totals, the original statement files kept, and a plain-words completeness check per account (no statements, a late start, an early end, a missing month, or balances that don't agree with the imported lines). Read-only. - [`tax.statements.pack`](https://app.getoatmilk.com/docs/api/tax.statements.pack.md) — Build the statements pack for { period }: one folder per account with its balances, a CSV and a PDF transaction listing for the period, and the original statement files kept for it, plus an index and a completeness summary. Returns a short-lived download. Each request builds a fresh pack and is audited. ## tax.treatment - [`tax.treatment.apply`](https://app.getoatmilk.com/docs/api/tax.treatment.apply.md) — Confirm a whole group of records at once for { period, code, entryIds, idempotencyKey }: each record listed that is still in the group gets the treatment Oatmilk suggests for that reason (for example no tax credit for purchases without a receipt), saved as a tax review in the caller's name and marked as confirmed together. Records that changed or are no longer in the group are skipped and reported. The closed-period and revision guards still apply. - [`tax.treatment.auto`](https://app.getoatmilk.com/docs/api/tax.treatment.auto.md) — Settle the sales tax of every record in a tax period that the rules can prove, as Autopilot, for { period, snapshotHash, pass, idempotencyKey }: bank fees, interest, payments to companies outside Canada or to contractors, small purchases with no receipt (no credit claimed) and receipts whose tax adds up. Each is saved as the same tax review a person would save, marked automatic with its plain reason, and can be undone. Records a person reviewed or undid, and records that changed, are left alone. Returns { applied, skipped, remaining }. - [`tax.treatment.undo`](https://app.getoatmilk.com/docs/api/tax.treatment.undo.md) — Undo a tax review that Autopilot or a group confirmation saved, for { entryId, expectedRevision, idempotencyKey }: the record goes back to open and Autopilot leaves it alone until it changes. A review a person saved cannot be undone this way. Refused in a closed period. ## company.shareholders.export Download the shareholder register as a spreadsheet with full tax numbers, to file Schedule 50 or T5 slips, with a reason. Only an administrator in the dashboard can; the download is audited with the reason and the number of shareholders, never the numbers. Not available to API keys or MCP clients. `POST /api/v1/accounting/company.shareholders.export` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin Not available over MCP: The shareholder register with full SINs and business numbers. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `reason` | string | Yes | A short note saying why, kept in the record's history. 5–500 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/company.shareholders.export \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/company.shareholders.export ## company.shareholders.get Read the shareholder register: the classes of shares (common or preferred, voting or not), each shareholder (person, corporation or trust; address; resident in Canada or not) with their holdings (number of shares, class, issue date, certificate, what was paid, end date), directors and officers, shares outstanding by class, each holder's share of the common, preferred and voting shares, the T2 Schedule 50 rows (holders of 10% or more, with the tax number masked), how much of the vote Canadians hold, and names found in company documents that aren't in the register yet. Tax numbers are only ever masked. `GET | POST /api/v1/accounting/company.shareholders.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_company_shareholders_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/company.shareholders.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/company.shareholders.get ## company.shareholders.taxNumber Save or remove a shareholder's tax number for Schedule 50 and T5 slips: { holderId, kind (sin, bn with an optional program account, itn, or foreign with country), value, country? } or value null to remove it. SINs are checked with their check digit. The number is stored encrypted and only ever returned masked; it is never logged or audited. `POST /api/v1/accounting/company.shareholders.taxNumber` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin MCP tool: `accounting_company_shareholders_tax_number` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `holderId` | string | Yes | Matches ^[a-z0-9][a-z0-9-]{7,39}$. | | `kind` | enum | | Which kind of record or job this is. One of: `sin`, `bn`, `itn`, `foreign`. | | `value` | string or null | Yes | | | `country` | string | | exactly 2 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/company.shareholders.taxNumber \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "holderId": "aaaaaaaa", "value": "example", "kind": "sin" }' ``` Reference page: https://app.getoatmilk.com/docs/api/company.shareholders.taxNumber ## company.shareholders.update Replace the shareholder register with { register: { classes, holders, holdings, officers }, expectedRevision, idempotencyKey } (from company.shareholders.get, changed). Ids are 8 to 40 lowercase letters, digits and hyphens; shares are whole numbers; every holding names a holder and class in the register. Removing a shareholder removes their saved tax number. Audited with counts only. `POST /api/v1/accounting/company.shareholders.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_company_shareholders_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `register` | object | Yes | No other fields. | | `register.classes` | array of objects | Yes | at most 20 items. | | `register.classes[].id` | string | Yes | The record's ID. Matches ^[a-z0-9][a-z0-9-]{7,39}$. | | `register.classes[].name` | string | Yes | A display name. 1–60 characters. | | `register.classes[].kind` | enum | Yes | Which kind of record or job this is. One of: `common`, `preferred`. | | `register.classes[].voting` | boolean | | Default `true`. | | `register.classes[].note` | string | | A short note, kept with the record. at most 500 characters. Default `""`. | | `register.holders` | array of objects | Yes | at most 200 items. | | `register.holders[].id` | string | Yes | The record's ID. Matches ^[a-z0-9][a-z0-9-]{7,39}$. | | `register.holders[].kind` | enum | | Which kind of record or job this is. One of: `person`, `corporation`, `trust`. Default `"person"`. | | `register.holders[].legalName` | string | Yes | 1–200 characters. | | `register.holders[].email` | "" or string (email) | | An email address. One of: ``. Default `""`. | | `register.holders[].address` | object | | No other fields. Default `{"line1":"","line2":"","city":"","region":"","postalCode":"","country":""}`. | | `register.holders[].address.line1` | string | | at most 120 characters. Default `""`. | | `register.holders[].address.line2` | string | | at most 120 characters. Default `""`. | | `register.holders[].address.city` | string | | at most 80 characters. Default `""`. | | `register.holders[].address.region` | string | | at most 80 characters. Default `""`. | | `register.holders[].address.postalCode` | string | | at most 20 characters. Default `""`. | | `register.holders[].address.country` | string | | at most 2 characters. Default `""`. | | `register.holders[].residency` | enum | | One of: `canada`, `other`. Default `"canada"`. | | `register.holders[].note` | string | | A short note, kept with the record. at most 500 characters. Default `""`. | | `register.holdings` | array of objects | Yes | at most 1000 items. | | `register.holdings[].id` | string | Yes | The record's ID. Matches ^[a-z0-9][a-z0-9-]{7,39}$. | | `register.holdings[].holderId` | string | Yes | Matches ^[a-z0-9][a-z0-9-]{7,39}$. | | `register.holdings[].classId` | string | Yes | Matches ^[a-z0-9][a-z0-9-]{7,39}$. | | `register.holdings[].shares` | string | Yes | Matches ^[1-9]\d{0,14}$. | | `register.holdings[].issuedOn` | string or null | | Default `null`. | | `register.holdings[].certificate` | string | | at most 40 characters. Default `""`. | | `register.holdings[].considerationMinor` | string or null | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Default `null`. | | `register.holdings[].endedOn` | string or null | | Default `null`. | | `register.holdings[].note` | string | | A short note, kept with the record. at most 500 characters. Default `""`. | | `register.officers` | array of objects | Yes | at most 50 items. | | `register.officers[].id` | string | Yes | The record's ID. Matches ^[a-z0-9][a-z0-9-]{7,39}$. | | `register.officers[].name` | string | Yes | A display name. 1–200 characters. | | `register.officers[].roles` | array of enum values | Yes | One of: `director`, `president`, `secretary`, `treasurer`, `officer`. 1–5 items. | | `register.officers[].startedOn` | string or null | | Default `null`. | | `register.officers[].endedOn` | string or null | | Default `null`. | | `register.officers[].residency` | enum | | One of: `canada`, `other`. Default `"canada"`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 1000000000. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/company.shareholders.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "register": { "classes": [ { "id": "aaaaaaaa", "name": "Synthetic Ventures Inc.", "kind": "common" } ], "holders": [ { "id": "aaaaaaaa", "legalName": "Synthetic Ventures Inc." } ], "holdings": [ { "id": "aaaaaaaa", "holderId": "aaaaaaaa", "classId": "aaaaaaaa", "shares": "1" } ], "officers": [ { "id": "aaaaaaaa", "name": "Synthetic Ventures Inc.", "roles": [ "director" ] } ] }, "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/company.shareholders.update ## handoff.export Export a versioned professional handoff with stable record identifiers, unresolved items and provider control totals. Employee records require supporting originals access. Generates no ledger postings. `GET | POST /api/v1/accounting/handoff.export` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_handoff_export` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string (date) | Yes | The first date to include, as YYYY-MM-DD. | | `to` | string (date) | Yes | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/handoff.export \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'from=2026-09-01' \ --data-urlencode 'to=2026-09-30' ``` Reference page: https://app.getoatmilk.com/docs/api/handoff.export ## handoff.get Read professional handoff, separate authorizations, filing confirmations and calendar-year payroll completeness. Prepared records never establish filing or create ledger postings. `GET | POST /api/v1/accounting/handoff.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_handoff_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string (date) | Yes | The first date to include, as YYYY-MM-DD. | | `to` | string (date) | Yes | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/handoff.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'from=2026-09-01' \ --data-urlencode 'to=2026-09-30' ``` Reference page: https://app.getoatmilk.com/docs/api/handoff.get ## handoff.update Record one handoff item with its owner, source, state, original documents and current revision. Filing acceptance requires retained confirmation. Stores no full payroll identity numbers and creates no postings. `POST /api/v1/accounting/handoff.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string (date) | Yes | The first date to include, as YYYY-MM-DD. | | `to` | string (date) | Yes | The last date to include, as YYYY-MM-DD. | | `key` | enum | Yes | One of: `engagement`, `prior_returns`, `prior_balances`, `workpapers`, `cra_income`, `cra_gst`, `cra_payroll`, `payroll_provider`, `t2_filing`, `gst_filing`, `payroll`, `contractor_identity`. | | `item` | object | Yes | No other fields. | | `item.status` | enum | Yes | Only include records with this status. One of: `not_started`, `requested`, `received`, `reviewed`, `prepared`, `submitted`, `accepted`, `not_applicable`. | | `item.owner` | string | Yes | at most 200 characters. | | `item.source` | string | Yes | Where the record came from. at most 500 characters. | | `item.note` | string | Yes | A short note, kept with the record. at most 1000 characters. | | `item.requestId` | string (ID) or null | | | | `item.documentIds` | array of strings (ID) | Yes | A list of record IDs. at most 20 items. | | `item.confirmation` | string | Yes | at most 200 characters. | | `item.authorizationEndsOn` | string (date) or null | Yes | | | `item.calendarYear` | integer or null | Yes | A calendar year, such as 2026. | | `item.payrollChecks` | array of enum values | Yes | One of: `employee_identity`, `full_calendar_year`, `gross_pay`, `taxable_benefits`, `cpp_cpp2`, `ei`, `income_tax`, `pension_adjustment`, `remittances`, `provider_reconciled`, `slips_delivered`, `filing_confirmation`. at most 12 items. | | `item.payrollReviews` | array of objects | | at most 6 items. Default `[]`. | | `item.payrollReviews[].calendarYear` | integer | Yes | A calendar year, such as 2026. 1990 to 2100. | | `item.payrollReviews[].checks` | array of enum values | Yes | One of: `employee_identity`, `full_calendar_year`, `gross_pay`, `taxable_benefits`, `cpp_cpp2`, `ei`, `income_tax`, `pension_adjustment`, `remittances`, `provider_reconciled`, `slips_delivered`, `filing_confirmation`. at most 12 items. | | `item.payrollReviews[].documentIds` | array of strings (ID) | Yes | A list of record IDs. at most 20 items. | | `item.payrollReviews[].register` | object | | No other fields. | | `item.payrollReviews[].register.payrollAccount` | string or null | Yes | | | `item.payrollReviews[].register.provider` | string | Yes | at most 200 characters. | | `item.payrollReviews[].register.coversFullCalendarYear` | boolean | Yes | | | `item.payrollReviews[].register.providerEmployeeCount` | integer or null | Yes | | | `item.payrollReviews[].register.providerGrossPayMinor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.payrollReviews[].register.remittedMinor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.payrollReviews[].register.expectedRemittanceMinor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.payrollReviews[].register.employees` | array of objects | Yes | at most 100 items. | | `item.payrollReviews[].register.employees[].employeeReference` | string | Yes | 1–100 characters. | | `item.payrollReviews[].register.employees[].name` | string | Yes | A display name. 1–200 characters. | | `item.payrollReviews[].register.employees[].provinceOfEmployment` | string | Yes | Two-letter country or province code. | | `item.payrollReviews[].register.employees[].identityOriginalId` | string (ID) | Yes | The ID of the related record. | | `item.payrollReviews[].register.employees[].grossPayMinor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.payrollReviews[].register.employees[].taxableBenefitsMinor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.payrollReviews[].register.employees[].cppMinor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.payrollReviews[].register.employees[].cpp2Minor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.payrollReviews[].register.employees[].eiMinor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.payrollReviews[].register.employees[].incomeTaxMinor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.payrollReviews[].register.employees[].pensionableEarningsMinor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.payrollReviews[].register.employees[].insurableEarningsMinor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.payrollReviews[].register.employees[].pensionAdjustmentMinor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.payrollReviews[].register.employees[].dentalCoverageCode` | integer or null | Yes | | | `item.engagementSetup` | object | | No other fields. | | `item.engagementSetup.firm` | string | Yes | at most 200 characters. | | `item.engagementSetup.contact` | string | Yes | at most 200 characters. | | `item.engagementSetup.approver` | string | Yes | at most 200 characters. | | `item.engagementSetup.workFrom` | string (date) or null | Yes | | | `item.engagementSetup.workTo` | string (date) or null | Yes | | | `item.engagementSetup.responsibilities` | array of enum values | Yes | One of: `bookkeeping`, `month_end`, `year_end`, `gst_hst`, `payroll`, `t2_return`, `information_slips`. at most 7 items. | | `item.engagementSetup.systemOfRecord` | enum or null | Yes | One of: `oatmilk`, `quickbooks`, `xero`, `sage`, `spreadsheet`, `other`. | | `item.engagementSetup.systemNote` | string | Yes | at most 300 characters. | | `item.engagementSetup.deliverables` | array of objects | Yes | at most 6 items. | | `item.engagementSetup.deliverables[].kind` | enum | Yes | Which kind of record or job this is. One of: `financial_statements`, `t2_return`, `gst_hst_return`, `t4_slips`, `t4a_slips`, `bookkeeping_close`. | | `item.engagementSetup.deliverables[].responsible` | enum | Yes | One of: `company`, `accountant`, `predecessor`, `payroll_provider`. | | `item.engagementSetup.deliverables[].dueOn` | string (date) or null | Yes | | | `item.engagementSetup.deliverables[].state` | enum | Yes | One of: `not_started`, `prepared`, `reviewed`, `approved`, `not_applicable`. | | `item.engagementSetup.deliverables[].approvedOn` | string (date) or null | Yes | | | `item.records` | array of objects | | at most 4 items. | | `item.records[].kind` | enum | Yes | Which kind of record or job this is. One of: `t2_schedules_gifi`, `assessments_correspondence`, `gst_hst_filings`, `payroll_slip_files`, `financial_statements`, `trial_balance_ledger`, `opening_balances`, `adjusting_entries`, `asset_cca_continuity`, `shareholder_loan_continuity`, `receivables_payables`, `unresolved_questions`. | | `item.records[].state` | enum | Yes | One of: `available`, `missing`, `requested`, `received`, `reviewed`, `not_applicable`. | | `item.records[].ownership` | enum | Yes | One of: `client`, `predecessor_working_paper`, `unknown`. | | `item.records[].holder` | string | Yes | at most 200 characters. | | `item.records[].owner` | string | Yes | at most 200 characters. | | `item.records[].documentIds` | array of strings (ID) | Yes | A list of record IDs. at most 20 items. | | `item.authorization` | object | | No other fields. | | `item.authorization.state` | enum | Yes | One of: `not_requested`, `requested`, `approved`, `revoked`. | | `item.authorization.representative` | string | Yes | at most 200 characters. | | `item.authorization.completedBy` | string | Yes | at most 200 characters. | | `item.authorization.requestedOn` | string (date) or null | Yes | | | `item.authorization.approvedOn` | string (date) or null | Yes | | | `item.authorization.revokedOn` | string (date) or null | Yes | | | `item.filings` | array of objects | | at most 8 items. | | `item.filings[].periodFrom` | string (date) | Yes | | | `item.filings[].periodTo` | string (date) | Yes | | | `item.filings[].filedBy` | string | Yes | at most 200 characters. | | `item.filings[].filedOn` | string (date) | Yes | A date, as YYYY-MM-DD. | | `item.filings[].confirmation` | string | Yes | at most 200 characters. | | `item.filings[].amended` | boolean | Yes | | | `item.filings[].amountPaidMinor` | integer or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `item.filings[].assessment` | enum | Yes | One of: `not_received`, `assessed`, `reassessed`. | | `item.filings[].assessedOn` | string (date) or null | Yes | | | `item.filings[].documentIds` | array of strings (ID) | Yes | A list of record IDs. at most 20 items. | | `item.balanceWorkpapers` | array of objects | | at most 3 items. | | `item.balanceWorkpapers[].kind` | enum | Yes | Which kind of record or job this is. One of: `opening_balances`, `trial_balance`, `closing_adjustments`. | | `item.balanceWorkpapers[].asOf` | string (date) | Yes | | | `item.balanceWorkpapers[].approvedBy` | string | Yes | at most 200 characters. | | `item.balanceWorkpapers[].source` | string | Yes | Where the record came from. at most 300 characters. | | `item.balanceWorkpapers[].lines` | array of objects | Yes | 1–120 items. | | `item.balanceWorkpapers[].lines[].account` | string | Yes | 1–120 characters. | | `item.balanceWorkpapers[].lines[].gifi` | string or null | Yes | | | `item.balanceWorkpapers[].lines[].debitMinor` | integer | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. 0 to 100000000000. | | `item.balanceWorkpapers[].lines[].creditMinor` | integer | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. 0 to 100000000000. | | `item.balanceWorkpapers[].lines[].memo` | string | Yes | at most 200 characters. | | `item.balanceWorkpapers[].documentIds` | array of strings (ID) | Yes | A list of record IDs. at most 20 items. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/handoff.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "from": "2026-09-01", "to": "2026-09-30", "key": "engagement", "item": { "status": "not_started", "owner": "example", "source": "example", "note": "Synthetic example from the docs", "documentIds": [ "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" ], "confirmation": "example", "authorizationEndsOn": "2026-09-01", "calendarYear": 2026, "payrollChecks": [ "employee_identity" ] }, "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/handoff.update ## insights.finance.get Read gross income, expenses, refunds, profit or loss and category totals per currency for an accounting period, with reviewed and unresolved coverage. `GET | POST /api/v1/accounting/insights.finance.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_insights_finance_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/insights.finance.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/insights.finance.get ## insights.finance.trends Read up to 24 months of ledger income, expenses, profit or loss, net burn proxy and top vendors per currency, with period and review coverage. `GET | POST /api/v1/accounting/insights.finance.trends` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_insights_finance_trends` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/insights.finance.trends \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/insights.finance.trends ## insights.forecast.get Read a 12-month finance projection for one currency from reviewed ledger history, with recurring item labels, seasonal assumptions and indicative ranges. No books are changed. `GET | POST /api/v1/accounting/insights.forecast.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_insights_forecast_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/insights.forecast.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'currency=CAD' ``` Reference page: https://app.getoatmilk.com/docs/api/insights.forecast.get ## insights.map.config.get Read availability of the interactive Google map and image export, plus its public referrer-restricted browser key. Private provider keys are never returned. `GET | POST /api/v1/accounting/insights.map.config.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_insights_map_config_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/insights.map.config.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/insights.map.config.get ## insights.map.drilldown Inspect up to 100 map records with the same filters, an optional returned clusterKey and an opaque nextCursor. Logical records and stable cursor paging preserve source access and missing-location coverage. `GET | POST /api/v1/accounting/insights.map.drilldown` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_insights_map_drilldown` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `layers` | array of enum values | | One of: `transactions`, `trips`, `stays`, `events`, `other`. 1–5 items. | | `query` | string | | Text to search for. 1–120 characters. | | `from` | string | | The first date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `entryTypes` | array of enum values | | One of: `expense`, `income`, `income_refund`, `refund`, `transfer`, `fee`, `suspense`. 1–7 items. | | `country` | string | | 1–120 characters. | | `region` | string | | 1–120 characters. | | `city` | string | | 1–120 characters. | | `bounds` | object | | No other fields. | | `bounds.south` | number | Yes | -90 to 90. | | `bounds.north` | number | Yes | -90 to 90. | | `bounds.west` | number | Yes | -180 to 180. | | `bounds.east` | number | Yes | -180 to 180. | | `zoom` | number | | 0 to 22. | | `groupBy` | enum | | One of: `auto`, `region`, `city`, `grid`. | | `clusterKey` | string | | at most 100 characters; Matches ^(?:grid:\d+(?:\.\d+)?:\d+:\d+\|(?:city\|region):[a-f0-9]{64})$. | | `cursor` | string | | Where the next page starts: the nextCursor value from the previous response. Matches ^[A-Za-z0-9_-]{1,300}$. | | `limit` | integer | | How many results to return at most. 1 to 100. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/insights.map.drilldown \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/insights.map.drilldown ## insights.map.export Generate a private PNG of the permitted Insights Map filters and supplied viewport, with native map attribution. Return a short-lived private download URL; no accounting records change. `GET | POST /api/v1/accounting/insights.map.export` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_insights_map_export` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filters` | object | Yes | No other fields. | | `filters.layers` | array of enum values | | One of: `transactions`, `trips`, `stays`, `events`, `other`. 1–5 items. | | `filters.query` | string | | Text to search for. 1–120 characters. | | `filters.from` | string | | The first date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `filters.to` | string | | The last date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `filters.currency` | string | | Three-letter currency code, such as CAD or USD. | | `filters.entryTypes` | array of enum values | | One of: `expense`, `income`, `income_refund`, `refund`, `transfer`, `fee`, `suspense`. 1–7 items. | | `filters.country` | string | | 1–120 characters. | | `filters.region` | string | | 1–120 characters. | | `filters.city` | string | | 1–120 characters. | | `filters.bounds` | object | | No other fields. | | `filters.bounds.south` | number | Yes | -90 to 90. | | `filters.bounds.north` | number | Yes | -90 to 90. | | `filters.bounds.west` | number | Yes | -180 to 180. | | `filters.bounds.east` | number | Yes | -180 to 180. | | `filters.zoom` | number | | 0 to 22. | | `filters.groupBy` | enum | | One of: `auto`, `region`, `city`, `grid`. | | `viewport` | object | Yes | No other fields. | | `viewport.center` | object | Yes | No other fields. | | `viewport.center.lat` | number | Yes | -90 to 90. | | `viewport.center.lon` | number | Yes | -180 to 180. | | `viewport.zoom` | number | Yes | 0 to 22. | | `viewport.bounds` | object | | No other fields. | | `viewport.bounds.south` | number | Yes | -90 to 90. | | `viewport.bounds.north` | number | Yes | -90 to 90. | | `viewport.bounds.west` | number | Yes | -180 to 180. | | `viewport.bounds.east` | number | Yes | -180 to 180. | | `width` | 640 or 1280 | | One of: `640`, `1280`. | | `height` | 360 or 720 | | One of: `360`, `720`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/insights.map.export \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": {}, "viewport": { "center": { "lat": 1, "lon": 1 }, "zoom": 1 } }' ``` Reference page: https://app.getoatmilk.com/docs/api/insights.map.export ## insights.map.get Read a bounded geographic map of transactions, trips, saved hotel groups and saved event venues. Filter by layers, dates, transaction types, currency, search, explicit country/region/city, bounds and zoom; group by saved city/region or geographic grids. Counts include missing-location coverage, and transaction values stay separate by currency. Only locations and sources the actor can read are included; no geocoding or model calls. `GET | POST /api/v1/accounting/insights.map.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_insights_map_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `layers` | array of enum values | | One of: `transactions`, `trips`, `stays`, `events`, `other`. 1–5 items. | | `query` | string | | Text to search for. 1–120 characters. | | `from` | string | | The first date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `entryTypes` | array of enum values | | One of: `expense`, `income`, `income_refund`, `refund`, `transfer`, `fee`, `suspense`. 1–7 items. | | `country` | string | | 1–120 characters. | | `region` | string | | 1–120 characters. | | `city` | string | | 1–120 characters. | | `bounds` | object | | No other fields. | | `bounds.south` | number | Yes | -90 to 90. | | `bounds.north` | number | Yes | -90 to 90. | | `bounds.west` | number | Yes | -180 to 180. | | `bounds.east` | number | Yes | -180 to 180. | | `zoom` | number | | 0 to 22. | | `groupBy` | enum | | One of: `auto`, `region`, `city`, `grid`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/insights.map.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/insights.map.get ## insights.map.view Create a safe local Insights Map URL from canonical filters and renderer center/selection. No financial records or saved locations change. `GET | POST /api/v1/accounting/insights.map.view` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_insights_map_view` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filters` | object | Yes | No other fields. | | `filters.layers` | array of enum values | | One of: `transactions`, `trips`, `stays`, `events`, `other`. 1–5 items. | | `filters.query` | string | | Text to search for. 1–120 characters. | | `filters.from` | string | | The first date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `filters.to` | string | | The last date to include, as YYYY-MM-DD. Date as YYYY-MM-DD. | | `filters.currency` | string | | Three-letter currency code, such as CAD or USD. | | `filters.entryTypes` | array of enum values | | One of: `expense`, `income`, `income_refund`, `refund`, `transfer`, `fee`, `suspense`. 1–7 items. | | `filters.country` | string | | 1–120 characters. | | `filters.region` | string | | 1–120 characters. | | `filters.city` | string | | 1–120 characters. | | `filters.bounds` | object | | No other fields. | | `filters.bounds.south` | number | Yes | -90 to 90. | | `filters.bounds.north` | number | Yes | -90 to 90. | | `filters.bounds.west` | number | Yes | -180 to 180. | | `filters.bounds.east` | number | Yes | -180 to 180. | | `filters.zoom` | number | | 0 to 22. | | `filters.groupBy` | enum | | One of: `auto`, `region`, `city`, `grid`. | | `renderer` | object | Yes | No other fields. | | `renderer.lat` | number | | -90 to 90. | | `renderer.lon` | number | | -180 to 180. | | `renderer.selected` | string | | at most 100 characters; Matches ^(?:grid:\d+(?:\.\d+)?:\d+:\d+\|(?:city\|region):[a-f0-9]{64})$. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/insights.map.view \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": {}, "renderer": {} }' ``` Reference page: https://app.getoatmilk.com/docs/api/insights.map.view ## insights.people.get Read all-time contractor cost, payments, hours and timesheet punctuality for one currency, with source coverage. `GET | POST /api/v1/accounting/insights.people.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_insights_people_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `currency` | string | | Three-letter currency code, such as CAD or USD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/insights.people.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/insights.people.get ## insights.projects.get Read project-tag income and spending per currency for an accounting period, including deduplicated totals and overlapping tags. `GET | POST /api/v1/accounting/insights.projects.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_insights_projects_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `currency` | string | | Three-letter currency code, such as CAD or USD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/insights.projects.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/insights.projects.get ## insights.tax.get Read summarized corporate or GST/HST tax workpaper readiness, issue counts and CAD lines for a period, without company details or transaction rows. This is a draft, not a filed return. `GET | POST /api/v1/accounting/insights.tax.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_insights_tax_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/insights.tax.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'from=2026-09-01' \ --data-urlencode 'to=2026-09-30' \ --data-urlencode 'kind=corporate' ``` Reference page: https://app.getoatmilk.com/docs/api/insights.tax.get ## reports.export Create a CSV or evidence ZIP export. Supply format, date filters and idempotencyKey. Tax exports require completed fiscal settings. Exports identify provisional records and unresolved items. `POST /api/v1/accounting/reports.export` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_export` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `accountId` | string (ID) | | The ID of a bank, card or payment account, from accounts.list. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `format` | enum | Yes | One of: `csv`, `bundle`. | | `tax` | boolean | | Default `false`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reports.export \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "format": "csv" }' ``` Reference page: https://app.getoatmilk.com/docs/api/reports.export ## reports.summary Return accounting totals separated by currency and provisional or reviewed record counts. `GET | POST /api/v1/accounting/reports.summary` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_report` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `accountId` | string (ID) | | The ID of a bank, card or payment account, from accounts.list. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/reports.summary \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/reports.summary ## subscriptions.duplicates.check On demand, find plans billed by the same vendor in one currency: name rules first, then the Classifier over every plan's normalized merchant descriptor, dates and amounts, then a second opinion on uncertain groups. Never sends raw names, payee names, receipts, accounts or notes. Saves suggestions only; nothing is merged. `POST /api/v1/accounting/subscriptions.duplicates.check` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_subscriptions_duplicates_check` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/subscriptions.duplicates.check \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/subscriptions.duplicates.check ## subscriptions.list List software subscription candidates from reviewed ledger charges, saved finance corrections, evidence-backed quantities, actual paid totals by calendar year and annualized estimates. Plans a person merged are combined within one currency, and possible duplicate plans are listed with the classifier's and the second opinion's views. A possibly stopped charge is not proof of cancellation. `GET | POST /api/v1/accounting/subscriptions.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_subscriptions_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/subscriptions.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/subscriptions.list ## subscriptions.merges.create Merge two or more plans in the same currency by hand, combining their history and yearly estimate into one plan. Requires an idempotencyKey. Changes no accounting entries and can be undone. `POST /api/v1/accounting/subscriptions.merges.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_subscriptions_merges_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `vendorKeys` | array of strings | Yes | 2–10 items; each 1–120 characters; each Matches ^[a-z0-9 ]+$. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/subscriptions.merges.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "currency": "CAD", "vendorKeys": [ "example", "example" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/subscriptions.merges.create ## subscriptions.merges.decide Merge suggested duplicate plans, or keep them separate so they are not suggested again, for one group or many at once. Requires each group's expectedRevision and an idempotencyKey. Changes no accounting entries and can be undone. `POST /api/v1/accounting/subscriptions.merges.decide` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_subscriptions_merges_decide` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `decision` | enum | Yes | What you decided. One of: `merge`, `dismiss`. | | `groups` | array of objects | Yes | 1–50 items. | | `groups[].id` | string (ID) or null | Yes | The record's ID. | | `groups[].currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `groups[].memberKeys` | array of strings | Yes | 2–20 items; each 1–120 characters; each Matches ^[a-z0-9 ]+$. | | `groups[].expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/subscriptions.merges.decide \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "decision": "merge", "groups": [ { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "currency": "CAD", "memberKeys": [ "example", "example" ], "expectedRevision": 3 } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/subscriptions.merges.decide ## subscriptions.merges.undo Undo the last decision on one merge group: a merge returns to a suggestion (or, when made by hand, the plans stay separate), and plans kept separate return to a suggestion. Requires expectedRevision and idempotencyKey. `POST /api/v1/accounting/subscriptions.merges.undo` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_subscriptions_merges_undo` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/subscriptions.merges.undo \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/subscriptions.merges.undo ## subscriptions.save Confirm or correct one subscription's cadence, status, renewal, amount or explicitly evidenced seat or usage quantity. Requires expectedRevision and idempotencyKey. Does not change accounting entries or cancel a provider subscription. `POST /api/v1/accounting/subscriptions.save` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_subscriptions_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `vendorKey` | string | Yes | 1–120 characters; Matches ^[a-z0-9 ]+$. | | `merchant` | string | Yes | 1–160 characters. | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `cadence` | enum or null | Yes | One of: `monthly`, `quarterly`, `yearly`, `unknown`. | | `status` | enum or null | Yes | Only include records with this status. One of: `active`, `paused`, `ended`. | | `renewalOn` | string (date) or null | Yes | | | `amountMinor` | string or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `quantityCount` | integer or null | Yes | | | `quantityUnit` | enum or null | Yes | One of: `seat`, `user`, `license`, `token`, `credit`. | | `note` | string | Yes | A short note, kept with the record. at most 500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/subscriptions.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "vendorKey": "example", "merchant": "example", "currency": "CAD", "expectedRevision": 3, "cadence": "monthly", "status": "active", "renewalOn": "2026-09-01", "amountMinor": "1250", "quantityCount": 1, "quantityUnit": "seat", "note": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/subscriptions.save ## subscriptions.suggest On demand, use the Classifier over the dates and amounts of up to 32 software purchase patterns to suggest recurring or one-off, without sending merchant names or receipt contents to the model. Returns uncertainty and coverage; no records change. `GET | POST /api/v1/accounting/subscriptions.suggest` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_subscriptions_suggest` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/subscriptions.suggest \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/subscriptions.suggest ## tax.adjustments.update Record reviewed regular-method GST/HST adjustments, instalments, rebates and self-assessments in CAD minor units, with reason, source evidence, revision and the current ledger snapshot. This does not remit tax or file a return. `POST /api/v1/accounting/tax.adjustments.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `line104Minor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `line107Minor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `line110Minor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `line111Minor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `line205Minor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `line405Minor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–2000 characters. | | `snapshotHash` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `evidenceIds` | array of strings (ID) | | A list of record IDs. at most 20 items. Default `[]`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.adjustments.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3, "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" }, "line104Minor": "1250", "line107Minor": "1250", "line110Minor": "1250", "line111Minor": "1250", "line205Minor": "1250", "line405Minor": "1250", "reason": "Synthetic example from the docs", "snapshotHash": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.adjustments.update ## tax.checks.update Record tax-preparation review evidence against the current workpaper snapshot, with revision checks. Later ledger changes invalidate completed checks. `POST /api/v1/accounting/tax.checks.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `check` | enum | Yes | One of: `bank_coverage`, `opening_balances`, `financial_statements`, `gifi_mapping`, `capital_assets`, `shareholder_loans`, `payroll`, `contractor_slips`, `gst_adjustments`, `instalments`, `prior_returns`. | | `status` | enum | Yes | Only include records with this status. One of: `todo`, `complete`, `not_applicable`. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–2000 characters. | | `snapshotHash` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `evidenceIds` | array of strings (ID) | | A list of record IDs. at most 20 items. Default `[]`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.checks.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3, "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" }, "check": "bank_coverage", "status": "todo", "reason": "Synthetic example from the docs", "snapshotHash": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.checks.update ## tax.entries.review Confirm recognition date, GST/HST treatment, supporting documentation, input tax credit eligibility and explicit CAD exchange-rate provenance for an entry. For GST/HST dated before the registration date, preRegistration records the decision: no_itc (a purchase with no input tax credit) or accountant. Requires both entry and tax-review revisions. `POST /api/v1/accounting/tax.entries.review` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `entryRevision` | integer | Yes | The entry's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `recognitionDate` | string | Yes | | | `taxPointDate` | string | Yes | | | `itcClaimDate` | string or null | | Default `null`. | | `taxKind` | enum | Yes | One of: `gst_hst`, `none`, `other`. | | `gstHstMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `otherSalesTaxMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `itcBasisPoints` | integer | Yes | 0 to 10000. | | `itcEligibility` | enum | Yes | One of: `eligible`, `ineligible`, `not_applicable`. | | `documentComplete` | boolean | Yes | | | `supplierRegistration` | string or null | | Default `null`. | | `recipientNamed` | boolean | Yes | | | `supplyDescribed` | boolean | Yes | | | `paymentTermsPresent` | boolean | Yes | | | `exchangeRate` | string or null | | Default `null`. | | `exchangeRateDate` | string or null | | Default `null`. | | `exchangeRateSource` | string or null | | Default `null`. | | `preRegistration` | enum or null | | One of: `no_itc`, `accountant`. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–2000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.entries.review \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "entryRevision": 3, "expectedRevision": 3, "recognitionDate": "2026-09-30", "taxPointDate": "2026-09-30", "taxKind": "gst_hst", "gstHstMinor": "1250", "otherSalesTaxMinor": "1250", "itcBasisPoints": 1, "itcEligibility": "eligible", "documentComplete": true, "recipientNamed": true, "supplyDescribed": true, "paymentTermsPresent": true, "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.entries.review ## tax.export Create a private year-end package for { period, expectedSnapshotHash, idempotencyKey }. The core zip (url) holds a README, a summary PDF, the workpaper JSON and CSV, record listings and a hash manifest; corporate years add a draft income statement by GIFI line, a general ledger, the year-end questionnaire, contractor payments and, when registered, a GST/HST summary, and GST/HST periods add the return lines and the period's records instead. Original receipts, statements, Stripe records and tax documents come as separate originals parts (parts[], each under 40 MB, hashed in the manifest). Repeating the request with the same key returns the same package with fresh links. Requires the current snapshot hash. Nothing is filed. `POST /api/v1/accounting/tax.export` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_tax_export` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `expectedSnapshotHash` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.export \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" }, "expectedSnapshotHash": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.export ## tax.filings.get Read a return or slips the company files itself, step by step: { kind: gst_hst (a GST/HST period), t4a (contractor T4A and T4A-NR slips for a calendarYear) or t2 (a corporate year), period? or calendarYear? (defaults to the newest one that ended) }. Returns the due dates (with weekend and holiday shifts), each walkthrough step and whether it's done, what was recorded (filed on, CRA confirmation number, paid on and amount, copies given), the matching compliance checklist items, the business number on file, the other periods to choose from and the official sources. The numbers to enter come from tax.prep.overview (GST/HST lines) and contractorOps.taxForms.get (slips). Oatmilk never files or pays anything. `GET | POST /api/v1/accounting/tax.filings.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_filings_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | enum | Yes | Which kind of record or job this is. One of: `gst_hst`, `t4a`, `t2`. | | `period` | object | | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `calendarYear` | integer | | A calendar year, such as 2026. 2000 to 2100. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/tax.filings.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'kind=gst_hst' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.filings.get ## tax.filings.update Record progress on a return or slips the company files itself: { kind, periodKey (from tax.filings.get), expectedRevision, idempotencyKey, steps? ({ stepKey: true|false }), filedOn?, confirmation? (the CRA confirmation number), paidOn?, amountPaidMinor?, copiesSentOn?, note? }. Once filed (and paid, or the copies given), the matching compliance checklist items are marked done. It only records what the person did on the CRA's site; nothing is sent to the CRA. `POST /api/v1/accounting/tax.filings.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_tax_filings_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | enum | Yes | Which kind of record or job this is. One of: `gst_hst`, `t4a`, `t2`. | | `periodKey` | string | Yes | 4–21 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 1000000000. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `steps` | map | | | | `filedOn` | string or null | | | | `confirmation` | string or null | | | | `paidOn` | string or null | | | | `amountPaidMinor` | string or null | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `copiesSentOn` | string or null | | | | `note` | string | | A short note, kept with the record. at most 2000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.filings.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "kind": "gst_hst", "periodKey": "example", "expectedRevision": 3, "steps": {} }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.filings.update ## tax.financialCounterparts.clear Clear a separate financial counterpart using its current revision, source fingerprint and retry key. Releases versioned repayment allocations. Advances with active repayments cannot be cleared. Preserves imported records, earlier evidence and audit history. Legacy purposes require dashboard confirmation. `POST /api/v1/accounting/tax.financialCounterparts.clear` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: Legacy untyped loans and share capital still require a person in Oatmilk. Explicit intercompany advances and repayments use the same authorized posting service through MCP, API and the dashboard. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `expectedEntryRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `sourceFingerprint` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–500 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.financialCounterparts.clear \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedEntryRevision": 3, "expectedRevision": 3, "sourceFingerprint": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.financialCounterparts.clear ## tax.financialCounterparts.get Read an existing CAD transfer's current original bank evidence, full allocation proof and separately reviewed balance-sheet counterpart. Unsupported or changed sources remain unresolved. Read-only. `GET | POST /api/v1/accounting/tax.financialCounterparts.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_financial_counterparts_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/tax.financialCounterparts.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'entryId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.financialCounterparts.get ## tax.financialCounterparts.preview Preview an explicit incoming or outgoing intercompany advance or repayment against its imported cash movement. Validates current source/review/obligation revisions, counterparties, original evidence, accounting dates, partial repayment allocations and remaining balances. No writes. `GET | POST /api/v1/accounting/tax.financialCounterparts.preview` Permissions: `accounting:read` · Roles: admin, finance · Send `expectedRevision` MCP tool: `accounting_tax_financial_counterparts_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `expectedEntryRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `sourceFingerprint` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `purpose` | enum | Yes | One of: `incoming_advance`, `outgoing_advance`, `incoming_repayment`, `outgoing_repayment`, `due_to_other`, `due_from_other`, `due_to_shareholder`, `due_from_shareholder`, `common_shares`. | | `label` | string | Yes | 1–100 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–500 characters. | | `evidence` | array of objects | Yes | 1–20 items. | | `evidence[].kind` | enum | Yes | Which kind of record or job this is. One of: `bank_original`, `receipt_original`, `company_original`. | | `evidence[].id` | string (ID) | Yes | The record's ID. | | `evidence[].sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `expectedSupportingSourceFingerprint` | string | | SHA-256 hash as 64 lowercase hex characters. | | `counterpartyId` | string (ID) | | The ID of the related record. | | `allocations` | array of objects | | at most 20 items. | | `allocations[].obligationId` | string (ID) | Yes | The ID of the related record. | | `allocations[].expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `allocations[].amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^[1-9][0-9]{0,18}$. | | `attestations` | array of objects | | at most 20 items. | | `attestations[].kind` | "owner_attestation" | Yes | Which kind of record or job this is. | | `attestations[].statement` | string | Yes | 10–2000 characters. | | `attestations[].attestedBy` | string | Yes | 1–100 characters. | | `attestations[].attestedAt` | string (date-time) | Yes | A date and time in ISO 8601 format. | | `instruction` | string | | 10–2000 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.financialCounterparts.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedEntryRevision": 3, "expectedRevision": 3, "sourceFingerprint": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "purpose": "incoming_advance", "label": "example", "reason": "Synthetic example from the docs", "evidence": [ { "kind": "bank_original", "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.financialCounterparts.preview ## tax.financialCounterparts.review Classify a supported CAD transfer with an explicit incoming advance, outgoing advance, incoming repayment or outgoing repayment. Uses current source/review/obligation revisions, original evidence, owner attestations, a posting preview and a retry key. Repayments allocate existing obligations and cannot overpay. Atomically saves the separate counterpart, balances and audit without duplicating imported cash or touching other companies. Legacy shareholder and share-capital purposes require dashboard confirmation. `POST /api/v1/accounting/tax.financialCounterparts.review` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: Legacy untyped loans and share capital still require a person in Oatmilk. Explicit intercompany advances and repayments use the same authorized posting service through MCP, API and the dashboard. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `expectedEntryRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `sourceFingerprint` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `purpose` | enum | Yes | One of: `incoming_advance`, `outgoing_advance`, `incoming_repayment`, `outgoing_repayment`, `due_to_other`, `due_from_other`, `due_to_shareholder`, `due_from_shareholder`, `common_shares`. | | `label` | string | Yes | 1–100 characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–500 characters. | | `evidence` | array of objects | Yes | 1–20 items. | | `evidence[].kind` | enum | Yes | Which kind of record or job this is. One of: `bank_original`, `receipt_original`, `company_original`. | | `evidence[].id` | string (ID) | Yes | The record's ID. | | `evidence[].sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `expectedSupportingSourceFingerprint` | string | | SHA-256 hash as 64 lowercase hex characters. | | `counterpartyId` | string (ID) | | The ID of the related record. | | `allocations` | array of objects | | at most 20 items. | | `allocations[].obligationId` | string (ID) | Yes | The ID of the related record. | | `allocations[].expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `allocations[].amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^[1-9][0-9]{0,18}$. | | `attestations` | array of objects | | at most 20 items. | | `attestations[].kind` | "owner_attestation" | Yes | Which kind of record or job this is. | | `attestations[].statement` | string | Yes | 10–2000 characters. | | `attestations[].attestedBy` | string | Yes | 1–100 characters. | | `attestations[].attestedAt` | string (date-time) | Yes | A date and time in ISO 8601 format. | | `instruction` | string | | 10–2000 characters. | | `confirmed` | true | Yes | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.financialCounterparts.review \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedEntryRevision": 3, "expectedRevision": 3, "sourceFingerprint": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "purpose": "incoming_advance", "label": "example", "reason": "Synthetic example from the docs", "evidence": [ { "kind": "bank_original", "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" } ], "confirmed": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.financialCounterparts.review ## tax.gifi.confirm Confirm a corporate year's tax lines (GIFI) for { period, snapshotHash, idempotencyKey } once every category with activity has a confirmed GIFI code and every record has a category: saves the financial statements and GIFI mapping checks the way the tax lines step does. Refused (INVALID_STATE) while a category still needs a code (tax.gifi.update) or a record has no category. A changed snapshotHash means records changed: read again. Nothing is filed. `POST /api/v1/accounting/tax.gifi.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_tax_gifi_confirm` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `snapshotHash` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.gifi.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" }, "snapshotHash": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.gifi.confirm ## tax.gifi.get Read the categories with records in a tax period ({ period }), their reviewed CAD totals, suggested and confirmed GIFI codes from the CRA RC4088 index, the supported code list, the mapping revision and a draft income statement by GIFI line. Suggestions are never saved until confirmed. `GET | POST /api/v1/accounting/tax.gifi.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_gifi_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.gifi.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.gifi.get ## tax.gifi.update Confirm GIFI codes for categories with mappings [{ categoryId, code }], the current mapping revision (expectedRevision) and an idempotency key. Codes must be in the supported GIFI list. Changes are audited and do not change any accounting record. `POST /api/v1/accounting/tax.gifi.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `mappings` | array of objects | Yes | 1–500 items. | | `mappings[].categoryId` | string (ID) | Yes | The ID of a category, from categories.list. | | `mappings[].code` | enum | Yes | One of: `1001`, `1002`, `1003`, `1060`, `1066`, `1300`, `1484`, `1740`, `1774`, `1787`, `2620`, `2621`, `2680`, `2780`, `3500`, `3600`, `3700`, `8000`, `8090`, `8091`, `8230`, `8231`, `8241`, `8242`, `8320`, `8340`, `8360`, `8370`, `8520`, `8521`, `8522`, `8523`, `8524`, `8590`, `8620`, `8621`, `8622`, `8690`, `8710`, `8711`. 75 values in total. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.gifi.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "mappings": [ { "categoryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "code": "1001" } ], "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.gifi.update ## tax.intercompanyCounterparties.create Create or reuse a named tenant-local intercompany counterparty with a retry key. Never creates a company, grants access, or writes reciprocal books. `POST /api/v1/accounting/tax.intercompanyCounterparties.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_tax_intercompany_counterparties_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | Yes | A display name. 1–100 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.intercompanyCounterparties.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Synthetic Ventures Inc." }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.intercompanyCounterparties.create ## tax.intercompanyCounterparties.list List stable counterparty IDs recorded locally in this organization. A counterparty grants no access to another company's books. `GET | POST /api/v1/accounting/tax.intercompanyCounterparties.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_intercompany_counterparties_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.intercompanyCounterparties.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/tax.intercompanyCounterparties.list ## tax.intercompanyObligations.list Inspect current intercompany advance IDs, revisions, allocated amounts and remaining CAD balances, optionally for one tenant-local counterparty. Retains committed allocations when later evidence needs review. `GET | POST /api/v1/accounting/tax.intercompanyObligations.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_intercompany_obligations_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `counterpartyId` | string (ID) | | The ID of the related record. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.intercompanyObligations.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/tax.intercompanyObligations.list ## tax.packages.download Download a ready package in Oatmilk: { packageId }. Returns a link that works for five minutes. Each download is audited. `POST /api/v1/accounting/tax.packages.download` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance MCP tool: `accounting_tax_packages_download` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `packageId` | string (ID) | Yes | The ID of the related record. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.packages.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "packageId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.packages.download ## tax.packages.list List the year-end data packages asked for in the last 60 days, newest first: each one's fiscal year, status (queued and building while it's being put together, then ready for seven days, failed or expired), size, what's inside (reports, bank statements, Stripe, company documents, shareholder register, and anything left out), who it was sent to, whether each person's email went out, and every download (through a link or in Oatmilk, and whether the person was signed in). Read-only; the links themselves are only ever in the emails. `GET | POST /api/v1/accounting/tax.packages.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_packages_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.packages.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/tax.packages.list ## tax.packages.request Ask for one ZIP of everything an outside accountant needs for a fiscal year: { period? (a corporate year; defaults to the newest ended one), recipients? (up to 10 email addresses besides the person asking), note? (up to 500 characters, shown in the email), idempotencyKey }. It's put together in the background, usually within a few minutes; the person asking and each recipient then get an email with their own download link, which works without signing in for seven days. Each download is audited. Tell the person it's being put together and that the email will come when it's ready; check on it with tax.packages.list. `POST /api/v1/accounting/tax.packages.request` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_tax_packages_request` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `recipients` | array of strings (email) | | at most 10 items; each at most 254 characters. | | `note` | string | | A short note, kept with the record. at most 500 characters. Default `""`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.packages.request \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.packages.request ## tax.packages.revoke Turn off a package's download links: { packageId, linkId? (one person's link; leave it out to turn off every link), idempotencyKey }. The team can still download the package in Oatmilk until it expires. `POST /api/v1/accounting/tax.packages.revoke` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Destructive: confirm with a person first MCP tool: `accounting_tax_packages_revoke` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `packageId` | string (ID) | Yes | The ID of the related record. | | `linkId` | string (ID) | | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.packages.revoke \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "packageId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.packages.revoke ## tax.packages.share Send a package to more people: { packageId, emails (1 to 10 addresses), idempotencyKey }. Each new person gets their own link by email, at once if the package is ready, otherwise as soon as it is. Someone whose link was turned off gets a new one. A package can go to 25 people at most. `POST /api/v1/accounting/tax.packages.share` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_tax_packages_share` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `packageId` | string (ID) | Yes | The ID of the related record. | | `emails` | array of strings (email) | Yes | at most 10 items; each at most 254 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.packages.share \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "packageId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "emails": [ "finance@example.com" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.packages.share ## tax.personalExpenses.confirm Confirm (confirmed: true) or withdraw (false) 'No business expenses were paid personally' for { period }, with an optional note, the current confirmation revision (0 when none) and an idempotency key. Audited. `POST /api/v1/accounting/tax.personalExpenses.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_tax_personal_expenses_confirm` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `confirmed` | boolean | Yes | | | `note` | string | | A short note, kept with the record. at most 1000 characters. Default `""`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.personalExpenses.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" }, "confirmed": true, "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.personalExpenses.confirm ## tax.personalExpenses.export The personally paid expenses for { period } as a CSV for the accountant: { filename, csv }. `GET | POST /api/v1/accounting/tax.personalExpenses.export` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_personal_expenses_export` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.personalExpenses.export \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.personalExpenses.export ## tax.personalExpenses.get The list the accountant asked for: business expenses paid personally in { period }, with date, amount, currency, category, merchant, who paid, receipt link, and whether each was reimbursed by Wise transfer, is still owed or was not claimed, plus totals and whether the company confirmed that none were paid personally. `GET | POST /api/v1/accounting/tax.personalExpenses.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_personal_expenses_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.personalExpenses.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.personalExpenses.get ## tax.prep.overview Read the guided year-end checklist for a corporate (T2) fiscal year or a GST/HST period: steps with status, counts and the next action, progress, the periods to choose from and deadlines. Input { kind, year? | from and to?, summary?, includeWorkpaper? }; without a year it opens the newest ended period, even when it is done. summary: true leaves out the record lists (for a period's steps left). includeWorkpaper: true returns the same period's full workpaper alongside the checklist, or null when unsupported. Read-only; nothing is filed or paid. `GET | POST /api/v1/accounting/tax.prep.overview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_prep_overview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `year` | integer | | 1900 to 2200. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `latest` | boolean | | | | `summary` | boolean | | | | `includeWorkpaper` | boolean | | Also include workpaper. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/tax.prep.overview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'kind=corporate' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.prep.overview ## tax.prizes.events.save Create or edit a prize event (hackathon, competition, event) with its date, notes and the event or promo documents kept as evidence. Input { id?, expectedRevision, name, kind, heldOn?, notes, evidenceIds?, venue?, archived?, idempotencyKey }. Optional venue { lat, lon, label, address?, city?, region?, countryCode? } adds its location to Insights Map; countryCode is an explicit two-letter uppercase country code. Omit venue to keep it, or set null to remove it. Never infer a venue from an event name. `POST /api/v1/accounting/tax.prizes.events.save` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_tax_prizes_events_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `name` | string | Yes | A display name. 1–160 characters. | | `kind` | enum | | Which kind of record or job this is. One of: `hackathon`, `competition`, `event`, `other`. Default `"hackathon"`. | | `heldOn` | string or null | | | | `notes` | string | | Notes kept with the record. at most 500 characters. Default `""`. | | `evidenceIds` | array of strings (ID) | | A list of record IDs. at most 30 items. | | `venue` | object or null | | | | `venue.lat` | number | Yes | -90 to 90. | | `venue.lon` | number | Yes | -180 to 180. | | `venue.label` | string | Yes | 1–200 characters. | | `venue.address` | string or null | | | | `venue.city` | string or null | | | | `venue.region` | string or null | | | | `venue.countryCode` | string or null | | | | `archived` | boolean | | Whether the record is archived. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.prizes.events.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3, "name": "Synthetic Ventures Inc." }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.prizes.events.save ## tax.prizes.overview Prize payouts for a calendar year (default: the latest with payouts): events, winners with masked details and intake link state, confirmed payouts, suggestions detected from outgoing transfers (never applied automatically), the per-winner total against the $500 T4A box 028 threshold with a plain status (under $500, T4A needed, waiting for winner details, ask your accountant), sponsorship invoices shown separately, and the checklist for the CRA RZ account and the filing deadline. Optional eventId includes that active workspace event when opening an older map result, without changing totals or other source counts. No HST applies to prizes. Nothing is sent or filed. `GET | POST /api/v1/accounting/tax.prizes.overview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_prizes_overview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `year` | integer | | 2000 to 2200. | | `eventId` | string (ID) | | The ID of the related record. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.prizes.overview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/tax.prizes.overview ## tax.prizes.payouts.decide Decide about one outgoing transfer or record: op record confirms it as a prize (optionally to a winner and event), dismiss says it wasn't a prize, update changes its winner, event, note or returned amount, undo puts it back to a suggestion. Never sends money. `POST /api/v1/accounting/tax.prizes.payouts.decide` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_tax_prizes_payouts_decide` ### Fields #### op: "record" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `op` | "record" | Yes | | | `bankTransactionId` | string (ID) | | The ID of the related record. | | `entryId` | string (ID) | | The ID of an accounting entry, from entries.list or attention.mine. | | `paidOn` | string | Yes | | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^[1-9]\d{0,14}$. | | `eventId` | string (ID) | | The ID of the related record. | | `recipientId` | string (ID) | | The ID of one signer on a document. | | `source` | enum | | Where the record came from. One of: `detected`, `manual`. Default `"manual"`. | | `reasons` | array of strings | | at most 8 items; each 1–120 characters. Default `[]`. | | `note` | string | | A short note, kept with the record. at most 500 characters. Default `""`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | #### op: "dismiss" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `op` | "dismiss" | Yes | | | `bankTransactionId` | string (ID) | | The ID of the related record. | | `entryId` | string (ID) | | The ID of an accounting entry, from entries.list or attention.mine. | | `paidOn` | string | Yes | | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^[1-9]\d{0,14}$. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | #### op: "update" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `op` | "update" | Yes | | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `eventId` | string (ID) or null | | | | `recipientId` | string (ID) or null | | The ID of one signer on a document. | | `returnedMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,15}$. | | `returnedOn` | string or null | | | | `note` | string | | A short note, kept with the record. at most 500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | #### op: "undo" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `op` | "undo" | Yes | | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.prizes.payouts.decide \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "op": "record", "paidOn": "2026-09-30", "currency": "CAD", "amountMinor": "1" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.prizes.payouts.decide ## tax.prizes.recipients.link Issue a fresh winner details link, send a reminder (at most every 12 hours and six times), or copy the current link ({ recipientId, mode: issue|remind|copy }). Copying and issuing without an email return the private link and work in the signed-in dashboard only. `POST /api/v1/accounting/tax.prizes.recipients.link` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) Not available over MCP: A winner's private link must not pass through the agent. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `recipientId` | string (ID) | Yes | The ID of one signer on a document. | | `mode` | enum | Yes | One of: `issue`, `remind`, `copy`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.prizes.recipients.link \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "recipientId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "mode": "issue" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.prizes.recipients.link ## tax.prizes.recipients.save Add a winner (individual or business), edit them, or archive them. With sendLink and an email address Oatmilk emails the winner a private, account-free link for their legal name, SIN or business number and mailing address, before any payment. The email never contains a number. `POST /api/v1/accounting/tax.prizes.recipients.save` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_tax_prizes_recipients_save` ### Fields #### op: "create" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `op` | "create" | Yes | | | `kind` | enum | | Which kind of record or job this is. One of: `individual`, `business`. Default `"individual"`. | | `displayName` | string | Yes | 1–200 characters. | | `email` | string (email) | | An email address. at most 254 characters. | | `sendLink` | boolean | | Default `false`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | #### op: "update" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `op` | "update" | Yes | | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `kind` | enum | | Which kind of record or job this is. One of: `individual`, `business`. | | `displayName` | string | Yes | 1–200 characters. | | `email` | string (email) or null | | An email address. | | `sendLink` | boolean | | Default `false`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | #### op: "archive" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `op` | "archive" | Yes | | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.prizes.recipients.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "op": "create", "displayName": "Synthetic Ventures Inc." }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.prizes.recipients.save ## tax.prizes.schedule The T4A summary schedule for a calendar year { year } as a CSV: winner, legal name, SIN or business number, address, amount, box 028 and payment dates. Numbers are masked. reveal { reason } shows full numbers to an administrator in the dashboard or to the accountant, and is audited with the reason. `POST /api/v1/accounting/tax.prizes.schedule` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance Not available over MCP: Winners' full tax numbers. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `year` | integer | Yes | 2000 to 2200. | | `reveal` | object | | No other fields. | | `reveal.reason` | string | Yes | A short note saying why, kept in the record's history. 10–500 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.prizes.schedule \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "year": 2026 }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.prizes.schedule ## tax.questions.answer Answer one year-end question of a corporate tax year: { period, question, answer: yes|no|not_sure, choice?, choice2?, text?, amount?, note?, evidenceIds?, snapshotHash, idempotencyKey }. tax.questions.list names the follow-up fields each answer needs; amounts are in dollars ("1250.00") and files are evidence ids, from tax.sources.confirm or an original already kept with a record. Oatmilk checks the answer as the questions step does, words it the same way and saves it as the question's tax checks at their current revisions, exactly like tax.checks.update. not_sure leaves the question open for the accountant. Returns the saved checks. A changed snapshotHash means records changed: read the questions again. Nothing is filed. `POST /api/v1/accounting/tax.questions.answer` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_tax_questions_answer` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `question` | enum | Yes | One of: `accounts`, `first_year`, `capital_assets`, `owner_money`, `payroll`, `contractors`, `instalments`, `unusual`. | | `answer` | enum | Yes | One of: `yes`, `no`, `not_sure`. | | `choice` | string | | at most 40 characters. | | `choice2` | string | | at most 40 characters. | | `text` | string | | at most 100 characters. | | `amount` | string or number | | | | `note` | string | | A short note, kept with the record. at most 2000 characters. | | `evidenceIds` | array of strings (ID) | | A list of record IDs. at most 20 items. | | `snapshotHash` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.questions.answer \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" }, "question": "accounts", "answer": "yes", "snapshotHash": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.questions.answer ## tax.questions.list Read the year-end questions of a corporate tax year ({ period }), the questions step of tax preparation: each question's key, title, question and plain hint; the answers it takes (yes, no, not_sure) with what each one means and the follow-up fields it asks for (choice and choice2 with their options, text, amount in dollars, note, files); the saved answer read back from the tax checks, open (left for the accountant) or stale (records changed after it was saved); Oatmilk's suggested answer from the books where it has one; progress; and the snapshotHash to answer with. Read-only. `GET | POST /api/v1/accounting/tax.questions.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_questions_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.questions.list \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.questions.list ## tax.readiness Read draft corporate or GST/HST tax workpapers, reviewed totals, unresolved records and preparation tasks. This does not file a return or calculate final T2 liability. `GET | POST /api/v1/accounting/tax.readiness` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_readiness` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/tax.readiness \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'from=2026-09-01' \ --data-urlencode 'to=2026-09-30' \ --data-urlencode 'kind=corporate' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.readiness ## tax.reports.download Download those reports for { period, format (xlsx default, or pdf) } and either report (everything, financial_statements, trial_balance, balance_sheet, income_statement, general_ledger, gst_hst, stripe, contractors) or reports, a list of the ones a person picked (balance_sheet, income_statement, trial_balance, general_ledger, gst_hst, stripe, contractors), which come as one file. The period can be any fiscal year from tax.reports.overview or any dates up to a year apart. Excel workbooks have one sheet per report with formulas for the totals; everything is the whole workbook. Returns a short-lived download. Each request builds a fresh file and is audited. `POST /api/v1/accounting/tax.reports.download` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance MCP tool: `accounting_tax_reports_download` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `report` | enum | | One of: `everything`, `financial_statements`, `trial_balance`, `balance_sheet`, `income_statement`, `general_ledger`, `gst_hst`, `stripe`, `contractors`. | | `reports` | array of enum values | | One of: `balance_sheet`, `income_statement`, `trial_balance`, `general_ledger`, `gst_hst`, `stripe`, `contractors`. 1–7 items. | | `format` | enum | | One of: `xlsx`, `pdf`. Default `"xlsx"`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.reports.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" }, "report": "everything" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.reports.download ## tax.reports.overview Read the reports an accountant asks for at year-end, for { period? (defaults to the newest ended fiscal year) }: the trial balance with debit and credit columns, the balance sheet and income statement (draft, from every record, in CAD, by account and GIFI line), plus GST/HST collected and claimed and money set aside for it, Stripe charges before fees with the fees and payouts, contractors and the T4A slips they likely need, key dates (balance due, GST/HST, T2, T4A) and the company documents on file. Also lists the fiscal years. Read-only. `GET | POST /api/v1/accounting/tax.reports.overview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_reports_overview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.reports.overview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/tax.reports.overview ## tax.requests.add Add one item to the accountant's checklist by hand, to a new request or to an existing one (requestId). Input { requestId?, kind, title, detail?, idempotencyKey }. `POST /api/v1/accounting/tax.requests.add` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_tax_requests_add` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `requestId` | string (ID) | | The ID of the related record. | | `kind` | enum | Yes | Which kind of record or job this is. One of: `bank_statements`, `card_statements`, `personal_expenses`, `payroll`, `accounting_software`, `general_ledger`, `contractor_payments`, `prize_payments`, `sales_tax`, `shareholder_loans`, `capital_assets`, `prior_returns`, `other`. | | `title` | string | Yes | A short title. 2–200 characters. | | `detail` | string | | at most 600 characters. Default `""`. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.requests.add \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "kind": "bank_statements", "title": "Synthetic services agreement" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.requests.add ## tax.requests.extract Turn the text of an accountant's email into checklist items with a strict-schema AI extraction. The text is untrusted: instructions inside it are ignored, no tools run, only the accountant's short quotes are kept, and nothing is sent or changed besides the new checklist. Input { text, receivedOn?, from?, to?, idempotencyKey }. `POST /api/v1/accounting/tax.requests.extract` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_tax_requests_extract` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `text` | string | Yes | 20–30000 characters. | | `receivedOn` | string | | | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.requests.extract \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "text": "9f2b5c1d7e3a4b6c8d0e" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.requests.extract ## tax.requests.list List what the accountant asked for as a checklist. Each item has a status (open, ready or sent), a link to where Oatmilk fulfils it, and whether Oatmilk already holds the answer (statements, personally paid expenses, payroll answer, accounting access). Also returns the one year-end question when the accountant names a year-end that differs from the company's. Input { from?, to? } picks the period whose facts decide what is ready. `GET | POST /api/v1/accounting/tax.requests.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_requests_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.requests.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/tax.requests.list ## tax.requests.update Change a checklist item ({ op: item, itemId, expectedRevision, status?: open|ready|sent|dismissed, note?, evidenceIds? }) or record how the year-end question was answered ({ op: year_end, requestId, expectedRevision, resolution: kept|changed }). Changing the company's year-end itself uses company.update. `POST /api/v1/accounting/tax.requests.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_tax_requests_update` ### Fields #### op: "item" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `op` | "item" | Yes | | | `itemId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `status` | enum | | Only include records with this status. One of: `open`, `ready`, `sent`, `dismissed`. | | `note` | string | | A short note, kept with the record. at most 1000 characters. | | `evidenceIds` | array of strings (ID) | | A list of record IDs. at most 20 items. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | #### op: "year_end" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `op` | "year_end" | Yes | | | `requestId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `resolution` | enum | Yes | One of: `kept`, `changed`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.requests.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "op": "item", "itemId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.requests.update ## tax.sources.confirm Verify uploaded company/tax source original bytes and preserve evidence without creating accounting entries. Returns an evidence ID for source references. `POST /api/v1/accounting/tax.sources.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `submissionId` | string (ID) | Yes | The ID of an upload, returned by uploads.prepare or uploads.inline. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.sources.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "submissionId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.sources.confirm ## tax.sources.prepare Prepare an immutable private PDF or photo company/tax source upload. Returns a signed upload URL; this does not create an expense or run AI. `POST /api/v1/accounting/tax.sources.prepare` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | string | Yes | The file's name, including its extension. 1–180 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `application/pdf`, `image/jpeg`, `image/png`, `image/webp`, `image/heic`, `image/heif`. | | `sizeBytes` | integer | Yes | The file's size in bytes. 1 to 52428800. | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.sources.prepare \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "filename": "receipt.pdf", "mimeType": "application/pdf", "sizeBytes": 1, "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.sources.prepare ## tax.statements.acknowledge Tell the accountant that an account's statement gap is known and nothing more can be done ({ period, accountId, acknowledged, note (required, plain words such as 'The card wasn't used this year'), expectedRevision (0 when new), idempotencyKey }). The account stops counting as needing attention, the gap stays listed for the accountant with the note, and the answer is audited. acknowledged false withdraws it. `POST /api/v1/accounting/tax.statements.acknowledge` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_tax_statements_acknowledge` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `accountId` | string (ID) | Yes | The ID of a bank, card or payment account, from accounts.list. | | `acknowledged` | boolean | Yes | | | `note` | string | | A short note, kept with the record. at most 500 characters. Default `""`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.statements.acknowledge \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" }, "accountId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "acknowledged": true, "expectedRevision": 3, "note": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.statements.acknowledge ## tax.statements.overview Read every bank, card and payment account's statement bundle for a period { period }: opening and closing balance as of the period end, transaction counts and totals, the original statement files kept, and a plain-words completeness check per account (no statements, a late start, an early end, a missing month, or balances that don't agree with the imported lines). Read-only. `GET | POST /api/v1/accounting/tax.statements.overview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_tax_statements_overview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.statements.overview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.statements.overview ## tax.statements.pack Build the statements pack for { period }: one folder per account with its balances, a CSV and a PDF transaction listing for the period, and the original statement files kept for it, plus an index and a completeness summary. Returns a short-lived download. Each request builds a fresh pack and is audited. `POST /api/v1/accounting/tax.statements.pack` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance MCP tool: `accounting_tax_statements_pack` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.statements.pack \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.statements.pack ## tax.treatment.apply Confirm a whole group of records at once for { period, code, entryIds, idempotencyKey }: each record listed that is still in the group gets the treatment Oatmilk suggests for that reason (for example no tax credit for purchases without a receipt), saved as a tax review in the caller's name and marked as confirmed together. Records that changed or are no longer in the group are skipped and reported. The closed-period and revision guards still apply. `POST /api/v1/accounting/tax.treatment.apply` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `code` | enum | Yes | One of: `bank_fee`, `exempt_category`, `interest`, `contractor`, `receipt_no_tax`, `receipt_tax`, `foreign_vendor`, `same_as_before`, `small_no_receipt`, `no_receipt`, `receipt_unread`, `recorded_tax`, `recorded_sale_tax`, `sale_no_tax`. | | `entryIds` | array of strings (ID) | Yes | IDs of accounting entries, from entries.list or attention.mine. 1–1000 items. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.treatment.apply \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" }, "code": "bank_fee", "entryIds": [ "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.treatment.apply ## tax.treatment.auto Settle the sales tax of every record in a tax period that the rules can prove, as Autopilot, for { period, snapshotHash, pass, idempotencyKey }: bank fees, interest, payments to companies outside Canada or to contractors, small purchases with no receipt (no credit claimed) and receipts whose tax adds up. Each is saved as the same tax review a person would save, marked automatic with its plain reason, and can be undone. Records a person reviewed or undid, and records that changed, are left alone. Returns { applied, skipped, remaining }. `POST /api/v1/accounting/tax.treatment.auto` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_tax_treatment_auto` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `snapshotHash` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `pass` | integer | Yes | 0 to 1000. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.treatment.auto \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" }, "snapshotHash": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "pass": 1 }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.treatment.auto ## tax.treatment.undo Undo a tax review that Autopilot or a group confirmation saved, for { entryId, expectedRevision, idempotencyKey }: the record goes back to open and Autopilot leaves it alone until it changes. A review a person saved cannot be undone this way. Refused in a closed period. `POST /api/v1/accounting/tax.treatment.undo` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/tax.treatment.undo \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/tax.treatment.undo # Group: Compliance > The compliance checklist, deadlines and reminders. MCP toolset: `compliance` (https://app.getoatmilk.com/api/mcp?toolset=compliance) ## calendar - [`calendar.events`](https://app.getoatmilk.com/docs/api/calendar.events.md) — List important company dates between from and to (YYYY-MM-DD, at most 400 days): compliance deadlines with what, why, citations and weekend-adjusted dates, plus invoice due dates, agreement expiries and contractor pay runs from other modules. Sources that fail are listed in unavailable. ## compliance - [`compliance.checklist`](https://app.getoatmilk.com/docs/api/compliance.checklist.md) — Read the actionable compliance checklist: stored obligations with days until due, summary counts, missing company profile details, assignable members, and related action items from invoices, agreements, contractors, receipts awaiting review, receipt match review and Stripe exceptions. Sources that fail are listed in unavailable. - [`compliance.refresh`](https://app.getoatmilk.com/docs/api/compliance.refresh.md) — Regenerate compliance obligations from the company profile and reminder settings. Adds new deadlines, updates changed dates and wording, marks untouched items whose rule no longer applies as not applicable with a system note, and never deletes items or changes human status, notes or completion. Requires an idempotencyKey. ## compliance.items - [`compliance.items.create`](https://app.getoatmilk.com/docs/api/compliance.items.create.md) — Add a custom compliance checklist item with title, what to do, why, dueDate, category, optional https citations, an optional in-app link (actionHref, a workspace page path starting with /, /finance, /legal or /people) and an optional owner. Custom items get the same reminders as built-in rules. Requires an idempotencyKey. - [`compliance.items.get`](https://app.getoatmilk.com/docs/api/compliance.items.get.md) — Read one compliance item by id with its full what and why, citations, notes, owner, change history and the reminder emails that included it. - [`compliance.items.list`](https://app.getoatmilk.com/docs/api/compliance.items.list.md) — List compliance checklist items with what to do, why it matters, official citations, statutory and effective due dates, days until due, status, owner and reminder state. Filter by status (upcoming, in_progress, done, skipped, open or all), category, a from/to due-date range, the assigned owner (assigneeUserId) or who assigned it (assignedByUserId). - [`compliance.items.update`](https://app.getoatmilk.com/docs/api/compliance.items.update.md) — Update a compliance item's status (upcoming, in_progress, done or skipped), notes, owner (assigneeUserId of an active administrator or finance member) or snoozedUntil date. Requires id, expectedRevision and idempotencyKey. Automatic refreshes never overwrite these human decisions. ## compliance.kickoff - [`compliance.kickoff.run`](https://app.getoatmilk.com/docs/api/compliance.kickoff.run.md) — Start tax preparation for a kickoff item now: marks it in progress, prepares the exact corporate or GST/HST workpaper period, adds follow-up checklist items and emails administrators a link that opens that workpaper. Records a compliance.kickoff run with prepare and notify steps. Requires itemId and idempotencyKey. ## compliance.reminders - [`compliance.reminders.preview`](https://app.getoatmilk.com/docs/api/compliance.reminders.preview.md) — Preview the grouped compliance reminder emails with recipients, subject, HTML and text. Mode upcoming (default) shows every open item within the reminder window, exactly what Send now would queue; mode scheduled shows only what the next daily digest would send. Nothing is sent. - [`compliance.reminders.sendNow`](https://app.getoatmilk.com/docs/api/compliance.reminders.sendNow.md) — Queue grouped compliance reminder emails now for every open item within the reminder window, from the reminders identity to the configured recipients or else all active administrators. Records each digest and updates item reminder state. Requires an idempotencyKey. ## calendar.events List important company dates between from and to (YYYY-MM-DD, at most 400 days): compliance deadlines with what, why, citations and weekend-adjusted dates, plus invoice due dates, agreement expiries and contractor pay runs from other modules. Sources that fail are listed in unavailable. `GET | POST /api/v1/accounting/calendar.events` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_calendar_events` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `to` | string | Yes | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/calendar.events \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'from=2026-09-01' \ --data-urlencode 'to=2026-09-30' ``` Reference page: https://app.getoatmilk.com/docs/api/calendar.events ## compliance.checklist Read the actionable compliance checklist: stored obligations with days until due, summary counts, missing company profile details, assignable members, and related action items from invoices, agreements, contractors, receipts awaiting review, receipt match review and Stripe exceptions. Sources that fail are listed in unavailable. `GET | POST /api/v1/accounting/compliance.checklist` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_compliance_checklist` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `includeDone` | boolean | | Also include done. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/compliance.checklist \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/compliance.checklist ## compliance.items.create Add a custom compliance checklist item with title, what to do, why, dueDate, category, optional https citations, an optional in-app link (actionHref, a workspace page path starting with /, /finance, /legal or /people) and an optional owner. Custom items get the same reminders as built-in rules. Requires an idempotencyKey. `POST /api/v1/accounting/compliance.items.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_compliance_items_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `title` | string | Yes | A short title. 1–200 characters. | | `what` | string | Yes | 1–4000 characters. | | `why` | string | | at most 4000 characters. Default `""`. | | `dueDate` | string | Yes | The date payment is due, as YYYY-MM-DD. | | `category` | enum | | One of: `corporate_filings`, `income_tax`, `sales_tax`, `payroll`, `information_returns`, `bookkeeping`, `other`. Default `"other"`. | | `citations` | array of objects | | at most 10 items. Default `[]`. | | `citations[].title` | string | Yes | A short title. 1–200 characters. | | `citations[].url` | string (uri) | Yes | A full web address, starting with https://. at most 2000 characters. | | `actionHref` | string | | at most 500 characters; Matches ^\/(?:(?:accounting\|ops\|inbox\|checklist\|calendar\|ai\|compliance\|chief-of-staff\|logs\|history\|developers\|settings\|finance\|legal\|people\|insights)(\/[A-Za-z0-9_/-]*)?)?(\?[A-Za-z0-9_.=&%:-]*)?$. | | `notes` | string | | Notes kept with the record. at most 4000 characters. Default `""`. | | `assigneeUserId` | string | | 1–200 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/compliance.items.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "title": "Synthetic services agreement", "what": "example", "dueDate": "2026-09-30" }' ``` Reference page: https://app.getoatmilk.com/docs/api/compliance.items.create ## compliance.items.get Read one compliance item by id with its full what and why, citations, notes, owner, change history and the reminder emails that included it. `GET | POST /api/v1/accounting/compliance.items.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_compliance_items_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/compliance.items.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/compliance.items.get ## compliance.items.list List compliance checklist items with what to do, why it matters, official citations, statutory and effective due dates, days until due, status, owner and reminder state. Filter by status (upcoming, in_progress, done, skipped, open or all), category, a from/to due-date range, the assigned owner (assigneeUserId) or who assigned it (assignedByUserId). `GET | POST /api/v1/accounting/compliance.items.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_compliance_items_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | enum | | Only include records with this status. One of: `upcoming`, `in_progress`, `done`, `skipped`, `open`, `all`. | | `category` | enum | | One of: `corporate_filings`, `income_tax`, `sales_tax`, `payroll`, `information_returns`, `bookkeeping`, `setup`, `other`. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `includeDone` | boolean | | Also include done. | | `assigneeUserId` | string | | 1–200 characters. | | `assignedByUserId` | string | | 1–200 characters. | | `limit` | integer | | How many results to return at most. 1 to 500. Default `200`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/compliance.items.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/compliance.items.list ## compliance.items.update Update a compliance item's status (upcoming, in_progress, done or skipped), notes, owner (assigneeUserId of an active administrator or finance member) or snoozedUntil date. Requires id, expectedRevision and idempotencyKey. Automatic refreshes never overwrite these human decisions. `POST /api/v1/accounting/compliance.items.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_compliance_items_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `status` | enum | | Only include records with this status. One of: `upcoming`, `in_progress`, `done`, `skipped`. | | `notes` | string | | Notes kept with the record. at most 4000 characters. | | `assigneeUserId` | string or null | | | | `snoozedUntil` | string or null | | | | `notifyOnResume` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/compliance.items.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "status": "upcoming" }' ``` Reference page: https://app.getoatmilk.com/docs/api/compliance.items.update ## compliance.kickoff.run Start tax preparation for a kickoff item now: marks it in progress, prepares the exact corporate or GST/HST workpaper period, adds follow-up checklist items and emails administrators a link that opens that workpaper. Records a compliance.kickoff run with prepare and notify steps. Requires itemId and idempotencyKey. `POST /api/v1/accounting/compliance.kickoff.run` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_compliance_kickoff_run` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `itemId` | string (ID) | Yes | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/compliance.kickoff.run \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "itemId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/compliance.kickoff.run ## compliance.refresh Regenerate compliance obligations from the company profile and reminder settings. Adds new deadlines, updates changed dates and wording, marks untouched items whose rule no longer applies as not applicable with a system note, and never deletes items or changes human status, notes or completion. Requires an idempotencyKey. `POST /api/v1/accounting/compliance.refresh` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_compliance_refresh` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/compliance.refresh \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/compliance.refresh ## compliance.reminders.preview Preview the grouped compliance reminder emails with recipients, subject, HTML and text. Mode upcoming (default) shows every open item within the reminder window, exactly what Send now would queue; mode scheduled shows only what the next daily digest would send. Nothing is sent. `GET | POST /api/v1/accounting/compliance.reminders.preview` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_compliance_reminders_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `mode` | enum | | One of: `upcoming`, `scheduled`. Default `"upcoming"`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/compliance.reminders.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/compliance.reminders.preview ## compliance.reminders.sendNow Queue grouped compliance reminder emails now for every open item within the reminder window, from the reminders identity to the configured recipients or else all active administrators. Records each digest and updates item reminder state. Requires an idempotencyKey. `POST /api/v1/accounting/compliance.reminders.sendNow` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_compliance_reminders_send_now` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/compliance.reminders.sendNow \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/compliance.reminders.sendNow # Group: Stripe > Stripe activity, payouts, exceptions and tax confirmation. MCP toolset: `stripe` (https://app.getoatmilk.com/api/mcp?toolset=stripe) ## stripe.payouts - [`stripe.payouts.get`](https://app.getoatmilk.com/docs/api/stripe.payouts.get.md) — Read authoritative payout constituents, original snapshots, bank candidates and allocations. - [`stripe.payouts.list`](https://app.getoatmilk.com/docs/api/stripe.payouts.list.md) — List Stripe payouts, original amounts, statuses and reconciliation state. - [`stripe.payouts.match`](https://app.getoatmilk.com/docs/api/stripe.payouts.match.md) — Match a paid Stripe payout to a bank deposit as a transfer, never revenue. Supply payoutId, transactionId, exact amountMinor, expectedRevision and idempotencyKey. Different currencies require bankAmountMinor plus exchangeRate in bank major units per payout major unit, exchangeRateDate and exchangeRateSource. - [`stripe.payouts.unmatch`](https://app.getoatmilk.com/docs/api/stripe.payouts.unmatch.md) — Unmatch a Stripe payout allocation with allocationId and expectedRevision from the payout. Closed periods must be reopened by an administrator. ## stripe - [`stripe.report`](https://app.getoatmilk.com/docs/api/stripe.report.md) — Report original Stripe gross receipts, customer refunds, fees, reserves and payouts separately by currency. Gross receipts include any collected tax. - [`stripe.status`](https://app.getoatmilk.com/docs/api/stripe.status.md) — Read Stripe connection identity, sync progress and configuration readiness. - [`stripe.sync`](https://app.getoatmilk.com/docs/api/stripe.sync.md) — Queue durable read-only Stripe synchronization. Autopilot finishes the new activity afterwards unless skipAi is true. ## stripe.review - [`stripe.review.acceptChange`](https://app.getoatmilk.com/docs/api/stripe.review.acceptChange.md) — Accept a verified changed Stripe source amount after finance review, with id, expectedRevision, reason and idempotencyKey. Open periods are required. Previous postings and snapshots remain preserved as reversed history; corrected facts create new provisional postings. - [`stripe.review.list`](https://app.getoatmilk.com/docs/api/stripe.review.list.md) — List Stripe exceptions, unpaid invoices and payments not settled. - [`stripe.review.resolve`](https://app.getoatmilk.com/docs/api/stripe.review.resolve.md) — Explicitly classify a Stripe suspense movement with id, treatment, provenance reason, expectedRevision and idempotencyKey. Original financial facts remain preserved. ## stripe.snapshots - [`stripe.snapshots.download`](https://app.getoatmilk.com/docs/api/stripe.snapshots.download.md) — Get a short-lived authorized original financial Stripe JSON snapshot download by id. Client secrets are redacted before archival. ## stripe.tax - [`stripe.tax.confirm`](https://app.getoatmilk.com/docs/api/stripe.tax.confirm.md) — Confirm the actual tax amount of a Stripe revenue entry with entryId, exact taxMinor, source reason, expectedRevision and idempotencyKey. Never assume unknown tax is zero. ## stripe.year - [`stripe.year.download`](https://app.getoatmilk.com/docs/api/stripe.year.download.md) — Download Stripe for one fiscal year, for { period, format (zip default, xlsx or pdf) }. The ZIP holds an Excel workbook (summary, charges with their fee and GST/HST when known, refunds, disputes, fees by month, payouts and whether each is matched to a bank deposit), the same lists as CSV files, and Stripe's original records when they fit (its README says when they were left out). xlsx is the workbook alone and pdf a short summary. Returns a short-lived download. Each request builds a fresh file and is audited with counts only. - [`stripe.year.get`](https://app.getoatmilk.com/docs/api/stripe.year.get.md) — Read everything Stripe for one fiscal year, for { period? (defaults to the newest ended fiscal year) }, by currency: charges at their full amount before Stripe's fee, Stripe's fees (bank charges), refunds, disputes, net, and payouts to the bank, every month of the year, and what still needs a look (payouts not matched to a bank deposit, charges whose GST/HST isn't known, Stripe items not sorted). Also lists the fiscal years. Read-only. ## stripe.payouts.get Read authoritative payout constituents, original snapshots, bank candidates and allocations. `GET | POST /api/v1/accounting/stripe.payouts.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_stripe_payout` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/stripe.payouts.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.payouts.get ## stripe.payouts.list List Stripe payouts, original amounts, statuses and reconciliation state. `GET | POST /api/v1/accounting/stripe.payouts.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_stripe_payouts` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 50000. Default `0`. | | `status` | enum | | Only include records with this status. One of: `pending`, `in_transit`, `paid`, `failed`, `canceled`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/stripe.payouts.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.payouts.list ## stripe.payouts.match Match a paid Stripe payout to a bank deposit as a transfer, never revenue. Supply payoutId, transactionId, exact amountMinor, expectedRevision and idempotencyKey. Different currencies require bankAmountMinor plus exchangeRate in bank major units per payout major unit, exchangeRateDate and exchangeRateSource. `POST /api/v1/accounting/stripe.payouts.match` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_stripe_match_payout` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `payoutId` | string (ID) | Yes | The ID of the related record. | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `bankAmountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `exchangeRate` | string | | Matches ^(?:0\|[1-9]\d{0,8})(?:\.\d{1,18})?$. | | `exchangeRateDate` | string | | | | `exchangeRateSource` | string | | 3–1000 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `reuseEntryId` | string (ID) | | The ID of the related record. | | `expectedEntryRevision` | integer | | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `expectedTransactionRevision` | integer | | The bank transaction's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/stripe.payouts.match \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "payoutId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "transactionId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "amountMinor": "1250", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.payouts.match ## stripe.payouts.unmatch Unmatch a Stripe payout allocation with allocationId and expectedRevision from the payout. Closed periods must be reopened by an administrator. `POST /api/v1/accounting/stripe.payouts.unmatch` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_stripe_unmatch_payout` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `allocationId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/stripe.payouts.unmatch \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "allocationId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.payouts.unmatch ## stripe.report Report original Stripe gross receipts, customer refunds, fees, reserves and payouts separately by currency. Gross receipts include any collected tax. `GET | POST /api/v1/accounting/stripe.report` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_stripe_report` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/stripe.report \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.report ## stripe.review.acceptChange Accept a verified changed Stripe source amount after finance review, with id, expectedRevision, reason and idempotencyKey. Open periods are required. Previous postings and snapshots remain preserved as reversed history; corrected facts create new provisional postings. `POST /api/v1/accounting/stripe.review.acceptChange` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_stripe_accept_change` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–2000 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/stripe.review.acceptChange \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "reason": "Synthetic example from the docs", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.review.acceptChange ## stripe.review.list List Stripe exceptions, unpaid invoices and payments not settled. `GET | POST /api/v1/accounting/stripe.review.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_stripe_review` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `currency` | string | | Three-letter currency code, such as CAD or USD. | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 50000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/stripe.review.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.review.list ## stripe.review.resolve Explicitly classify a Stripe suspense movement with id, treatment, provenance reason, expectedRevision and idempotencyKey. Original financial facts remain preserved. `POST /api/v1/accounting/stripe.review.resolve` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_stripe_classify` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `treatment` | enum | Yes | One of: `income`, `income_refund`, `expense`, `refund`, `fee`, `transfer`. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–2000 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/stripe.review.resolve \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "treatment": "income", "reason": "Synthetic example from the docs", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.review.resolve ## stripe.snapshots.download Get a short-lived authorized original financial Stripe JSON snapshot download by id. Client secrets are redacted before archival. `GET | POST /api/v1/accounting/stripe.snapshots.download` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_stripe_snapshot` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/stripe.snapshots.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.snapshots.download ## stripe.status Read Stripe connection identity, sync progress and configuration readiness. `GET | POST /api/v1/accounting/stripe.status` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_stripe_status` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `jobId` | string (ID) | | The ID of a background job, returned when the work was started. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/stripe.status \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.status ## stripe.sync Queue durable read-only Stripe synchronization. Autopilot finishes the new activity afterwards unless skipAi is true. `POST /api/v1/accounting/stripe.sync` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_stripe_sync` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `skipAi` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/stripe.sync \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.sync ## stripe.tax.confirm Confirm the actual tax amount of a Stripe revenue entry with entryId, exact taxMinor, source reason, expectedRevision and idempotencyKey. Never assume unknown tax is zero. `POST /api/v1/accounting/stripe.tax.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_stripe_confirm_tax` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `taxMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Whole number written as a string. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–2000 characters. | | `evidenceId` | string (ID) | | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/stripe.tax.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "taxMinor": "1250", "reason": "Synthetic example from the docs", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.tax.confirm ## stripe.year.download Download Stripe for one fiscal year, for { period, format (zip default, xlsx or pdf) }. The ZIP holds an Excel workbook (summary, charges with their fee and GST/HST when known, refunds, disputes, fees by month, payouts and whether each is matched to a bank deposit), the same lists as CSV files, and Stripe's original records when they fit (its README says when they were left out). xlsx is the workbook alone and pdf a short summary. Returns a short-lived download. Each request builds a fresh file and is audited with counts only. `POST /api/v1/accounting/stripe.year.download` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance MCP tool: `accounting_stripe_year_download` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | Yes | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | | `format` | enum | | One of: `zip`, `xlsx`, `pdf`. Default `"zip"`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/stripe.year.download \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "period": { "from": "2026-09-01", "to": "2026-09-30", "kind": "corporate" } }' ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.year.download ## stripe.year.get Read everything Stripe for one fiscal year, for { period? (defaults to the newest ended fiscal year) }, by currency: charges at their full amount before Stripe's fee, Stripe's fees (bank charges), refunds, disputes, net, and payouts to the bank, every month of the year, and what still needs a look (payouts not matched to a bank deposit, charges whose GST/HST isn't known, Stripe items not sorted). Also lists the fiscal years. Read-only. `GET | POST /api/v1/accounting/stripe.year.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_stripe_year_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `period` | object | | The period to cover. No other fields. | | `period.from` | string | Yes | The first date to include, as YYYY-MM-DD. | | `period.to` | string | Yes | The last date to include, as YYYY-MM-DD. | | `period.kind` | enum | Yes | Which kind of record or job this is. One of: `corporate`, `gst_hst`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/stripe.year.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/stripe.year.get # Group: Connectors and webhooks > Webhooks, Wise, Notion, Google Drive and other data sources. MCP toolset: `connectors` (https://app.getoatmilk.com/api/mcp?toolset=connectors) ## connectors.credentials - [`connectors.credentials.remove`](https://app.getoatmilk.com/docs/api/connectors.credentials.remove.md) — Disconnect organization-scoped Stripe, Wise, Notion or Google Drive credentials from the administrator dashboard. Existing accounting records remain. Requires expectedRevision and idempotencyKey. - [`connectors.credentials.save`](https://app.getoatmilk.com/docs/api/connectors.credentials.save.md) — Store organization-scoped Stripe, Wise, or Notion credentials from the administrator dashboard. Values are encrypted server-side, never returned, and replace the prior credentials after verification. Requires idempotencyKey and the current revision when replacing. ## connectors - [`connectors.get`](https://app.getoatmilk.com/docs/api/connectors.get.md) — Read one connector's status and non-secret configuration. For Notion this includes the mapped invoice, customer, vendor, and contact databases, property and status mappings, sync settings, and the last sync summary. - [`connectors.list`](https://app.getoatmilk.com/docs/api/connectors.list.md) — List available connectors grouped as financial (Stripe, Wise), data (Notion), and communication (email delivery), with connection status and capabilities. Organization admins manage provider credentials in Settings; secret values are never returned. - [`connectors.test`](https://app.getoatmilk.com/docs/api/connectors.test.md) — Check a connector's connection now and record the result. For Notion this verifies the integration token and returns the workspace and integration name. - [`connectors.update`](https://app.getoatmilk.com/docs/api/connectors.update.md) — Save Notion connector settings: database mappings (data source, property mapping, status option mapping) for invoices, customers, vendors, and contacts, and sync settings. Mappings are validated against the live Notion schema. Supply idempotencyKey and optionally expectedRevision. ## connectors.googleDrive - [`connectors.googleDrive.connect`](https://app.getoatmilk.com/docs/api/connectors.googleDrive.connect.md) — Start connecting the company's Google Drive with read-only access: returns url, the Google consent page for the admin to open while signed in to Oatmilk (only the admin who asked can finish it, within ten minutes). Optional returnTo is an Oatmilk path to come back to. Once connected, import files and folders with dataSources.import (source google_drive; target company_documents to file incorporation papers and fill the company profile). Disconnect with connectors.credentials.remove (id google_drive). - [`connectors.googleDrive.status`](https://app.getoatmilk.com/docs/api/connectors.googleDrive.status.md) — Read whether Google Drive can be connected here (configured), whether it is connected, and the Google account that connected it. Never returns a token. ## connectors.wise - [`connectors.wise.signingKey.create`](https://app.getoatmilk.com/docs/api/connectors.wise.signingKey.create.md) — Create a signing key for Wise's strong customer authentication from the administrator dashboard. The private half is saved encrypted with the Wise credential; only the public half is returned, to add in Wise (Settings › API tokens › Manage public keys). A saved key is replaced only with replace true, because payouts sign with the same key. Requires the credential's expectedRevision and idempotencyKey. - [`connectors.wise.statementAccess`](https://app.getoatmilk.com/docs/api/connectors.wise.statementAccess.md) — Read whether Wise's monthly statement PDFs reach Oatmilk: the state (working, waiting, needs a signing key, key not in Wise, refused, not found), how many are kept, when Oatmilk last approved Wise's strong customer authentication and when Wise will ask again (about every 90 days; Oatmilk approves it automatically with the saved signing key), the last refusal, and the saved key's public half to add in Wise. The private key is never returned. - [`connectors.wise.statements.check`](https://app.getoatmilk.com/docs/api/connectors.wise.statements.check.md) — Ask Wise for statement PDFs now without waiting out a refusal: one month per synced balance, oldest missing first, signing Wise's approval request with the saved key when Wise asks. Returns what was kept and the statement access status. Supply idempotencyKey. ## dataImports - [`dataImports.commit`](https://app.getoatmilk.com/docs/api/dataImports.commit.md) — Start an import in the background with the mapping and decisions: create or choose customers and contractors, link or skip invoice numbers already in Oatmilk, and import or skip possible duplicate payments. Invoices keep their original numbers without using the invoice sequence, paid rows get their payment, the first PDF becomes the invoice's PDF, and Notion rows are linked for sync. Contractor payments are recorded as paid outside Wise; no money is sent and nobody is emailed. Rows already imported are skipped. Returns the import to follow with dataImports.get. Supply idempotencyKey. - [`dataImports.definitions`](https://app.getoatmilk.com/docs/api/dataImports.definitions.md) — List what can be imported from a connected database (invoices and contractor payment history): each field with its type, whether it's required, and the kinds of columns it accepts, plus the databases imported from recently. - [`dataImports.files`](https://app.getoatmilk.com/docs/api/dataImports.files.md) — Get short-lived download links for the files an imported invoice came with, and where it was imported from. - [`dataImports.get`](https://app.getoatmilk.com/docs/api/dataImports.get.md) — Read one import with its progress, counts, the customers or contractors it added, and each row's outcome with a link to what it created. - [`dataImports.history`](https://app.getoatmilk.com/docs/api/dataImports.history.md) — List imports, newest first, with their source database, status and counts. Filter by target or database. - [`dataImports.preview`](https://app.getoatmilk.com/docs/api/dataImports.preview.md) — Read every row of the database with a column mapping and group the rows: ready, needs a decision (a new or ambiguous customer or contractor, an invoice number already in Oatmilk, or a possible duplicate payment), already imported, can't import (with the reason), or left out. Nothing is saved. Payment and tax columns are never read, and what they hold is hidden anywhere else in the preview. - [`dataImports.resume`](https://app.getoatmilk.com/docs/api/dataImports.resume.md) — Continue an import that paused before it finished, for example after a time limit. It continues as the person who started it. Supply idempotencyKey. - [`dataImports.suggest`](https://app.getoatmilk.com/docs/api/dataImports.suggest.md) — Suggest a column of a connected database (Notion) for each field of an import target, with a sample value per column, the status options found, and the mapping remembered for that database. Supply target, source and databaseId. A column whose name says it holds payment or tax details is marked sensitive, has no sample or options, and is never suggested or imported. - [`dataImports.undo`](https://app.getoatmilk.com/docs/api/dataImports.undo.md) — Undo a contractor payment import: the payouts it recorded are cancelled when they haven't changed since, and the rows can be imported again. Imported invoices are voided from each invoice instead. Supply reason and idempotencyKey. ## dataSources - [`dataSources.files`](https://app.getoatmilk.com/docs/api/dataSources.files.md) — List the files attached to a row or page: files in file columns and files embedded in the page. For google_drive, itemId is a folder (its files, partial when there are more than 200) or a file (itself); Google Docs, Sheets and Slides are listed as PDFs. With target, files already imported there are marked. File download addresses are never returned. - [`dataSources.importFiles`](https://app.getoatmilk.com/docs/api/dataSources.importFiles.md) — Import up to 25 files from a data source into Oatmilk: receipts (processed like uploaded receipts), tax_sources (supporting originals, no expense), signing_files (documents to send for signature), executed_agreements (agreements signed elsewhere; targetOptions parties, executedOn, title) contractor_agreements (draft documents; optional targetOptions contractorId, expectedContractorRevision and title create the draft agreement) or company_documents (incorporation papers and other company records: each is filed through intake as a company record, read, and fills the company profile's empty fields; the record is the document id). Sources are notion and google_drive. Each file is downloaded server-side, checked against its target's size limit and file types by its contents, and run through that target's usual upload path. Importing the same file into the same place again returns the existing record. Returns a result for each file. Supply idempotencyKey. - [`dataSources.list`](https://app.getoatmilk.com/docs/api/dataSources.list.md) — List data sources files and records can be imported from (Notion, and Google Drive once an admin connects it with connectors.googleDrive.connect; OneDrive is listed as not available yet), with connection status, pinned databases and databases mapped in Settings › Connectors. - [`dataSources.pin`](https://app.getoatmilk.com/docs/api/dataSources.pin.md) — Pin or unpin a database for everyone in the workspace so it appears first when importing. Supply source, databaseId, pinned and idempotencyKey. - [`dataSources.readText`](https://app.getoatmilk.com/docs/api/dataSources.readText.md) — Read the text of a CSV or plain-text file attached to a row or page (up to 2 MB, UTF-8), for example a bank statement to import with imports.preview and imports.commit. Nothing is stored. - [`dataSources.resolve`](https://app.getoatmilk.com/docs/api/dataSources.resolve.md) — Open a pasted link or ID from a data source (for Notion: notion.so, app.notion.com, notion.site links, database and page IDs; for Google Drive: drive.google.com and docs.google.com links to files and folders, or a Drive ID). Returns whether it is a database or a page and the database ID to read rows from. - [`dataSources.rows`](https://app.getoatmilk.com/docs/api/dataSources.rows.md) — Read rows of a database with readable, typed values for every column: text, numbers with their format, dates, options, people, related page titles, checkboxes, links and attached files. Supports search, sort by a column, only rows with files, and cursor paging. With target, files already imported there are marked. File download addresses are never returned. Columns whose names say they hold payment or tax details (bank, account, transit, institution and routing numbers, IBAN, SWIFT/BIC, Wise or Interac addresses, SIN, SSN, business or tax numbers) read as [hidden], what they hold is hidden anywhere else in the rows too, and they're never searched or sorted by. - [`dataSources.schema`](https://app.getoatmilk.com/docs/api/dataSources.schema.md) — Read a database's columns with their types, options, number formats (and the currency they imply), related databases, and the columns suggested for a first view. Columns whose names say they hold payment or tax details list no options and can't be sorted or searched by. - [`dataSources.search`](https://app.getoatmilk.com/docs/api/dataSources.search.md) — Search a data source for databases and pages shared with Oatmilk, most recently edited first. Supply source, optional query, kind (database, page, file or any), cursor and limit. For google_drive it searches file and folder names across My Drive and shared drives (kind file leaves folders out; Drive has no databases). ## notion.dataSources - [`notion.dataSources.get`](https://app.getoatmilk.com/docs/api/notion.dataSources.get.md) — Read a Notion data source's properties and suggested mappings for each role (invoices, customers, vendors, contacts), including suggested status option mappings. - [`notion.dataSources.query`](https://app.getoatmilk.com/docs/api/notion.dataSources.query.md) — Preview rows of a Notion data source with simplified property values, optionally filtered by a title search. Paged with cursor. Properties whose names say they hold payment or tax details (bank, account, transit, institution and routing numbers, IBAN, SWIFT/BIC, Wise or Interac addresses, SIN, SSN, business or tax numbers) read as [hidden], and what they hold is hidden anywhere else in the rows too. ## notion.invoices - [`notion.invoices.import`](https://app.getoatmilk.com/docs/api/notion.invoices.import.md) — Link existing Notion invoice rows to Oatmilk invoices. Status differences become suggestions for review; nothing is overwritten. Supply selections of pageId and invoiceId, plus idempotencyKey. - [`notion.invoices.importPreview`](https://app.getoatmilk.com/docs/api/notion.invoices.importPreview.md) — Preview existing rows in the mapped Notion invoice database with the Oatmilk invoice each one most likely matches, whether it's already linked, and whether the statuses differ. ## notion.links - [`notion.links.confirm`](https://app.getoatmilk.com/docs/api/notion.links.confirm.md) — Confirm a suggested Notion link. Any other confirmed link for the same record and database is replaced. Requires expectedRevision and idempotencyKey. - [`notion.links.create`](https://app.getoatmilk.com/docs/api/notion.links.create.md) — Link an Oatmilk account, invoice, or contractor to a Notion page by page ID or pasted link. Links created by a person are confirmed unless status is proposed. Supply idempotencyKey. - [`notion.links.list`](https://app.getoatmilk.com/docs/api/notion.links.list.md) — List links between Oatmilk records (accounts, invoices, contractors) and Notion pages, with their status (suggested, confirmed, declined), method, match reasons, and sync state. - [`notion.links.reject`](https://app.getoatmilk.com/docs/api/notion.links.reject.md) — Decline a suggested Notion link so it isn't suggested again. Requires expectedRevision and idempotencyKey. - [`notion.links.remove`](https://app.getoatmilk.com/docs/api/notion.links.remove.md) — Remove a Notion link. The Notion page itself is not changed. Requires expectedRevision and idempotencyKey. ## notion - [`notion.lookup`](https://app.getoatmilk.com/docs/api/notion.lookup.md) — Find likely Notion pages for an Oatmilk account, invoice, or contractor in the mapped databases. Candidates are ranked by name, email, website domain, invoice number, amount, and dates, with the reasons for each match. Nothing is linked until a person confirms. - [`notion.resolveLink`](https://app.getoatmilk.com/docs/api/notion.resolveLink.md) — Resolve a pasted Notion link or ID (notion.so, app.notion.com, notion.site, collection://) to the page, database, or data source it refers to. - [`notion.search`](https://app.getoatmilk.com/docs/api/notion.search.md) — Search Notion databases (data sources) or pages that are shared with the integration, most recently edited first. ## notion.sync - [`notion.sync.run`](https://app.getoatmilk.com/docs/api/notion.sync.run.md) — Start a Notion sync now: read changed invoice rows and prepare suggestions for differences, then update Notion rows from Oatmilk invoices. Returns a runId to follow with runs.get. Supply direction (push, pull, both, full) and idempotencyKey. ## webhooks.deliveries - [`webhooks.deliveries.list`](https://app.getoatmilk.com/docs/api/webhooks.deliveries.list.md) — List webhook deliveries with status, attempts, response code, duration, a redacted response excerpt, and the signed payload. Filter by endpoint, status, or event type. - [`webhooks.deliveries.retry`](https://app.getoatmilk.com/docs/api/webhooks.deliveries.retry.md) — Queue a failed or cancelled delivery to be sent again with the same event payload. Requires expectedRevision and idempotencyKey. - [`webhooks.deliveries.stats`](https://app.getoatmilk.com/docs/api/webhooks.deliveries.stats.md) — Summarize webhook deliveries of real events (not tests) for the last 1, 7 or 30 days: how many were delivered, failed or are still waiting, median and 95th percentile response times, deliveries per day, each endpoint's counts, the busiest event types and the most common failure reasons. ## webhooks.endpoints - [`webhooks.endpoints.archive`](https://app.getoatmilk.com/docs/api/webhooks.endpoints.archive.md) — Remove an endpoint and cancel its queued deliveries. Delivery history is kept. Requires expectedRevision and idempotencyKey. - [`webhooks.endpoints.create`](https://app.getoatmilk.com/docs/api/webhooks.endpoints.create.md) — Create an outgoing webhook endpoint with an https URL on the standard port (443) or 8443 and event subscriptions (exact names, group wildcards such as invoice.*, or *). Private and local network addresses are refused. The signing secret is returned once; replaying the same idempotencyKey returns the same response to the same person. Supply idempotencyKey. - [`webhooks.endpoints.list`](https://app.getoatmilk.com/docs/api/webhooks.endpoints.list.md) — List outgoing webhook endpoints with their subscribed events, status, recent delivery counts, and the last four characters of each signing secret. - [`webhooks.endpoints.rotateSecret`](https://app.getoatmilk.com/docs/api/webhooks.endpoints.rotateSecret.md) — Replace an endpoint's signing secret. The new secret is returned once and signs every later delivery. Requires expectedRevision and idempotencyKey. - [`webhooks.endpoints.update`](https://app.getoatmilk.com/docs/api/webhooks.endpoints.update.md) — Change an endpoint's URL, description, events, or turn it on or off. Turning a disabled endpoint back on resets its failure count. Requires expectedRevision and idempotencyKey. ## webhooks.events - [`webhooks.events.catalog`](https://app.getoatmilk.com/docs/api/webhooks.events.catalog.md) — List the event types Oatmilk can send to webhooks, grouped by area, with descriptions and sample payloads. ## webhooks - [`webhooks.test`](https://app.getoatmilk.com/docs/api/webhooks.test.md) — Send a test event to an endpoint, either webhook.test or a sample of a catalog event, signed like real deliveries. Supply idempotencyKey. ## wise - [`wise.profiles`](https://app.getoatmilk.com/docs/api/wise.profiles.md) — Discover eligible Wise profiles using backend read-only credentials. - [`wise.status`](https://app.getoatmilk.com/docs/api/wise.status.md) — Read safe Wise synchronization readiness and last successful sync. - [`wise.sync`](https://app.getoatmilk.com/docs/api/wise.sync.md) — Synchronize configured Wise statements with durable history and duplicate handling, and keep Wise's own monthly statement PDF for each balance once the month is over (each month once). Optional from and to fetch that inclusive history period (up to 400 days), including quiet balances, without changing the normal sync cursor. Each run fetches up to three monthly windows; remainingWindows and nextFrom say what remains. Pass nextFrom as from with the same to to continue. Future end dates stop at the database cutoff accepted for that request. Retrying the same key resumes its frozen original inputs; completed retries return the saved response. Use a new key for the next history pass or a new sync. Receipt jobs may be queued with imported zero and continue in their own worker. Matching and Autopilot jobs are queued once per accepted sync; Autopilot classifies the new lines afterwards unless skipAi is true. ## wise.receipts - [`wise.receipts.decide`](https://app.getoatmilk.com/docs/api/wise.receipts.decide.md) — Keep saved fields or accept proved incoming merchant, canonical tax, and address values from an immutable Wise receipt review. Values come from the server. Requires exact review and entry revisions, fingerprint, reason, and idempotency key. Bank money and date use the existing correction workflow. - [`wise.receipts.review`](https://app.getoatmilk.com/docs/api/wise.receipts.review.md) — Compare the previous Wise source, newly read evidence, and saved transaction using a private immutable review. Unknown tax stays distinct from confirmed zero. - [`wise.receipts.status`](https://app.getoatmilk.com/docs/api/wise.receipts.status.md) — Read scoped durable receipt-sync progress for one Wise purchase without starting provider work. - [`wise.receipts.sync`](https://app.getoatmilk.com/docs/api/wise.receipts.sync.md) — Queue a preserved, evidence-only read of receipts attached to this exact existing Wise purchase. Requires current entry revision and a stable idempotency key. Reviewed purchases are supported; closed purchases retain evidence without financial changes. ## connectors.credentials.remove Disconnect organization-scoped Stripe, Wise, Notion or Google Drive credentials from the administrator dashboard. Existing accounting records remain. Requires expectedRevision and idempotencyKey. `POST /api/v1/accounting/connectors.credentials.remove` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first Not available over MCP: Disconnecting a provider account is done where it was connected. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | enum | Yes | The record's ID. One of: `stripe`, `wise`, `notion`, `google_drive`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/connectors.credentials.remove \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "stripe", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/connectors.credentials.remove ## connectors.credentials.save Store organization-scoped Stripe, Wise, or Notion credentials from the administrator dashboard. Values are encrypted server-side, never returned, and replace the prior credentials after verification. Requires idempotencyKey and the current revision when replacing. `POST /api/v1/accounting/connectors.credentials.save` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` Not available over MCP: Provider secrets would pass through the agent. ### Fields #### id: "stripe" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | "stripe" | Yes | The record's ID. | | `readKey` | string | Yes | at most 512 characters; Matches ^rk_(test\|live)_[A-Za-z0-9]+$. | | `accountId` | string | Yes | The ID of a bank, card or payment account, from accounts.list. Matches ^acct_[A-Za-z0-9]+$. | | `livemode` | boolean | Yes | | | `webhookSecret` | string | | at most 512 characters; Matches ^whsec_[A-Za-z0-9]+$. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | #### id: "wise" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | "wise" | Yes | The record's ID. | | `readToken` | string | Yes | 20–2048 characters. | | `payoutToken` | string | | 20–2048 characters. | | `scaPrivateKey` | string | | at most 8000 characters. | | `environment` | enum | Yes | One of: `sandbox`, `production`. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | #### id: "notion" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | "notion" | Yes | The record's ID. | | `token` | string | Yes | 20–1024 characters. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/connectors.credentials.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "stripe", "readKey": "rk_test_a", "accountId": "acct_a", "livemode": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/connectors.credentials.save ## connectors.get Read one connector's status and non-secret configuration. For Notion this includes the mapped invoice, customer, vendor, and contact databases, property and status mappings, sync settings, and the last sync summary. `GET | POST /api/v1/accounting/connectors.get` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_connectors_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | enum | Yes | The record's ID. One of: `stripe`, `wise`, `notion`, `email`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/connectors.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=stripe' ``` Reference page: https://app.getoatmilk.com/docs/api/connectors.get ## connectors.googleDrive.connect Start connecting the company's Google Drive with read-only access: returns url, the Google consent page for the admin to open while signed in to Oatmilk (only the admin who asked can finish it, within ten minutes). Optional returnTo is an Oatmilk path to come back to. Once connected, import files and folders with dataSources.import (source google_drive; target company_documents to file incorporation papers and fill the company profile). Disconnect with connectors.credentials.remove (id google_drive). `POST /api/v1/accounting/connectors.googleDrive.connect` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin MCP tool: `accounting_connectors_google_drive_connect` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `returnTo` | string | | at most 300 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/connectors.googleDrive.connect \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/connectors.googleDrive.connect ## connectors.googleDrive.status Read whether Google Drive can be connected here (configured), whether it is connected, and the Google account that connected it. Never returns a token. `GET | POST /api/v1/accounting/connectors.googleDrive.status` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_connectors_google_drive_status` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/connectors.googleDrive.status \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/connectors.googleDrive.status ## connectors.list List available connectors grouped as financial (Stripe, Wise), data (Notion), and communication (email delivery), with connection status and capabilities. Organization admins manage provider credentials in Settings; secret values are never returned. `GET | POST /api/v1/accounting/connectors.list` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_connectors_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `live` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/connectors.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/connectors.list ## connectors.test Check a connector's connection now and record the result. For Notion this verifies the integration token and returns the workspace and integration name. `POST /api/v1/accounting/connectors.test` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_connectors_test` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | enum | Yes | The record's ID. One of: `stripe`, `wise`, `notion`, `email`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/connectors.test \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "stripe" }' ``` Reference page: https://app.getoatmilk.com/docs/api/connectors.test ## connectors.update Save Notion connector settings: database mappings (data source, property mapping, status option mapping) for invoices, customers, vendors, and contacts, and sync settings. Mappings are validated against the live Notion schema. Supply idempotencyKey and optionally expectedRevision. `POST /api/v1/accounting/connectors.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_connectors_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | "notion" | Yes | The record's ID. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `config` | object | Yes | No other fields. | | `config.databases` | map | | | | `config.sync` | object | | No other fields. | | `config.sync.pushInvoices` | boolean | | Default `true`. | | `config.sync.createMissingRows` | boolean | | Default `true`. | | `config.sync.importInvoices` | boolean | | Default `false`. | | `config.sync.proposeFromNotion` | boolean | | Default `true`. | | `config.sync.autoLookupParties` | boolean | | Default `true`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/connectors.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "notion", "config": {} }' ``` Reference page: https://app.getoatmilk.com/docs/api/connectors.update ## connectors.wise.signingKey.create Create a signing key for Wise's strong customer authentication from the administrator dashboard. The private half is saved encrypted with the Wise credential; only the public half is returned, to add in Wise (Settings › API tokens › Manage public keys). A saved key is replaced only with replace true, because payouts sign with the same key. Requires the credential's expectedRevision and idempotencyKey. `POST /api/v1/accounting/connectors.wise.signingKey.create` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` Not available over MCP: It replaces a provider secret that payouts also sign with. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `replace` | boolean | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/connectors.wise.signingKey.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/connectors.wise.signingKey.create ## connectors.wise.statementAccess Read whether Wise's monthly statement PDFs reach Oatmilk: the state (working, waiting, needs a signing key, key not in Wise, refused, not found), how many are kept, when Oatmilk last approved Wise's strong customer authentication and when Wise will ask again (about every 90 days; Oatmilk approves it automatically with the saved signing key), the last refusal, and the saved key's public half to add in Wise. The private key is never returned. `GET | POST /api/v1/accounting/connectors.wise.statementAccess` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_connectors_wise_statement_access` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/connectors.wise.statementAccess \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/connectors.wise.statementAccess ## connectors.wise.statements.check Ask Wise for statement PDFs now without waiting out a refusal: one month per synced balance, oldest missing first, signing Wise's approval request with the saved key when Wise asks. Returns what was kept and the statement access status. Supply idempotencyKey. `POST /api/v1/accounting/connectors.wise.statements.check` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_connectors_wise_statements_check` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/connectors.wise.statements.check \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/connectors.wise.statements.check ## dataImports.commit Start an import in the background with the mapping and decisions: create or choose customers and contractors, link or skip invoice numbers already in Oatmilk, and import or skip possible duplicate payments. Invoices keep their original numbers without using the invoice sequence, paid rows get their payment, the first PDF becomes the invoice's PDF, and Notion rows are linked for sync. Contractor payments are recorded as paid outside Wise; no money is sent and nobody is emailed. Rows already imported are skipped. Returns the import to follow with dataImports.get. Supply idempotencyKey. `POST /api/v1/accounting/dataImports.commit` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_data_imports_commit` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `target` | enum | Yes | One of: `invoices`, `contractor_payments`. | | `source` | enum | | Where the record came from. One of: `notion`. Default `"notion"`. | | `databaseId` | string | Yes | 1–200 characters. | | `mapping` | object | Yes | No other fields. | | `mapping.columns` | map | Yes | | | `mapping.statusMap` | map | | Default `{}`. | | `mapping.defaultCurrency` | string or null | | Default `null`. | | `mapping.dateOrder` | enum or null | | One of: `mdy`, `dmy`. Default `null`. | | `mapping.reviewCustomers` | boolean | | | | `mapping.customerRelationPath` | array of strings | | at most 3 items; each 1–100 characters. | | `mapping.customerRelationDirect` | boolean | | | | `mapping.rowOverrides` | map | | | | `decisions` | object | | No other fields. | | `decisions.groups` | map | | Default `{}`. | | `decisions.rows` | map | | Default `{}`. | | `rowIds` | array of strings | | A list of record IDs. 1–2000 items; each 1–200 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/dataImports.commit \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "target": "invoices", "databaseId": "example", "mapping": { "columns": {} } }' ``` Reference page: https://app.getoatmilk.com/docs/api/dataImports.commit ## dataImports.definitions List what can be imported from a connected database (invoices and contractor payment history): each field with its type, whether it's required, and the kinds of columns it accepts, plus the databases imported from recently. `GET | POST /api/v1/accounting/dataImports.definitions` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_imports_definitions` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/dataImports.definitions \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/dataImports.definitions ## dataImports.files Get short-lived download links for the files an imported invoice came with, and where it was imported from. `GET | POST /api/v1/accounting/dataImports.files` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_imports_files` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `targetType` | enum | Yes | One of: `invoice`, `contractor_payout`. | | `targetId` | string (ID) | Yes | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/dataImports.files \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'targetType=invoice' \ --data-urlencode 'targetId=4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a' ``` Reference page: https://app.getoatmilk.com/docs/api/dataImports.files ## dataImports.get Read one import with its progress, counts, the customers or contractors it added, and each row's outcome with a link to what it created. `GET | POST /api/v1/accounting/dataImports.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_imports_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/dataImports.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/dataImports.get ## dataImports.history List imports, newest first, with their source database, status and counts. Filter by target or database. `GET | POST /api/v1/accounting/dataImports.history` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_imports_history` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `target` | enum | | One of: `invoices`, `contractor_payments`. | | `databaseId` | string | | 1–200 characters. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `20`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/dataImports.history \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/dataImports.history ## dataImports.preview Read every row of the database with a column mapping and group the rows: ready, needs a decision (a new or ambiguous customer or contractor, an invoice number already in Oatmilk, or a possible duplicate payment), already imported, can't import (with the reason), or left out. Nothing is saved. Payment and tax columns are never read, and what they hold is hidden anywhere else in the preview. `GET | POST /api/v1/accounting/dataImports.preview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_imports_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `target` | enum | Yes | One of: `invoices`, `contractor_payments`. | | `source` | enum | | Where the record came from. One of: `notion`. Default `"notion"`. | | `databaseId` | string | Yes | 1–200 characters. | | `mapping` | object | Yes | No other fields. | | `mapping.columns` | map | Yes | | | `mapping.statusMap` | map | | Default `{}`. | | `mapping.defaultCurrency` | string or null | | Default `null`. | | `mapping.dateOrder` | enum or null | | One of: `mdy`, `dmy`. Default `null`. | | `mapping.reviewCustomers` | boolean | | | | `mapping.customerRelationPath` | array of strings | | at most 3 items; each 1–100 characters. | | `mapping.customerRelationDirect` | boolean | | | | `mapping.rowOverrides` | map | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/dataImports.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target": "invoices", "databaseId": "example", "mapping": { "columns": {} } }' ``` Reference page: https://app.getoatmilk.com/docs/api/dataImports.preview ## dataImports.resume Continue an import that paused before it finished, for example after a time limit. It continues as the person who started it. Supply idempotencyKey. `POST /api/v1/accounting/dataImports.resume` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_data_imports_resume` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/dataImports.resume \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/dataImports.resume ## dataImports.suggest Suggest a column of a connected database (Notion) for each field of an import target, with a sample value per column, the status options found, and the mapping remembered for that database. Supply target, source and databaseId. A column whose name says it holds payment or tax details is marked sensitive, has no sample or options, and is never suggested or imported. `GET | POST /api/v1/accounting/dataImports.suggest` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_imports_suggest` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `target` | enum | Yes | One of: `invoices`, `contractor_payments`. | | `source` | enum | | Where the record came from. One of: `notion`. Default `"notion"`. | | `databaseId` | string | Yes | 1–200 characters. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/dataImports.suggest \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'target=invoices' \ --data-urlencode 'databaseId=example' ``` Reference page: https://app.getoatmilk.com/docs/api/dataImports.suggest ## dataImports.undo Undo a contractor payment import: the payouts it recorded are cancelled when they haven't changed since, and the rows can be imported again. Imported invoices are voided from each invoice instead. Supply reason and idempotencyKey. `POST /api/v1/accounting/dataImports.undo` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Destructive: confirm with a person first MCP tool: `accounting_data_imports_undo` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/dataImports.undo \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/dataImports.undo ## dataSources.files List the files attached to a row or page: files in file columns and files embedded in the page. For google_drive, itemId is a folder (its files, partial when there are more than 200) or a file (itself); Google Docs, Sheets and Slides are listed as PDFs. With target, files already imported there are marked. File download addresses are never returned. `GET | POST /api/v1/accounting/dataSources.files` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_sources_files` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | enum | Yes | Where the record came from. One of: `notion`, `google_drive`, `onedrive`. | | `itemId` | string | Yes | 1–200 characters; Matches ^[A-Za-z0-9_.:-]+$. | | `target` | enum | | One of: `receipts`, `tax_sources`, `signing_files`, `executed_agreements`, `contractor_agreements`, `company_documents`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/dataSources.files \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'source=notion' \ --data-urlencode 'itemId=item_id' ``` Reference page: https://app.getoatmilk.com/docs/api/dataSources.files ## dataSources.importFiles Import up to 25 files from a data source into Oatmilk: receipts (processed like uploaded receipts), tax_sources (supporting originals, no expense), signing_files (documents to send for signature), executed_agreements (agreements signed elsewhere; targetOptions parties, executedOn, title) contractor_agreements (draft documents; optional targetOptions contractorId, expectedContractorRevision and title create the draft agreement) or company_documents (incorporation papers and other company records: each is filed through intake as a company record, read, and fills the company profile's empty fields; the record is the document id). Sources are notion and google_drive. Each file is downloaded server-side, checked against its target's size limit and file types by its contents, and run through that target's usual upload path. Importing the same file into the same place again returns the existing record. Returns a result for each file. Supply idempotencyKey. `POST /api/v1/accounting/dataSources.importFiles` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_data_sources_import_files` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | enum | Yes | Where the record came from. One of: `notion`, `google_drive`, `onedrive`. | | `target` | enum | Yes | One of: `receipts`, `tax_sources`, `signing_files`, `executed_agreements`, `contractor_agreements`, `company_documents`. | | `items` | array of objects | Yes | 1–25 items. | | `items[].itemId` | string | Yes | 1–200 characters; Matches ^[A-Za-z0-9_.:-]+$. | | `items[].fileId` | string | Yes | 1–200 characters; Matches ^[A-Za-z0-9_.:-]+$. | | `targetOptions` | map | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/dataSources.importFiles \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "source": "notion", "target": "receipts", "items": [ { "itemId": "item_id", "fileId": "file_id" } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/dataSources.importFiles ## dataSources.list List data sources files and records can be imported from (Notion, and Google Drive once an admin connects it with connectors.googleDrive.connect; OneDrive is listed as not available yet), with connection status, pinned databases and databases mapped in Settings › Connectors. `GET | POST /api/v1/accounting/dataSources.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_sources_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/dataSources.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/dataSources.list ## dataSources.pin Pin or unpin a database for everyone in the workspace so it appears first when importing. Supply source, databaseId, pinned and idempotencyKey. `POST /api/v1/accounting/dataSources.pin` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_data_sources_pin` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | enum | Yes | Where the record came from. One of: `notion`, `google_drive`, `onedrive`. | | `databaseId` | string | Yes | 1–200 characters; Matches ^[A-Za-z0-9_.:-]+$. | | `pinned` | boolean | Yes | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/dataSources.pin \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "source": "notion", "databaseId": "database_id", "pinned": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/dataSources.pin ## dataSources.readText Read the text of a CSV or plain-text file attached to a row or page (up to 2 MB, UTF-8), for example a bank statement to import with imports.preview and imports.commit. Nothing is stored. `GET | POST /api/v1/accounting/dataSources.readText` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_sources_read_text` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | enum | Yes | Where the record came from. One of: `notion`, `google_drive`, `onedrive`. | | `itemId` | string | Yes | 1–200 characters; Matches ^[A-Za-z0-9_.:-]+$. | | `fileId` | string | Yes | 1–200 characters; Matches ^[A-Za-z0-9_.:-]+$. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/dataSources.readText \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'source=notion' \ --data-urlencode 'itemId=item_id' \ --data-urlencode 'fileId=file_id' ``` Reference page: https://app.getoatmilk.com/docs/api/dataSources.readText ## dataSources.resolve Open a pasted link or ID from a data source (for Notion: notion.so, app.notion.com, notion.site links, database and page IDs; for Google Drive: drive.google.com and docs.google.com links to files and folders, or a Drive ID). Returns whether it is a database or a page and the database ID to read rows from. `GET | POST /api/v1/accounting/dataSources.resolve` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_sources_resolve` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | enum | Yes | Where the record came from. One of: `notion`, `google_drive`, `onedrive`. | | `url` | string | Yes | A full web address, starting with https://. 1–2000 characters. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/dataSources.resolve \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'source=notion' \ --data-urlencode 'url=https://example.com/webhooks/oatmilk' ``` Reference page: https://app.getoatmilk.com/docs/api/dataSources.resolve ## dataSources.rows Read rows of a database with readable, typed values for every column: text, numbers with their format, dates, options, people, related page titles, checkboxes, links and attached files. Supports search, sort by a column, only rows with files, and cursor paging. With target, files already imported there are marked. File download addresses are never returned. Columns whose names say they hold payment or tax details (bank, account, transit, institution and routing numbers, IBAN, SWIFT/BIC, Wise or Interac addresses, SIN, SSN, business or tax numbers) read as [hidden], what they hold is hidden anywhere else in the rows too, and they're never searched or sorted by. `GET | POST /api/v1/accounting/dataSources.rows` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_sources_rows` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | enum | Yes | Where the record came from. One of: `notion`, `google_drive`, `onedrive`. | | `databaseId` | string | Yes | 1–200 characters; Matches ^[A-Za-z0-9_.:-]+$. | | `cursor` | string | | Where the next page starts: the nextCursor value from the previous response. 1–500 characters. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | | `search` | string | | Text to search for. at most 100 characters. | | `sortBy` | string | | Which field to sort by. 1–200 characters. | | `sortDirection` | enum | | asc for oldest or smallest first, desc for newest or largest first. One of: `ascending`, `descending`. Default `"descending"`. | | `withFiles` | boolean | | | | `target` | enum | | One of: `receipts`, `tax_sources`, `signing_files`, `executed_agreements`, `contractor_agreements`, `company_documents`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/dataSources.rows \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'source=notion' \ --data-urlencode 'databaseId=database_id' ``` Reference page: https://app.getoatmilk.com/docs/api/dataSources.rows ## dataSources.schema Read a database's columns with their types, options, number formats (and the currency they imply), related databases, and the columns suggested for a first view. Columns whose names say they hold payment or tax details list no options and can't be sorted or searched by. `GET | POST /api/v1/accounting/dataSources.schema` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_sources_schema` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | enum | Yes | Where the record came from. One of: `notion`, `google_drive`, `onedrive`. | | `databaseId` | string | Yes | 1–200 characters; Matches ^[A-Za-z0-9_.:-]+$. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/dataSources.schema \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'source=notion' \ --data-urlencode 'databaseId=database_id' ``` Reference page: https://app.getoatmilk.com/docs/api/dataSources.schema ## dataSources.search Search a data source for databases and pages shared with Oatmilk, most recently edited first. Supply source, optional query, kind (database, page, file or any), cursor and limit. For google_drive it searches file and folder names across My Drive and shared drives (kind file leaves folders out; Drive has no databases). `GET | POST /api/v1/accounting/dataSources.search` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_data_sources_search` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | enum | Yes | Where the record came from. One of: `notion`, `google_drive`, `onedrive`. | | `query` | string | | Text to search for. at most 200 characters. | | `kind` | enum | | Which kind of record or job this is. One of: `database`, `page`, `file`, `any`. Default `"any"`. | | `cursor` | string | | Where the next page starts: the nextCursor value from the previous response. 1–500 characters. | | `limit` | integer | | How many results to return at most. 1 to 50. Default `20`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/dataSources.search \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'source=notion' ``` Reference page: https://app.getoatmilk.com/docs/api/dataSources.search ## notion.dataSources.get Read a Notion data source's properties and suggested mappings for each role (invoices, customers, vendors, contacts), including suggested status option mappings. `GET | POST /api/v1/accounting/notion.dataSources.get` Permissions: `accounting:read` · Roles: admin, finance · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_data_sources_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | Yes | The record's ID. 32–36 characters. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/notion.dataSources.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e' ``` Reference page: https://app.getoatmilk.com/docs/api/notion.dataSources.get ## notion.dataSources.query Preview rows of a Notion data source with simplified property values, optionally filtered by a title search. Paged with cursor. Properties whose names say they hold payment or tax details (bank, account, transit, institution and routing numbers, IBAN, SWIFT/BIC, Wise or Interac addresses, SIN, SSN, business or tax numbers) read as [hidden], and what they hold is hidden anywhere else in the rows too. `GET | POST /api/v1/accounting/notion.dataSources.query` Permissions: `accounting:read` · Roles: admin, finance · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_data_sources_query` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | Yes | The record's ID. 32–36 characters. | | `search` | string | | Text to search for. at most 100 characters. | | `cursor` | string | | Where the next page starts: the nextCursor value from the previous response. 1–200 characters. | | `limit` | integer | | How many results to return at most. 1 to 50. Default `20`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/notion.dataSources.query \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e' ``` Reference page: https://app.getoatmilk.com/docs/api/notion.dataSources.query ## notion.invoices.import Link existing Notion invoice rows to Oatmilk invoices. Status differences become suggestions for review; nothing is overwritten. Supply selections of pageId and invoiceId, plus idempotencyKey. `POST /api/v1/accounting/notion.invoices.import` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_invoices_import` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `selections` | array of objects | Yes | 1–100 items. | | `selections[].pageId` | string | Yes | 32–36 characters. | | `selections[].invoiceId` | string (ID) | Yes | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notion.invoices.import \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "selections": [ { "pageId": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e", "invoiceId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/notion.invoices.import ## notion.invoices.importPreview Preview existing rows in the mapped Notion invoice database with the Oatmilk invoice each one most likely matches, whether it's already linked, and whether the statuses differ. `GET | POST /api/v1/accounting/notion.invoices.importPreview` Permissions: `accounting:read` · Roles: admin, finance · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_invoices_import_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `cursor` | string | | Where the next page starts: the nextCursor value from the previous response. 1–200 characters. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notion.invoices.importPreview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/notion.invoices.importPreview ## notion.links.confirm Confirm a suggested Notion link. Any other confirmed link for the same record and database is replaced. Requires expectedRevision and idempotencyKey. `POST /api/v1/accounting/notion.links.confirm` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_links_confirm` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notion.links.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/notion.links.confirm ## notion.links.create Link an Oatmilk account, invoice, or contractor to a Notion page by page ID or pasted link. Links created by a person are confirmed unless status is proposed. Supply idempotencyKey. `POST /api/v1/accounting/notion.links.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_links_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `localType` | enum | Yes | One of: `party`, `invoice`, `contractor`. | | `localId` | string (ID) | Yes | The ID of the related record. | | `role` | enum | | A person's access level in the company. One of: `invoices`, `companies`, `vendors`, `contacts`. | | `pageId` | string | | 32–36 characters. | | `url` | string | | A full web address, starting with https://. 1–2000 characters. | | `method` | enum | | One of: `manual`, `link`, `smart_lookup`. | | `status` | enum | | Only include records with this status. One of: `confirmed`, `proposed`. | | `candidate` | object | | No other fields. | | `candidate.score` | integer | | 0 to 100. | | `candidate.confidence` | enum | | One of: `low`, `medium`, `high`. | | `candidate.reasons` | array of strings | | at most 20 items; each at most 200 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notion.links.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "localType": "party", "localId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "pageId": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e" }' ``` Reference page: https://app.getoatmilk.com/docs/api/notion.links.create ## notion.links.list List links between Oatmilk records (accounts, invoices, contractors) and Notion pages, with their status (suggested, confirmed, declined), method, match reasons, and sync state. `GET | POST /api/v1/accounting/notion.links.list` Permissions: `accounting:read` · Roles: admin, finance · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_links_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `localType` | enum | | One of: `party`, `invoice`, `contractor`. | | `localId` | string | | 1–200 characters. | | `status` | enum | | Only include records with this status. One of: `proposed`, `confirmed`, `rejected`. | | `role` | enum | | A person's access level in the company. One of: `invoices`, `companies`, `vendors`, `contacts`. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notion.links.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/notion.links.list ## notion.links.reject Decline a suggested Notion link so it isn't suggested again. Requires expectedRevision and idempotencyKey. `POST /api/v1/accounting/notion.links.reject` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_links_reject` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notion.links.reject \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/notion.links.reject ## notion.links.remove Remove a Notion link. The Notion page itself is not changed. Requires expectedRevision and idempotencyKey. `POST /api/v1/accounting/notion.links.remove` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_links_remove` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notion.links.remove \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/notion.links.remove ## notion.lookup Find likely Notion pages for an Oatmilk account, invoice, or contractor in the mapped databases. Candidates are ranked by name, email, website domain, invoice number, amount, and dates, with the reasons for each match. Nothing is linked until a person confirms. `GET | POST /api/v1/accounting/notion.lookup` Permissions: `accounting:read` · Roles: admin, finance · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_lookup` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `localType` | enum | Yes | One of: `party`, `invoice`, `contractor`. | | `localId` | string (ID) | Yes | The ID of the related record. | | `role` | enum | | A person's access level in the company. One of: `invoices`, `companies`, `vendors`, `contacts`. | | `query` | string | | Text to search for. at most 100 characters. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/notion.lookup \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'localType=party' \ --data-urlencode 'localId=1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d' ``` Reference page: https://app.getoatmilk.com/docs/api/notion.lookup ## notion.resolveLink Resolve a pasted Notion link or ID (notion.so, app.notion.com, notion.site, collection://) to the page, database, or data source it refers to. `GET | POST /api/v1/accounting/notion.resolveLink` Permissions: `accounting:read` · Roles: admin, finance · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_resolve_link` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `url` | string | Yes | A full web address, starting with https://. 1–2000 characters. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/notion.resolveLink \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'url=https://example.com/webhooks/oatmilk' ``` Reference page: https://app.getoatmilk.com/docs/api/notion.resolveLink ## notion.search Search Notion databases (data sources) or pages that are shared with the integration, most recently edited first. `GET | POST /api/v1/accounting/notion.search` Permissions: `accounting:read` · Roles: admin, finance · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_search` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `query` | string | | Text to search for. at most 200 characters. | | `kind` | enum | | Which kind of record or job this is. One of: `data_source`, `page`. Default `"data_source"`. | | `cursor` | string | | Where the next page starts: the nextCursor value from the previous response. 1–200 characters. | | `limit` | integer | | How many results to return at most. 1 to 50. Default `20`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notion.search \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/notion.search ## notion.sync.run Start a Notion sync now: read changed invoice rows and prepare suggestions for differences, then update Notion rows from Oatmilk invoices. Returns a runId to follow with runs.get. Supply direction (push, pull, both, full) and idempotencyKey. `POST /api/v1/accounting/notion.sync.run` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_notion_sync_run` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `direction` | enum | | One of: `push`, `pull`, `both`, `full`. Default `"both"`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notion.sync.run \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/notion.sync.run ## webhooks.deliveries.list List webhook deliveries with status, attempts, response code, duration, a redacted response excerpt, and the signed payload. Filter by endpoint, status, or event type. `GET | POST /api/v1/accounting/webhooks.deliveries.list` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_webhooks_deliveries_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `endpointId` | string (ID) | | The ID of the related record. | | `status` | enum | | Only include records with this status. One of: `queued`, `processing`, `delivered`, `failed`, `cancelled`. | | `eventType` | string | | Matches ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*){1,3}$. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/webhooks.deliveries.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/webhooks.deliveries.list ## webhooks.deliveries.retry Queue a failed or cancelled delivery to be sent again with the same event payload. Requires expectedRevision and idempotencyKey. `POST /api/v1/accounting/webhooks.deliveries.retry` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_webhooks_deliveries_retry` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/webhooks.deliveries.retry \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/webhooks.deliveries.retry ## webhooks.deliveries.stats Summarize webhook deliveries of real events (not tests) for the last 1, 7 or 30 days: how many were delivered, failed or are still waiting, median and 95th percentile response times, deliveries per day, each endpoint's counts, the busiest event types and the most common failure reasons. `GET | POST /api/v1/accounting/webhooks.deliveries.stats` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_webhooks_deliveries_stats` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `days` | integer | | -9007199254740991 to 9007199254740991. Default `7`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/webhooks.deliveries.stats \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/webhooks.deliveries.stats ## webhooks.endpoints.archive Remove an endpoint and cancel its queued deliveries. Delivery history is kept. Requires expectedRevision and idempotencyKey. `POST /api/v1/accounting/webhooks.endpoints.archive` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_webhooks_endpoints_archive` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/webhooks.endpoints.archive \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/webhooks.endpoints.archive ## webhooks.endpoints.create Create an outgoing webhook endpoint with an https URL on the standard port (443) or 8443 and event subscriptions (exact names, group wildcards such as invoice.*, or *). Private and local network addresses are refused. The signing secret is returned once; replaying the same idempotencyKey returns the same response to the same person. Supply idempotencyKey. `POST /api/v1/accounting/webhooks.endpoints.create` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_webhooks_endpoints_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `url` | string | Yes | A full web address, starting with https://. 10–2000 characters. | | `description` | string | | A short description. at most 500 characters. | | `events` | array of strings | Yes | Event types to receive: exact names such as invoice.paid, a group wildcard such as invoice.*, or * for everything. 1–100 items; each 1–80 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/webhooks.endpoints.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "url": "https://example.com/webhooks/oatmilk", "events": [ "invoice.paid" ] }' ``` ### Example response ```json { "data": { "endpoint": { "id": "5d4c3b2a-1f0e-4d9c-8b7a-6e5f4d3c2b1a", "url": "https://example.com/webhooks/oatmilk", "description": "CRM sync", "events": [ "invoice.*" ], "secret_hint": "a1B2", "secret_version": 1, "active": true, "failure_count": 0, "disabled_reason": null, "disabled_at": null, "last_delivery_at": null, "last_success_at": null, "created_by": "user_synthetic", "archived_at": null, "revision": 1, "created_at": "2026-09-30T14:00:00Z", "updated_at": "2026-09-30T14:00:00Z", "status": "active" }, "secret": "whsec_synthetic_shown_once_store_it_now" } } ``` Reference page: https://app.getoatmilk.com/docs/api/webhooks.endpoints.create ## webhooks.endpoints.list List outgoing webhook endpoints with their subscribed events, status, recent delivery counts, and the last four characters of each signing secret. `GET | POST /api/v1/accounting/webhooks.endpoints.list` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_webhooks_endpoints_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/webhooks.endpoints.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` ### Example response ```json { "data": { "items": [ { "id": "5d4c3b2a-1f0e-4d9c-8b7a-6e5f4d3c2b1a", "url": "https://example.com/webhooks/oatmilk", "description": "CRM sync", "events": [ "invoice.*" ], "secret_hint": "a1B2", "secret_version": 1, "active": true, "failure_count": 0, "disabled_reason": null, "disabled_at": null, "last_delivery_at": null, "last_success_at": null, "created_by": "user_synthetic", "archived_at": null, "revision": 1, "created_at": "2026-09-30T14:00:00Z", "updated_at": "2026-09-30T14:00:00Z", "status": "active", "recent": { "delivered": 42, "failed": 1, "pending": 0 } } ], "signingReady": true } } ``` Reference page: https://app.getoatmilk.com/docs/api/webhooks.endpoints.list ## webhooks.endpoints.rotateSecret Replace an endpoint's signing secret. The new secret is returned once and signs every later delivery. Requires expectedRevision and idempotencyKey. `POST /api/v1/accounting/webhooks.endpoints.rotateSecret` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_webhooks_endpoints_rotate_secret` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/webhooks.endpoints.rotateSecret \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/webhooks.endpoints.rotateSecret ## webhooks.endpoints.update Change an endpoint's URL, description, events, or turn it on or off. Turning a disabled endpoint back on resets its failure count. Requires expectedRevision and idempotencyKey. `POST /api/v1/accounting/webhooks.endpoints.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_webhooks_endpoints_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `url` | string | | A full web address, starting with https://. 10–2000 characters. | | `description` | string | | A short description. at most 500 characters. | | `events` | array of strings | | Event types to receive: exact names such as invoice.paid, a group wildcard such as invoice.*, or * for everything. 1–100 items; each 1–80 characters. | | `active` | boolean | | Whether the record is turned on. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/webhooks.endpoints.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/webhooks.endpoints.update ## webhooks.events.catalog List the event types Oatmilk can send to webhooks, grouped by area, with descriptions and sample payloads. `GET | POST /api/v1/accounting/webhooks.events.catalog` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_webhooks_events_catalog` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/webhooks.events.catalog \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/webhooks.events.catalog ## webhooks.test Send a test event to an endpoint, either webhook.test or a sample of a catalog event, signed like real deliveries. Supply idempotencyKey. `POST /api/v1/accounting/webhooks.test` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_webhooks_test` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `endpointId` | string (ID) | Yes | The ID of the related record. | | `eventType` | string | | Matches ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*){1,3}$. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/webhooks.test \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "endpointId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a" }' ``` Reference page: https://app.getoatmilk.com/docs/api/webhooks.test ## wise.profiles Discover eligible Wise profiles using backend read-only credentials. `GET | POST /api/v1/accounting/wise.profiles` Permissions: `accounting:read`, `accounting:admin` · Roles: admin · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_wise_profiles` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/wise.profiles \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/wise.profiles ## wise.receipts.decide Keep saved fields or accept proved incoming merchant, canonical tax, and address values from an immutable Wise receipt review. Values come from the server. Requires exact review and entry revisions, fingerprint, reason, and idempotency key. Bank money and date use the existing correction workflow. `POST /api/v1/accounting/wise.receipts.decide` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_wise_receipts_decide` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `reviewId` | string (ID) | Yes | The ID of the related record. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `reviewRevision` | integer | Yes | The related record's current revision, from the last time you read it. at most 9007199254740991; greater than 0. | | `sourceFingerprint` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `selections` | array of objects | Yes | at most 7 items. | | `selections[].field` | enum | Yes | One of: `merchant`, `date`, `currency`, `amountMinor`, `taxMinor`, `merchantAddress`, `travelAddress`. | | `selections[].choice` | enum | Yes | One of: `keep`, `incoming`. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/wise.receipts.decide \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "reviewId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "reviewRevision": 3, "sourceFingerprint": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "selections": [ { "field": "merchant", "choice": "keep" } ], "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/wise.receipts.decide ## wise.receipts.review Compare the previous Wise source, newly read evidence, and saved transaction using a private immutable review. Unknown tax stays distinct from confirmed zero. `GET | POST /api/v1/accounting/wise.receipts.review` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_wise_receipts_review` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `reviewId` | string (ID) | | The ID of the related record. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/wise.receipts.review \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'entryId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/wise.receipts.review ## wise.receipts.status Read scoped durable receipt-sync progress for one Wise purchase without starting provider work. `GET | POST /api/v1/accounting/wise.receipts.status` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_wise_receipts_status` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/wise.receipts.status \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'entryId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/wise.receipts.status ## wise.receipts.sync Queue a preserved, evidence-only read of receipts attached to this exact existing Wise purchase. Requires current entry revision and a stable idempotency key. Reviewed purchases are supported; closed purchases retain evidence without financial changes. `POST /api/v1/accounting/wise.receipts.sync` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_wise_receipts_sync` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/wise.receipts.sync \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/wise.receipts.sync ## wise.status Read safe Wise synchronization readiness and last successful sync. `GET | POST /api/v1/accounting/wise.status` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_wise_status` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/wise.status \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/wise.status ## wise.sync Synchronize configured Wise statements with durable history and duplicate handling, and keep Wise's own monthly statement PDF for each balance once the month is over (each month once). Optional from and to fetch that inclusive history period (up to 400 days), including quiet balances, without changing the normal sync cursor. Each run fetches up to three monthly windows; remainingWindows and nextFrom say what remains. Pass nextFrom as from with the same to to continue. Future end dates stop at the database cutoff accepted for that request. Retrying the same key resumes its frozen original inputs; completed retries return the saved response. Use a new key for the next history pass or a new sync. Receipt jobs may be queued with imported zero and continue in their own worker. Matching and Autopilot jobs are queued once per accepted sync; Autopilot classifies the new lines afterwards unless skipAi is true. `POST /api/v1/accounting/wise.sync` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_wise_sync` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `skipAi` | boolean | | | | `from` | string | | The first date to include, as YYYY-MM-DD. | | `to` | string | | The last date to include, as YYYY-MM-DD. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/wise.sync \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/wise.sync # Group: AI and memory > AI settings, classifier guidance, memory, the sandbox and compliance research. MCP toolset: `ai` (https://app.getoatmilk.com/api/mcp?toolset=ai) ## agents - [`agents.overview`](https://app.getoatmilk.com/docs/api/agents.overview.md) — List the platform's agents (Autopilot, receipt reader, matcher, mail router, inbox scanner, bank sync and others) with what each checks for, its triggers, schedule, models and safeguards, and its live status: running now with the current step, needs attention, scheduled with the next run, or idle, plus the last run and the 24-hour run count, success rate and median duration. Follow a run with runs.get. ## agents.playground - [`agents.playground.match`](https://app.getoatmilk.com/docs/api/agents.playground.match.md) — Dry run of the receipt matcher for one receipt entry (entryId): return the shortlisted bank and card candidates in ranked order and the matcher's judgement with the organization's saved model and thresholds. Nothing is saved or matched. - [`agents.playground.receipt`](https://app.getoatmilk.com/docs/api/agents.playground.receipt.md) — Dry run of the receipt reader: read a sample receipt (filename, mimeType, contentBase64 of up to 2 MB; JPEG, PNG, WebP, HEIC, PDF or .eml) the same way intake does and return the extracted facts and validation flags. Nothing is saved: no submission, evidence, entry or run. ## ai.keys - [`ai.keys.get`](https://app.getoatmilk.com/docs/api/ai.keys.get.md) — Which AI providers this organization brought its own keys for (TypeSafe, Vercel AI Gateway, OpenAI, Google Gemini, Z.ai), each with the key's last four characters and when it was checked, never the key itself; whether Oatmilk's own AI credits are on for it; and whether its keys cover decisions (categorizing, matching, sorting) and reading documents. - [`ai.keys.remove`](https://app.getoatmilk.com/docs/api/ai.keys.remove.md) — Remove this organization's key for one AI provider, at the current revision. Features that need it ask for a key again unless Oatmilk's AI credits are on. Administrators only, from the dashboard. - [`ai.keys.save`](https://app.getoatmilk.com/docs/api/ai.keys.save.md) — Save or replace this organization's key for one AI provider, at the current revision (0 for the first key). Oatmilk checks the key with the provider first and saves it encrypted. Administrators only, from the dashboard. ## ai.models - [`ai.models.list`](https://app.getoatmilk.com/docs/api/ai.models.list.md) — List the decision models the receipt-matching and mail-routing classifiers can run on: AI Gateway's live list of evaluation models (id, name, provider, description, context window, input price per million tokens, whether the provider trains on the data, and whether Oatmilk has tested it), or the install's own model server when it runs local models. Falls back to Oatmilk's tested models when the gateway can't be reached. A model Oatmilk hasn't tested must pass ai.sandbox.evaluate before it is saved. ## ai.sandbox - [`ai.sandbox.evaluate`](https://app.getoatmilk.com/docs/api/ai.sandbox.evaluate.md) — Evaluate a supplied fixture against saved or draft automation settings without writing anything. kind routing classifies an email fixture; kind matching scores a receipt against bank candidates; kind document reads a real file exactly as the inbox does (file {filename, mimeType, contentBase64}, up to 2 MB: a receipt, invoice or statement as a PDF, image, office or text document, or an original .eml email, which is classified first): every page is turned into images with logos and signature graphics set aside, read twice with vision and settled by the stronger model when the readings disagree, and checked as the books would take it. It returns the classifier answer, the facts, the vision check, the entry or flags for each document, and the parts read. kind record reads a kept-record file (file as for document, recordKind tax_document, company_document or other) exactly as Tax › Records reads one, with the same pages, model, prompt and schema, and returns its fields, warnings, model, prompt version and parts; for a company record also describesCompany, wouldFill (filled, nothing_new, other_company or needs_admin) and the profile fields it fills or differs on. kind email runs the email-sorting classifier on an email already in the company inbox (messageId), rebuilt from what was stored when it arrived, and returns the new decision with what was decided at the time (example.stored). kind receipt runs the matcher on a receipt entry (entryId) against its current bank and card candidates (draft may also set autoApply). Both accept the same draft model and thresholds as routing and matching. Nothing is saved. Use it to test the pipeline without the dashboard, and to test custom models before saving them. ## ai.settings - [`ai.settings.get`](https://app.getoatmilk.com/docs/api/ai.settings.get.md) — Read the organization's effective matching and routing automation settings, including models, thresholds, and revision. - [`ai.settings.update`](https://app.getoatmilk.com/docs/api/ai.settings.update.md) — Revise matching and routing models, thresholds, and automatic matching with revision and idempotency checks. Changes apply to future evaluations only; stored decisions keep their recorded provenance. ## chiefOfStaff.channels - [`chiefOfStaff.channels.update`](https://app.getoatmilk.com/docs/api/chiefOfStaff.channels.update.md) — Set rules for one Discord channel and its threads: how the Chief of Staff replies there (or that it stays out), its profile, tools turned on or off, and instructions. Pass reset to clear the channel's rules. Pass the channel's revision as expectedRevision (0 the first time). Only the platform owner's workspace has a Chief of Staff. Administrator access required. ## chiefOfStaff.general - [`chiefOfStaff.general.update`](https://app.getoatmilk.com/docs/api/chiefOfStaff.general.update.md) — Change the Chief of Staff's settings that apply everywhere: pause it, turn direct messages on or off, turn tools off everywhere, or replace the list of people it never replies to. Takes effect on its next message. Pass expectedRevision from chiefOfStaff.settings.get. Only the platform owner's workspace has a Chief of Staff. Administrator access required. ## chiefOfStaff.people - [`chiefOfStaff.people.find`](https://app.getoatmilk.com/docs/api/chiefOfStaff.people.find.md) — Find people in the Chief of Staff's Discord server by Discord id or part of their name, to add them to a rule or the ignored list. Only the platform owner's workspace has a Chief of Staff. Finance access required. ## chiefOfStaff.rules - [`chiefOfStaff.rules.delete`](https://app.getoatmilk.com/docs/api/chiefOfStaff.rules.delete.md) — Remove a rule for people. Pass its revision as expectedRevision. History can put it back. Only the platform owner's workspace has a Chief of Staff. Administrator access required. - [`chiefOfStaff.rules.save`](https://app.getoatmilk.com/docs/api/chiefOfStaff.rules.save.md) — Add or change a rule for some people: by Discord person or role, optionally only in some channels, give them tools, take tools away, answer them with a profile, or add instructions. A tool turned off anywhere else stays off. Pass id and expectedRevision to change a rule. Only the platform owner's workspace has a Chief of Staff. Administrator access required. ## chiefOfStaff.schedules - [`chiefOfStaff.schedules.update`](https://app.getoatmilk.com/docs/api/chiefOfStaff.schedules.update.md) — Pause or resume one of the Chief of Staff's scheduled posts. A paused post skips its turns and resumes at its next one, without catching up. Only the platform owner's workspace has a Chief of Staff. Administrator access required. ## chiefOfStaff.servers - [`chiefOfStaff.servers.list`](https://app.getoatmilk.com/docs/api/chiefOfStaff.servers.list.md) — List the Discord servers the Chief of Staff can see, with each connected server's channels (grouped by category) and roles, read live from Discord. Pass refresh to skip the one-minute cache. Only the platform owner's workspace has a Chief of Staff. Finance access required. - [`chiefOfStaff.servers.update`](https://app.getoatmilk.com/docs/api/chiefOfStaff.servers.update.md) — Change how the Chief of Staff behaves in one Discord server: turn it on or off there, choose how it replies in channels that don't say otherwise, a default profile, and instructions for the whole server. Pass the server's revision as expectedRevision (0 the first time). Only the platform owner's workspace has a Chief of Staff. Administrator access required. ## chiefOfStaff.settings - [`chiefOfStaff.settings.get`](https://app.getoatmilk.com/docs/api/chiefOfStaff.settings.get.md) — Read every Chief of Staff setting with its revision: whether it is paused, direct messages, tools turned off everywhere, ignored people, each Discord server's and channel's rules (who it answers, profile, tools and instructions), rules for people and roles, and paused scheduled posts. Only the platform owner's workspace has a Chief of Staff. Finance access required. ## chiefOfStaff - [`chiefOfStaff.status`](https://app.getoatmilk.com/docs/api/chiefOfStaff.status.md) — Read how the Chief of Staff (the Oatmilk agent in Discord) is set up: the Discord server it serves, whether its Gateway and scheduled posts run on this deployment, its recent activity, which integrations are connected, its profiles and their tools, the channel names that pick a profile, and its scheduled posts. Only the platform owner's workspace has a Chief of Staff. Finance access required. ## classifier.mailboxes - [`classifier.mailboxes.list`](https://app.getoatmilk.com/docs/api/classifier.mailboxes.list.md) — List organization email types, receiving addresses, and classifier guidance. - [`classifier.mailboxes.save`](https://app.getoatmilk.com/docs/api/classifier.mailboxes.save.md) — Create a receiving email type or revise its routing and category classifier guidance with revision and idempotency checks. Credential screening and human review remain mandatory. ## complianceLibrary.entries - [`complianceLibrary.entries.create`](https://app.getoatmilk.com/docs/api/complianceLibrary.entries.create.md) — Platform administrators only: write a new compliance library entry (title, topic, and optional summary, keyPoints, meaning and markdown notes). A person's entry is protected: an agent's later change to it is proposed, never applied. - [`complianceLibrary.entries.update`](https://app.getoatmilk.com/docs/api/complianceLibrary.entries.update.md) — Platform administrators only: edit a compliance library entry's title, topic, summary, keyPoints, meaning or markdown notes at its current revision, archive or restore it (archived), or mark it looked at (resolved). Editing protects the entry from agent overwrites. ## complianceLibrary.files - [`complianceLibrary.files.confirm`](https://app.getoatmilk.com/docs/api/complianceLibrary.files.confirm.md) — Platform administrators only: confirm an uploaded library file. Oatmilk checks its size, hash and type, then an agent reads it as untrusted data, summarises it, links it to the right entry or suggests a new one, and flags entries it may affect. - [`complianceLibrary.files.prepare`](https://app.getoatmilk.com/docs/api/complianceLibrary.files.prepare.md) — Platform administrators only: prepare a private upload of a PDF, Word document, text file or picture (up to 25 MB) for the library. Returns a signed uploadUrl; then call complianceLibrary.files.confirm. ## complianceLibrary - [`complianceLibrary.get`](https://app.getoatmilk.com/docs/api/complianceLibrary.get.md) — One compliance library guide by id or slug with its official sources and check dates and its history. The platform's administrators also see the changes waiting for a person and the links and files added to it. - [`complianceLibrary.list`](https://app.getoatmilk.com/docs/api/complianceLibrary.list.md) — The compliance library Oatmilk keeps for every organization: guides by topic on hiring, contractors, agreements, employment standards, payroll, privacy and taxes, each with its plain-words summary, key points, what it means for a company, status (Current, Needs a look or Out of date) and when its official sources were last checked. editable says whether this member keeps the library; only the platform's administrators also see what was recently added and suggested new entries. ## complianceLibrary.inputs - [`complianceLibrary.inputs.addUrl`](https://app.getoatmilk.com/docs/api/complianceLibrary.inputs.addUrl.md) — Platform administrators only: add an official government page to the library. An agent reads it, links it to the right entry or updates it with the page as its source. Pages that are not on an official government host are refused. The page is untrusted data. - [`complianceLibrary.inputs.retry`](https://app.getoatmilk.com/docs/api/complianceLibrary.inputs.retry.md) — Platform administrators only: read again a link or file that failed to be read. ## complianceLibrary.proposals - [`complianceLibrary.proposals.decide`](https://app.getoatmilk.com/docs/api/complianceLibrary.proposals.decide.md) — Platform administrators only: accept or dismiss a change an agent proposed to an entry a person edited, or a suggested new entry from an uploaded document. Accepting applies the before-and-after shown and its official sources. ## compliancePortal - [`compliancePortal.overview`](https://app.getoatmilk.com/docs/api/compliancePortal.overview.md) — The compliance portal's home for this member: topics with how many library guides each has, recently updated guides, rules from official sources coming into force soon, what waits for a decision (this organization's regulation updates for administrators; for the platform's administrators also changes only an Oatmilk update can follow and library changes), and the search index. platformAdmin says whether this member runs research for the platform. - [`compliancePortal.search`](https://app.getoatmilk.com/docs/api/compliancePortal.search.md) — Search the compliance portal in plain words: library guides, rules from official sources, decisions waiting for this member and topics. Word matching shortlists candidates and a fast classifier (Jev) ranks what the person means. Optional kinds (entry, rule, item, topic) narrows it, limit up to 20. Each hit has its kind, title, the passage that matched and how relevant the classifier judged it. ## complianceResearch.chat - [`complianceResearch.chat.ask`](https://app.getoatmilk.com/docs/api/complianceResearch.chat.ask.md) — Platform administrators only: ask the research agent about official tax sources, look up recent changes, or explicitly start the full compliance research flow. Ad-hoc answers do not change organization settings or tax calculations. - [`complianceResearch.chat.history`](https://app.getoatmilk.com/docs/api/complianceResearch.chat.history.md) — Platform administrators only: read your own recent compliance research conversation and cited official sources. ## complianceResearch.findings - [`complianceResearch.findings.update`](https://app.getoatmilk.com/docs/api/complianceResearch.findings.update.md) — Platform administrators only: mark a finding (usually a platform notice) acknowledged, done or dismissed. ## complianceResearch - [`complianceResearch.overview`](https://app.getoatmilk.com/docs/api/complianceResearch.overview.md) — Platform administrators only (the platform's own organization): the compliance research agent's schedule, official sources, memory of verified facts with their effective dates and sources, and findings, each an organization suggestion or a platform notice with its status and how organizations decided. - [`complianceResearch.run`](https://app.getoatmilk.com/docs/api/complianceResearch.run.md) — Platform administrators only: start a compliance research run now. It reads the official sources, remembers new facts, suggests setting changes to the organizations they apply to and tells platform administrators about changes only code can make. Returns the runId to follow with runs.get. ## complianceResearch.settings - [`complianceResearch.settings.update`](https://app.getoatmilk.com/docs/api/complianceResearch.settings.update.md) — Platform administrators only: turn scheduled compliance research on or off (enabled) and set how often it runs (intervalDays, 7 to 365; 30 by default). ## complianceResearch.sources - [`complianceResearch.sources.add`](https://app.getoatmilk.com/docs/api/complianceResearch.sources.add.md) — Add an https page on an official Canadian or U.S. federal, provincial, state or territorial government host, a label and its jurisdiction (CA, US, CA-ON or US-TX style). Facts found on an organization's own source are suggested to that organization only. - [`complianceResearch.sources.remove`](https://app.getoatmilk.com/docs/api/complianceResearch.sources.remove.md) — Stop reading one of this organization's compliance research sources. ## complianceResearch.suggestions - [`complianceResearch.suggestions.decide`](https://app.getoatmilk.com/docs/api/complianceResearch.suggestions.decide.md) — Decide a regulation update: accept applies it through the usual audited action (autopilot.settings.update, categories.addRecommended and categories.update, or tax.gifi.update); decline or keep leaves the setting as it is. - [`complianceResearch.suggestions.list`](https://app.getoatmilk.com/docs/api/complianceResearch.suggestions.list.md) — Regulation updates for this organization: open suggestions to change a setting (receipt minimum, recommended category, GIFI line), each with the current and suggested value, whether a person set the current value, and its official source, plus recent decisions and this organization's own sources. ## memory - [`memory.list`](https://app.getoatmilk.com/docs/api/memory.list.md) — List organization-wide remembered facts that guide future classification. - [`memory.save`](https://app.getoatmilk.com/docs/api/memory.save.md) — Create or revise an organization-wide remembered fact with revision and idempotency checks. Applies to future classification only. ## agents.overview List the platform's agents (Autopilot, receipt reader, matcher, mail router, inbox scanner, bank sync and others) with what each checks for, its triggers, schedule, models and safeguards, and its live status: running now with the current step, needs attention, scheduled with the next run, or idle, plus the last run and the 24-hour run count, success rate and median duration. Follow a run with runs.get. `GET | POST /api/v1/accounting/agents.overview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_agents_overview` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/agents.overview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/agents.overview ## agents.playground.match Dry run of the receipt matcher for one receipt entry (entryId): return the shortlisted bank and card candidates in ranked order and the matcher's judgement with the organization's saved model and thresholds. Nothing is saved or matched. `GET | POST /api/v1/accounting/agents.playground.match` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_agents_playground_match` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/agents.playground.match \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'entryId=7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10' ``` Reference page: https://app.getoatmilk.com/docs/api/agents.playground.match ## agents.playground.receipt Dry run of the receipt reader: read a sample receipt (filename, mimeType, contentBase64 of up to 2 MB; JPEG, PNG, WebP, HEIC, PDF or .eml) the same way intake does and return the extracted facts and validation flags. Nothing is saved: no submission, evidence, entry or run. `GET | POST /api/v1/accounting/agents.playground.receipt` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_agents_playground_receipt` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | string | Yes | The file's name, including its extension. 1–200 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `image/jpeg`, `image/png`, `image/webp`, `image/heic`, `image/heif`, `application/pdf`, `message/rfc822`. | | `contentBase64` | string | Yes | The file's exact bytes, encoded as base64. 4–2800000 characters; Matches ^[A-Za-z0-9+/]+={0,2}$. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/agents.playground.receipt \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'filename=receipt.jpg' \ --data-urlencode 'mimeType=image/jpeg' \ --data-urlencode 'contentBase64=contentBase64' ``` Reference page: https://app.getoatmilk.com/docs/api/agents.playground.receipt ## ai.keys.get Which AI providers this organization brought its own keys for (TypeSafe, Vercel AI Gateway, OpenAI, Google Gemini, Z.ai), each with the key's last four characters and when it was checked, never the key itself; whether Oatmilk's own AI credits are on for it; and whether its keys cover decisions (categorizing, matching, sorting) and reading documents. `GET | POST /api/v1/accounting/ai.keys.get` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_ai_keys_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/ai.keys.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/ai.keys.get ## ai.keys.remove Remove this organization's key for one AI provider, at the current revision. Features that need it ask for a key again unless Oatmilk's AI credits are on. Administrators only, from the dashboard. `POST /api/v1/accounting/ai.keys.remove` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` Not available over MCP: AI keys are added and removed where they're entered, in Settings › AI keys. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `provider` | enum | Yes | One of: `typesafe-ai`, `gateway`, `openai`, `google`, `zai`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/ai.keys.remove \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "provider": "typesafe-ai", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/ai.keys.remove ## ai.keys.save Save or replace this organization's key for one AI provider, at the current revision (0 for the first key). Oatmilk checks the key with the provider first and saves it encrypted. Administrators only, from the dashboard. `POST /api/v1/accounting/ai.keys.save` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` Not available over MCP: An AI provider key would pass through the agent. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `provider` | enum | Yes | One of: `typesafe-ai`, `gateway`, `openai`, `google`, `zai`. | | `apiKey` | string | Yes | 8–500 characters; Matches ^\S+$. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/ai.keys.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "provider": "typesafe-ai", "apiKey": "api_key", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/ai.keys.save ## ai.models.list List the decision models the receipt-matching and mail-routing classifiers can run on: AI Gateway's live list of evaluation models (id, name, provider, description, context window, input price per million tokens, whether the provider trains on the data, and whether Oatmilk has tested it), or the install's own model server when it runs local models. Falls back to Oatmilk's tested models when the gateway can't be reached. A model Oatmilk hasn't tested must pass ai.sandbox.evaluate before it is saved. `GET | POST /api/v1/accounting/ai.models.list` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_ai_models_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/ai.models.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/ai.models.list ## ai.sandbox.evaluate Evaluate a supplied fixture against saved or draft automation settings without writing anything. kind routing classifies an email fixture; kind matching scores a receipt against bank candidates; kind document reads a real file exactly as the inbox does (file {filename, mimeType, contentBase64}, up to 2 MB: a receipt, invoice or statement as a PDF, image, office or text document, or an original .eml email, which is classified first): every page is turned into images with logos and signature graphics set aside, read twice with vision and settled by the stronger model when the readings disagree, and checked as the books would take it. It returns the classifier answer, the facts, the vision check, the entry or flags for each document, and the parts read. kind record reads a kept-record file (file as for document, recordKind tax_document, company_document or other) exactly as Tax › Records reads one, with the same pages, model, prompt and schema, and returns its fields, warnings, model, prompt version and parts; for a company record also describesCompany, wouldFill (filled, nothing_new, other_company or needs_admin) and the profile fields it fills or differs on. kind email runs the email-sorting classifier on an email already in the company inbox (messageId), rebuilt from what was stored when it arrived, and returns the new decision with what was decided at the time (example.stored). kind receipt runs the matcher on a receipt entry (entryId) against its current bank and card candidates (draft may also set autoApply). Both accept the same draft model and thresholds as routing and matching. Nothing is saved. Use it to test the pipeline without the dashboard, and to test custom models before saving them. `GET | POST /api/v1/accounting/ai.sandbox.evaluate` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_ai_sandbox_evaluate` ### Fields #### kind: "routing" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | "routing" | Yes | Which kind of record or job this is. | | `mailboxKey` | string | | at most 40 characters. Default `"accounting"`. | | `fixture` | object | Yes | No other fields. | | `fixture.subject` | string | Yes | at most 2000 characters. | | `fixture.text` | string | Yes | at most 20000 characters. | | `fixture.senderDomain` | string | Yes | at most 200 characters. | | `fixture.attachmentNames` | array of strings | Yes | at most 30 items; each at most 300 characters. | | `useSavedConfig` | boolean | | Default `true`. | | `draft` | object | | No other fields. Default `{}`. | | `draft.model` | string | | 1–120 characters. | | `draft.matchThresholds` | object | | No other fields. | | `draft.matchThresholds.selectedProbability` | number | | 0 to 1. | | `draft.matchThresholds.providerConfidence` | number | | 0 to 1. | | `draft.matchThresholds.sameEventProbability` | number | | 0 to 1. | | `draft.matchThresholds.runnerUpMargin` | number | | 0 to 1. | | `draft.routingThresholds` | object | | No other fields. | | `draft.routingThresholds.probability` | number | | 0 to 1. | | `draft.routingThresholds.confidence` | number | | 0 to 1. | #### kind: "matching" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | "matching" | Yes | Which kind of record or job this is. | | `fixture` | object | Yes | No other fields. | | `fixture.receipt` | object | Yes | No other fields. | | `fixture.receipt.entryId` | string | Yes | The ID of an accounting entry, from entries.list or attention.mine. 1–100 characters. | | `fixture.receipt.revision` | integer | Yes | 0 to 9007199254740991. | | `fixture.receipt.date` | string | Yes | A date, as YYYY-MM-DD. at most 10 characters. | | `fixture.receipt.currency` | string | Yes | Three-letter currency code, such as CAD or USD. at most 3 characters. | | `fixture.receipt.amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. at most 30 characters. | | `fixture.receipt.type` | enum | Yes | One of: `expense`, `income`, `income_refund`, `refund`, `fee`, `transfer`, `suspense`. | | `fixture.receipt.merchant` | string | Yes | at most 300 characters. | | `fixture.receipt.description` | string | Yes | A short description. at most 2000 characters. | | `fixture.receipt.invoiceNumber` | string or null | | | | `fixture.receipt.sourceEvidenceIds` | array of strings | Yes | A list of record IDs. at most 20 items; each at most 100 characters. | | `fixture.receipt.paymentAccountId` | string or null | Yes | The card or account the purchase was paid with, from accounts.list. | | `fixture.receipt.allocatedMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. at most 30 characters. | | `fixture.receipt.split` | boolean | Yes | | | `fixture.receipt.closed` | boolean | Yes | | | `fixture.receipt.requiresReview` | boolean | Yes | | | `fixture.candidates` | array of objects | Yes | 1–5 items. | | `fixture.candidates[].transactionId` | string | Yes | The ID of a bank or card transaction, from transactions.list. 1–100 characters. | | `fixture.candidates[].revision` | integer | Yes | 0 to 9007199254740991. | | `fixture.candidates[].provider` | enum | Yes | One of: `wise`, `rbc`, `stripe`, `other`. | | `fixture.candidates[].accountId` | string | Yes | The ID of a bank, card or payment account, from accounts.list. 1–100 characters. | | `fixture.candidates[].date` | string | Yes | A date, as YYYY-MM-DD. at most 10 characters. | | `fixture.candidates[].currency` | string | Yes | Three-letter currency code, such as CAD or USD. at most 3 characters. | | `fixture.candidates[].amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. at most 30 characters. | | `fixture.candidates[].availableAmountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. at most 30 characters. | | `fixture.candidates[].description` | string | Yes | A short description. at most 2000 characters. | | `fixture.candidates[].reference` | string | Yes | at most 1000 characters. | | `fixture.candidates[].sourceEvidenceIds` | array of strings | Yes | A list of record IDs. at most 20 items; each at most 100 characters. | | `fixture.candidates[].availability` | enum | Yes | One of: `unallocated`, `bank_entry_without_receipt`, `partial`, `allocated`, `unavailable`. | | `fixture.candidates[].status` | enum | Yes | Only include records with this status. One of: `posted`, `pending`, `failed`. | | `fixture.candidates[].reversed` | boolean | Yes | | | `fixture.candidates[].closed` | boolean | Yes | | | `fixture.candidates[].transactionType` | enum | | One of: `payment`, `refund`, `transfer`, `fee`, `unknown`. | | `useSavedConfig` | boolean | | Default `true`. | | `draft` | object | | No other fields. Default `{}`. | | `draft.model` | string | | 1–120 characters. | | `draft.matchThresholds` | object | | No other fields. | | `draft.matchThresholds.selectedProbability` | number | | 0 to 1. | | `draft.matchThresholds.providerConfidence` | number | | 0 to 1. | | `draft.matchThresholds.sameEventProbability` | number | | 0 to 1. | | `draft.matchThresholds.runnerUpMargin` | number | | 0 to 1. | | `draft.routingThresholds` | object | | No other fields. | | `draft.routingThresholds.probability` | number | | 0 to 1. | | `draft.routingThresholds.confidence` | number | | 0 to 1. | #### kind: "document" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | "document" | Yes | Which kind of record or job this is. | | `file` | object | Yes | No other fields. | | `file.filename` | string | Yes | The file's name, including its extension. 1–200 characters. | | `file.mimeType` | string | Yes | The file's type, such as image/jpeg or application/pdf. 3–120 characters. | | `file.contentBase64` | string | Yes | The file's exact bytes, encoded as base64. 4–2900000 characters. | | `documentKind` | enum | | One of: `receipt`, `invoice`, `credit_note`, `statement`, `other`, `unknown`. | | `useSavedConfig` | boolean | | Default `true`. | | `draft` | object | | No other fields. Default `{}`. | | `draft.model` | string | | 1–120 characters. | | `draft.matchThresholds` | object | | No other fields. | | `draft.matchThresholds.selectedProbability` | number | | 0 to 1. | | `draft.matchThresholds.providerConfidence` | number | | 0 to 1. | | `draft.matchThresholds.sameEventProbability` | number | | 0 to 1. | | `draft.matchThresholds.runnerUpMargin` | number | | 0 to 1. | | `draft.routingThresholds` | object | | No other fields. | | `draft.routingThresholds.probability` | number | | 0 to 1. | | `draft.routingThresholds.confidence` | number | | 0 to 1. | #### kind: "record" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | "record" | Yes | Which kind of record or job this is. | | `recordKind` | enum | Yes | One of: `tax_document`, `company_document`, `other`. | | `file` | object | Yes | No other fields. | | `file.filename` | string | Yes | The file's name, including its extension. 1–200 characters. | | `file.mimeType` | string | Yes | The file's type, such as image/jpeg or application/pdf. 3–120 characters. | | `file.contentBase64` | string | Yes | The file's exact bytes, encoded as base64. 4–2900000 characters. | #### kind: "email" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | "email" | Yes | Which kind of record or job this is. | | `messageId` | string (ID) | Yes | The ID of the related record. | | `useSavedConfig` | boolean | | Default `true`. | | `draft` | object | | No other fields. Default `{}`. | | `draft.model` | string | | 1–120 characters. | | `draft.matchThresholds` | object | | No other fields. | | `draft.matchThresholds.selectedProbability` | number | | 0 to 1. | | `draft.matchThresholds.providerConfidence` | number | | 0 to 1. | | `draft.matchThresholds.sameEventProbability` | number | | 0 to 1. | | `draft.matchThresholds.runnerUpMargin` | number | | 0 to 1. | | `draft.routingThresholds` | object | | No other fields. | | `draft.routingThresholds.probability` | number | | 0 to 1. | | `draft.routingThresholds.confidence` | number | | 0 to 1. | #### kind: "receipt" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | "receipt" | Yes | Which kind of record or job this is. | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `useSavedConfig` | boolean | | Default `true`. | | `draft` | object | | No other fields. Default `{}`. | | `draft.model` | string | | 1–120 characters. | | `draft.matchThresholds` | object | | No other fields. | | `draft.matchThresholds.selectedProbability` | number | | 0 to 1. | | `draft.matchThresholds.providerConfidence` | number | | 0 to 1. | | `draft.matchThresholds.sameEventProbability` | number | | 0 to 1. | | `draft.matchThresholds.runnerUpMargin` | number | | 0 to 1. | | `draft.routingThresholds` | object | | No other fields. | | `draft.routingThresholds.probability` | number | | 0 to 1. | | `draft.routingThresholds.confidence` | number | | 0 to 1. | | `draft.autoApply` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/ai.sandbox.evaluate \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "kind": "routing", "fixture": { "subject": "example", "text": "example", "senderDomain": "example", "attachmentNames": [ "Synthetic Ventures Inc." ] } }' ``` Reference page: https://app.getoatmilk.com/docs/api/ai.sandbox.evaluate ## ai.settings.get Read the organization's effective matching and routing automation settings, including models, thresholds, and revision. `GET | POST /api/v1/accounting/ai.settings.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_ai_settings_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/ai.settings.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/ai.settings.get ## ai.settings.update Revise matching and routing models, thresholds, and automatic matching with revision and idempotency checks. Changes apply to future evaluations only; stored decisions keep their recorded provenance. `POST /api/v1/accounting/ai.settings.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_ai_settings_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `matchModel` | string | Yes | 1–120 characters. | | `matchThresholds` | object | Yes | No other fields. | | `matchThresholds.selectedProbability` | number | Yes | 0.9 to 1. | | `matchThresholds.providerConfidence` | number | Yes | 0.85 to 1. | | `matchThresholds.sameEventProbability` | number | Yes | 0.9 to 1. | | `matchThresholds.runnerUpMargin` | number | Yes | 0.1 to 1. | | `routingModel` | string | Yes | 1–120 characters. | | `routingThresholds` | object | Yes | No other fields. | | `routingThresholds.probability` | number | Yes | 0 to 1. | | `routingThresholds.confidence` | number | Yes | 0 to 1. | | `autoApply` | boolean | Yes | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/ai.settings.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "matchModel": "example", "matchThresholds": { "selectedProbability": 1, "providerConfidence": 1, "sameEventProbability": 1, "runnerUpMargin": 1 }, "routingModel": "example", "routingThresholds": { "probability": 1, "confidence": 1 }, "autoApply": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/ai.settings.update ## chiefOfStaff.channels.update Set rules for one Discord channel and its threads: how the Chief of Staff replies there (or that it stays out), its profile, tools turned on or off, and instructions. Pass reset to clear the channel's rules. Pass the channel's revision as expectedRevision (0 the first time). Only the platform owner's workspace has a Chief of Staff. Administrator access required. `POST /api/v1/accounting/chiefOfStaff.channels.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_chief_of_staff_channels_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `guildId` | string | Yes | Matches ^\d{17,20}$. | | `channelId` | string | Yes | Matches ^\d{17,20}$. | | `channelName` | string or null | | | | `mode` | enum | | inherit follows the server; conversation, mentions or off replace it here and in the channel's threads. One of: `inherit`, `conversation`, `mentions`, `off`. | | `profileId` | enum or null | | The profile for this channel. Null keeps the one its name picks. One of: `general`, `hackathon`, `organizer`, `news`. | | `allowTools` | array of enum values | | Tools turned on here on top of the profile's. One of: `getCurrentEventsFromLuma`, `getEventStatsFromLuma`, `getEventDetailsFromLuma`, `getEventGuestsFromLuma`, `getCommunityContacts`, `webSearch`, `scrapeUrl`, `github`, `weather`. at most 9 items. | | `blockTools` | array of enum values | | Tools turned off here. One of: `getCurrentEventsFromLuma`, `getEventStatsFromLuma`, `getEventDetailsFromLuma`, `getEventGuestsFromLuma`, `getCommunityContacts`, `webSearch`, `scrapeUrl`, `github`, `weather`. at most 9 items. | | `instructions` | string | | Extra instructions the Chief of Staff follows there, in plain words. Empty clears them. at most 2000 characters. | | `reset` | boolean | | Clear every setting for this channel so it follows the server again. | | `expectedRevision` | integer | | The revision you last read: 0 for something never saved. A different revision means someone else changed it first. 0 to 9007199254740990. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chiefOfStaff.channels.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "guildId": "11111111111111111", "channelId": "11111111111111111" }' ``` Reference page: https://app.getoatmilk.com/docs/api/chiefOfStaff.channels.update ## chiefOfStaff.general.update Change the Chief of Staff's settings that apply everywhere: pause it, turn direct messages on or off, turn tools off everywhere, or replace the list of people it never replies to. Takes effect on its next message. Pass expectedRevision from chiefOfStaff.settings.get. Only the platform owner's workspace has a Chief of Staff. Administrator access required. `POST /api/v1/accounting/chiefOfStaff.general.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_chief_of_staff_general_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `paused` | boolean | | Stop every reply and scheduled post until turned back on. Slash commands still answer. | | `directMessages` | enum | | One of: `on`, `off`. | | `disabledTools` | array of enum values | | Tools turned off everywhere, whatever else allows them. One of: `getCurrentEventsFromLuma`, `getEventStatsFromLuma`, `getEventDetailsFromLuma`, `getEventGuestsFromLuma`, `getCommunityContacts`, `webSearch`, `scrapeUrl`, `github`, `weather`. at most 9 items. | | `ignoredPeople` | array of objects | | People the Chief of Staff never replies to. Replaces the list. at most 200 items. | | `ignoredPeople[].userId` | string | Yes | The ID of a person in your company. Matches ^\d{17,20}$. | | `ignoredPeople[].name` | string or null | | A display name. | | `expectedRevision` | integer | | The revision you last read: 0 for something never saved. A different revision means someone else changed it first. 0 to 9007199254740990. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chiefOfStaff.general.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/chiefOfStaff.general.update ## chiefOfStaff.people.find Find people in the Chief of Staff's Discord server by Discord id or part of their name, to add them to a rule or the ignored list. Only the platform owner's workspace has a Chief of Staff. Finance access required. `GET | POST /api/v1/accounting/chiefOfStaff.people.find` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_chief_of_staff_people_find` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `guildId` | string | Yes | Matches ^\d{17,20}$. | | `query` | string | Yes | A Discord id, or part of a name or username. 1–100 characters. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/chiefOfStaff.people.find \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'guildId=11111111111111111' \ --data-urlencode 'query=office supplies' ``` Reference page: https://app.getoatmilk.com/docs/api/chiefOfStaff.people.find ## chiefOfStaff.rules.delete Remove a rule for people. Pass its revision as expectedRevision. History can put it back. Only the platform owner's workspace has a Chief of Staff. Administrator access required. `POST /api/v1/accounting/chiefOfStaff.rules.delete` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_chief_of_staff_rules_delete` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | | The revision you last read: 0 for something never saved. A different revision means someone else changed it first. 0 to 9007199254740990. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chiefOfStaff.rules.delete \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/chiefOfStaff.rules.delete ## chiefOfStaff.rules.save Add or change a rule for some people: by Discord person or role, optionally only in some channels, give them tools, take tools away, answer them with a profile, or add instructions. A tool turned off anywhere else stays off. Pass id and expectedRevision to change a rule. Only the platform owner's workspace has a Chief of Staff. Administrator access required. `POST /api/v1/accounting/chiefOfStaff.rules.save` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_chief_of_staff_rules_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The rule to change. Leave it out to add a rule. | | `name` | string | Yes | A display name. 1–100 characters. | | `enabled` | boolean | | | | `people` | array of objects | | Discord people the rule applies to, each with the name to show. at most 50 items. | | `people[].userId` | string | Yes | The ID of a person in your company. Matches ^\d{17,20}$. | | `people[].name` | string or null | | A display name. | | `roles` | array of strings | | Discord role names the rule applies to; anyone with one of them matches. at most 50 items; each 1–100 characters. | | `channelIds` | array of strings | | Only in these channels and their threads. Empty means everywhere. at most 50 items; each Matches ^\d{17,20}$. | | `allowTools` | array of enum values | | One of: `getCurrentEventsFromLuma`, `getEventStatsFromLuma`, `getEventDetailsFromLuma`, `getEventGuestsFromLuma`, `getCommunityContacts`, `webSearch`, `scrapeUrl`, `github`, `weather`. at most 9 items. | | `blockTools` | array of enum values | | One of: `getCurrentEventsFromLuma`, `getEventStatsFromLuma`, `getEventDetailsFromLuma`, `getEventGuestsFromLuma`, `getCommunityContacts`, `webSearch`, `scrapeUrl`, `github`, `weather`. at most 9 items. | | `profileId` | enum or null | | Answer these people with this profile. One of: `general`, `hackathon`, `organizer`, `news`. | | `instructions` | string | | Extra instructions the Chief of Staff follows there, in plain words. Empty clears them. at most 2000 characters. | | `expectedRevision` | integer | | The revision you last read: 0 for something never saved. A different revision means someone else changed it first. 0 to 9007199254740990. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chiefOfStaff.rules.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Synthetic Ventures Inc." }' ``` Reference page: https://app.getoatmilk.com/docs/api/chiefOfStaff.rules.save ## chiefOfStaff.schedules.update Pause or resume one of the Chief of Staff's scheduled posts. A paused post skips its turns and resumes at its next one, without catching up. Only the platform owner's workspace has a Chief of Staff. Administrator access required. `POST /api/v1/accounting/chiefOfStaff.schedules.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_chief_of_staff_schedules_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `jobId` | string | Yes | A scheduled post, from chiefOfStaff.status. Matches ^[a-z0-9][a-z0-9-]{0,63}$. | | `paused` | boolean | Yes | | | `expectedRevision` | integer | | The revision you last read: 0 for something never saved. A different revision means someone else changed it first. 0 to 9007199254740990. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chiefOfStaff.schedules.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "jobId": "example", "paused": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/chiefOfStaff.schedules.update ## chiefOfStaff.servers.list List the Discord servers the Chief of Staff can see, with each connected server's channels (grouped by category) and roles, read live from Discord. Pass refresh to skip the one-minute cache. Only the platform owner's workspace has a Chief of Staff. Finance access required. `GET | POST /api/v1/accounting/chiefOfStaff.servers.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_chief_of_staff_servers_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `refresh` | boolean | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chiefOfStaff.servers.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/chiefOfStaff.servers.list ## chiefOfStaff.servers.update Change how the Chief of Staff behaves in one Discord server: turn it on or off there, choose how it replies in channels that don't say otherwise, a default profile, and instructions for the whole server. Pass the server's revision as expectedRevision (0 the first time). Only the platform owner's workspace has a Chief of Staff. Administrator access required. `POST /api/v1/accounting/chiefOfStaff.servers.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_chief_of_staff_servers_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `guildId` | string | Yes | Matches ^\d{17,20}$. | | `name` | string or null | | A display name. | | `enabled` | boolean | | Off keeps the Chief of Staff out of the whole server: no replies and no scheduled posts. | | `replyMode` | enum | | How it replies in channels that don't say otherwise: conversation (answers mentions and replies and may join in on threads it follows), mentions (only when mentioned or replied to) or off. One of: `conversation`, `mentions`, `off`. | | `defaultProfileId` | enum or null | | The profile for channels whose name picks none. Null uses the agent's default (general). One of: `general`, `hackathon`, `organizer`, `news`. | | `instructions` | string | | Extra instructions the Chief of Staff follows there, in plain words. Empty clears them. at most 2000 characters. | | `expectedRevision` | integer | | The revision you last read: 0 for something never saved. A different revision means someone else changed it first. 0 to 9007199254740990. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chiefOfStaff.servers.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "guildId": "11111111111111111" }' ``` Reference page: https://app.getoatmilk.com/docs/api/chiefOfStaff.servers.update ## chiefOfStaff.settings.get Read every Chief of Staff setting with its revision: whether it is paused, direct messages, tools turned off everywhere, ignored people, each Discord server's and channel's rules (who it answers, profile, tools and instructions), rules for people and roles, and paused scheduled posts. Only the platform owner's workspace has a Chief of Staff. Finance access required. `GET | POST /api/v1/accounting/chiefOfStaff.settings.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_chief_of_staff_settings_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chiefOfStaff.settings.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/chiefOfStaff.settings.get ## chiefOfStaff.status Read how the Chief of Staff (the Oatmilk agent in Discord) is set up: the Discord server it serves, whether its Gateway and scheduled posts run on this deployment, its recent activity, which integrations are connected, its profiles and their tools, the channel names that pick a profile, and its scheduled posts. Only the platform owner's workspace has a Chief of Staff. Finance access required. `GET | POST /api/v1/accounting/chiefOfStaff.status` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_chief_of_staff_status` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/chiefOfStaff.status \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/chiefOfStaff.status ## classifier.mailboxes.list List organization email types, receiving addresses, and classifier guidance. `GET | POST /api/v1/accounting/classifier.mailboxes.list` Permissions: `accounting:read`, `mail:read` · Roles: admin, finance MCP tool: `accounting_classifier_mailboxes_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/classifier.mailboxes.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/classifier.mailboxes.list ## classifier.mailboxes.save Create a receiving email type or revise its routing and category classifier guidance with revision and idempotency checks. Credential screening and human review remain mandatory. `POST /api/v1/accounting/classifier.mailboxes.save` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_classifier_mailboxes_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `mailboxKey` | enum | | One of: `accounting`, `invoices`, `signatures`, `reminders`, `contractors`, `notifications`. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `displayName` | string | Yes | 1–100 characters. | | `routingGuidance` | string | Yes | at most 2000 characters. | | `categoryGuidance` | string | Yes | at most 2000 characters. | | `active` | boolean | | Whether the record is turned on. Default `true`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/classifier.mailboxes.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "displayName": "Synthetic Ventures Inc.", "routingGuidance": "example", "categoryGuidance": "example" }' ``` Reference page: https://app.getoatmilk.com/docs/api/classifier.mailboxes.save ## complianceLibrary.entries.create Platform administrators only: write a new compliance library entry (title, topic, and optional summary, keyPoints, meaning and markdown notes). A person's entry is protected: an agent's later change to it is proposed, never applied. `POST /api/v1/accounting/complianceLibrary.entries.create` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_compliance_library_entries_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `title` | string | Yes | A short title. 1–200 characters. | | `topic` | enum | Yes | One of: `hiring`, `contractors`, `agreements`, `employment`, `payroll`, `privacy`, `taxes`. | | `summary` | string | | at most 2000 characters. | | `keyPoints` | array of strings | | at most 20 items; each at most 400 characters. | | `meaning` | string | | at most 4000 characters. | | `notes` | string | | Notes kept with the record. at most 20000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceLibrary.entries.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "title": "Synthetic services agreement", "topic": "hiring" }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceLibrary.entries.create ## complianceLibrary.entries.update Platform administrators only: edit a compliance library entry's title, topic, summary, keyPoints, meaning or markdown notes at its current revision, archive or restore it (archived), or mark it looked at (resolved). Editing protects the entry from agent overwrites. `POST /api/v1/accounting/complianceLibrary.entries.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_compliance_library_entries_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `title` | string | | A short title. 1–200 characters. | | `topic` | enum | | One of: `hiring`, `contractors`, `agreements`, `employment`, `payroll`, `privacy`, `taxes`. | | `summary` | string | | at most 2000 characters. | | `keyPoints` | array of strings | | at most 20 items; each at most 400 characters. | | `meaning` | string | | at most 4000 characters. | | `notes` | string | | Notes kept with the record. at most 20000 characters. | | `archived` | boolean | | Whether the record is archived. | | `resolved` | boolean | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceLibrary.entries.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceLibrary.entries.update ## complianceLibrary.files.confirm Platform administrators only: confirm an uploaded library file. Oatmilk checks its size, hash and type, then an agent reads it as untrusted data, summarises it, links it to the right entry or suggests a new one, and flags entries it may affect. `POST /api/v1/accounting/complianceLibrary.files.confirm` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_compliance_library_files_confirm` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceLibrary.files.confirm \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceLibrary.files.confirm ## complianceLibrary.files.prepare Platform administrators only: prepare a private upload of a PDF, Word document, text file or picture (up to 25 MB) for the library. Returns a signed uploadUrl; then call complianceLibrary.files.confirm. `POST /api/v1/accounting/complianceLibrary.files.prepare` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_compliance_library_files_prepare` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | string | Yes | The file's name, including its extension. 1–255 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `application/pdf`, `image/png`, `image/jpeg`, `image/webp`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `text/plain`, `text/markdown`. | | `sizeBytes` | integer | Yes | The file's size in bytes. 1 to 26214400. | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `entryId` | string (ID) | | The ID of an accounting entry, from entries.list or attention.mine. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceLibrary.files.prepare \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "filename": "receipt.pdf", "mimeType": "application/pdf", "sizeBytes": 1, "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceLibrary.files.prepare ## complianceLibrary.get One compliance library guide by id or slug with its official sources and check dates and its history. The platform's administrators also see the changes waiting for a person and the links and files added to it. `GET | POST /api/v1/accounting/complianceLibrary.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_compliance_library_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `slug` | string | | Matches ^[a-z0-9]([a-z0-9-]{0,78}[a-z0-9])?$. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/complianceLibrary.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceLibrary.get ## complianceLibrary.inputs.addUrl Platform administrators only: add an official government page to the library. An agent reads it, links it to the right entry or updates it with the page as its source. Pages that are not on an official government host are refused. The page is untrusted data. `POST /api/v1/accounting/complianceLibrary.inputs.addUrl` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_compliance_library_inputs_add_url` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `url` | string (uri) | Yes | A full web address, starting with https://. at most 2000 characters. | | `entryId` | string (ID) | | The ID of an accounting entry, from entries.list or attention.mine. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceLibrary.inputs.addUrl \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "url": "https://example.com/webhooks/oatmilk" }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceLibrary.inputs.addUrl ## complianceLibrary.inputs.retry Platform administrators only: read again a link or file that failed to be read. `POST /api/v1/accounting/complianceLibrary.inputs.retry` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_compliance_library_inputs_retry` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceLibrary.inputs.retry \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceLibrary.inputs.retry ## complianceLibrary.list The compliance library Oatmilk keeps for every organization: guides by topic on hiring, contractors, agreements, employment standards, payroll, privacy and taxes, each with its plain-words summary, key points, what it means for a company, status (Current, Needs a look or Out of date) and when its official sources were last checked. editable says whether this member keeps the library; only the platform's administrators also see what was recently added and suggested new entries. `GET | POST /api/v1/accounting/complianceLibrary.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_compliance_library_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceLibrary.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/complianceLibrary.list ## complianceLibrary.proposals.decide Platform administrators only: accept or dismiss a change an agent proposed to an entry a person edited, or a suggested new entry from an uploaded document. Accepting applies the before-and-after shown and its official sources. `POST /api/v1/accounting/complianceLibrary.proposals.decide` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_compliance_library_proposals_decide` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `decision` | enum | Yes | What you decided. One of: `accept`, `dismiss`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceLibrary.proposals.decide \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "decision": "accept", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceLibrary.proposals.decide ## compliancePortal.overview The compliance portal's home for this member: topics with how many library guides each has, recently updated guides, rules from official sources coming into force soon, what waits for a decision (this organization's regulation updates for administrators; for the platform's administrators also changes only an Oatmilk update can follow and library changes), and the search index. platformAdmin says whether this member runs research for the platform. `GET | POST /api/v1/accounting/compliancePortal.overview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_compliance_portal_overview` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/compliancePortal.overview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/compliancePortal.overview ## compliancePortal.search Search the compliance portal in plain words: library guides, rules from official sources, decisions waiting for this member and topics. Word matching shortlists candidates and a fast classifier (Jev) ranks what the person means. Optional kinds (entry, rule, item, topic) narrows it, limit up to 20. Each hit has its kind, title, the passage that matched and how relevant the classifier judged it. `GET | POST /api/v1/accounting/compliancePortal.search` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_compliance_portal_search` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `query` | string | Yes | Text to search for. 2–500 characters. | | `limit` | integer | | How many results to return at most. 1 to 20. | | `kinds` | array of enum values | | One of: `entry`, `rule`, `item`, `topic`. at most 4 items. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/compliancePortal.search \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'query=office supplies' ``` Reference page: https://app.getoatmilk.com/docs/api/compliancePortal.search ## complianceResearch.chat.ask Platform administrators only: ask the research agent about official tax sources, look up recent changes, or explicitly start the full compliance research flow. Ad-hoc answers do not change organization settings or tax calculations. `POST /api/v1/accounting/complianceResearch.chat.ask` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_compliance_research_chat_ask` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `message` | string | Yes | 2–2000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceResearch.chat.ask \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "message": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceResearch.chat.ask ## complianceResearch.chat.history Platform administrators only: read your own recent compliance research conversation and cited official sources. `GET | POST /api/v1/accounting/complianceResearch.chat.history` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_compliance_research_chat_history` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceResearch.chat.history \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/complianceResearch.chat.history ## complianceResearch.findings.update Platform administrators only: mark a finding (usually a platform notice) acknowledged, done or dismissed. `POST /api/v1/accounting/complianceResearch.findings.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_compliance_research_findings_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `status` | enum | Yes | Only include records with this status. One of: `open`, `acknowledged`, `done`, `dismissed`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceResearch.findings.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "status": "open", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceResearch.findings.update ## complianceResearch.overview Platform administrators only (the platform's own organization): the compliance research agent's schedule, official sources, memory of verified facts with their effective dates and sources, and findings, each an organization suggestion or a platform notice with its status and how organizations decided. `GET | POST /api/v1/accounting/complianceResearch.overview` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_compliance_research_overview` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceResearch.overview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/complianceResearch.overview ## complianceResearch.run Platform administrators only: start a compliance research run now. It reads the official sources, remembers new facts, suggests setting changes to the organizations they apply to and tells platform administrators about changes only code can make. Returns the runId to follow with runs.get. `POST /api/v1/accounting/complianceResearch.run` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_compliance_research_run` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceResearch.run \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceResearch.run ## complianceResearch.settings.update Platform administrators only: turn scheduled compliance research on or off (enabled) and set how often it runs (intervalDays, 7 to 365; 30 by default). `POST /api/v1/accounting/complianceResearch.settings.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_compliance_research_settings_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `enabled` | boolean | | | | `intervalDays` | integer | | 7 to 365. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceResearch.settings.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceResearch.settings.update ## complianceResearch.sources.add Add an https page on an official Canadian or U.S. federal, provincial, state or territorial government host, a label and its jurisdiction (CA, US, CA-ON or US-TX style). Facts found on an organization's own source are suggested to that organization only. `POST /api/v1/accounting/complianceResearch.sources.add` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_compliance_research_sources_add` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `url` | string (uri) | Yes | A full web address, starting with https://. at most 2000 characters. | | `label` | string | Yes | 1–120 characters. | | `jurisdiction` | string | Yes | Matches ^(CA\|US\|CA-[A-Z]{2}\|US-[A-Z]{2})$. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceResearch.sources.add \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "url": "https://example.com/webhooks/oatmilk", "label": "example", "jurisdiction": "CA" }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceResearch.sources.add ## complianceResearch.sources.remove Stop reading one of this organization's compliance research sources. `POST /api/v1/accounting/complianceResearch.sources.remove` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_compliance_research_sources_remove` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceResearch.sources.remove \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceResearch.sources.remove ## complianceResearch.suggestions.decide Decide a regulation update: accept applies it through the usual audited action (autopilot.settings.update, categories.addRecommended and categories.update, or tax.gifi.update); decline or keep leaves the setting as it is. `POST /api/v1/accounting/complianceResearch.suggestions.decide` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_compliance_research_suggestions_decide` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `decision` | enum | Yes | What you decided. One of: `accept`, `decline`, `keep`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceResearch.suggestions.decide \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "decision": "accept", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/complianceResearch.suggestions.decide ## complianceResearch.suggestions.list Regulation updates for this organization: open suggestions to change a setting (receipt minimum, recommended category, GIFI line), each with the current and suggested value, whether a person set the current value, and its official source, plus recent decisions and this organization's own sources. `GET | POST /api/v1/accounting/complianceResearch.suggestions.list` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_compliance_research_suggestions_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/complianceResearch.suggestions.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/complianceResearch.suggestions.list ## memory.list List organization-wide remembered facts that guide future classification. `GET | POST /api/v1/accounting/memory.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_memory_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/memory.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/memory.list ## memory.save Create or revise an organization-wide remembered fact with revision and idempotency checks. Applies to future classification only. `POST /api/v1/accounting/memory.save` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_memory_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `title` | string | Yes | A short title. 1–120 characters. | | `content` | string | Yes | 1–2000 characters. | | `active` | boolean | | Whether the record is turned on. Default `true`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/memory.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "title": "Synthetic services agreement", "content": "example" }' ``` Reference page: https://app.getoatmilk.com/docs/api/memory.save # Group: Administration > Company settings, people, API keys, email, runs and suggestions. MCP toolset: `admin` (https://app.getoatmilk.com/api/mcp?toolset=admin) ## accountant.access - [`accountant.access.get`](https://app.getoatmilk.com/docs/api/accountant.access.get.md) — Accountants only: read your access to this organization, its end date and days left, your permissions and your requests. ## accountant.changes - [`accountant.changes.list`](https://app.getoatmilk.com/docs/api/accountant.changes.list.md) — Accountants only: read the changes you asked the company to make and what was decided, newest first. Who decided is not shown. - [`accountant.changes.request`](https://app.getoatmilk.com/docs/api/accountant.changes.request.md) — Accountants with corrections access only: ask the company to attach an existing receipt to a bank record (attach_receipt with entryId and receiptEntryId), match a receipt to a same-currency bank payment (settle_match with entryId and transactionId), or check which contractor a payment belongs to (contractor_attribution with contractorId and transactionId), with a reason and idempotency key. Nothing changes until a company administrator approves it; payouts, agreements and settings are never offered. ## accountant.comments - [`accountant.comments.create`](https://app.getoatmilk.com/docs/api/accountant.comments.create.md) — Accountants with comment access only: ask a question on one transaction (entry) or receipt with targetType, targetId, body and an idempotency key. The company is emailed a short grouped summary. - [`accountant.comments.list`](https://app.getoatmilk.com/docs/api/accountant.comments.list.md) — Accountants with comment access only: read the questions and replies on one record (targetType and targetId), or the most recent ones on any record. ## accountant.requests - [`accountant.requests.create`](https://app.getoatmilk.com/docs/api/accountant.requests.create.md) — Accountants only: ask for an extra permission with scope and reason, or for more time with kind more_time, an optional proposedUntil date and reason. Requires an idempotency key. ## accountant.slips - [`accountant.slips.readiness`](https://app.getoatmilk.com/docs/api/accountant.slips.readiness.md) — Accountants only: contractor reporting readiness for one calendarYear, selected by the company's formation country. Canadian organizations show the indicated T4A/T4A-NR checklist; U.S. organizations show a review checklist and do not infer 1099 applicability from payment totals. The response includes fees, masked Canadian and U.S. taxpayer numbers, self-reported W-9/W-8 selection and missing company follow-up. Mailing addresses and full numbers are never included. ## accountant.tax - [`accountant.tax.request`](https://app.getoatmilk.com/docs/api/accountant.tax.request.md) — Accountants only: ask the company to collect or confirm missing tax details for one contractor and calendar year. The request contains missing field names only, never tax number values, and goes to the company question queue. ## accountant.welcome - [`accountant.welcome.update`](https://app.getoatmilk.com/docs/api/accountant.welcome.update.md) — Accountants only: save where you stopped in the welcome steps (see, first, ask, done), and whether you skipped or finished it. ## accountants.access - [`accountants.access.update`](https://app.getoatmilk.com/docs/api/accountants.access.update.md) — Extend, shorten or clear an accountant's access end date and, optionally, change their access level to read, comment or prepare (exactly that preset, nothing more), with userId, expiresOn (YYYY-MM-DD or null) and/or preset, expectedRevision and idempotencyKey. Extra permissions never outlast the end date. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. ## accountants - [`accountants.activity`](https://app.getoatmilk.com/docs/api/accountants.activity.md) — Read an accountant's retained activity timeline (sign-ins, downloads, exports, requests, decisions, end-date changes and removal), newest first. Supply userId and optionally beforeId. - [`accountants.invite`](https://app.getoatmilk.com/docs/api/accountants.invite.md) — Invite an accountant by email with an access level (preset read, comment or prepare), an access end date (or null for no end date), optional name, firm and message, and an idempotency key. Returns the invitation and a private join link. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. - [`accountants.inviteMany`](https://app.getoatmilk.com/docs/api/accountants.inviteMany.md) — Invite up to 10 people from one accounting firm in one go. Each person has their own email, name, access level (preset) and end date; the firm name and message are shared. Returns which invitations were sent and which failed, each with its private join link. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. - [`accountants.list`](https://app.getoatmilk.com/docs/api/accountants.list.md) — List active and former accountants with their access, end dates and days left, plus pending invitations and access requests. Invitation links are never included. - [`accountants.remove`](https://app.getoatmilk.com/docs/api/accountants.remove.md) — Remove an accountant now: ends access, revokes extra permissions and open requests, and removes the organization membership. Their history is kept. Requires userId, expectedRevision and idempotencyKey. ## accountants.comments - [`accountants.comments.list`](https://app.getoatmilk.com/docs/api/accountants.comments.list.md) — List accountants' questions on transactions and receipts with the replies to each, newest first. Filter by status (active, open, answered, resolved, all) or by one record with targetType and targetId. Contributors never see these. - [`accountants.comments.reply`](https://app.getoatmilk.com/docs/api/accountants.comments.reply.md) — Reply to an accountant's question with id, body and idempotencyKey. The accountant sees the reply and gets a short email. - [`accountants.comments.resolve`](https://app.getoatmilk.com/docs/api/accountants.comments.resolve.md) — Mark an accountant's question resolved with id, expectedRevision and idempotencyKey. ## accountants.grants - [`accountants.grants.revoke`](https://app.getoatmilk.com/docs/api/accountants.grants.revoke.md) — Revoke an extra permission granted to an accountant, with id, expectedRevision and idempotencyKey. ## accountants.invitations - [`accountants.invitations.link`](https://app.getoatmilk.com/docs/api/accountants.invitations.link.md) — Return the private join link of a pending invitation again so it can be shared in your own message. Audited. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. - [`accountants.invitations.preview`](https://app.getoatmilk.com/docs/api/accountants.invitations.preview.md) — Preview the invitation email (subject, HTML and plain text) for an access level, end date, name and message before sending it. Sends nothing and contains no private link. - [`accountants.invitations.resend`](https://app.getoatmilk.com/docs/api/accountants.invitations.resend.md) — Email a pending accountant invitation again and restart its 14-day link window, with expectedRevision and idempotencyKey. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. - [`accountants.invitations.revoke`](https://app.getoatmilk.com/docs/api/accountants.invitations.revoke.md) — Revoke a pending accountant invitation so its link stops working, with expectedRevision and idempotencyKey. - [`accountants.invitations.update`](https://app.getoatmilk.com/docs/api/accountants.invitations.update.md) — Change a pending or expired accountant invitation: access level, end date, name, firm or message, with id, expectedRevision and idempotencyKey. Correcting the email address cancels the old link and sends a new invitation. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. ## accountants.requests - [`accountants.requests.decide`](https://app.getoatmilk.com/docs/api/accountants.requests.decide.md) — Approve or deny an accountant's access or more-time request with id, decision, optional expiresInDays (30, 90 or null), accessExpiresOn for more-time approvals, note, expectedRevision and idempotencyKey. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. ## api_keys - [`api_keys.create`](https://app.getoatmilk.com/docs/api/api_keys.create.md) — Create a scoped expiring API key within your current role and credential ceiling. The secret is returned once. - [`api_keys.list`](https://app.getoatmilk.com/docs/api/api_keys.list.md) — List metadata for your API keys. Secret values are never returned. - [`api_keys.revoke`](https://app.getoatmilk.com/docs/api/api_keys.revoke.md) — Revoke your API key; administrators may revoke organization keys. - [`api_keys.rotate`](https://app.getoatmilk.com/docs/api/api_keys.rotate.md) — Replace your API key while preserving scope ceilings and revoking the original. ## company - [`company.close`](https://app.getoatmilk.com/docs/api/company.close.md) — Permanently close the company: delete its stored files, every record it has in Oatmilk, and the company itself with its memberships. It can't be undone; export the data first with company.export. confirmName must be the company's name (case doesn't matter). Only an administrator, with their own credential (the web app, or the terminal app or an API key), can close it; AI apps and Ask AI can't. The installation's own company can't be closed. Returns the counts of rows and files deleted and whether the company's sign-in organization was deleted. - [`company.export`](https://app.getoatmilk.com/docs/api/company.export.md) — Export all of the company's data as one ZIP: manifest.json, data/.json with every row the company has in every table, files// with its stored files (receipts, emails, statements, agreements, invoices) and a README. Secret values (encrypted credentials and bank or tax details, sealed keys, one-time codes, private link tokens) are replaced with "[redacted]" and listed in redactedColumns. One download holds at most 50 MB: the records come first and files fill the rest; files that don't fit are listed with included false, and company.export.files gives a link for each. includeFiles false leaves files out (default true). Returns a link valid for 5 minutes, the counts of tables, rows and files, and how many files were skipped. Administrators only. - [`company.get`](https://app.getoatmilk.com/docs/api/company.get.md) — Read company identity, tax registrations, filing settings and source provenance. GST/HST dates require profile.gstHst.registered, frequency and yearEnd; null means not configured, not a permission failure. warnings identifies conflicting saved corporate fiscal year-ends that need administrator confirmation. - [`company.update`](https://app.getoatmilk.com/docs/api/company.update.md) — Update company metadata and tax settings with the current revision and an idempotency key. Corporate annual returns remain distinct from tax periods. ## company.export - [`company.export.files`](https://app.getoatmilk.com/docs/api/company.export.files.md) — List the company's stored files (all but its earlier company exports) with offset and limit (1 to 100, default 50), each with its bucket, path, size and a download link valid for 5 minutes, so a client can download every file, including those too large for company.export. Returns items, total and nextOffset (null on the last page). Administrators only. ## developers.requests - [`developers.requests.list`](https://app.getoatmilk.com/docs/api/developers.requests.list.md) — List recent API and MCP requests, newest first, with the request ID returned in X-Request-Id, the action, method, status, error code, duration and the key or app that sent it. Filter by status (ok, client_error, server_error), source (api, mcp), key, action or request ID. Kept for 30 days. Administrators see the whole company; everyone else sees their own requests. ## developers - [`developers.usage`](https://app.getoatmilk.com/docs/api/developers.usage.md) — Summarize API and MCP requests for the last 1, 7 or 30 days: totals, client and server errors, median and 95th percentile response times, requests per day, the busiest keys and apps, the busiest actions and the most common error codes. Administrators see the whole company; everyone else sees requests made with their own keys and apps. Inputs and responses are never recorded. ## email.domain - [`email.domain.activate`](https://app.getoatmilk.com/docs/api/email.domain.activate.md) — Use a custom domain only after its sending and receiving DNS records are verified. - [`email.domain.configure`](https://app.getoatmilk.com/docs/api/email.domain.configure.md) — Start verification of an organization-owned custom sending and receiving domain. - [`email.domain.status`](https://app.getoatmilk.com/docs/api/email.domain.status.md) — Inspect the organization's custom email domain and the provider's public DNS records. - [`email.domain.usePlatform`](https://app.getoatmilk.com/docs/api/email.domain.usePlatform.md) — Return new outbound and receiving addresses to the default Oatmilk domains. - [`email.domain.verify`](https://app.getoatmilk.com/docs/api/email.domain.verify.md) — Ask the email provider to verify the current custom domain's DNS records. ## email.outbox - [`email.outbox.list`](https://app.getoatmilk.com/docs/api/email.outbox.list.md) — List outbound platform emails with their sender identity, recipients, subject, delivery state, attempts, and related record. ## email - [`email.status`](https://app.getoatmilk.com/docs/api/email.status.md) — Read outbound email readiness: the sending domain, each sender identity address, and whether delivery is enabled in this environment. - [`email.test`](https://app.getoatmilk.com/docs/api/email.test.md) — Queue a test email from one sender identity to the requesting administrator to verify outbound delivery. ## experiments - [`experiments.get`](https://app.getoatmilk.com/docs/api/experiments.get.md) — Read the experimental features the signed-in person can try in this workspace (such as RTS mode, which shows the workspace as a 3D strategy game), whether they turned experimental mode on, which experiments they turned on, and the revision. Each person chooses for themselves; the platform decides which experiments are offered. - [`experiments.update`](https://app.getoatmilk.com/docs/api/experiments.update.md) — Turn experimental mode on or off for the signed-in person (mode), or turn one experiment on or off (experiment with enabled). Only an experiment offered to this workspace can be turned on. Pass expectedRevision from experiments.get and an idempotencyKey. It never changes anyone else's choice. ## identity - [`identity.get`](https://app.getoatmilk.com/docs/api/identity.get.md) — Read the authenticated user, organization, whether it is a company or their personal workspace (workspaceKind), role and granted permissions without exposing credentials. ## members - [`members.list`](https://app.getoatmilk.com/docs/api/members.list.md) — Read organization accounting membership and roles. - [`members.update`](https://app.getoatmilk.com/docs/api/members.update.md) — Manage organization accounting roles and active access. ## notifications.checklist - [`notifications.checklist.dismiss`](https://app.getoatmilk.com/docs/api/notifications.checklist.dismiss.md) — Mark a snooze reminder as seen for the signed-in member only. Requires an itemId and idempotencyKey. Repeated dismissal changes nothing. - [`notifications.checklist.list`](https://app.getoatmilk.com/docs/api/notifications.checklist.list.md) — List the signed-in member's unseen reminders that a snoozed checklist item returned. Only their own open items in this organization, at most ten from the past 30 days. ## notifications.comments - [`notifications.comments.dismiss`](https://app.getoatmilk.com/docs/api/notifications.comments.dismiss.md) — Mark one of the signed-in member's document comment notifications as read. Requires an idempotencyKey; other members' notifications cannot be changed. - [`notifications.comments.list`](https://app.getoatmilk.com/docs/api/notifications.comments.list.md) — List the signed-in member's unread mentions and replies on organization documents, with a direct link to the comment thread. ## notifications.joins - [`notifications.joins.dismiss`](https://app.getoatmilk.com/docs/api/notifications.joins.dismiss.md) — Mark one join notice as seen for the signed-in member only. Requires an idempotencyKey. Dismissing twice, or a notice that isn't theirs, changes nothing. - [`notifications.joins.list`](https://app.getoatmilk.com/docs/api/notifications.joins.list.md) — List the signed-in member's unseen notices that someone joined by an invitation (a contractor, an outside accountant or a team member): who joined, who invited them and a link to the person. Only the member's own notices, from the last 30 days, at most 10. ## notifications.preferences - [`notifications.preferences.get`](https://app.getoatmilk.com/docs/api/notifications.preferences.get.md) — Read the signed-in member's choice to get join notices by email and in Oatmilk. Both are on until they turn them off. - [`notifications.preferences.update`](https://app.getoatmilk.com/docs/api/notifications.preferences.update.md) — Turn the signed-in member's join notices on or off, by email and in Oatmilk. Requires an idempotencyKey. It never changes another member's choice. ## onboarding - [`onboarding.complete`](https://app.getoatmilk.com/docs/api/onboarding.complete.md) — Finish or skip onboarding for the workspace with an idempotencyKey (skipped true when skipping). Finishing again keeps the first time. - [`onboarding.inboxPrompt`](https://app.getoatmilk.com/docs/api/onboarding.inboxPrompt.md) — Whether the signed-in member should see the optional read-only Gmail or Outlook connection suggestion, and which providers are configured. A connected inbox or this member's Not now answer hides it. - [`onboarding.status`](https://app.getoatmilk.com/docs/api/onboarding.status.md) — Read a new workspace's setup: whether onboarding is finished, the receipt address, its kind (company or personal), and which steps are done (company details, a bank account or connection, a first receipt or connected inbox, a teammate, and AI keys that cover decisions or Oatmilk's AI credits; a personal workspace has only the bank, receipt and AI steps). ## onboarding.inboxPrompt - [`onboarding.inboxPrompt.dismiss`](https://app.getoatmilk.com/docs/api/onboarding.inboxPrompt.dismiss.md) — Remember Not now for the signed-in member's optional inbox connection suggestion in this organization. Requires an idempotencyKey; it never changes another member's preference or any mailbox setting. ## platform.admin - [`platform.admin.accessRequests.list`](https://app.getoatmilk.com/docs/api/platform.admin.accessRequests.list.md) — Platform administrators only: people who asked for access on the waitlist, newest first, with what they told us (email, name, company, role, size, website, kinds of financial data, banks, interests, notes) and the request's status and revision. status filters pending (default), approved, declined or all. - [`platform.admin.accessRequests.review`](https://app.getoatmilk.com/docs/api/platform.admin.accessRequests.review.md) — Platform administrators only: approve or decline a waitlist request at its current revision. Approving emails the person a link to set up their workspace; platformAi lets their company use Oatmilk's AI credits instead of only its own keys. Declining sends nothing, and a declined request can be approved later. - [`platform.admin.experiments.list`](https://app.getoatmilk.com/docs/api/platform.admin.experiments.list.md) — Platform administrators only: every experimental feature (such as RTS mode), who it is offered to (off, team for the platform's own workspace, or everyone), its Vercel flag key and that flag's value in Vercel Flags when the deployment is connected, plus the deployment-wide experimental-mode flag. People still turn each experiment on for themselves in Settings › Experimental. - [`platform.admin.experiments.update`](https://app.getoatmilk.com/docs/api/platform.admin.experiments.update.md) — Platform administrators only: offer one experiment to nobody (off), only the platform's own workspace (team) or every workspace (everyone). A Vercel flag set to off in Vercel Flags still keeps it off. Turning it off stops it at once for everyone who had it on. Pass expectedRevision from platform.admin.experiments.list. - [`platform.admin.organizations.get`](https://app.getoatmilk.com/docs/api/platform.admin.organizations.get.md) — Platform administrators only: one workspace in God Mode with aggregate numbers only: its counts, its access (approved or paused, AI credits), which AI providers it brought keys for (never the keys), AI usage by feature and model, what kinds of actions it took in the period with how many of each, and the waitlist request it came from. - [`platform.admin.organizations.update`](https://app.getoatmilk.com/docs/api/platform.admin.organizations.update.md) — Platform administrators only: pause or resume a workspace (status approved or suspended) and turn Oatmilk's AI credits on or off for it (platformAi), at its current revision (0 for a workspace with no access record yet). The platform's own organization can't be changed. - [`platform.admin.overview`](https://app.getoatmilk.com/docs/api/platform.admin.overview.md) — Platform administrators only (the platform's own organization): God Mode's overview. Signup mode, totals (workspaces, new ones, members, sign-ups, waiting requests, AI requests and cost) and every workspace with aggregate numbers only: members, transactions and receipts brought in, agreements, contractors, invoices, actions in the period, last activity, whose AI keys it uses and its AI usage and cost (AI Gateway's spend report when available, otherwise Oatmilk's own count). Never records, people or contents. days (1 to 90, default 30) sets the period. ## platform.branding - [`platform.branding.confirmLogo`](https://app.getoatmilk.com/docs/api/platform.branding.confirmLogo.md) — Verify an uploaded logo's bytes and hash, then make it the organization letterhead logo. - [`platform.branding.logo`](https://app.getoatmilk.com/docs/api/platform.branding.logo.md) — Get a short-lived link to the current organization logo. - [`platform.branding.prepareLogo`](https://app.getoatmilk.com/docs/api/platform.branding.prepareLogo.md) — Prepare a private upload for the organization logo used on letterheads, agreements, and invoices. PNG or JPEG up to 2 MB. ## platform.settings - [`platform.settings.get`](https://app.getoatmilk.com/docs/api/platform.settings.get.md) — Read workspace preferences for invoicing numbers and defaults, document branding, compliance reminders, and the inbox AI models: which model reads each email and which gives the second opinion, and at what effort. - [`platform.settings.update`](https://app.getoatmilk.com/docs/api/platform.settings.update.md) — Update invoicing numbering and defaults, letterhead text, compliance reminder preferences, and the inbox AI models, with a revision check. inboxAi.reading reads each email and its attachments into a draft; inboxAi.checking is the second opinion on anything left unclear. Each takes a model, one of openai/gpt-6.1-sol (GPT-6.1 Sol), openai/gpt-6-luna (GPT-6 Luna), google/gemini-3.8-flash (Gemini 3.8 Flash), zai/glm-5.3-flash (GLM-5.3 Flash), and a low, medium or high effort; null puts that step back on the recommended GPT-6 Luna at medium effort for reading and GPT-6 Luna at medium for the second opinion. ## proposals - [`proposals.create`](https://app.getoatmilk.com/docs/api/proposals.create.md) — Suggest a change to a record for someone to review. Nothing changes until the suggestion is approved. A newer suggestion for the same record and field replaces the older one. Supply subjectType, subjectId, field, proposedValue, reasoning, optional evidence, and idempotencyKey. - [`proposals.decide`](https://app.getoatmilk.com/docs/api/proposals.decide.md) — Decline a suggested change, or approve it when a person is signed in to Oatmilk. API keys and agents can decline but can't approve; approving through them is refused with "Approve suggestions in Oatmilk." Approving applies the change through the normal audited action for that record, such as recording an invoice payment or recategorizing an entry. A partly paid suggestion without an amount needs paidAmountMinor. A failed or stale approval can be approved again or declined. Requires expectedRevision and idempotencyKey; an optional note is kept with the decision. - [`proposals.get`](https://app.getoatmilk.com/docs/api/proposals.get.md) — Read one suggested change with its reasoning, evidence links, and decision history. - [`proposals.list`](https://app.getoatmilk.com/docs/api/proposals.list.md) — List suggested changes to invoices, entries, mail, accounts, and Notion links, with the current and proposed values, plain-language reasoning, evidence, whether the evidence is newer or older, and status. Defaults to suggestions waiting for review. ## runs - [`runs.get`](https://app.getoatmilk.com/docs/api/runs.get.md) — Read one run, or the latest run for a subject, with its ordered step events: steps started and finished, model calls, outputs, decisions, and errors. Use afterEventId to fetch only new events while a run is active. - [`runs.list`](https://app.getoatmilk.com/docs/api/runs.list.md) — List AI and automation runs with their current step, status, and summary. Filter by kind (or a comma-separated kinds list), subject, or status. Contributors see only runs for their own submissions. - [`runs.trace`](https://app.getoatmilk.com/docs/api/runs.trace.md) — Read an authorized process trace with its stage timeline, logs, model metadata, and linked Workflow SDK run, steps, and events. Restricted mail remains visible only to a security administrator. ## runs.workflows - [`runs.workflows.catalog`](https://app.getoatmilk.com/docs/api/runs.workflows.catalog.md) — List organization-owned Workflow SDK jobs and indicate which job sources are unavailable while a database update is pending. - [`runs.workflows.get`](https://app.getoatmilk.com/docs/api/runs.workflows.get.md) — Inspect an organization-owned Workflow SDK run with its steps, events, statuses, and redacted input and output. - [`runs.workflows.list`](https://app.getoatmilk.com/docs/api/runs.workflows.list.md) — List organization-owned Workflow SDK jobs for receipts, mail, matching, Stripe, and accounting digest replies. ## senders - [`senders.list`](https://app.getoatmilk.com/docs/api/senders.list.md) — Read exact approved employee sending addresses. - [`senders.remove`](https://app.getoatmilk.com/docs/api/senders.remove.md) — Remove an approved sending address. - [`senders.upsert`](https://app.getoatmilk.com/docs/api/senders.upsert.md) — Authorize an exact employee sending address with active membership. ## settings - [`settings.get`](https://app.getoatmilk.com/docs/api/settings.get.md) — Read accounting fiscal settings and integration configuration. Administrator access required. - [`settings.rotateMailbox`](https://app.getoatmilk.com/docs/api/settings.rotateMailbox.md) — Rotate the randomly generated receiving alias with revision checks. - [`settings.update`](https://app.getoatmilk.com/docs/api/settings.update.md) — Update fiscal and bank configuration with revision checks. ## team.invitations - [`team.invitations.link`](https://app.getoatmilk.com/docs/api/team.invitations.link.md) — Get the private link of a pending team invitation, to share it another way. Only organization administrators can manage the team. MCP calls also require the accounting:admin scope; the direct API cannot send team invitations or remove members. - [`team.invitations.resend`](https://app.getoatmilk.com/docs/api/team.invitations.resend.md) — Email a pending team invitation again and give it another 14 days, with id, expectedRevision and idempotencyKey. Only organization administrators can manage the team. MCP calls also require the accounting:admin scope; the direct API cannot send team invitations or remove members. - [`team.invitations.revoke`](https://app.getoatmilk.com/docs/api/team.invitations.revoke.md) — Cancel a pending team invitation so its link stops working, with source (oatmilk, or clerk for an invitation sent before Oatmilk sent its own), id, expectedRevision for an Oatmilk invitation, and idempotencyKey. Only organization administrators can manage the team. MCP calls also require the accounting:admin scope; the direct API cannot send team invitations or remove members. ## team - [`team.invite`](https://app.getoatmilk.com/docs/api/team.invite.md) — Invite a new team member by email with a role (admin, finance or contributor), an optional message and an idempotency key. Oatmilk emails them a private link when email is enabled; the result always includes a link to share and a delivery status. The link works for 14 days. Only organization administrators can manage the team. MCP calls also require the accounting:admin scope; the direct API cannot send team invitations or remove members. - [`team.list`](https://app.getoatmilk.com/docs/api/team.list.md) — List the team: everyone in the company's organization with their role and whether they can open Oatmilk, plus pending team invitations. Invitation links are never included. ## team.members - [`team.members.remove`](https://app.getoatmilk.com/docs/api/team.members.remove.md) — Remove someone from the company: turns off their Oatmilk access and removes them from the organization. Their records stay. You can't remove yourself. Requires userId and idempotencyKey. Only organization administrators can manage the team. MCP calls also require the accounting:admin scope; the direct API cannot send team invitations or remove members. ## accountant.access.get Accountants only: read your access to this organization, its end date and days left, your permissions and your requests. `GET | POST /api/v1/accounting/accountant.access.get` Permissions: `accounting:read` · Roles: MCP tool: `accounting_accountant_access_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountant.access.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/accountant.access.get ## accountant.changes.list Accountants only: read the changes you asked the company to make and what was decided, newest first. Who decided is not shown. `GET | POST /api/v1/accounting/accountant.changes.list` Permissions: `accounting:read` · Roles: MCP tool: `accounting_accountant_changes_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | enum | | Only include records with this status. One of: `pending`, `all`. Default `"all"`. | | `limit` | integer | | How many results to return at most. 1 to 50. Default `20`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountant.changes.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/accountant.changes.list ## accountant.changes.request Accountants with corrections access only: ask the company to attach an existing receipt to a bank record (attach_receipt with entryId and receiptEntryId), match a receipt to a same-currency bank payment (settle_match with entryId and transactionId), or check which contractor a payment belongs to (contractor_attribution with contractorId and transactionId), with a reason and idempotency key. Nothing changes until a company administrator approves it; payouts, agreements and settings are never offered. `POST /api/v1/accounting/accountant.changes.request` Permissions: `accounting:read`, `accounting:write` · Roles: · Idempotency key required Not available over MCP: Accountants' corrections grant isn't offered over MCP yet: phase 1 is read and comment only. ### Fields #### kind: "attach_receipt" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | "attach_receipt" | Yes | Which kind of record or job this is. | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `receiptEntryId` | string (ID) | Yes | The ID of the related record. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | #### kind: "settle_match" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | "settle_match" | Yes | Which kind of record or job this is. | | `entryId` | string (ID) | Yes | The ID of an accounting entry, from entries.list or attention.mine. | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | #### kind: "contractor_attribution" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | "contractor_attribution" | Yes | Which kind of record or job this is. | | `contractorId` | string | Yes | The ID of a contractor, from contractors.list. 1–200 characters. | | `transactionId` | string (ID) | Yes | The ID of a bank or card transaction, from transactions.list. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 10–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountant.changes.request \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "kind": "attach_receipt", "entryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "receiptEntryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountant.changes.request ## accountant.comments.create Accountants with comment access only: ask a question on one transaction (entry) or receipt with targetType, targetId, body and an idempotency key. The company is emailed a short grouped summary. `POST /api/v1/accounting/accountant.comments.create` Permissions: `accounting:read`, `accounting:write` · Roles: · Idempotency key required MCP tool: `accounting_accountant_comments_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `targetType` | enum | Yes | One of: `entry`, `receipt`. | | `targetId` | string (ID) | Yes | The ID of the related record. | | `body` | string | Yes | 1–2000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountant.comments.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "targetType": "entry", "targetId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "body": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountant.comments.create ## accountant.comments.list Accountants with comment access only: read the questions and replies on one record (targetType and targetId), or the most recent ones on any record. `GET | POST /api/v1/accounting/accountant.comments.list` Permissions: `accounting:read` · Roles: MCP tool: `accounting_accountant_comments_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `targetType` | enum | | One of: `entry`, `receipt`, `contractor`. | | `targetId` | string (ID) | | The ID of the related record. | | `limit` | integer | | How many results to return at most. 1 to 50. Default `50`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountant.comments.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/accountant.comments.list ## accountant.requests.create Accountants only: ask for an extra permission with scope and reason, or for more time with kind more_time, an optional proposedUntil date and reason. Requires an idempotency key. `POST /api/v1/accounting/accountant.requests.create` Permissions: `accounting:read`, `accounting:write` · Roles: · Idempotency key required Not available over MCP: Asking for more access and the welcome tour happen in the accountant's dashboard. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | enum | | Which kind of record or job this is. One of: `scope`, `more_time`. Default `"scope"`. | | `scope` | enum | | One of: `comment`, `corrections`, `periods`, `documents`, `email`. | | `proposedUntil` | string | | | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountant.requests.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "reason": "Synthetic example from the docs", "scope": "comment" }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountant.requests.create ## accountant.slips.readiness Accountants only: contractor reporting readiness for one calendarYear, selected by the company's formation country. Canadian organizations show the indicated T4A/T4A-NR checklist; U.S. organizations show a review checklist and do not infer 1099 applicability from payment totals. The response includes fees, masked Canadian and U.S. taxpayer numbers, self-reported W-9/W-8 selection and missing company follow-up. Mailing addresses and full numbers are never included. `GET | POST /api/v1/accounting/accountant.slips.readiness` Permissions: `accounting:read` · Roles: MCP tool: `accounting_accountant_slips_readiness` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `calendarYear` | integer | Yes | A calendar year, such as 2026. 2000 to 2100. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/accountant.slips.readiness \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'calendarYear=2026' ``` Reference page: https://app.getoatmilk.com/docs/api/accountant.slips.readiness ## accountant.tax.request Accountants only: ask the company to collect or confirm missing tax details for one contractor and calendar year. The request contains missing field names only, never tax number values, and goes to the company question queue. `POST /api/v1/accounting/accountant.tax.request` Permissions: `accounting:read` · Roles: · Idempotency key required MCP tool: `accounting_accountant_tax_request` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `contractorId` | string (ID) | Yes | The ID of a contractor, from contractors.list. | | `calendarYear` | integer | Yes | A calendar year, such as 2026. 2000 to 2100. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountant.tax.request \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "contractorId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "calendarYear": 2026 }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountant.tax.request ## accountant.welcome.update Accountants only: save where you stopped in the welcome steps (see, first, ask, done), and whether you skipped or finished it. `POST /api/v1/accounting/accountant.welcome.update` Permissions: `accounting:read`, `accounting:write` · Roles: Not available over MCP: Asking for more access and the welcome tour happen in the accountant's dashboard. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `step` | enum | Yes | One of: `see`, `first`, `ask`, `done`. | | `outcome` | enum | | One of: `skipped`, `finished`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountant.welcome.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "step": "see" }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountant.welcome.update ## accountants.access.update Extend, shorten or clear an accountant's access end date and, optionally, change their access level to read, comment or prepare (exactly that preset, nothing more), with userId, expiresOn (YYYY-MM-DD or null) and/or preset, expectedRevision and idempotencyKey. Extra permissions never outlast the end date. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. `POST /api/v1/accounting/accountants.access.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_accountants_access_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `userId` | string | Yes | The ID of a person in your company. 1–200 characters. | | `expiresOn` | string or null | | | | `preset` | enum | | One of: `read`, `comment`, `prepare`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.access.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "userId": "example", "expectedRevision": 3, "expiresOn": "2026-09-30" }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.access.update ## accountants.activity Read an accountant's retained activity timeline (sign-ins, downloads, exports, requests, decisions, end-date changes and removal), newest first. Supply userId and optionally beforeId. `GET | POST /api/v1/accounting/accountants.activity` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_accountants_activity` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `userId` | string | Yes | The ID of a person in your company. 1–200 characters. | | `beforeId` | integer | | at most 9007199254740991; greater than 0. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/accountants.activity \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'userId=example' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.activity ## accountants.comments.list List accountants' questions on transactions and receipts with the replies to each, newest first. Filter by status (active, open, answered, resolved, all) or by one record with targetType and targetId. Contributors never see these. `GET | POST /api/v1/accounting/accountants.comments.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_accountants_comments_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | enum | | Only include records with this status. One of: `active`, `open`, `answered`, `resolved`, `all`. Default `"active"`. | | `targetType` | enum | | One of: `entry`, `receipt`, `contractor`. | | `targetId` | string (ID) | | The ID of the related record. | | `limit` | integer | | How many results to return at most. 1 to 50. Default `50`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.comments.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.comments.list ## accountants.comments.reply Reply to an accountant's question with id, body and idempotencyKey. The accountant sees the reply and gets a short email. `POST /api/v1/accounting/accountants.comments.reply` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_accountants_comments_reply` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `body` | string | Yes | 1–2000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.comments.reply \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "body": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.comments.reply ## accountants.comments.resolve Mark an accountant's question resolved with id, expectedRevision and idempotencyKey. `POST /api/v1/accounting/accountants.comments.resolve` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_accountants_comments_resolve` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.comments.resolve \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.comments.resolve ## accountants.grants.revoke Revoke an extra permission granted to an accountant, with id, expectedRevision and idempotencyKey. `POST /api/v1/accounting/accountants.grants.revoke` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_accountants_grants_revoke` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `reason` | string | | A short note saying why, kept in the record's history. at most 500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.grants.revoke \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.grants.revoke ## accountants.invitations.link Return the private join link of a pending invitation again so it can be shared in your own message. Audited. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. `POST /api/v1/accounting/accountants.invitations.link` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin MCP tool: `accounting_accountants_invitations_link` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.invitations.link \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.invitations.link ## accountants.invitations.preview Preview the invitation email (subject, HTML and plain text) for an access level, end date, name and message before sending it. Sends nothing and contains no private link. `GET | POST /api/v1/accounting/accountants.invitations.preview` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_accountants_invitations_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `email` | string | | An email address. at most 320 characters. | | `name` | string | | A display name. at most 200 characters. | | `firm` | string | | at most 200 characters. | | `message` | string | | at most 2000 characters. | | `preset` | enum | | One of: `read`, `comment`, `prepare`. Default `"read"`. | | `accessExpiresOn` | string or null | | Default `null`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.invitations.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.invitations.preview ## accountants.invitations.resend Email a pending accountant invitation again and restart its 14-day link window, with expectedRevision and idempotencyKey. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. `POST /api/v1/accounting/accountants.invitations.resend` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_accountants_invitations_resend` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.invitations.resend \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.invitations.resend ## accountants.invitations.revoke Revoke a pending accountant invitation so its link stops working, with expectedRevision and idempotencyKey. `POST /api/v1/accounting/accountants.invitations.revoke` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_accountants_invitations_revoke` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.invitations.revoke \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.invitations.revoke ## accountants.invitations.update Change a pending or expired accountant invitation: access level, end date, name, firm or message, with id, expectedRevision and idempotencyKey. Correcting the email address cancels the old link and sends a new invitation. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. `POST /api/v1/accounting/accountants.invitations.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_accountants_invitations_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `email` | string (email) | | An email address. at most 320 characters. | | `preset` | enum | | One of: `read`, `comment`, `prepare`. | | `accessExpiresOn` | string or null | | | | `name` | string or null | | A display name. | | `firm` | string or null | | | | `message` | string or null | | | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.invitations.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "email": "finance@example.com" }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.invitations.update ## accountants.invite Invite an accountant by email with an access level (preset read, comment or prepare), an access end date (or null for no end date), optional name, firm and message, and an idempotency key. Returns the invitation and a private join link. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. `POST /api/v1/accounting/accountants.invite` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_accountants_invite` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `email` | string (email) | Yes | An email address. at most 320 characters. | | `accessExpiresOn` | string or null | Yes | | | `name` | string | | A display name. at most 200 characters. | | `firm` | string | | at most 200 characters. | | `message` | string | | at most 2000 characters. | | `preset` | enum | | One of: `read`, `comment`, `prepare`. Default `"read"`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.invite \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "email": "finance@example.com", "accessExpiresOn": "2026-09-30" }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.invite ## accountants.inviteMany Invite up to 10 people from one accounting firm in one go. Each person has their own email, name, access level (preset) and end date; the firm name and message are shared. Returns which invitations were sent and which failed, each with its private join link. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. `POST /api/v1/accounting/accountants.inviteMany` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_accountants_invite_many` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `firm` | string | | at most 200 characters. | | `message` | string | | at most 2000 characters. | | `people` | array of objects | Yes | 1–10 items. | | `people[].email` | string (email) | Yes | An email address. at most 320 characters. | | `people[].name` | string | | A display name. at most 200 characters. | | `people[].preset` | enum | | One of: `read`, `comment`, `prepare`. Default `"read"`. | | `people[].accessExpiresOn` | string or null | Yes | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–150 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.inviteMany \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "people": [ { "email": "finance@example.com", "accessExpiresOn": "2026-09-30" } ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.inviteMany ## accountants.list List active and former accountants with their access, end dates and days left, plus pending invitations and access requests. Invitation links are never included. `GET | POST /api/v1/accounting/accountants.list` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_accountants_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.list ## accountants.remove Remove an accountant now: ends access, revokes extra permissions and open requests, and removes the organization membership. Their history is kept. Requires userId, expectedRevision and idempotencyKey. `POST /api/v1/accounting/accountants.remove` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_accountants_remove` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `userId` | string | Yes | The ID of a person in your company. 1–200 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `reason` | string | | A short note saying why, kept in the record's history. at most 500 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.remove \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "userId": "example", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.remove ## accountants.requests.decide Approve or deny an accountant's access or more-time request with id, decision, optional expiresInDays (30, 90 or null), accessExpiresOn for more-time approvals, note, expectedRevision and idempotencyKey. Administrator access is required. MCP calls also require accounting:admin; the web agent asks for approval before changing outside access. Direct API requests cannot perform these access-granting actions. `POST /api/v1/accounting/accountants.requests.decide` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_accountants_requests_decide` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `decision` | enum | Yes | What you decided. One of: `approve`, `deny`. | | `expiresInDays` | 30 or 90 or null | | | | `accessExpiresOn` | string or null | | | | `note` | string | | A short note, kept with the record. at most 1000 characters. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/accountants.requests.decide \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "decision": "approve", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/accountants.requests.decide ## api_keys.create Create a scoped expiring API key within your current role and credential ceiling. The secret is returned once. `POST /api/v1/accounting/api_keys.create` Permissions: `api_keys:manage` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_api_keys_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | Yes | A display name. 1–100 characters; Matches ^[^\u0000-\u001f\u007f]+$. | | `scopes` | array of enum values | Yes | What the API key may do. A key can never do more than the person who made it. One of: `accounting:read`, `accounting:write`, `accounting:admin`, `mail:read`, `mail:write`, `mail:security`, `api_keys:manage`, `mailboxes:search`. 1–8 items. | | `expiresAt` | string (date-time) | | When this stops working, as an ISO 8601 date and time. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/api_keys.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "name", "scopes": [ "accounting:read" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/api_keys.create ## api_keys.list List metadata for your API keys. Secret values are never returned. `GET | POST /api/v1/accounting/api_keys.list` Permissions: `api_keys:manage` · Roles: admin, finance, contributor MCP tool: `accounting_api_keys_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `all` | boolean | | | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 9007199254740991. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/api_keys.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/api_keys.list ## api_keys.revoke Revoke your API key; administrators may revoke organization keys. `POST /api/v1/accounting/api_keys.revoke` Permissions: `api_keys:manage` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_api_keys_revoke` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/api_keys.revoke \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/api_keys.revoke ## api_keys.rotate Replace your API key while preserving scope ceilings and revoking the original. `POST /api/v1/accounting/api_keys.rotate` Permissions: `api_keys:manage` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_api_keys_rotate` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–160 characters. | | `name` | string | | A display name. 1–100 characters; Matches ^[^\u0000-\u001f\u007f]+$. | | `scopes` | array of enum values | | What the API key may do. A key can never do more than the person who made it. One of: `accounting:read`, `accounting:write`, `accounting:admin`, `mail:read`, `mail:write`, `mail:security`, `api_keys:manage`, `mailboxes:search`. 1–8 items. | | `expiresAt` | string (date-time) | | When this stops working, as an ISO 8601 date and time. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/api_keys.rotate \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/api_keys.rotate ## company.close Permanently close the company: delete its stored files, every record it has in Oatmilk, and the company itself with its memberships. It can't be undone; export the data first with company.export. confirmName must be the company's name (case doesn't matter). Only an administrator, with their own credential (the web app, or the terminal app or an API key), can close it; AI apps and Ask AI can't. The installation's own company can't be closed. Returns the counts of rows and files deleted and whether the company's sign-in organization was deleted. `POST /api/v1/accounting/company.close` Permissions: `accounting:write`, `accounting:admin` · Roles: admin · Destructive: confirm with a person first Not available over MCP: Permanently deletes the company; a person closes it in Oatmilk's Settings or the terminal app. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `confirmName` | string | Yes | 1–300 characters. | | `idempotencyKey` | string | | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/company.close \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "confirmName": "Synthetic Ventures Inc." }' ``` Reference page: https://app.getoatmilk.com/docs/api/company.close ## company.export Export all of the company's data as one ZIP: manifest.json, data/
.json with every row the company has in every table, files// with its stored files (receipts, emails, statements, agreements, invoices) and a README. Secret values (encrypted credentials and bank or tax details, sealed keys, one-time codes, private link tokens) are replaced with "[redacted]" and listed in redactedColumns. One download holds at most 50 MB: the records come first and files fill the rest; files that don't fit are listed with included false, and company.export.files gives a link for each. includeFiles false leaves files out (default true). Returns a link valid for 5 minutes, the counts of tables, rows and files, and how many files were skipped. Administrators only. `POST /api/v1/accounting/company.export` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_company_export` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `includeFiles` | boolean | | Also include files. Default `true`. | | `idempotencyKey` | string | | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/company.export \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/company.export ## company.export.files List the company's stored files (all but its earlier company exports) with offset and limit (1 to 100, default 50), each with its bucket, path, size and a download link valid for 5 minutes, so a client can download every file, including those too large for company.export. Returns items, total and nextOffset (null on the last page). Administrators only. `GET | POST /api/v1/accounting/company.export.files` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_company_export_files` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 10000000. Default `0`. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `50`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/company.export.files \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/company.export.files ## company.get Read company identity, tax registrations, filing settings and source provenance. GST/HST dates require profile.gstHst.registered, frequency and yearEnd; null means not configured, not a permission failure. warnings identifies conflicting saved corporate fiscal year-ends that need administrator confirmation. `GET | POST /api/v1/accounting/company.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_company_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/company.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/company.get ## company.update Update company metadata and tax settings with the current revision and an idempotency key. Corporate annual returns remain distinct from tax periods. `POST /api/v1/accounting/company.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_company_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `profile` | object | Yes | No other fields. | | `profile.legalName` | string or null | | Default `null`. | | `profile.tradingName` | string or null | | Default `null`. | | `profile.businessNumber` | string or null | | Default `null`. | | `profile.gstHstAccount` | string or null | | Default `null`. | | `profile.corporationNumber` | string or null | | Default `null`. | | `profile.provincialCorporationNumber` | string or null | | Default `null`. | | `profile.incorporationDate` | string or null | | Default `null`. | | `profile.jurisdiction` | string or null | | Default `null`. | | `profile.entity` | object | | No other fields. Default `{"country":null,"formationJurisdiction":null,"otherJurisdictionName":null,"legalForm":null,"usFederalTaxClassification":null,"taxResidenceCountry":null,"operatingJurisdictions":[],"registrations":[]}`. | | `profile.entity.country` | enum or null | | One of: `CA`, `US`, `OTHER`. Default `null`. | | `profile.entity.formationJurisdiction` | string or null | | Default `null`. | | `profile.entity.otherJurisdictionName` | string or null | | Default `null`. | | `profile.entity.legalForm` | enum or null | | One of: `corporation`, `llc`, `partnership`, `sole_proprietorship`, `nonprofit`, `cooperative`, `other`, `not_sure`. Default `null`. | | `profile.entity.usFederalTaxClassification` | enum or null | | One of: `c_corporation`, `s_corporation`, `partnership`, `disregarded_entity`, `tax_exempt`, `other`, `not_sure`. Default `null`. | | `profile.entity.taxResidenceCountry` | string or null | | Default `null`. | | `profile.entity.operatingJurisdictions` | array of objects | | at most 100 items. Default `[]`. | | `profile.entity.operatingJurisdictions[].jurisdictionCode` | string | Yes | 2–20 characters. | | `profile.entity.operatingJurisdictions[].activities` | array of enum values | Yes | One of: `registered_office`, `employees`, `contractors`, `sales`, `property`, `other`. 1–6 items. | | `profile.entity.operatingJurisdictions[].status` | enum | Yes | Only include records with this status. One of: `registered`, `assessing`, `not_registered`. | | `profile.entity.registrations` | array of objects | | at most 200 items. Default `[]`. | | `profile.entity.registrations[].id` | string (ID) | Yes | The record's ID. | | `profile.entity.registrations[].jurisdictionCode` | string | Yes | 2–20 characters. | | `profile.entity.registrations[].program` | enum | Yes | One of: `business_registration`, `foreign_qualification`, `tax_identification`, `corporate_income_tax`, `sales_tax`, `payroll`, `information_returns`, `annual_report`, `other`. | | `profile.entity.registrations[].status` | enum | Yes | Only include records with this status. One of: `registered`, `not_registered`, `review_needed`. | | `profile.entity.registrations[].registrationNumber` | string or null | | Default `null`. | | `profile.entity.registrations[].effectiveDate` | string or null | | Default `null`. | | `profile.entity.registrations[].filingFrequency` | enum or null | | One of: `annual`, `quarterly`, `monthly`. Default `null`. | | `profile.entity.registrations[].nextDueDate` | string or null | | Default `null`. | | `profile.entity.registrations[].sourceDocumentId` | string (ID) or null | | Default `null`. | | `profile.address` | object or null | | Default `null`. | | `profile.address.line1` | string | Yes | at most 500 characters. | | `profile.address.line2` | string | | at most 500 characters. Default `""`. | | `profile.address.city` | string | Yes | at most 500 characters. | | `profile.address.province` | string | Yes | at most 500 characters. | | `profile.address.postalCode` | string | Yes | at most 500 characters. | | `profile.address.country` | string | | Two-letter country or province code. Default `"CA"`. | | `profile.corporateAnnualReturnMonthDay` | string or null | | Default `null`. | | `profile.fiscalYearEnd` | string or null | | Default `null`. | | `profile.bankHistoryStart` | string or null | | Default `null`. | | `profile.gstHst` | object | | No other fields. Default `{"registered":null,"effectiveDate":null,"frequency":null,"yearEnd":null,"method":null}`. | | `profile.gstHst.registered` | boolean or null | | Default `null`. | | `profile.gstHst.effectiveDate` | string or null | | Default `null`. | | `profile.gstHst.frequency` | enum or null | | One of: `annual`, `quarterly`, `monthly`. Default `null`. | | `profile.gstHst.yearEnd` | string or null | | Default `null`. | | `profile.gstHst.method` | enum or null | | One of: `regular`, `quick`, `special`. Default `null`. | | `profile.t2PaymentMonths` | 2 or 3 or null | | Default `null`. | | `profile.t2ThreeMonthEligibilitySource` | string or null | | Default `null`. | | `profile.metadata` | map | | Default `{}`. | | `profile.sources` | map | | Default `{}`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/company.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3, "profile": {} }' ``` Reference page: https://app.getoatmilk.com/docs/api/company.update ## developers.requests.list List recent API and MCP requests, newest first, with the request ID returned in X-Request-Id, the action, method, status, error code, duration and the key or app that sent it. Filter by status (ok, client_error, server_error), source (api, mcp), key, action or request ID. Kept for 30 days. Administrators see the whole company; everyone else sees their own requests. `GET | POST /api/v1/accounting/developers.requests.list` Permissions: `api_keys:manage` · Roles: admin, finance, contributor MCP tool: `accounting_developers_requests_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | enum | | Only include records with this status. One of: `ok`, `client_error`, `server_error`. | | `source` | enum | | Where the record came from. One of: `api`, `mcp`. | | `credentialId` | string (ID) | | The ID of the related record. | | `action` | string | | Matches ^[A-Za-z][A-Za-z0-9_.]{0,119}$. | | `requestId` | string (ID) | | The ID of the related record. | | `days` | integer | | 1 to 30. Default `30`. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `25`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 10000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/developers.requests.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/developers.requests.list ## developers.usage Summarize API and MCP requests for the last 1, 7 or 30 days: totals, client and server errors, median and 95th percentile response times, requests per day, the busiest keys and apps, the busiest actions and the most common error codes. Administrators see the whole company; everyone else sees requests made with their own keys and apps. Inputs and responses are never recorded. `GET | POST /api/v1/accounting/developers.usage` Permissions: `api_keys:manage` · Roles: admin, finance, contributor MCP tool: `accounting_developers_usage` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `days` | integer | | -9007199254740991 to 9007199254740991. Default `7`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/developers.usage \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/developers.usage ## email.domain.activate Use a custom domain only after its sending and receiving DNS records are verified. `POST /api/v1/accounting/email.domain.activate` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_email_domain_activate` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/email.domain.activate \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/email.domain.activate ## email.domain.configure Start verification of an organization-owned custom sending and receiving domain. `POST /api/v1/accounting/email.domain.configure` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_email_domain_configure` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `domain` | string | Yes | 4–253 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/email.domain.configure \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "domain": "example" }' ``` Reference page: https://app.getoatmilk.com/docs/api/email.domain.configure ## email.domain.status Inspect the organization's custom email domain and the provider's public DNS records. `GET | POST /api/v1/accounting/email.domain.status` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_email_domain_status` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/email.domain.status \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/email.domain.status ## email.domain.usePlatform Return new outbound and receiving addresses to the default Oatmilk domains. `POST /api/v1/accounting/email.domain.usePlatform` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_email_domain_use_platform` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/email.domain.usePlatform \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/email.domain.usePlatform ## email.domain.verify Ask the email provider to verify the current custom domain's DNS records. `POST /api/v1/accounting/email.domain.verify` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_email_domain_verify` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/email.domain.verify \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/email.domain.verify ## email.outbox.list List outbound platform emails with their sender identity, recipients, subject, delivery state, attempts, and related record. `GET | POST /api/v1/accounting/email.outbox.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_email_outbox_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `relatedType` | string | | Matches ^[a-z][a-z0-9_]{1,40}$. | | `relatedId` | string | | 1–200 characters. | | `kind` | string | | Which kind of record or job this is. Matches ^[a-z][a-z0-9_.]{1,60}$. | | `status` | enum | | Only include records with this status. One of: `queued`, `processing`, `sent`, `failed`, `cancelled`. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/email.outbox.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/email.outbox.list ## email.status Read outbound email readiness: the sending domain, each sender identity address, and whether delivery is enabled in this environment. `GET | POST /api/v1/accounting/email.status` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_email_status` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/email.status \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/email.status ## email.test Queue a test email from one sender identity to the requesting administrator to verify outbound delivery. `POST /api/v1/accounting/email.test` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_email_test` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `identity` | enum | Yes | One of: `accounting`, `invoices`, `signatures`, `reminders`, `contractors`, `notifications`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/email.test \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "identity": "accounting" }' ``` Reference page: https://app.getoatmilk.com/docs/api/email.test ## experiments.get Read the experimental features the signed-in person can try in this workspace (such as RTS mode, which shows the workspace as a 3D strategy game), whether they turned experimental mode on, which experiments they turned on, and the revision. Each person chooses for themselves; the platform decides which experiments are offered. `GET | POST /api/v1/accounting/experiments.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_experiments_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/experiments.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/experiments.get ## experiments.update Turn experimental mode on or off for the signed-in person (mode), or turn one experiment on or off (experiment with enabled). Only an experiment offered to this workspace can be turned on. Pass expectedRevision from experiments.get and an idempotencyKey. It never changes anyone else's choice. `POST /api/v1/accounting/experiments.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_experiments_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `mode` | boolean | | Experimental mode for the signed-in person. Off stops every experiment for them without forgetting which ones they turned on. | | `experiment` | enum | | The experiment to turn on or off. One of: `rts-mode`. | | `enabled` | boolean | | On or off for the experiment named in experiment. | | `expectedRevision` | integer | | The revision you last read. A different revision means the choices changed first. 0 to 9007199254740990. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/experiments.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "mode": true }' ``` Reference page: https://app.getoatmilk.com/docs/api/experiments.update ## identity.get Read the authenticated user, organization, whether it is a company or their personal workspace (workspaceKind), role and granted permissions without exposing credentials. `GET | POST /api/v1/accounting/identity.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_identity_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/identity.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/identity.get ## members.list Read organization accounting membership and roles. `GET | POST /api/v1/accounting/members.list` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_members_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/members.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/members.list ## members.update Manage organization accounting roles and active access. `POST /api/v1/accounting/members.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_members_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `userId` | string | Yes | The ID of a person in your company. 1–200 characters. | | `email` | string (email) | Yes | An email address. | | `role` | enum | Yes | A person's access level in the company. One of: `admin`, `finance`, `contributor`. | | `active` | boolean | | Whether the record is turned on. Default `true`. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/members.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "userId": "example", "email": "finance@example.com", "role": "admin" }' ``` Reference page: https://app.getoatmilk.com/docs/api/members.update ## notifications.checklist.dismiss Mark a snooze reminder as seen for the signed-in member only. Requires an itemId and idempotencyKey. Repeated dismissal changes nothing. `POST /api/v1/accounting/notifications.checklist.dismiss` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_notifications_checklist_dismiss` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `itemId` | string (ID) | Yes | The ID of the related record. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notifications.checklist.dismiss \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "itemId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/notifications.checklist.dismiss ## notifications.checklist.list List the signed-in member's unseen reminders that a snoozed checklist item returned. Only their own open items in this organization, at most ten from the past 30 days. `GET | POST /api/v1/accounting/notifications.checklist.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_notifications_checklist_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notifications.checklist.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/notifications.checklist.list ## notifications.comments.dismiss Mark one of the signed-in member's document comment notifications as read. Requires an idempotencyKey; other members' notifications cannot be changed. `POST /api/v1/accounting/notifications.comments.dismiss` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_notifications_comments_dismiss` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notifications.comments.dismiss \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/notifications.comments.dismiss ## notifications.comments.list List the signed-in member's unread mentions and replies on organization documents, with a direct link to the comment thread. `GET | POST /api/v1/accounting/notifications.comments.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_notifications_comments_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notifications.comments.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/notifications.comments.list ## notifications.joins.dismiss Mark one join notice as seen for the signed-in member only. Requires an idempotencyKey. Dismissing twice, or a notice that isn't theirs, changes nothing. `POST /api/v1/accounting/notifications.joins.dismiss` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_notifications_joins_dismiss` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notifications.joins.dismiss \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/notifications.joins.dismiss ## notifications.joins.list List the signed-in member's unseen notices that someone joined by an invitation (a contractor, an outside accountant or a team member): who joined, who invited them and a link to the person. Only the member's own notices, from the last 30 days, at most 10. `GET | POST /api/v1/accounting/notifications.joins.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_notifications_joins_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notifications.joins.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/notifications.joins.list ## notifications.preferences.get Read the signed-in member's choice to get join notices by email and in Oatmilk. Both are on until they turn them off. `GET | POST /api/v1/accounting/notifications.preferences.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_notifications_preferences_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notifications.preferences.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/notifications.preferences.get ## notifications.preferences.update Turn the signed-in member's join notices on or off, by email and in Oatmilk. Requires an idempotencyKey. It never changes another member's choice. `POST /api/v1/accounting/notifications.preferences.update` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_notifications_preferences_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `joins` | object | Yes | No other fields. | | `joins.email` | boolean | | An email address. | | `joins.inApp` | boolean | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/notifications.preferences.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "joins": { "email": true } }' ``` Reference page: https://app.getoatmilk.com/docs/api/notifications.preferences.update ## onboarding.complete Finish or skip onboarding for the workspace with an idempotencyKey (skipped true when skipping). Finishing again keeps the first time. `POST /api/v1/accounting/onboarding.complete` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_onboarding_complete` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `skipped` | boolean | | Default `false`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/onboarding.complete \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/onboarding.complete ## onboarding.inboxPrompt Whether the signed-in member should see the optional read-only Gmail or Outlook connection suggestion, and which providers are configured. A connected inbox or this member's Not now answer hides it. `GET | POST /api/v1/accounting/onboarding.inboxPrompt` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_onboarding_inbox_prompt` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/onboarding.inboxPrompt \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/onboarding.inboxPrompt ## onboarding.inboxPrompt.dismiss Remember Not now for the signed-in member's optional inbox connection suggestion in this organization. Requires an idempotencyKey; it never changes another member's preference or any mailbox setting. `POST /api/v1/accounting/onboarding.inboxPrompt.dismiss` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance, contributor · Idempotency key required MCP tool: `accounting_onboarding_inbox_prompt_dismiss` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/onboarding.inboxPrompt.dismiss \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/onboarding.inboxPrompt.dismiss ## onboarding.status Read a new workspace's setup: whether onboarding is finished, the receipt address, its kind (company or personal), and which steps are done (company details, a bank account or connection, a first receipt or connected inbox, a teammate, and AI keys that cover decisions or Oatmilk's AI credits; a personal workspace has only the bank, receipt and AI steps). `GET | POST /api/v1/accounting/onboarding.status` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_onboarding_status` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/onboarding.status \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/onboarding.status ## platform.admin.accessRequests.list Platform administrators only: people who asked for access on the waitlist, newest first, with what they told us (email, name, company, role, size, website, kinds of financial data, banks, interests, notes) and the request's status and revision. status filters pending (default), approved, declined or all. `GET | POST /api/v1/accounting/platform.admin.accessRequests.list` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_platform_admin_access_requests_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | enum | | Only include records with this status. One of: `pending`, `approved`, `declined`, `all`. Default `"pending"`. | | `limit` | integer | | How many results to return at most. 1 to 500. Default `100`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/platform.admin.accessRequests.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/platform.admin.accessRequests.list ## platform.admin.accessRequests.review Platform administrators only: approve or decline a waitlist request at its current revision. Approving emails the person a link to set up their workspace; platformAi lets their company use Oatmilk's AI credits instead of only its own keys. Declining sends nothing, and a declined request can be approved later. `POST /api/v1/accounting/platform.admin.accessRequests.review` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_platform_admin_access_requests_review` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `requestId` | string (ID) | Yes | The ID of the related record. | | `decision` | enum | Yes | What you decided. One of: `approve`, `decline`. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `platformAi` | boolean | | Default `false`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/platform.admin.accessRequests.review \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "requestId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "decision": "approve", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/platform.admin.accessRequests.review ## platform.admin.experiments.list Platform administrators only: every experimental feature (such as RTS mode), who it is offered to (off, team for the platform's own workspace, or everyone), its Vercel flag key and that flag's value in Vercel Flags when the deployment is connected, plus the deployment-wide experimental-mode flag. People still turn each experiment on for themselves in Settings › Experimental. `GET | POST /api/v1/accounting/platform.admin.experiments.list` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_platform_admin_experiments_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/platform.admin.experiments.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/platform.admin.experiments.list ## platform.admin.experiments.update Platform administrators only: offer one experiment to nobody (off), only the platform's own workspace (team) or every workspace (everyone). A Vercel flag set to off in Vercel Flags still keeps it off. Turning it off stops it at once for everyone who had it on. Pass expectedRevision from platform.admin.experiments.list. `POST /api/v1/accounting/platform.admin.experiments.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_platform_admin_experiments_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `experiment` | enum | Yes | One of: `rts-mode`. | | `audience` | enum | Yes | off offers it to nobody, team only to the platform's own workspace, everyone to every workspace. One of: `off`, `team`, `everyone`. | | `expectedRevision` | integer | | The revision you last read. A different revision means the choices changed first. 0 to 9007199254740990. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/platform.admin.experiments.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "experiment": "rts-mode", "audience": "off" }' ``` Reference page: https://app.getoatmilk.com/docs/api/platform.admin.experiments.update ## platform.admin.organizations.get Platform administrators only: one workspace in God Mode with aggregate numbers only: its counts, its access (approved or paused, AI credits), which AI providers it brought keys for (never the keys), AI usage by feature and model, what kinds of actions it took in the period with how many of each, and the waitlist request it came from. `GET | POST /api/v1/accounting/platform.admin.organizations.get` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_platform_admin_organizations_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `organizationId` | string | Yes | The company to work in, for people who belong to more than one. API keys belong to one company. Matches ^[A-Za-z0-9_-]{1,200}$. | | `days` | integer | | 1 to 90. Default `30`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/platform.admin.organizations.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'organizationId=organization_id' ``` Reference page: https://app.getoatmilk.com/docs/api/platform.admin.organizations.get ## platform.admin.organizations.update Platform administrators only: pause or resume a workspace (status approved or suspended) and turn Oatmilk's AI credits on or off for it (platformAi), at its current revision (0 for a workspace with no access record yet). The platform's own organization can't be changed. `POST /api/v1/accounting/platform.admin.organizations.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_platform_admin_organizations_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `organizationId` | string | Yes | The company to work in, for people who belong to more than one. API keys belong to one company. Matches ^[A-Za-z0-9_-]{1,200}$. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `status` | enum | | Only include records with this status. One of: `approved`, `suspended`. | | `platformAi` | boolean | | | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/platform.admin.organizations.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "organizationId": "organization_id", "expectedRevision": 3, "status": "approved" }' ``` Reference page: https://app.getoatmilk.com/docs/api/platform.admin.organizations.update ## platform.admin.overview Platform administrators only (the platform's own organization): God Mode's overview. Signup mode, totals (workspaces, new ones, members, sign-ups, waiting requests, AI requests and cost) and every workspace with aggregate numbers only: members, transactions and receipts brought in, agreements, contractors, invoices, actions in the period, last activity, whose AI keys it uses and its AI usage and cost (AI Gateway's spend report when available, otherwise Oatmilk's own count). Never records, people or contents. days (1 to 90, default 30) sets the period. `GET | POST /api/v1/accounting/platform.admin.overview` Permissions: `accounting:read` · Roles: admin MCP tool: `accounting_platform_admin_overview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `days` | integer | | 1 to 90. Default `30`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/platform.admin.overview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/platform.admin.overview ## platform.branding.confirmLogo Verify an uploaded logo's bytes and hash, then make it the organization letterhead logo. `POST /api/v1/accounting/platform.branding.confirmLogo` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_platform_branding_confirm_logo` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `storagePath` | string | Yes | 10–500 characters. | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `image/png`, `image/jpeg`. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/platform.branding.confirmLogo \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "storagePath": "example example", "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "mimeType": "image/png" }' ``` Reference page: https://app.getoatmilk.com/docs/api/platform.branding.confirmLogo ## platform.branding.logo Get a short-lived link to the current organization logo. `GET | POST /api/v1/accounting/platform.branding.logo` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_platform_branding_logo` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/platform.branding.logo \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/platform.branding.logo ## platform.branding.prepareLogo Prepare a private upload for the organization logo used on letterheads, agreements, and invoices. PNG or JPEG up to 2 MB. `POST /api/v1/accounting/platform.branding.prepareLogo` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required MCP tool: `accounting_platform_branding_prepare_logo` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | string | Yes | The file's name, including its extension. 1–200 characters. | | `mimeType` | enum | Yes | The file's type, such as image/jpeg or application/pdf. One of: `image/png`, `image/jpeg`. | | `sizeBytes` | integer | Yes | The file's size in bytes. 1 to 2000000. | | `sha256` | string | Yes | The SHA-256 hash of the file's exact bytes, as 64 lowercase hex characters. SHA-256 hash as 64 lowercase hex characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/platform.branding.prepareLogo \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "filename": "receipt.png", "mimeType": "image/png", "sizeBytes": 1, "sha256": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c" }' ``` Reference page: https://app.getoatmilk.com/docs/api/platform.branding.prepareLogo ## platform.settings.get Read workspace preferences for invoicing numbers and defaults, document branding, compliance reminders, and the inbox AI models: which model reads each email and which gives the second opinion, and at what effort. `GET | POST /api/v1/accounting/platform.settings.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_platform_settings_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/platform.settings.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/platform.settings.get ## platform.settings.update Update invoicing numbering and defaults, letterhead text, compliance reminder preferences, and the inbox AI models, with a revision check. inboxAi.reading reads each email and its attachments into a draft; inboxAi.checking is the second opinion on anything left unclear. Each takes a model, one of openai/gpt-6.1-sol (GPT-6.1 Sol), openai/gpt-6-luna (GPT-6 Luna), google/gemini-3.8-flash (Gemini 3.8 Flash), zai/glm-5.3-flash (GLM-5.3 Flash), and a low, medium or high effort; null puts that step back on the recommended GPT-6 Luna at medium effort for reading and GPT-6 Luna at medium for the second opinion. `POST /api/v1/accounting/platform.settings.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_platform_settings_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `invoicing` | object | | No other fields. | | `invoicing.numberPattern` | string | | 3–60 characters; Matches \{N+\}. Default `"INV-{YYYY}-{NNN}"`. | | `invoicing.nextNumber` | integer | | 1 to 999999999. Default `1`. | | `invoicing.defaultDueDays` | integer | | 0 to 365. Default `30`. | | `invoicing.defaultNotes` | string | | at most 2000 characters. Default `""`. | | `invoicing.senderName` | string | | at most 200 characters. Default `""`. | | `invoicing.senderTitle` | string | | at most 200 characters. Default `""`. | | `invoicing.replyTo` | string (email) or null | | | | `invoicing.reviewLeadDays` | integer | | 0 to 60. Default `3`. | | `invoicing.showTaxNumber` | boolean | | Default `false`. | | `invoicing.showLogo` | boolean | | Default `false`. | | `invoicing.payUrl` | string (uri) or null | | Default `null`. | | `invoicing.wisePayLink` | string or null | | | | `branding` | object | | No other fields. | | `branding.letterheadTagline` | string or null | | Default `null`. | | `compliance` | object | | No other fields. | | `compliance.remindersEnabled` | boolean | | Default `true`. | | `compliance.weeklyAccountingDigestEnabled` | boolean | | Default `true`. | | `compliance.monthlySubscriptionsDigestEnabled` | boolean | | Default `false`. | | `compliance.receiptChaseEnabled` | boolean | | Default `true`. | | `compliance.receiptChaseEveryDays` | 7 or 14 | | One of: `7`, `14`. Default `7`. | | `compliance.receiptChaseEscalate` | boolean | | Default `true`. | | `compliance.receiptChaseEscalateDays` | integer | | 4 to 60. Default `14`. | | `compliance.recipients` | array of strings (email) | | at most 20 items; each at most 320 characters. | | `compliance.leadDays` | array of integers | | 1–8 items; each 0 to 120. Default `[30,14,7,3,1,0]`. | | `compliance.overdueEveryDays` | integer | | 1 to 30. Default `3`. | | `compliance.digestHour` | integer | | 0 to 23. Default `9`. | | `compliance.timezone` | string | | 3–60 characters. Default `"America/Toronto"`. | | `compliance.taxPrepStartDaysAfterYearEnd` | integer | | 0 to 180. Default `30`. | | `compliance.disabledRules` | array of strings | | at most 50 items; each Matches ^[a-z][a-z0-9_]{1,60}$. Default `[]`. | | `compliance.corporateJurisdiction` | enum or null | | One of: `federal`, `ON`, `BC`, `AB`, `QC`, `other`. Default `null`. | | `compliance.instalments` | enum | | One of: `none`, `monthly`, `quarterly`. Default `"none"`. | | `compliance.payrollEnabled` | boolean | | Default `false`. | | `compliance.constructionServices` | boolean | | Default `false`. | | `compliance.annualReturnMonthDay` | string or null | | Default `null`. | | `compliance.employeeCount` | enum or null | | One of: `none`, `1-24`, `25-plus`. Default `null`. | | `compliance.postsJobsPublicly` | boolean or null | | Default `null`. | | `compliance.usesAiScreening` | boolean or null | | Default `null`. | | `inboxAi` | object | | No other fields. | | `inboxAi.reading` | object or null | | | | `inboxAi.reading.model` | enum | Yes | One of: `openai/gpt-6.1-sol`, `openai/gpt-6-luna`, `google/gemini-3.8-flash`, `zai/glm-5.3-flash`. | | `inboxAi.reading.effort` | enum | Yes | One of: `low`, `medium`, `high`. | | `inboxAi.checking` | object or null | | | | `inboxAi.checking.model` | enum | Yes | One of: `openai/gpt-6.1-sol`, `openai/gpt-6-luna`, `google/gemini-3.8-flash`, `zai/glm-5.3-flash`. | | `inboxAi.checking.effort` | enum | Yes | One of: `low`, `medium`, `high`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/platform.settings.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/platform.settings.update ## proposals.create Suggest a change to a record for someone to review. Nothing changes until the suggestion is approved. A newer suggestion for the same record and field replaces the older one. Supply subjectType, subjectId, field, proposedValue, reasoning, optional evidence, and idempotencyKey. `POST /api/v1/accounting/proposals.create` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_proposals_create` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `subjectType` | enum | Yes | The kind of record this is about, such as entry, invoice or mail. One of: `invoice`, `entry`, `mail`, `party`, `notion_link`, `contractor`, `merchant`. | | `subjectId` | string | Yes | The ID of the record this is about. 1–200 characters. | | `field` | enum | Yes | One of: `status`, `category`, `classification`, `payment`, `mapping`, `details`. | | `proposedValue` | map | Yes | | | `currentValue` | map | | | | `reasoning` | string | Yes | 3–2000 characters. | | `evidence` | array of objects | | at most 20 items. | | `evidence[].type` | string | Yes | Matches ^[a-z][a-z0-9_]{1,39}$. | | `evidence[].id` | string | Yes | The record's ID. 1–200 characters. | | `evidence[].label` | string | Yes | 1–200 characters. | | `evidence[].date` | string or null | | A date, as YYYY-MM-DD. | | `evidence[].url` | string or null | | A full web address, starting with https://. | | `confidence` | enum | | One of: `low`, `medium`, `high`. Default `"medium"`. | | `freshness` | enum | | One of: `newer`, `older`, `same`, `unknown`. Default `"unknown"`. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/proposals.create \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "subjectType": "invoice", "subjectId": "example", "field": "status", "proposedValue": {}, "reasoning": "example" }' ``` Reference page: https://app.getoatmilk.com/docs/api/proposals.create ## proposals.decide Decline a suggested change, or approve it when a person is signed in to Oatmilk. API keys and agents can decline but can't approve; approving through them is refused with "Approve suggestions in Oatmilk." Approving applies the change through the normal audited action for that record, such as recording an invoice payment or recategorizing an entry. A partly paid suggestion without an amount needs paidAmountMinor. A failed or stale approval can be approved again or declined. Requires expectedRevision and idempotencyKey; an optional note is kept with the decision. `POST /api/v1/accounting/proposals.decide` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` Not available over MCP: Approving a suggestion is the human in the loop. ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `decision` | enum | Yes | What you decided. One of: `approve`, `reject`. | | `note` | string | | A short note, kept with the record. at most 1000 characters. | | `paidAmountMinor` | string | | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^(?!0+$)\d{1,18}$. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/proposals.decide \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3, "decision": "approve" }' ``` Reference page: https://app.getoatmilk.com/docs/api/proposals.decide ## proposals.get Read one suggested change with its reasoning, evidence links, and decision history. `GET | POST /api/v1/accounting/proposals.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_proposals_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/proposals.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/proposals.get ## proposals.list List suggested changes to invoices, entries, mail, accounts, and Notion links, with the current and proposed values, plain-language reasoning, evidence, whether the evidence is newer or older, and status. Defaults to suggestions waiting for review. `GET | POST /api/v1/accounting/proposals.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_proposals_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `subjectType` | enum | | The kind of record this is about, such as entry, invoice or mail. One of: `invoice`, `entry`, `mail`, `party`, `notion_link`, `contractor`, `merchant`. | | `subjectId` | string | | The ID of the record this is about. 1–200 characters. | | `status` | enum | | Only include records with this status. One of: `pending`, `approved`, `rejected`, `superseded`, `applied`, `failed`, `all`. Default `"pending"`. | | `source` | enum | | Where the record came from. One of: `ai`, `rule`, `connector`, `user`, `accountant`. | | `field` | enum | | One of: `status`, `category`, `classification`, `payment`, `mapping`, `details`, `evidence`, `match`, `attribution`. | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/proposals.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/proposals.list ## runs.get Read one run, or the latest run for a subject, with its ordered step events: steps started and finished, model calls, outputs, decisions, and errors. Use afterEventId to fetch only new events while a run is active. `GET | POST /api/v1/accounting/runs.get` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_runs_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `subjectType` | string | | The kind of record this is about, such as entry, invoice or mail. Matches ^[a-z][a-z0-9_]{1,40}$. | | `subjectId` | string | | The ID of the record this is about. 1–200 characters. | | `afterEventId` | integer | | 0 to 9007199254740991. Default `0`. | | `limit` | integer | | How many results to return at most. 1 to 1000. Default `500`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/runs.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/runs.get ## runs.list List AI and automation runs with their current step, status, and summary. Filter by kind (or a comma-separated kinds list), subject, or status. Contributors see only runs for their own submissions. `GET | POST /api/v1/accounting/runs.list` Permissions: `accounting:read` · Roles: admin, finance, contributor MCP tool: `accounting_runs_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `kind` | string | | Which kind of record or job this is. Matches ^[a-z][a-z0-9_.]{1,60}$. | | `kinds` | string | | at most 1000 characters; Matches ^[a-z][a-z0-9_.]{1,60}(,[a-z][a-z0-9_.]{1,60}){0,19}$. | | `subjectType` | string | | The kind of record this is about, such as entry, invoice or mail. Matches ^[a-z][a-z0-9_]{1,40}$. | | `subjectId` | string | | The ID of the record this is about. 1–200 characters. | | `subjectIds` | string | | at most 4000 characters; Matches ^[A-Za-z0-9_:.,-]*$. | | `status` | enum | | Only include records with this status. One of: `queued`, `running`, `succeeded`, `failed`, `needs_review`, `cancelled`. | | `activeOnly` | boolean | | | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | | `offset` | integer | | How many results to skip, for the next page of a list. 0 to 100000. Default `0`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/runs.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/runs.list ## runs.trace Read an authorized process trace with its stage timeline, logs, model metadata, and linked Workflow SDK run, steps, and events. Restricted mail remains visible only to a security administrator. `GET | POST /api/v1/accounting/runs.trace` Permissions: `accounting:read`, `mail:read` · Roles: admin, finance MCP tool: `accounting_runs_trace` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `limit` | integer | | How many results to return at most. 1 to 1000. Default `500`. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/runs.trace \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/runs.trace ## runs.workflows.catalog List organization-owned Workflow SDK jobs and indicate which job sources are unavailable while a database update is pending. `GET | POST /api/v1/accounting/runs.workflows.catalog` Permissions: `accounting:read`, `mail:read`, `mail:security` · Roles: admin MCP tool: `accounting_runs_workflows_catalog` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/runs.workflows.catalog \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/runs.workflows.catalog ## runs.workflows.get Inspect an organization-owned Workflow SDK run with its steps, events, statuses, and redacted input and output. `GET | POST /api/v1/accounting/runs.workflows.get` Permissions: `accounting:read`, `mail:read`, `mail:security` · Roles: admin MCP tool: `accounting_runs_workflows_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `runId` | string | Yes | 1–200 characters. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/runs.workflows.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'runId=example' ``` Reference page: https://app.getoatmilk.com/docs/api/runs.workflows.get ## runs.workflows.list List organization-owned Workflow SDK jobs for receipts, mail, matching, Stripe, and accounting digest replies. `GET | POST /api/v1/accounting/runs.workflows.list` Permissions: `accounting:read`, `mail:read`, `mail:security` · Roles: admin MCP tool: `accounting_runs_workflows_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `limit` | integer | | How many results to return at most. 1 to 100. Default `30`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/runs.workflows.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/runs.workflows.list ## senders.list Read exact approved employee sending addresses. `GET | POST /api/v1/accounting/senders.list` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_senders_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/senders.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/senders.list ## senders.remove Remove an approved sending address. `POST /api/v1/accounting/senders.remove` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_senders_remove` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/senders.remove \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/senders.remove ## senders.upsert Authorize an exact employee sending address with active membership. `POST /api/v1/accounting/senders.upsert` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_senders_upsert` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `email` | string (email) | Yes | An email address. | | `userId` | string | Yes | The ID of a person in your company. 1–200 characters. | | `expectedRevision` | integer | | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/senders.upsert \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "email": "finance@example.com", "userId": "example" }' ``` Reference page: https://app.getoatmilk.com/docs/api/senders.upsert ## settings.get Read accounting fiscal settings and integration configuration. Administrator access required. `GET | POST /api/v1/accounting/settings.get` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_settings_get` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/settings.get \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/settings.get ## settings.rotateMailbox Rotate the randomly generated receiving alias with revision checks. `POST /api/v1/accounting/settings.rotateMailbox` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_settings_rotate_mailbox` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `label` | string | | Matches ^[a-z][a-z0-9-]{1,23}$. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/settings.rotateMailbox \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/settings.rotateMailbox ## settings.update Update fiscal and bank configuration with revision checks. `POST /api/v1/accounting/settings.update` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_settings_update` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `fiscalYearEnd` | string | | | | `taxRegistration` | string | | 1–100 characters. | | `bankHistoryStart` | string | | | | `wiseProfileId` | string | | Whole number written as a string. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/settings.update \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/settings.update ## team.invitations.link Get the private link of a pending team invitation, to share it another way. Only organization administrators can manage the team. MCP calls also require the accounting:admin scope; the direct API cannot send team invitations or remove members. `POST /api/v1/accounting/team.invitations.link` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin MCP tool: `accounting_team_invitations_link` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/team.invitations.link \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/team.invitations.link ## team.invitations.resend Email a pending team invitation again and give it another 14 days, with id, expectedRevision and idempotencyKey. Only organization administrators can manage the team. MCP calls also require the accounting:admin scope; the direct API cannot send team invitations or remove members. `POST /api/v1/accounting/team.invitations.resend` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_team_invitations_resend` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/team.invitations.resend \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/team.invitations.resend ## team.invitations.revoke Cancel a pending team invitation so its link stops working, with source (oatmilk, or clerk for an invitation sent before Oatmilk sent its own), id, expectedRevision for an Oatmilk invitation, and idempotencyKey. Only organization administrators can manage the team. MCP calls also require the accounting:admin scope; the direct API cannot send team invitations or remove members. `POST /api/v1/accounting/team.invitations.revoke` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Send `expectedRevision` · Destructive: confirm with a person first MCP tool: `accounting_team_invitations_revoke` ### Fields #### source: "oatmilk" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | "oatmilk" | Yes | Where the record came from. | | `id` | string (ID) | Yes | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. at most 9007199254740991; greater than 0. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | #### source: "clerk" | Field | Type | Required | Notes | | --- | --- | --- | --- | | `source` | "clerk" | Yes | Where the record came from. | | `id` | string | Yes | The record's ID. Matches ^orginv_[A-Za-z0-9]{8,64}$. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/team.invitations.revoke \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "source": "oatmilk", "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "expectedRevision": 3 }' ``` Reference page: https://app.getoatmilk.com/docs/api/team.invitations.revoke ## team.invite Invite a new team member by email with a role (admin, finance or contributor), an optional message and an idempotency key. Oatmilk emails them a private link when email is enabled; the result always includes a link to share and a delivery status. The link works for 14 days. Only organization administrators can manage the team. MCP calls also require the accounting:admin scope; the direct API cannot send team invitations or remove members. `POST /api/v1/accounting/team.invite` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Reaches outside Oatmilk (email or a provider) MCP tool: `accounting_team_invite` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `email` | string (email) | Yes | An email address. at most 320 characters. | | `role` | enum | Yes | A person's access level in the company. One of: `admin`, `finance`, `contributor`. | | `message` | string | | at most 1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/team.invite \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "email": "finance@example.com", "role": "admin" }' ``` Reference page: https://app.getoatmilk.com/docs/api/team.invite ## team.list List the team: everyone in the company's organization with their role and whether they can open Oatmilk, plus pending team invitations. Invitation links are never included. `GET | POST /api/v1/accounting/team.list` Permissions: `accounting:read`, `accounting:admin` · Roles: admin MCP tool: `accounting_team_list` ### Fields This action takes no input fields. Send `{}`. ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/team.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/team.list ## team.members.remove Remove someone from the company: turns off their Oatmilk access and removes them from the organization. Their records stay. You can't remove yourself. Requires userId and idempotencyKey. Only organization administrators can manage the team. MCP calls also require the accounting:admin scope; the direct API cannot send team invitations or remove members. `POST /api/v1/accounting/team.members.remove` Permissions: `accounting:read`, `accounting:write`, `accounting:admin` · Roles: admin · Idempotency key required · Destructive: confirm with a person first MCP tool: `accounting_team_members_remove` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `userId` | string | Yes | The ID of a person in your company. 1–200 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/team.members.remove \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "userId": "example" }' ``` Reference page: https://app.getoatmilk.com/docs/api/team.members.remove # Group: History and rollback > Every change anyone made to the company, who made it and how, and previewed rollbacks. MCP toolset: `history` (https://app.getoatmilk.com/api/mcp?toolset=history) ## history - [`history.facets`](https://app.getoatmilk.com/docs/api/history.facets.md) — Count History by person, source and action for a date range (from/to), with the first and last change, so filters only offer choices that exist. - [`history.get`](https://app.getoatmilk.com/docs/api/history.get.md) — Read one change from History by its id: every record it touched in that step, the values before and after where Oatmilk kept them, how it was made (Ask AI, an AI app, the API, the integration key), the error when it failed, any rollback of it (or the change it rolled back), and the same record's other recent changes. - [`history.list`](https://app.getoatmilk.com/docs/api/history.list.md) — List everything anyone changed in the company, newest first: people in Oatmilk, Ask AI, AI apps over MCP, the API, contractors and signers in their portals, and Oatmilk's own automation (Autopilot, syncs, readers). Each item is one step (all records one person changed at once), with who, how (source), what, the record, and whether it was rolled back. Filter by from/to dates, query (text), actorIds, sources (web, assistant, mcp, api, automation, portal, signer), areas (banking, transactions, budgets, inbox, invoices, reimbursements, agreements, contractors, recruiting, tax, compliance, subscriptions, access, settings), actions (exact action names), statuses (succeeded, failed, rolledBack, active, revertible) and targetId (one record's changes). Sort by at, actor, action, area, size or source, asc or desc. Pages with cursor; total counts every match. ## history.rollback - [`history.rollback.apply`](https://app.getoatmilk.com/docs/api/history.rollback.apply.md) — Roll back one change from History using the previewFingerprint from history.rollback.preview, a reason and an idempotencyKey. Every step runs as the person through the same checks as doing it by hand (role, revision, closed periods), and is itself recorded in History, so a rollback can be rolled back too. A change made since the preview stops it with a conflict; preview again. - [`history.rollback.applyMany`](https://app.getoatmilk.com/docs/api/history.rollback.applyMany.md) — Roll back up to 50 changes from History, newest first, using the previewFingerprint from history.rollback.previewMany, a reason and an idempotencyKey. Blocked changes are skipped and reported; each result says what was rolled back. - [`history.rollback.preview`](https://app.getoatmilk.com/docs/api/history.rollback.preview.md) — Check whether one change from History can be rolled back and exactly what rolling it back would do, record by record: the values it puts back, what is already back, and what blocks it (a later edit, a deleted record, a closed period). For a statement or CSV import it previews undoing every line that can be undone. Returns previewFingerprint for history.rollback.apply. Nothing changes. - [`history.rollback.previewMany`](https://app.getoatmilk.com/docs/api/history.rollback.previewMany.md) — Preview rolling back up to 50 changes from History at once, newest first, with each change's plan and one previewFingerprint for history.rollback.applyMany. Nothing changes. ## history.facets Count History by person, source and action for a date range (from/to), with the first and last change, so filters only offer choices that exist. `GET | POST /api/v1/accounting/history.facets` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_history_facets` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string | | The first date to include, as YYYY-MM-DD. at most 40 characters. | | `to` | string | | The last date to include, as YYYY-MM-DD. at most 40 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/history.facets \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/history.facets ## history.get Read one change from History by its id: every record it touched in that step, the values before and after where Oatmilk kept them, how it was made (Ask AI, an AI app, the API, the integration key), the error when it failed, any rollback of it (or the change it rolled back), and the same record's other recent changes. `GET | POST /api/v1/accounting/history.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_history_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | Yes | The record's ID. Matches ^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\|sig-[0-9]{1,19})$. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/history.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=11111111-1111-1111-1111-111111111111' ``` Reference page: https://app.getoatmilk.com/docs/api/history.get ## history.list List everything anyone changed in the company, newest first: people in Oatmilk, Ask AI, AI apps over MCP, the API, contractors and signers in their portals, and Oatmilk's own automation (Autopilot, syncs, readers). Each item is one step (all records one person changed at once), with who, how (source), what, the record, and whether it was rolled back. Filter by from/to dates, query (text), actorIds, sources (web, assistant, mcp, api, automation, portal, signer), areas (banking, transactions, budgets, inbox, invoices, reimbursements, agreements, contractors, recruiting, tax, compliance, subscriptions, access, settings), actions (exact action names), statuses (succeeded, failed, rolledBack, active, revertible) and targetId (one record's changes). Sort by at, actor, action, area, size or source, asc or desc. Pages with cursor; total counts every match. `GET | POST /api/v1/accounting/history.list` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_history_list` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string | | The first date to include, as YYYY-MM-DD. at most 40 characters. | | `to` | string | | The last date to include, as YYYY-MM-DD. at most 40 characters. | | `query` | string | | Text to search for. at most 200 characters. | | `actorIds` | array of strings | | A list of record IDs. at most 50 items; each 1–200 characters. | | `sources` | array of enum values | | One of: `web`, `assistant`, `mcp`, `api`, `automation`, `portal`, `signer`. at most 7 items. | | `areas` | array of enum values | | One of: `banking`, `transactions`, `budgets`, `inbox`, `invoices`, `reimbursements`, `agreements`, `contractors`, `recruiting`, `tax`, `compliance`, `subscriptions`, `access`, `settings`. at most 14 items. | | `actions` | array of strings | | at most 100 items; each Matches ^[A-Za-z][A-Za-z0-9_.]{0,119}$. | | `statuses` | array of enum values | | One of: `succeeded`, `failed`, `rolledBack`, `active`, `revertible`. at most 5 items. | | `targetId` | string | | 1–200 characters. | | `sort` | enum | | One of: `at`, `actor`, `action`, `area`, `size`, `source`. Default `"at"`. | | `direction` | enum | | One of: `asc`, `desc`. Default `"desc"`. | | `cursor` | string | | Where the next page starts: the nextCursor value from the previous response. 1–2000 characters. | | `limit` | integer | | How many results to return at most. 1 to 200. Default `50`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/history.list \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/history.list ## history.rollback.apply Roll back one change from History using the previewFingerprint from history.rollback.preview, a reason and an idempotencyKey. Every step runs as the person through the same checks as doing it by hand (role, revision, closed periods), and is itself recorded in History, so a rollback can be rolled back too. A change made since the preview stops it with a conflict; preview again. `POST /api/v1/accounting/history.rollback.apply` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Destructive: confirm with a person first MCP tool: `accounting_history_rollback_apply` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | Yes | The record's ID. Matches ^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\|sig-[0-9]{1,19})$. | | `previewFingerprint` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/history.rollback.apply \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "id": "11111111-1111-1111-1111-111111111111", "previewFingerprint": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/history.rollback.apply ## history.rollback.applyMany Roll back up to 50 changes from History, newest first, using the previewFingerprint from history.rollback.previewMany, a reason and an idempotencyKey. Blocked changes are skipped and reported; each result says what was rolled back. `POST /api/v1/accounting/history.rollback.applyMany` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Destructive: confirm with a person first MCP tool: `accounting_history_rollback_apply_many` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `ids` | array of strings | Yes | 1–50 items; each Matches ^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\|sig-[0-9]{1,19})$. | | `previewFingerprint` | string | Yes | SHA-256 hash as 64 lowercase hex characters. | | `reason` | string | Yes | A short note saying why, kept in the record's history. 3–1000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/history.rollback.applyMany \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "ids": [ "11111111-1111-1111-1111-111111111111" ], "previewFingerprint": "9f2b5c1d7e3a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c", "reason": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/history.rollback.applyMany ## history.rollback.preview Check whether one change from History can be rolled back and exactly what rolling it back would do, record by record: the values it puts back, what is already back, and what blocks it (a later edit, a deleted record, a closed period). For a statement or CSV import it previews undoing every line that can be undone. Returns previewFingerprint for history.rollback.apply. Nothing changes. `GET | POST /api/v1/accounting/history.rollback.preview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_history_rollback_preview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string | Yes | The record's ID. Matches ^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\|sig-[0-9]{1,19})$. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/history.rollback.preview \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=11111111-1111-1111-1111-111111111111' ``` Reference page: https://app.getoatmilk.com/docs/api/history.rollback.preview ## history.rollback.previewMany Preview rolling back up to 50 changes from History at once, newest first, with each change's plan and one previewFingerprint for history.rollback.applyMany. Nothing changes. `GET | POST /api/v1/accounting/history.rollback.previewMany` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_history_rollback_preview_many` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `ids` | array of strings | Yes | 1–50 items; each Matches ^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\|sig-[0-9]{1,19})$. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/history.rollback.previewMany \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "ids": [ "11111111-1111-1111-1111-111111111111" ] }' ``` Reference page: https://app.getoatmilk.com/docs/api/history.rollback.previewMany # Group: Budgets and reserves > Spending plans, tax and accountant costs, reserve accounts and funding gaps before filing. MCP toolset: `budgets` (https://app.getoatmilk.com/api/mcp?toolset=budgets) ## budgets.accounts - [`budgets.accounts.designate`](https://app.getoatmilk.com/docs/api/budgets.accounts.designate.md) — Designate a bank account for operating cash, GST/HST, income tax, accountant fees or another reserve, optionally capping the reserved amount. The account must belong to this company. Requires its purposeRevision as expectedRevision, or zero when no designation exists. Does not move funds, change a Wise account or make a payment. - [`budgets.accounts.get`](https://app.getoatmilk.com/docs/api/budgets.accounts.get.md) — Read one bank account's saved reserve designation and revision. Returns null when no designation exists in this company. ## budgets - [`budgets.evaluate`](https://app.getoatmilk.com/docs/api/budgets.evaluate.md) — Refresh deterministic budget and filing funding checks, adding or resolving funding warnings in the existing Checklist. Existing reminder settings control scheduled emails. Does not file returns, pay tax or move funds. Requires an idempotencyKey. - [`budgets.get`](https://app.getoatmilk.com/docs/api/budgets.get.md) — Read one saved spending budget by ID, including an inactive plan. Returns null when unavailable in this company. - [`budgets.overview`](https://app.getoatmilk.com/docs/api/budgets.overview.md) — Read spending budgets, reviewed actuals, upcoming tax and accountant obligations, reserve coverage, and future recurring purchase forecasts. Use sections to load only due, spending, reserves or recurring; omitted sections return empty arrays and recurringForecast null. No sections returns all. Recurring forecasts reuse reviewed charges, saved subscription status and accepted same-currency merges, with evidence, amount basis, uncertainty and per-currency totals. They are speculative future costs, kept separate from actual spend, tax liabilities and funding obligations. Reuses official compliance dates and reviewed GST/HST workpapers. Unknown tax amounts and stale or missing balances remain unknown; each reserve is counted once. - [`budgets.save`](https://app.getoatmilk.com/docs/api/budgets.save.md) — Create or edit a monthly, quarterly or yearly spending budget, for all expenses or one category. Actuals use ledger entries and splits, with refunds deducted and transfers excluded. Requires current expectedRevision or zero for a new budget and an idempotencyKey. Does not change bookkeeping entries. ## budgets.obligations - [`budgets.obligations.get`](https://app.getoatmilk.com/docs/api/budgets.obligations.get.md) — Read one saved payment plan by ID, including an inactive plan, or the current compliance funding plan by compliance:. Returns null when unavailable in this company. - [`budgets.obligations.save`](https://app.getoatmilk.com/docs/api/budgets.obligations.save.md) — Create or edit a tax, GST/HST, accountant or other payment plan, keeping filing and payment dates separate and distinguishing an estimate from a confirmed amount. A negative amount is an expected refund and never offsets another obligation. Recording paidMinor is a planning note, not a bank payment or filed return. Link a complianceItemId to replace its automatic funding plan. ## budgets.accounts.designate Designate a bank account for operating cash, GST/HST, income tax, accountant fees or another reserve, optionally capping the reserved amount. The account must belong to this company. Requires its purposeRevision as expectedRevision, or zero when no designation exists. Does not move funds, change a Wise account or make a payment. `POST /api/v1/accounting/budgets.accounts.designate` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_budgets_accounts_designate` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `accountId` | string (ID) | Yes | The ID of a bank, card or payment account, from accounts.list. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `purpose` | enum | Yes | One of: `operating`, `gst_hst`, `income_tax`, `accountant`, `other`. | | `reserveLimitMinor` | string or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `note` | string | Yes | A short note, kept with the record. at most 2000 characters. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/budgets.accounts.designate \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "accountId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "expectedRevision": 3, "purpose": "operating", "reserveLimitMinor": "1250", "note": "Synthetic example from the docs" }' ``` Reference page: https://app.getoatmilk.com/docs/api/budgets.accounts.designate ## budgets.accounts.get Read one bank account's saved reserve designation and revision. Returns null when no designation exists in this company. `GET | POST /api/v1/accounting/budgets.accounts.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_budgets_accounts_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `accountId` | string (ID) | Yes | The ID of a bank, card or payment account, from accounts.list. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/budgets.accounts.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'accountId=4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a' ``` Reference page: https://app.getoatmilk.com/docs/api/budgets.accounts.get ## budgets.evaluate Refresh deterministic budget and filing funding checks, adding or resolving funding warnings in the existing Checklist. Existing reminder settings control scheduled emails. Does not file returns, pay tax or move funds. Requires an idempotencyKey. `POST /api/v1/accounting/budgets.evaluate` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required MCP tool: `accounting_budgets_evaluate` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/budgets.evaluate \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{}' ``` Reference page: https://app.getoatmilk.com/docs/api/budgets.evaluate ## budgets.get Read one saved spending budget by ID, including an inactive plan. Returns null when unavailable in this company. `GET | POST /api/v1/accounting/budgets.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_budgets_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/budgets.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/budgets.get ## budgets.obligations.get Read one saved payment plan by ID, including an inactive plan, or the current compliance funding plan by compliance:. Returns null when unavailable in this company. `GET | POST /api/v1/accounting/budgets.obligations.get` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_budgets_obligations_get` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) or string | Yes | The record's ID. | ### Example request ```bash curl -G https://app.getoatmilk.com/api/v1/accounting/budgets.obligations.get \ -H "Authorization: Bearer $OATMILK_API_KEY" \ --data-urlencode 'id=9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d' ``` Reference page: https://app.getoatmilk.com/docs/api/budgets.obligations.get ## budgets.obligations.save Create or edit a tax, GST/HST, accountant or other payment plan, keeping filing and payment dates separate and distinguishing an estimate from a confirmed amount. A negative amount is an expected refund and never offsets another obligation. Recording paidMinor is a planning note, not a bank payment or filed return. Link a complianceItemId to replace its automatic funding plan. `POST /api/v1/accounting/budgets.obligations.save` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_budgets_obligations_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `name` | string | Yes | A display name. 1–160 characters. | | `kind` | enum | Yes | Which kind of record or job this is. One of: `gst_hst`, `income_tax`, `accountant`, `other`. | | `amountMinor` | string or null | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. | | `amountBasis` | enum | Yes | One of: `estimate`, `confirmed`. | | `paidMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,15}$. | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `periodFrom` | string (date) or null | Yes | | | `periodTo` | string (date) or null | Yes | | | `filingDueOn` | string (date) or null | Yes | | | `paymentDueOn` | string (date) or null | Yes | | | `accountId` | string (ID) or null | Yes | The ID of a bank, card or payment account, from accounts.list. | | `reminderDays` | integer | Yes | 0 to 365. | | `note` | string | Yes | A short note, kept with the record. at most 2000 characters. | | `active` | boolean | Yes | Whether the record is turned on. | | `complianceItemId` | string (ID) or null | | Default `null`. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/budgets.obligations.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3, "name": "Synthetic Ventures Inc.", "kind": "gst_hst", "amountMinor": "1", "amountBasis": "estimate", "paidMinor": "1250", "currency": "CAD", "periodFrom": "2026-09-01", "periodTo": "2026-09-01", "filingDueOn": "2026-09-01", "paymentDueOn": "2026-09-01", "accountId": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a", "reminderDays": 7, "note": "Synthetic example from the docs", "active": true, "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/budgets.obligations.save ## budgets.overview Read spending budgets, reviewed actuals, upcoming tax and accountant obligations, reserve coverage, and future recurring purchase forecasts. Use sections to load only due, spending, reserves or recurring; omitted sections return empty arrays and recurringForecast null. No sections returns all. Recurring forecasts reuse reviewed charges, saved subscription status and accepted same-currency merges, with evidence, amount basis, uncertainty and per-currency totals. They are speculative future costs, kept separate from actual spend, tax liabilities and funding obligations. Reuses official compliance dates and reviewed GST/HST workpapers. Unknown tax amounts and stale or missing balances remain unknown; each reserve is counted once. `GET | POST /api/v1/accounting/budgets.overview` Permissions: `accounting:read` · Roles: admin, finance MCP tool: `accounting_budgets_overview` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `from` | string (date) | | The first date to include, as YYYY-MM-DD. | | `to` | string (date) | | The last date to include, as YYYY-MM-DD. | | `sections` | array of enum values | | One of: `due`, `spending`, `reserves`, `recurring`. 1–4 items. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/budgets.overview \ -H "Authorization: Bearer $OATMILK_API_KEY" ``` Reference page: https://app.getoatmilk.com/docs/api/budgets.overview ## budgets.save Create or edit a monthly, quarterly or yearly spending budget, for all expenses or one category. Actuals use ledger entries and splits, with refunds deducted and transfers excluded. Requires current expectedRevision or zero for a new budget and an idempotencyKey. Does not change bookkeeping entries. `POST /api/v1/accounting/budgets.save` Permissions: `accounting:read`, `accounting:write` · Roles: admin, finance · Idempotency key required · Send `expectedRevision` MCP tool: `accounting_budgets_save` ### Fields | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | string (ID) | | The record's ID. | | `expectedRevision` | integer | Yes | The record's current revision, from the last time you read it. If someone changed the record since, the request is refused with a conflict so you can reload and check before trying again. 0 to 9007199254740991. | | `idempotencyKey` | string | Yes | Any unique text you generate once per intended change, so a retried request only happens once. Send it as the Idempotency-Key header instead if you prefer; if you send both they must match. 8–200 characters. | | `name` | string | Yes | A display name. 1–160 characters. | | `categoryId` | string (ID) or null | Yes | The ID of a category, from categories.list. | | `currency` | string | Yes | Three-letter currency code, such as CAD or USD. | | `amountMinor` | string | Yes | An amount in cents (the currency's smallest unit), written as a whole-number string such as "1250" for $12.50. Matches ^\d{1,15}$. | | `period` | enum | Yes | The period to cover. One of: `monthly`, `quarterly`, `yearly`. | | `startsOn` | string (date) | Yes | A date, as YYYY-MM-DD. | | `endsOn` | string (date) or null | Yes | | | `active` | boolean | Yes | Whether the record is turned on. | ### Example request ```bash curl https://app.getoatmilk.com/api/v1/accounting/budgets.save \ -H "Authorization: Bearer $OATMILK_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "expectedRevision": 3, "name": "Synthetic Ventures Inc.", "categoryId": "7f0f6c1e-1c1f-4b5e-9c8d-2f5e8e3c1a10", "currency": "CAD", "amountMinor": "1250", "period": "monthly", "startsOn": "2026-09-01", "endsOn": "2026-09-01", "active": true, "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }' ``` Reference page: https://app.getoatmilk.com/docs/api/budgets.save