# Solarium API Reference > All requests are `POST https://api.trysolarium.com/graphql/` with `Authorization: Bearer ` and a JSON body `{"query": ..., "variables": ...}`. Every query and mutation takes an `organizationId` (find it with `myOrganizations`). ## Contents - [Use With AI (MCP)](#ai) (https://trysolarium.com/docs/ai.md) - [Quickstart](#quickstart) (https://trysolarium.com/docs/quickstart.md) - [Authentication](#authentication) (https://trysolarium.com/docs/authentication.md) - [Bank Import](#bank-import) (https://trysolarium.com/docs/bank-import.md) - [Intake Addresses](#intake-addresses) (https://trysolarium.com/docs/intake-addresses.md) - [Category Rules](#category-rules) (https://trysolarium.com/docs/category-rules.md) - [Country & Tax Setup](#jurisdiction) (https://trysolarium.com/docs/jurisdiction.md) - [Stripe Import](#skill-stripe) (https://trysolarium.com/docs/skill-stripe.md) - [Venn / Bank CSV Import](#skill-venn) (https://trysolarium.com/docs/skill-venn.md) - [Transactions](#transactions) (https://trysolarium.com/docs/transactions.md) - [Accounts](#accounts) (https://trysolarium.com/docs/accounts.md) - [Contacts](#contacts) (https://trysolarium.com/docs/contacts.md) - [Receipts](#receipts) (https://trysolarium.com/docs/receipts.md) - [Reports](#reports) (https://trysolarium.com/docs/reports.md) - [Batch Operations](#batch) (https://trysolarium.com/docs/batch.md) - [Errors](#errors) (https://trysolarium.com/docs/errors.md) - [Pagination](#pagination) (https://trysolarium.com/docs/pagination.md) # Use With AI (MCP) Solarium runs a hosted MCP server, so Claude, ChatGPT, Claude Code, Cursor and other MCP clients can work in your books directly. There's no API key to copy: the app signs you in to Solarium, you pick the business, and you choose whether it may only look or also make changes. Server address: `https://mcp.trysolarium.com/mcp` ### Connect - **Claude:** Settings, Connectors, Add custom connector, then paste the address. - **ChatGPT:** add a custom connector (developer mode) with the address. - **Claude Code:** `claude mcp add --transport http solarium https://mcp.trysolarium.com/mcp` - **Anything else:** any MCP client that supports remote servers with OAuth sign-in. See [Solarium in Claude and ChatGPT](https://trysolarium.com/ai) for the walkthrough. ### How Changes Work Tools that change your books take an `apply` argument. Without it, or with `apply: false`, the tool previews what it would do and keeps nothing. Your AI app should show you the preview and only call it again with `apply: true` once you agree. Every applied change is logged in Settings, under MCP Access. A connection only ever sees the one business you picked. ### Tools That Only Read | Tool | What it does | | --- | --- | | `audit_qbo_totals` | List synced transactions whose QuickBooks total or sales tax differs from the books, between two dates (default the last 90 days), largest first, with the difference per payment account (a card or bank). Also lists any that are gone from QuickBooks. Read only. | | `compare_with_qbo` | Compare Solarium's books with QuickBooks', account by account, on a date (default today): QuickBooks' trial balance next to Solarium's, with the difference. Amounts are debit minus credit; P&L accounts are the fiscal year to date. Lists QuickBooks accounts Solarium doesn't have. Read only. | | `find_qbo_matches` | Find the QuickBooks transactions something probably already is: same amount within 3 days, or a bill / vendor credit with the same invoice number or vendor within 45 days, with their attachments and whether Solarium has them. Pass a receipt_id, an event_id, or the raw facts (amount + date, optionally vendor, doc_number, currency) for something not in Solarium yet. Flags likely double bills. Read-only. | | `get_connection` | Who this connection is: the organization's name and id, who connected it, what it may do (look only, or look and make changes), and a stable account id for this person in this organization. Read-only. | | `get_event` | One transaction in full: its lines (staged and booked), receipts (amount, currency, card, line items, in QuickBooks; get_receipt for a receipt's full text and file), FX, duplicate and closed-period flags, notes, and its QuickBooks link and sync state. | | `get_qbo_account` | One QuickBooks account as QuickBooks has it: name, type, subtype, active, balance and SyncToken, plus the Solarium account mapped to it. Pass qb_id or name. Read-only. | | `get_qbo_transaction` | Read one QuickBooks transaction: its total, tax, date, payee, lines (ids, accounts, tax codes, received from) and attachments, plus the Solarium event linked to it. Read-only. | | `get_receipt` | One receipt or invoice in full: its text as read, line items (freight, fees and tax lines included), invoice number, card, payment method, totals, the transaction it's on and whether it's in QuickBooks, plus a link to the file (valid for an hour). Use it to check a document against a bill or a charge, line by line. | | `list_categories` | The chart of accounts: each account's id, name, type, tax code, card number and QuickBooks mapping. Filter by account_type; inactive accounts only with include_inactive. | | `list_contacts` | Customers and vendors: id, name, type, email, phone and balance. Filter by contact_type (customer or vendor) or name (contains). Use a contact's id with book_invoice, record_customer_payment or report_account_lines. | | `list_events` | Find transactions, newest first. Filter by status, category_id, payment_account_id, payment_account_type (credit_card, bank…), vendor (contains), date_from / date_to, amount_min / amount_max (home currency), and has_document (whether a receipt is attached). Ignored transactions are left out unless status=ignored or include_ignored. Page with limit and offset. | | `list_op_runs` | The history of operations applied to these books: who ran what, when, from where (chat, API, command), what it changed (including QuickBooks writes and whether they checked out), or why it was refused. Newest first. | | `list_receipts` | Receipts and invoices sent in (by email, text, upload or Drive). By default only those not on any transaction yet and not dismissed: the ones to attach, book (promote_receipts) or dismiss. unmatched=false lists all. Filter by vendor (contains) and date_from / date_to. Newest first. | | `reconcile_card_statement` | Hold a card or bank statement up against the books (read-only). Pass the statement as rows (date, description, amount, is_debit, optional foreign_amount + foreign_currency; without is_debit a negative amount is a credit) or an uploaded PDF (s3_key). Returns matches, FX corrections (with the set_event_fx to run), lines missing from the books, transactions not on the statement, possible doubles, and foreign-transaction fees. | | `report_account_lines` | The booked lines behind a report number: every line posted to these accounts in the range, with the transaction (event_id) each came from. Narrow to one vendor (a report_spending_by_vendor key) or one contact (a contact id, or 'none'). Capped at 500 lines (truncated says so). | | `report_balance_sheet` | Balance sheet as of a date: assets, liabilities and equity by account (net income to date included), optionally side by side with compare_as_of. out_of_balance is zero unless something posted unbalanced. | | `report_open_balances` | Open balances, aged (current, 31-60, 61-90, over 90 days): side=receivable lists customers who owe you, side=payable lists vendors you owe. Each row's key is the contact id for report_account_lines. | | `report_profit_and_loss` | Profit and loss for a date range: income, cost of sales, expenses and other items by account, with gross profit and net income, one column per month or quarter. Amounts are in the org's home currency. Only booked (confirmed or synced) transactions count. Use report_account_lines to see what's behind a number. | | `report_qbo_account_lines` | One QuickBooks account's register between two dates (default the last 30 days): each line's date, type, QuickBooks id, name, memo and amount, and the Solarium event it is (or none, if the books don't have it). Use it next to report_account_lines to find where a bank, card or loan differs. Read only. | | `report_sales_tax` | The sales tax report for a period, in the form the org's country files: in Canada the GST/HST return (collected, input tax credits, net owing or refund, filing deadline) plus any PST/QST return; in the US sales tax collected by state; elsewhere tax collected and claimed back by tax. Only booked transactions count, and cards not booked yet are reported as a warning. | | `report_spending_by_vendor` | Spending by vendor for a date range, one column per month or quarter, largest first. Each vendor's key and the account_ids go to report_account_lines (as vendor and account_ids) to list its transactions. | | `send_feedback` | Tell the Solarium team when you can't do what the user asked with these tools, or something looks wrong: a tool you needed that doesn't exist, a missing field or option, a result that didn't match the books or QuickBooks, an error, or a description that misled you. Say what you were trying to do and what would have helped. It doesn't change the books. Don't include card numbers, passwords or other secrets. | | `unmapped_card_numbers` | List card numbers read off receipts that belong to no payment account (read-only). | ### Tools That Change Your Books | Tool | What it does | | --- | --- | | `attach_receipt_to_event` | Attach an existing unmatched receipt to an already-booked event (moves it — no duplicate receipt row). If the event is in QuickBooks, the document is pushed as an attachable after commit. | | `book_deduction` | Book a retailer/distributor deduction: DR expense line(s) / CR the clearing account. From an existing pre-confirm event, an unmatched receipt (promoted first), or new (merchant + txn_date). | | `book_invoice` | Book a customer invoice: DR A/R (gross, that customer) / CR revenue (net) / CR sales tax. | | `book_processor_income` | Book income received through a payment processor (e.g. PayPal) with its fee split out: CR revenue (gross) / DR fees / DR processor (net). Foreign currency uses the processor's own converted amounts. | | `book_receipt` | Book a receipt in one call: make it a transaction, set its category and card (defaults to the card read off the receipt), tax (tax_amount, or tax_included to work it out at your rate), and a note. It stays in review unless confirm is true, which also runs confirm_event's QuickBooks checks. | | `book_reclass` | Reclassify an amount: DR to_account / CR from_account, posted as a journal entry. | | `book_remittance` | Book a retailer/distributor remittance: DR bank (net) + DR discount line(s) / CR the clearing account (gross). Rolled-in coupon events are ignored with a note so they aren't booked twice. | | `book_vendor_credit` | Book a vendor's credit memo (damaged goods, short shipment, price fix) against what the business owes them: DR Accounts Payable / CR the category (net) / CR the input tax credit. In QuickBooks it becomes a VendorCredit for that vendor, which is applied when the bill is paid. Give the QuickBooks bill_id it credits (its vendor, A/P account and category are used), or a vendor and category. Pass the positive amount from the memo and its tax. Refuses if QuickBooks already has a credit for that vendor and amount. | | `categorize_event` | Set a review item's category and payment account (tax kept unless tax_amount is given), or make it a transfer between two accounts (from_account_id / to_account_id). | | `confirm_event` | Book a review item and send it to QuickBooks. Refuses when an account isn't in QuickBooks, or when QuickBooks already has the same transaction (same amount within 3 days, or a bill with the same invoice number or vendor within 45) — set allow_qbo_match only after checking it's really different. | | `confirm_high_confidence` | Confirm NEEDS_REVIEW events that pass every high-confidence check (including a QuickBooks duplicate check). | | `create_category` | Create a category (account) from its tax code — its type follows the code — and add it to QuickBooks with the right subtype, or link the QuickBooks account that already has that name. Pass account_type instead of tax_code only where your country has no tax codes. | | `create_transaction` | Add a transaction by hand (a cash expense, a missing card charge, a transfer). It goes to review, not the books: confirm it with confirm_event. With category_id and payment_account_id the lines are staged (for a transfer, payment_account_id is from and category_id is to); tax_amount adds a tax line. Warns about a likely duplicate already in Solarium. | | `daily_receipts` | Run the daily receipts routine: classify every unmatched receipt (attach / promote / dismiss / review), apply attach + promote, and report the rest. With auto_confirm=true, high-confidence promotions are also confirmed (booked, and sent to QuickBooks when connected); off by default. | | `deactivate_category` | Hide a category you no longer use, in Solarium and QuickBooks. Refuses while it has a balance, unbooked transactions use it (category, card or any split line), it has active sub-accounts, or vendor rules file into it. | | `dismiss_receipts` | Dismiss unmatched receipts that aren't receipts (logos, signatures, duplicates). | | `edit_event` | Change a review item's date, memo, title or type. | | `import_qbo_transaction` | Bring one QuickBooks transaction that's missing from the books into Solarium (for example a journal entry the import skipped), through the same path as the nightly sync: synced when every account maps, else in review, and linked to an existing event that's the same transaction. Refuses one already in the books. | | `import_stripe_balance` | Import a Stripe account's activity from Stripe's export: charges as revenue, Stripe's processing fees (their sales tax as an input tax credit), refunds, Connect transfers, and payouts to the bank. Accepts the Balance summary report's itemized CSVs (Reports > Balance: 'Balance change from activity' and 'Payouts', each Export > CSV > Itemized) or the older balance history export. Nothing booked twice: anything already in the books is skipped, a payout already on the bank feed is linked. Ends by tying the Stripe account to the export; if it doesn't tie, nothing is written. Preview first, then apply. | | `link_to_qbo_entity` | Link a needs review event to the QuickBooks transaction that already records it (booked directly in QuickBooks, never imported), then rebuild its entry from QuickBooks and attach its receipts there. | | `merge_events` | Fold a duplicate (donor_id) into the one to keep (keep_id): receipts, evidence and bank links move over and the duplicate is archived. If the duplicate is already in QuickBooks, it's removed there first. | | `promote_receipts` | Promote unmatched receipts to NEEDS_REVIEW events with full enrichment (vendor, rules, dedup, tax). | | `push_receipts_to_qbo` | Attach an event's receipts to its QuickBooks transaction (skips ones already there). | | `push_to_qbo` | Send a confirmed transaction that never reached QuickBooks. QuickBooks is checked first for the same amount around that date (it may have been entered there by hand); a match refuses unless check_qbo is false. Refuses anything already in QuickBooks or kept in Solarium only. Reads the result back. | | `qbo_remove_attachment` | Remove one attachment (like a duplicate PDF) from a QuickBooks transaction. The transaction itself isn't changed. Refuses to remove the last attachment unless allow_last is set. When a Solarium receipt is that file, pass points_to: the attachment still on the transaction that holds the same document. | | `qbo_set_purchase_tax` | Repair a synced purchase whose QuickBooks total doesn't match the books because the sales tax was dropped or rounded differently: put its expense lines on the given QuickBooks tax code so QuickBooks' total and tax equal the books exactly (where QuickBooks' rounding can't land on the books' tax, a cent or two moves to an untaxed line). Every line is echoed by id, and it's read back. Use get_qbo_transaction first to see the lines and tax codes. | | `recategorize_event` | Rebook a confirmed event's type and DR/CR lines in place (rebuilds its JE + tax), e.g. income that was really a customer payment. Refused for events already in QuickBooks (void + rebook there instead). | | `recategorize_events` | Move many booked transactions to another category at once, in Solarium and QuickBooks. Pick them by event_ids, or by from_category_id (optionally narrowed by vendor and date_from / date_to). The preview lists which would move and which would be refused (e.g. changed in QuickBooks since the last sync). | | `record_customer_payment` | Record a customer paying an invoice: DR bank / CR A/R (that customer). Never touches revenue. Optionally rebooks the bank-feed deposit event for the payment. | | `reject_event` | Set a review item aside (it won't be booked), recording why. Booked items can't be rejected. | | `replace_with_qbo_twin` | Replace a needs review event with the copy QuickBooks already has: its receipts move to the imported event, the review event is archived, and the document is pushed to QuickBooks. Finds the twin when not given. Amounts must match unless accept_difference is true: then QuickBooks' amount is kept and the gap is reported. Check the document first (get_receipt) to see which amount is right. | | `restate_event_fx` | Record that a booked (or synced) charge was really in another currency — e.g. 455.50 USD for the 648.27 CAD the card charged — keeping the home-currency amount booked. Nothing in the ledger or QuickBooks changes; only the foreign amount, currency and the card's actual rate are recorded. To change the booked amount too, use the edit drawer. | | `resync_event_from_qb` | Rebuild events from their QuickBooks copy when Solarium has drifted (QuickBooks is only read). Checks the entry balances and moves the bank or card by the QuickBooks total. | | `set_card_last_four` | Record a card number on a card or bank account, then file receipts waiting on that number and fill the payment account on their needs review events. | | `set_event_fx` | Say a review item was in another currency: original_currency (e.g. USD), optionally original_amount (the foreign amount, if it differs) and charged_amount (what your card or bank charged in your home currency, which keeps their rate including fees). Without charged_amount the Bank of Canada rate is used. | | `update_category` | Rename a category, and/or set its QuickBooks subtype from its tax code (fix_subtype), in Solarium and QuickBooks together — so the nightly sync doesn't change it back. | ### Every Tool In Detail #### attach_receipt_to_event Attach an existing unmatched receipt to an already-booked event (moves it — no duplicate receipt row). If the event is in QuickBooks, the document is pushed as an attachable after commit. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `receipt_id` | `string` | Yes | | | `event_id` | `string` | Yes | | #### audit_qbo_totals List synced transactions whose QuickBooks total or sales tax differs from the books, between two dates (default the last 90 days), largest first, with the difference per payment account (a card or bank). Also lists any that are gone from QuickBooks. Read only. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `since` | `string` | No | | | `until` | `string` | No | | | `limit` | `integer` | No | How many to list. Default 100. | #### book_deduction Book a retailer/distributor deduction: DR expense line(s) / CR the clearing account. From an existing pre-confirm event, an unmatched receipt (promoted first), or new (merchant + txn_date). *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `clearing_account_id` | `string` | Yes | | | `expense_lines` | `array` | Yes | | | `source_event_id` | `string` | No | | | `source_receipt_id` | `string` | No | | | `merchant` | `string` | No | | | `txn_date` | `string` | No | | | `notes` | `string` | No | | | `check_qbo` | `boolean` | No | | | `dedup_key` | `string` | No | | | `push` | `boolean` | No | | #### book_invoice Book a customer invoice: DR A/R (gross, that customer) / CR revenue (net) / CR sales tax. *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `ar_account_id` | `string` | Yes | | | `revenue_account_id` | `string` | Yes | | | `contact_id` | `string` | Yes | | | `net` | `string` | Yes | | | `tax_lines` | `array` | No | | | `txn_date` | `string` | Yes | | | `reference` | `string` | Yes | Invoice number / memo (also the dedup key) | #### book_processor_income Book income received through a payment processor (e.g. PayPal) with its fee split out: CR revenue (gross) / DR fees / DR processor (net). Foreign currency uses the processor's own converted amounts. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `revenue_account_id` | `string` | Yes | | | `fee_account_id` | `string` | Yes | | | `processor_account_id` | `string` | Yes | | | `gross` | `string` | Yes | Gross in the org's functional currency | | `fee` | `string` | Yes | Fee in the org's functional currency | | `txn_date` | `string` | Yes | | | `payer` | `string` | Yes | | | `processor_txn_id` | `string` | Yes | The processor's transaction id (dedup key) | | `original_currency` | `string` | No | | | `original_gross` | `string` | No | | | `check_qbo` | `boolean` | No | | | `push` | `boolean` | No | | #### book_receipt Book a receipt in one call: make it a transaction, set its category and card (defaults to the card read off the receipt), tax (tax_amount, or tax_included to work it out at your rate), and a note. It stays in review unless confirm is true, which also runs confirm_event's QuickBooks checks. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `receipt_id` | `string` | Yes | | | `category_id` | `string` | Yes | | | `payment_account_id` | `string` | No | | | `tax_amount` | `string` | No | | | `tax_included` | `boolean` | No | The total includes sales tax at the standard rate | | `tax_code` | `string` | No | | | `note` | `string` | No | | | `confirm` | `boolean` | No | | #### book_reclass Reclassify an amount: DR to_account / CR from_account, posted as a journal entry. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `from_account_id` | `string` | Yes | Account credited (amount moves out) | | `to_account_id` | `string` | Yes | Account debited (amount moves in) | | `amount` | `string` | Yes | | | `txn_date` | `string` | Yes | | | `memo` | `string` | Yes | | | `dedup_key` | `string` | No | | | `push` | `boolean` | No | | #### book_remittance Book a retailer/distributor remittance: DR bank (net) + DR discount line(s) / CR the clearing account (gross). Rolled-in coupon events are ignored with a note so they aren't booked twice. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `bank_account_id` | `string` | Yes | | | `clearing_account_id` | `string` | Yes | | | `gross` | `string` | Yes | | | `discount_lines` | `array` | No | | | `txn_date` | `string` | Yes | | | `memo` | `string` | Yes | | | `source_event_id` | `string` | No | The bank-feed deposit event for the net (optional) | | `rolled_in_event_ids` | `array` | No | | | `check_qbo` | `boolean` | No | | | `dedup_key` | `string` | No | | | `push` | `boolean` | No | | #### book_vendor_credit Book a vendor's credit memo (damaged goods, short shipment, price fix) against what the business owes them: DR Accounts Payable / CR the category (net) / CR the input tax credit. In QuickBooks it becomes a VendorCredit for that vendor, which is applied when the bill is paid. Give the QuickBooks bill_id it credits (its vendor, A/P account and category are used), or a vendor and category. Pass the positive amount from the memo and its tax. Refuses if QuickBooks already has a credit for that vendor and amount. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `amount` | `string` | Yes | The credit's total including tax, positive (69.18). | | `reference` | `string` | Yes | The vendor's credit memo number (9967152). | | `bill_id` | `string` | No | QuickBooks Bill Id it credits (find_qbo_matches or get_qbo_transaction). | | `vendor` | `string` | No | QuickBooks vendor name, when there's no bill_id. | | `category_account_id` | `string` | No | Defaults to the bill's category. | | `tax_amount` | `string` | No | Tax on the credit (0 for zero-rated goods). | | `sales_tax_code` | `string` | No | hst, gst, pst or qst (when taxed). | | `txn_date` | `string` | Yes | The credit memo's date. | | `description` | `string` | No | What it's for, like damaged sugar. | | `receipt_id` | `string` | No | The credit memo's receipt, attached to it. | | `check_qbo` | `boolean` | No | Refuse if QuickBooks has it already. Default true. | #### categorize_event Set a review item's category and payment account (tax kept unless tax_amount is given), or make it a transfer between two accounts (from_account_id / to_account_id). *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | | `category_id` | `string` | No | | | `payment_account_id` | `string` | No | | | `tax_amount` | `string` | No | Total tax on it; '0' removes tax | | `tax_code` | `string` | No | e.g. hst, gst (from list_categories / the tax line) | | `from_account_id` | `string` | No | | | `to_account_id` | `string` | No | | #### compare_with_qbo Compare Solarium's books with QuickBooks', account by account, on a date (default today): QuickBooks' trial balance next to Solarium's, with the difference. Amounts are debit minus credit; P&L accounts are the fiscal year to date. Lists QuickBooks accounts Solarium doesn't have. Read only. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `as_of` | `string` | No | | | `only_differences` | `boolean` | No | Default true. | #### confirm_event Book a review item and send it to QuickBooks. Refuses when an account isn't in QuickBooks, or when QuickBooks already has the same transaction (same amount within 3 days, or a bill with the same invoice number or vendor within 45) — set allow_qbo_match only after checking it's really different. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | | `allow_qbo_match` | `boolean` | No | | #### confirm_high_confidence Confirm NEEDS_REVIEW events that pass every high-confidence check (including a QuickBooks duplicate check). *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_ids` | `array` | No | | | `since_days` | `integer` | No | Only events created in the last N days | | `limit` | `integer` | No | | #### create_category Create a category (account) from its tax code — its type follows the code — and add it to QuickBooks with the right subtype, or link the QuickBooks account that already has that name. Pass account_type instead of tax_code only where your country has no tax codes. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `name` | `string` | Yes | | | `tax_code` | `string` | No | e.g. 8320 purchases (see list_categories) | | `account_type` | `string` | No | | | `parent_id` | `string` | No | | #### create_transaction Add a transaction by hand (a cash expense, a missing card charge, a transfer). It goes to review, not the books: confirm it with confirm_event. With category_id and payment_account_id the lines are staged (for a transfer, payment_account_id is from and category_id is to); tax_amount adds a tax line. Warns about a likely duplicate already in Solarium. *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `vendor` | `string` | Yes | Who it was paid to or received from. | | `amount` | `string` | Yes | The total in home currency, like 42.50. | | `date` | `string` | Yes | | | `event_type` | `expense`, `fee`, `fee_rebate`, `fraud_dispute`, `income`, `loan_proceeds`, `loan_repayment`, `owner_contribution`, `owner_draw`, `personal_holding`, `refund`, `tax_payment`, `transfer` | No | Defaults to expense. | | `category_id` | `string` | No | The category account (or the to-account). | | `payment_account_id` | `string` | No | The bank or card (or the from-account). | | `tax_amount` | `string` | No | Sales tax included in amount. | | `tax_code` | `string` | No | The tax code, like HST ON. | #### daily_receipts Run the daily receipts routine: classify every unmatched receipt (attach / promote / dismiss / review), apply attach + promote, and report the rest. With auto_confirm=true, high-confidence promotions are also confirmed (booked, and sent to QuickBooks when connected); off by default. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `since_days` | `integer` | No | Receipts created in the last N days (default 14) | | `auto_confirm` | `boolean` | No | Also book high-confidence promotions (default false). | #### deactivate_category Hide a category you no longer use, in Solarium and QuickBooks. Refuses while it has a balance, unbooked transactions use it (category, card or any split line), it has active sub-accounts, or vendor rules file into it. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `account_id` | `string` | Yes | | #### dismiss_receipts Dismiss unmatched receipts that aren't receipts (logos, signatures, duplicates). *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `receipt_ids` | `array` | Yes | | | `reason` | `string` | Yes | | #### edit_event Change a review item's date, memo, title or type. *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | | `transaction_date` | `string` | No | YYYY-MM-DD | | `memo` | `string` | No | | | `title` | `string` | No | | | `event_type` | `expense`, `fee`, `fee_rebate`, `fraud_dispute`, `income`, `loan_proceeds`, `loan_repayment`, `owner_contribution`, `owner_draw`, `personal_holding`, `refund`, `tax_payment`, `transfer` | No | | #### find_qbo_matches Find the QuickBooks transactions something probably already is: same amount within 3 days, or a bill / vendor credit with the same invoice number or vendor within 45 days, with their attachments and whether Solarium has them. Pass a receipt_id, an event_id, or the raw facts (amount + date, optionally vendor, doc_number, currency) for something not in Solarium yet. Flags likely double bills. Read-only. *Read-only, reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `receipt_id` | `string` | No | | | `event_id` | `string` | No | | | `amount` | `string` | No | The total, like 455.50 (with date, instead of an id). | | `date` | `string` | No | | | `vendor` | `string` | No | | | `doc_number` | `string` | No | The invoice or bill number. | | `currency` | `string` | No | Three letters, like USD. Defaults to home currency. | #### get_connection Who this connection is: the organization's name and id, who connected it, what it may do (look only, or look and make changes), and a stable account id for this person in this organization. Read-only. *Read-only.* #### get_event One transaction in full: its lines (staged and booked), receipts (amount, currency, card, line items, in QuickBooks; get_receipt for a receipt's full text and file), FX, duplicate and closed-period flags, notes, and its QuickBooks link and sync state. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | #### get_qbo_account One QuickBooks account as QuickBooks has it: name, type, subtype, active, balance and SyncToken, plus the Solarium account mapped to it. Pass qb_id or name. Read-only. *Read-only, reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `qb_id` | `string` | No | | | `name` | `string` | No | | #### get_qbo_transaction Read one QuickBooks transaction: its total, tax, date, payee, lines (ids, accounts, tax codes, received from) and attachments, plus the Solarium event linked to it. Read-only. *Read-only, reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `entity` | `Bill`, `Purchase`, `Deposit`, `JournalEntry`, `SalesReceipt`, `VendorCredit`, `BillPayment`, `Payment`, `Invoice`, `Transfer`, `CreditCardPaymentTxn`, `RefundReceipt`, `CreditMemo` | Yes | | | `qb_id` | `string` | Yes | The QuickBooks transaction id | #### get_receipt One receipt or invoice in full: its text as read, line items (freight, fees and tax lines included), invoice number, card, payment method, totals, the transaction it's on and whether it's in QuickBooks, plus a link to the file (valid for an hour). Use it to check a document against a bill or a charge, line by line. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `receipt_id` | `string` | Yes | | #### import_qbo_transaction Bring one QuickBooks transaction that's missing from the books into Solarium (for example a journal entry the import skipped), through the same path as the nightly sync: synced when every account maps, else in review, and linked to an existing event that's the same transaction. Refuses one already in the books. *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `entity` | `Bill`, `Purchase`, `Deposit`, `JournalEntry`, `SalesReceipt`, `VendorCredit`, `BillPayment`, `Payment`, `Invoice`, `Transfer`, `CreditCardPaymentTxn`, `RefundReceipt`, `CreditMemo` | Yes | | | `qb_id` | `string` | Yes | The QuickBooks transaction id | #### import_stripe_balance Import a Stripe account's activity from Stripe's export: charges as revenue, Stripe's processing fees (their sales tax as an input tax credit), refunds, Connect transfers, and payouts to the bank. Accepts the Balance summary report's itemized CSVs (Reports > Balance: 'Balance change from activity' and 'Payouts', each Export > CSV > Itemized) or the older balance history export. Nothing booked twice: anything already in the books is skipped, a payout already on the bank feed is linked. Ends by tying the Stripe account to the export; if it doesn't tie, nothing is written. Preview first, then apply. *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `csv` | `string` | Yes | The export's text (Balance change from activity, or history). | | `payouts_csv` | `string` | No | The Payouts itemized export, with the Balance summary. | | `stripe_account_id` | `string` | Yes | The bank-type account that is Stripe's balance. | | `bank_account_id` | `string` | Yes | Where Stripe pays out to. | | `revenue_account_id` | `string` | Yes | Where charges go (sales). | | `fees_account_id` | `string` | Yes | Where Stripe's fees go. | | `transfers_account_id` | `string` | No | Connect platforms: where transfers to connected accounts go (cost of sales). | | `allow_window_mismatch` | `boolean` | No | Book even if Stripe's total over the export's days doesn't tie (exports that overlap). | #### link_to_qbo_entity Link a needs review event to the QuickBooks transaction that already records it (booked directly in QuickBooks, never imported), then rebuild its entry from QuickBooks and attach its receipts there. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | | `entity` | `Bill`, `Purchase`, `VendorCredit`, `Deposit`, `SalesReceipt` | Yes | | | `qb_id` | `string` | Yes | | #### list_categories The chart of accounts: each account's id, name, type, tax code, card number and QuickBooks mapping. Filter by account_type; inactive accounts only with include_inactive. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `account_type` | `string` | No | | | `include_inactive` | `boolean` | No | | #### list_contacts Customers and vendors: id, name, type, email, phone and balance. Filter by contact_type (customer or vendor) or name (contains). Use a contact's id with book_invoice, record_customer_payment or report_account_lines. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `contact_type` | `customer`, `vendor` | No | | | `name` | `string` | No | Part of the name. | | `include_inactive` | `boolean` | No | | | `limit` | `integer` | No | At most 200 (default 50). | | `offset` | `integer` | No | | #### list_events Find transactions, newest first. Filter by status, category_id, payment_account_id, payment_account_type (credit_card, bank…), vendor (contains), date_from / date_to, amount_min / amount_max (home currency), and has_document (whether a receipt is attached). Ignored transactions are left out unless status=ignored or include_ignored. Page with limit and offset. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `status` | `ingested`, `enriched`, `auto_confirmed`, `awaiting_confirmation`, `needs_review`, `confirmed`, `syncing`, `synced`, `reconciled`, `pending`, `ignored`, `error`, `disputed` | No | | | `category_id` | `string` | No | An account id from list_categories. | | `payment_account_id` | `string` | No | The bank or card account id. | | `payment_account_type` | `string` | No | credit_card, bank, and so on. | | `vendor` | `string` | No | Part of the vendor name. | | `date_from` | `string` | No | | | `date_to` | `string` | No | | | `amount_min` | `string` | No | Home-currency amount, like 25.00. | | `amount_max` | `string` | No | Home-currency amount, like 250.00. | | `has_document` | `boolean` | No | Only with (true) or without (false) a receipt. | | `include_ignored` | `boolean` | No | Also list ignored transactions. | | `limit` | `integer` | No | At most 200 (default 50). | | `offset` | `integer` | No | Skip this many (for the next page). | #### list_op_runs The history of operations applied to these books: who ran what, when, from where (chat, API, command), what it changed (including QuickBooks writes and whether they checked out), or why it was refused. Newest first. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `op` | `string` | No | | | `limit` | `integer` | No | | #### list_receipts Receipts and invoices sent in (by email, text, upload or Drive). By default only those not on any transaction yet and not dismissed: the ones to attach, book (promote_receipts) or dismiss. unmatched=false lists all. Filter by vendor (contains) and date_from / date_to. Newest first. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `unmatched` | `boolean` | No | Only receipts on no transaction (default true). | | `vendor` | `string` | No | | | `date_from` | `string` | No | | | `date_to` | `string` | No | | | `limit` | `integer` | No | At most 200 (default 50). | | `offset` | `integer` | No | | #### merge_events Fold a duplicate (donor_id) into the one to keep (keep_id): receipts, evidence and bank links move over and the duplicate is archived. If the duplicate is already in QuickBooks, it's removed there first. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `keep_id` | `string` | Yes | | | `donor_id` | `string` | Yes | | #### promote_receipts Promote unmatched receipts to NEEDS_REVIEW events with full enrichment (vendor, rules, dedup, tax). *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `receipt_ids` | `array` | Yes | | #### push_receipts_to_qbo Attach an event's receipts to its QuickBooks transaction (skips ones already there). *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | #### push_to_qbo Send a confirmed transaction that never reached QuickBooks. QuickBooks is checked first for the same amount around that date (it may have been entered there by hand); a match refuses unless check_qbo is false. Refuses anything already in QuickBooks or kept in Solarium only. Reads the result back. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | | `check_qbo` | `boolean` | No | Look for it in QuickBooks first. Default true. | #### qbo_remove_attachment Remove one attachment (like a duplicate PDF) from a QuickBooks transaction. The transaction itself isn't changed. Refuses to remove the last attachment unless allow_last is set. When a Solarium receipt is that file, pass points_to: the attachment still on the transaction that holds the same document. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `entity` | `Bill`, `Purchase`, `Deposit`, `JournalEntry`, `SalesReceipt`, `VendorCredit`, `BillPayment`, `Payment`, `Invoice`, `Transfer`, `CreditCardPaymentTxn`, `RefundReceipt`, `CreditMemo` | Yes | | | `qb_id` | `string` | Yes | The QuickBooks transaction id | | `attachable_id` | `string` | Yes | | | `allow_last` | `boolean` | No | | | `points_to` | `string` | No | The remaining attachment that is the same document as the receipt being removed. | #### qbo_set_purchase_tax Repair a synced purchase whose QuickBooks total doesn't match the books because the sales tax was dropped or rounded differently: put its expense lines on the given QuickBooks tax code so QuickBooks' total and tax equal the books exactly (where QuickBooks' rounding can't land on the books' tax, a cent or two moves to an untaxed line). Every line is echoed by id, and it's read back. Use get_qbo_transaction first to see the lines and tax codes. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | | `tax_code` | `string` | Yes | QuickBooks tax code name or id, like H or HST ON. | #### recategorize_event Rebook a confirmed event's type and DR/CR lines in place (rebuilds its JE + tax), e.g. income that was really a customer payment. Refused for events already in QuickBooks (void + rebook there instead). *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | | `event_type` | `expense`, `fee`, `fee_rebate`, `fraud_dispute`, `income`, `loan_proceeds`, `loan_repayment`, `owner_contribution`, `owner_draw`, `personal_holding`, `refund`, `tax_payment`, `transfer` | Yes | | | `lines` | `array` | Yes | | | `contact_id` | `string` | No | | | `reason` | `string` | Yes | | #### recategorize_events Move many booked transactions to another category at once, in Solarium and QuickBooks. Pick them by event_ids, or by from_category_id (optionally narrowed by vendor and date_from / date_to). The preview lists which would move and which would be refused (e.g. changed in QuickBooks since the last sync). *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `to_category_id` | `string` | Yes | | | `event_ids` | `array` | No | | | `from_category_id` | `string` | No | | | `vendor` | `string` | No | | | `date_from` | `string` | No | | | `date_to` | `string` | No | | #### reconcile_card_statement Hold a card or bank statement up against the books (read-only). Pass the statement as rows (date, description, amount, is_debit, optional foreign_amount + foreign_currency; without is_debit a negative amount is a credit) or an uploaded PDF (s3_key). Returns matches, FX corrections (with the set_event_fx to run), lines missing from the books, transactions not on the statement, possible doubles, and foreign-transaction fees. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `account_id` | `string` | Yes | The card or bank account (see list_categories) | | `rows` | `array` | No | | | `s3_key` | `string` | No | | | `date_from` | `string` | No | | | `date_to` | `string` | No | | #### record_customer_payment Record a customer paying an invoice: DR bank / CR A/R (that customer). Never touches revenue. Optionally rebooks the bank-feed deposit event for the payment. *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `bank_account_id` | `string` | Yes | | | `ar_account_id` | `string` | Yes | | | `contact_id` | `string` | Yes | | | `amount` | `string` | Yes | | | `txn_date` | `string` | Yes | | | `reference` | `string` | Yes | | | `source_event_id` | `string` | No | The bank-feed deposit event (pre-confirm) | | `allow_overpayment` | `boolean` | No | | #### reject_event Set a review item aside (it won't be booked), recording why. Booked items can't be rejected. *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | | `reason` | `string` | Yes | | #### replace_with_qbo_twin Replace a needs review event with the copy QuickBooks already has: its receipts move to the imported event, the review event is archived, and the document is pushed to QuickBooks. Finds the twin when not given. Amounts must match unless accept_difference is true: then QuickBooks' amount is kept and the gap is reported. Check the document first (get_receipt) to see which amount is right. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | | `qbo_event_id` | `string` | No | | | `accept_difference` | `boolean` | No | Replace even though the amounts differ, keeping QuickBooks' amount (default false). | #### report_account_lines The booked lines behind a report number: every line posted to these accounts in the range, with the transaction (event_id) each came from. Narrow to one vendor (a report_spending_by_vendor key) or one contact (a contact id, or 'none'). Capped at 500 lines (truncated says so). *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `account_ids` | `array` | Yes | Account ids to list. | | `start_date` | `string` | No | First day, YYYY-MM-DD. Omit for all time. | | `end_date` | `string` | No | Last day, YYYY-MM-DD. Omit for all time. | | `vendor` | `string` | No | A vendor key from report_spending_by_vendor. | | `contact` | `string` | No | A contact id, or 'none' for lines with no contact. | #### report_balance_sheet Balance sheet as of a date: assets, liabilities and equity by account (net income to date included), optionally side by side with compare_as_of. out_of_balance is zero unless something posted unbalanced. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `as_of` | `string` | Yes | The balance sheet date, YYYY-MM-DD. | | `compare_as_of` | `string` | No | An earlier date to compare against. | #### report_open_balances Open balances, aged (current, 31-60, 61-90, over 90 days): side=receivable lists customers who owe you, side=payable lists vendors you owe. Each row's key is the contact id for report_account_lines. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `side` | `payable`, `receivable` | Yes | receivable or payable. | | `as_of` | `string` | Yes | Age balances as of this date, YYYY-MM-DD. | #### report_profit_and_loss Profit and loss for a date range: income, cost of sales, expenses and other items by account, with gross profit and net income, one column per month or quarter. Amounts are in the org's home currency. Only booked (confirmed or synced) transactions count. Use report_account_lines to see what's behind a number. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `start_date` | `string` | Yes | First day, YYYY-MM-DD. | | `end_date` | `string` | Yes | Last day, YYYY-MM-DD (inclusive). | | `granularity` | `month`, `quarter` | No | One column per month (default) or per quarter. | #### report_qbo_account_lines One QuickBooks account's register between two dates (default the last 30 days): each line's date, type, QuickBooks id, name, memo and amount, and the Solarium event it is (or none, if the books don't have it). Use it next to report_account_lines to find where a bank, card or loan differs. Read only. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `account_id` | `string` | Yes | The Solarium account (mapped to QuickBooks). | | `since` | `string` | No | | | `until` | `string` | No | | #### report_sales_tax The sales tax report for a period, in the form the org's country files: in Canada the GST/HST return (collected, input tax credits, net owing or refund, filing deadline) plus any PST/QST return; in the US sales tax collected by state; elsewhere tax collected and claimed back by tax. Only booked transactions count, and cards not booked yet are reported as a warning. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `start_date` | `string` | Yes | First day, YYYY-MM-DD. | | `end_date` | `string` | Yes | Last day, YYYY-MM-DD (inclusive). | #### report_spending_by_vendor Spending by vendor for a date range, one column per month or quarter, largest first. Each vendor's key and the account_ids go to report_account_lines (as vendor and account_ids) to list its transactions. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `start_date` | `string` | Yes | First day, YYYY-MM-DD. | | `end_date` | `string` | Yes | Last day, YYYY-MM-DD (inclusive). | | `granularity` | `month`, `quarter` | No | One column per month (default) or per quarter. | #### restate_event_fx Record that a booked (or synced) charge was really in another currency — e.g. 455.50 USD for the 648.27 CAD the card charged — keeping the home-currency amount booked. Nothing in the ledger or QuickBooks changes; only the foreign amount, currency and the card's actual rate are recorded. To change the booked amount too, use the edit drawer. *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | | `original_currency` | `string` | Yes | | | `original_amount` | `string` | Yes | | | `charged_amount` | `string` | No | Optional check: must equal the booked amount | #### resync_event_from_qb Rebuild events from their QuickBooks copy when Solarium has drifted (QuickBooks is only read). Checks the entry balances and moves the bank or card by the QuickBooks total. *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_ids` | `array` | Yes | | #### send_feedback Tell the Solarium team when you can't do what the user asked with these tools, or something looks wrong: a tool you needed that doesn't exist, a missing field or option, a result that didn't match the books or QuickBooks, an error, or a description that misled you. Say what you were trying to do and what would have helped. It doesn't change the books. Don't include card numbers, passwords or other secrets. *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `missing_tool`, `wrong_result`, `error`, `confusing`, `other` | Yes | | | `message` | `string` | Yes | What you were trying to do, what was missing or wrong, and what would have helped. | | `tool` | `string` | No | The tool it's about, if any. | | `example` | `string` | No | The call you made or wanted to make, and what came back. | #### set_card_last_four Record a card number on a card or bank account, then file receipts waiting on that number and fill the payment account on their needs review events. *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `account_id` | `string` | Yes | | | `last_four` | `string` | Yes | | #### set_event_fx Say a review item was in another currency: original_currency (e.g. USD), optionally original_amount (the foreign amount, if it differs) and charged_amount (what your card or bank charged in your home currency, which keeps their rate including fees). Without charged_amount the Bank of Canada rate is used. *Changes your books (previews unless apply is true).* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `event_id` | `string` | Yes | | | `original_currency` | `string` | Yes | | | `original_amount` | `string` | No | | | `charged_amount` | `string` | No | | #### unmapped_card_numbers List card numbers read off receipts that belong to no payment account (read-only). *Read-only.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `since_days` | `integer` | No | | #### update_category Rename a category, and/or set its QuickBooks subtype from its tax code (fix_subtype), in Solarium and QuickBooks together — so the nightly sync doesn't change it back. *Changes your books (previews unless apply is true), reaches QuickBooks.* | Argument | Type | Required | Description | | --- | --- | --- | --- | | `account_id` | `string` | Yes | | | `name` | `string` | No | | | `fix_subtype` | `boolean` | No | | ## Example ``` # Claude Code claude mcp add --transport http solarium https://mcp.trysolarium.com/mcp # What your AI app sends (JSON-RPC over POST https://mcp.trysolarium.com/mcp) { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "report_profit_and_loss", "arguments": { "start_date": "2026-01-01", "end_date": "2026-09-30", "granularity": "quarter" } } } ``` ## Example Response ``` { "jsonrpc": "2.0", "id": 1, "result": { "isError": false, "structuredContent": { "op": "report_profit_and_loss", "applied": false, "summary": "Net Income 41250.18 CAD (2026-01-01 To 2026-09-30).", "data": { "periods": ["…"], "sections": ["…"], "net_income": { "total": "41250.18" } } } } } ``` # Quickstart The Solarium API is GraphQL. Every request is a `POST` to `https://api.trysolarium.com/graphql/` with your API key, and every query and mutation takes an `organizationId`. This walks through booking one transaction from start to finish. Want your AI app to do this instead? [Use With AI](/docs/ai) connects Claude or ChatGPT in a minute, with no code. ## 1. Get An API Key Create one in [Settings > API Keys](https://admin.trysolarium.com/app/settings/api-keys). Choose **Look Only** to read, or **Look And Make Changes** to book transactions. The key is shown once, so keep it somewhere safe. See [Authentication](/docs/authentication). ## 2. Find Your Organization A key belongs to one organization. Ask for its id: ```bash curl -X POST https://api.trysolarium.com/graphql/ \ -H "Authorization: Bearer sol_your_key_here" \ -H "Content-Type: application/json" \ -d '{"query": "{ myOrganizations { id name } }"}' ``` ```json { "data": { "myOrganizations": [{ "id": "a1b2c3d4-...", "name": "My Company Inc." }] } } ``` The rest of this page uses a small Python helper. Any HTTP client works the same way. ```python import httpx API_URL = "https://api.trysolarium.com/graphql/" API_KEY = "sol_your_key_here" def query(q, variables=None): resp = httpx.post(API_URL, json={"query": q, "variables": variables or {}}, headers={"Authorization": f"Bearer {API_KEY}"}) return resp.json()["data"] org_id = query("{ myOrganizations { id name } }")["myOrganizations"][0]["id"] ``` ## 3. Look Up Your Accounts A transaction needs a category (what it was for) and a payment account (the card or bank it came from). Both are accounts. ```python accounts = query(""" query($orgId: UUID!) { listAccounts(organizationId: $orgId, limit: 50) { items { id name accountType } } } """, {"orgId": org_id}) ``` ## 4. Create A Transaction It lands in Review, waiting to be categorized and confirmed. ```python created = query(""" mutation($orgId: UUID!) { createLedgerEvent(organizationId: $orgId, merchantName: "Staples Canada", amount: "42.50", transactionDate: "2026-01-15", eventType: "expense") { success eventId } } """, {"orgId": org_id}) event_id = created["createLedgerEvent"]["eventId"] ``` ## 5. Categorize It Set the category and the payment account, using ids from step 3. ```python query(""" mutation($orgId: UUID!, $eventId: UUID!) { updateStagedEntry(organizationId: $orgId, eventId: $eventId, categoryId: "category-account-uuid", paymentId: "payment-account-uuid") { success } } """, {"orgId": org_id, "eventId": event_id}) ``` ## 6. Confirm It Confirming posts it to the books, and to QuickBooks when it's connected. ```python query(""" mutation($orgId: UUID!, $eventId: UUID!) { confirmLedgerEvent(organizationId: $orgId, eventId: $eventId) { success journalEntryId } } """, {"orgId": org_id, "eventId": event_id}) ``` ## Give An Agent The API For an AI agent that can't add a connector, paste these instructions into it with your key in place of `sol_your_key_here`. Keys you create in the dashboard come with these filled in. ```text You have access to the Solarium accounting API. Endpoint: https://api.trysolarium.com/graphql/ Method: POST with JSON body {"query": "...", "variables": {...}} Auth header: Authorization: Bearer sol_your_key_here First, find the organization: query { myOrganizations { id name } } Use its id as organizationId on every call. Transactions go: create, then categorize, then confirm. listLedgerEvents(organizationId, limit, status, search, dateFrom, dateTo) createLedgerEvent(organizationId, merchantName, amount, transactionDate, eventType) updateStagedEntry(organizationId, eventId, categoryId, paymentId) confirmLedgerEvent(organizationId, eventId) listAccounts(organizationId, limit, search) listContacts(organizationId, limit, search) incomeStatement(organizationId, startDate, endDate) importBankStatement(organizationId, paymentAccountId, rows: [...]) Amounts are strings ("42.50"). Dates are YYYY-MM-DD. Event types: "expense", "income", "transfer". Full docs as Markdown: https://trysolarium.com/llms.txt ``` ## Next Steps - [Transactions](/docs/transactions): every way to list, edit and confirm them. - [Reports](/docs/reports): profit and loss, balance sheet and aging. - [Errors](/docs/errors): what a failed request returns. # Authentication Authenticate by passing your API key as a Bearer token in the Authorization header. API keys are created in the Solarium dashboard at **Settings > API Keys**. API keys start with `sol_` and are shown once at creation. Store them securely. Only the SHA-256 hash is stored on our end. API keys are scoped to a single organization. You cannot access another organization's data with an API key. Admin operations (managing members, creating other API keys) are not available via API key. ### Rate Limiting API key requests are limited to **5,000 requests per hour** per API key. The limit resets on a rolling 1-hour window from the first request. Every API key response includes rate limit headers: - `X-RateLimit-Limit`: your hourly quota (5000) - `X-RateLimit-Remaining`: requests left in the current window If you exceed the limit, requests return **429 Too Many Requests** with a `Retry-After` header. JWT-authenticated requests (dashboard sessions) are not rate limited. ## Example ``` # API Key auth (recommended for integrations) curl -X POST https://api.trysolarium.com/graphql/ \ -H "Authorization: Bearer sol_abc123..." \ -H "Content-Type: application/json" \ -d '{"query": "{ myOrganizations { id name } }"}' # JWT auth (for user sessions) curl -X POST https://api.trysolarium.com/graphql/ \ -H "Authorization: JWT eyJhbGciOiJIUzI1NiIs..." \ -H "Content-Type: application/json" \ -d '{"query": "{ myOrganizations { id name } }"}' ``` ## Example Response ``` # Unauthenticated request { "errors": [ { "message": "Authentication Required." } ] } # Wrong Organization { "errors": [ { "message": "API Key Not Authorized For This Organization." } ] } # Rate limit exceeded (429) { "errors": [ { "message": "Rate Limit Exceeded. 5,000 Requests/Hour." } ] } # Headers: Retry-After: 60 ``` # Bank Import Import bank statement rows as transactions. Each row becomes a LedgerEvent with status needs_review. Use checkBankStatementDups first to detect duplicates. ## Check Bank Statement Duplicates Check rows against existing transactions before importing. Returns per-row duplicate flags. Full match = same date + amount + vendor. Possible match = same date + amount, different vendor. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `paymentAccountId` | `UUID!` | Yes | Bank or credit card account to check against | | `rows` | `[BankStatementRowInput!]!` | Yes | Array of statement rows to check. Each row: transactionDate (String!), description (String!), amount (String!), eventType (String!), memo (String), reference (String) | ### Request ```graphql mutation { checkBankStatementDups( organizationId: "a1b2c3d4-..." paymentAccountId: "chequing-uuid" rows: [ { transactionDate: "2025-03-01" description: "SHOPIFY *MAPLE ROASTERS" amount: "149.50" eventType: "expense" }, { transactionDate: "2025-03-02" description: "TIM HORTONS #4821" amount: "11.30" eventType: "expense" memo: "Team coffee run" } ] ) { results { rowIndex isDuplicate isPossibleDuplicate matchingEventId } success } } ``` ### Response ```json { "data": { "checkBankStatementDups": { "results": [ { "rowIndex": 0, "isDuplicate": false, "isPossibleDuplicate": false, "matchingEventId": null }, { "rowIndex": 1, "isDuplicate": true, "isPossibleDuplicate": false, "matchingEventId": "d8e9f0a1-..." } ], "success": true } } } ``` ## Import Bank Statement Imports bank statement rows as LedgerEvents attached to a payment account. Each row is created with status needs_review. Rows that exactly match existing transactions (same date, amount, and vendor) are skipped automatically. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `paymentAccountId` | `UUID!` | Yes | Bank or credit card account to import into | | `currency` | `String` | No | ISO 4217 currency code (default "CAD") | | `rows` | `[BankStatementRowInput!]!` | Yes | Array of statement rows. Each row: transactionDate (String!), description (String!), amount (String!), eventType (String!), memo (String), reference (String) | ### Request ```graphql mutation { importBankStatement( organizationId: "a1b2c3d4-..." paymentAccountId: "chequing-uuid" currency: "CAD" rows: [ { transactionDate: "2025-03-01" description: "SHOPIFY *MAPLE ROASTERS" amount: "149.50" eventType: "expense" reference: "TXN-20250301-001" }, { transactionDate: "2025-03-01" description: "E-TRANSFER FROM CLAIRE DUBOIS" amount: "2500.00" eventType: "income" memo: "Invoice #1042 payment" }, { transactionDate: "2025-03-03" description: "CANADA POST CORP" amount: "18.75" eventType: "expense" memo: "Shipping label — order #887" reference: "CP-88712345" } ] ) { importedCount skippedCount success error } } ``` ### Response ```json { "data": { "importBankStatement": { "importedCount": 3, "skippedCount": 0, "success": true, "error": null } } } ``` # Intake Addresses ## List Intake Addresses Returns all registered email addresses and phone numbers that route receipts to the organization. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | ### Request ```graphql query { listIntakeAddresses(organizationId: "a1b2c3d4-...") { id addressType address label active created } } ``` ### Response ```json { "data": { "listIntakeAddresses": [ { "id": "e1f2a3b4-...", "addressType": "email", "address": "accounting@company.com", "label": "Main Office", "active": true, "created": "2026-05-12T10:30:00Z" }, { "id": "c5d6e7f8-...", "addressType": "sms", "address": "14165551234", "label": "Owner", "active": true, "created": "2026-05-12T10:00:00Z" } ] } } ``` ## Create Intake Address Register a new email address or phone number for receipt intake. Each address is globally unique — it can only belong to one organization. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `addressType` | `String!` | Yes | "email" or "sms" | | `address` | `String!` | Yes | Email address or phone number (digits only for SMS) | | `label` | `String` | No | Optional display label (e.g. 'Main Office') | ### Request ```graphql mutation { createIntakeAddress( organizationId: "a1b2c3d4-..." addressType: "email" address: "receipts@company.com" label: "Shared Inbox" ) { intakeAddress { id addressType address label } success error } } ``` ### Response ```json { "data": { "createIntakeAddress": { "intakeAddress": { "id": "f9a8b7c6-...", "addressType": "email", "address": "receipts@company.com", "label": "Shared Inbox" }, "success": true, "error": null } } } ``` ## Delete Intake Address Remove a registered intake address. Receipts from this address will no longer route to the organization. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `intakeAddressId` | `UUID!` | Yes | Intake address ID to remove | ### Request ```graphql mutation { deleteIntakeAddress( organizationId: "a1b2c3d4-..." intakeAddressId: "f9a8b7c6-..." ) { success error } } ``` ### Response ```json { "data": { "deleteIntakeAddress": { "success": true, "error": null } } } ``` # Category Rules Auto-categorization rules map vendor name patterns to accounts. When a transaction's merchant name matches a rule's pattern, Solarium automatically fills in the expense category during import and review. Rules are deterministic and auditable — they complement the AI agent's suggestions with a predictable, user-controlled layer. ## List Category Rules List auto-categorization rules. Rules map vendor name patterns to accounts. When a transaction's merchant matches a pattern, Solarium auto-fills the category. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `limit` | `Int` | No | Page size (default 25, max 200) | | `offset` | `Int` | No | Number of records to skip | | `search` | `String` | No | Filter by vendor pattern (case-insensitive) | | `sortBy` | `String` | No | Field to sort by (e.g. vendorPattern, matchCount) | | `sortDir` | `String` | No | asc or desc (default asc) | ### Request ```graphql query { listCategoryRules( organizationId: "a1b2c3d4-..." limit: 10 search: "tim" ) { items { id vendorPattern account { id name } matchCount } totalCount pageSize offset } } ``` ### Response ```json { "data": { "listCategoryRules": { "items": [ { "id": "r1a2b3c4-...", "vendorPattern": "%TIM HORTONS%", "account": { "id": "f7e6d5c4-...", "name": "Meals & Entertainment" }, "matchCount": 34 }, { "id": "r5d6e7f8-...", "vendorPattern": "%TIMMIES%", "account": { "id": "f7e6d5c4-...", "name": "Meals & Entertainment" }, "matchCount": 3 } ], "totalCount": 2, "pageSize": 10, "offset": 0 } } } ``` ## Create a Category Rule Create an auto-categorization rule. The vendorPattern is matched case-insensitively against merchant names. Use SQL ILIKE patterns (% for wildcard). ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `vendorPattern` | `String!` | Yes | ILIKE pattern to match vendor names (e.g. "%SHOPIFY%", "%CANADA POST%") | | `categoryId` | `UUID!` | Yes | Account ID to auto-assign when pattern matches | ### Request ```graphql mutation { createCategoryRule( organizationId: "a1b2c3d4-..." vendorPattern: "%PETRO-CANADA%" categoryId: "vehicle-expense-uuid" ) { categoryRule { id vendorPattern account { id name } } success error } } ``` ### Response ```json { "data": { "createCategoryRule": { "categoryRule": { "id": "r9a8b7c6-...", "vendorPattern": "%PETRO-CANADA%", "account": { "id": "vehicle-expense-uuid", "name": "Vehicle Expenses" } }, "success": true, "error": null } } } ``` # Country & Tax Setup Every organization books under one country's rules. Canada has GST/HST, PST and QST with input tax credits and CRA GIFI account codes. The United States has sales tax collected per state and IRS form lines as account codes, chosen by business type. Any other supported country uses its own VAT- or GST-style rates and no account codes. The home currency is set at signup and can't change once transactions are booked. ## List Countries Countries an organization can book in, each with its default home currency (empty when it must be picked from the supported currencies). ### Request ```graphql query { countries { code name currency } } ``` ### Response ```json { "data": { "countries": [ { "code": "CA", "name": "Canada", "currency": "CAD" }, { "code": "US", "name": "United States", "currency": "USD" }, { "code": "GB", "name": "United Kingdom", "currency": "GBP" }, { "code": "NZ", "name": "New Zealand", "currency": "" } ] } } ``` ## Country Options A country's region picker, currency, account code system and capabilities before an organization exists. The same shape is on every organization as jurisdiction. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `country` | `String` | No | ISO country code (default CA) | ### Request ```graphql query { jurisdictionOptions(country: "US") { country currency regionLabel regions { code name } codeSystem codeLabel businessTypes { value label } capabilities } } ``` ### Response ```json { "data": { "jurisdictionOptions": { "country": "US", "currency": "USD", "regionLabel": "State", "regions": [{ "code": "AL", "name": "Alabama" }, { "code": "AK", "name": "Alaska" }], "codeSystem": "us_sch_c", "codeLabel": "IRS Line", "businessTypes": [ { "value": "sch_c", "label": "Sole proprietor or single-member LLC (Schedule C)" }, { "value": "1065", "label": "Partnership or multi-member LLC (Form 1065)" }, { "value": "1120", "label": "C corporation (Form 1120)" }, { "value": "1120s", "label": "S corporation (Form 1120-S)" } ], "capabilities": ["sales_tax_by_state"] } } } ``` ## Update Country Settings For an organization outside Canada: its region, tax registration number, and either its own sales tax rates (generic countries) or its business type (US). The currency (generic countries) and the business type (US) can only change before any transactions are booked. Changing the business type re-codes every account to the new form's lines. Requires the owner or admin role. A Canadian organization's province and filing frequency are set with updateOrganization. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `region` | `String` | No | US: a state code. Elsewhere: free text | | `currency` | `String` | No | Generic countries, before any transactions | | `taxRates` | `[TaxRateInput!]` | No | Generic countries: replaces the rates. Each has name, ratePercent and recoverable (tax paid on purchases is claimed back) | | `registrationNumber` | `String` | No | Sales tax registration number | | `businessType` | `String` | No | US, before any transactions: sch_c, 1065, 1120 or 1120s | ### Request ```graphql mutation { updateJurisdiction( organizationId: "a1b2c3d4-..." taxRates: [ { name: "VAT", ratePercent: "20", recoverable: true } { name: "Reduced VAT", ratePercent: "5", recoverable: true } ] ) { success error jurisdiction { currency taxRates { code name ratePercent recoverable } } } } ``` ### Response ```json { "data": { "updateJurisdiction": { "success": true, "error": null, "jurisdiction": { "currency": "GBP", "taxRates": [ { "code": "vat", "name": "VAT", "ratePercent": "20", "recoverable": true }, { "code": "reduced_vat", "name": "Reduced VAT", "ratePercent": "5", "recoverable": true } ] } } } } ``` # Stripe Import Your Stripe balance should match your books. This import makes sure it does. It books your charges, Stripe's fees, refunds and payouts. Then it checks that the Stripe account in your books moved by exactly what Stripe says. If it doesn't, nothing is written. It's free on every plan. ## Step 1: Export your activity from Stripe 1. In the Stripe Dashboard, open **Reports**, then **Balance** ([dashboard.stripe.com/reports/balance](https://dashboard.stripe.com/reports/balance)). 2. Pick the date range. Start the day after your last import. Stripe's reports only include whole days. 3. In the **Balance change from activity** section, click **Export**, choose **CSV**, then **Itemized**. 4. In the **Payouts** section, click **Export**, choose **CSV**, then **Itemized**. Payouts are their own section, so this is a second file. Optional: when you export, add the **source_id** column. It lets the import match each charge to the same charge anywhere else in your books. Stripe's guide to this report: [Balance summary report](https://docs.stripe.com/reports/balance). ## Step 2: Ask your AI With Solarium connected to your AI (see [Use With AI](/docs/ai)), attach both files and ask: > Import these Stripe exports into Solarium. Show me the preview first. It asks which accounts to use: your Stripe account, the bank Stripe pays out to, your sales account and your fees account. Then it shows the preview. ## Step 3: Check the preview, then book it The preview lists every transaction it would book and ends with one line: > Stripe Oct 1 To Oct 15: Books 562.68, Stripe's Net 562.68, Difference 0.00 If the difference is 0.00, tell it to go ahead. The new transactions land in **Review**. Confirm them there to post them. ## What it books | Stripe activity | In your books | |---|---| | A charge | Sales, into your Stripe account | | Stripe's processing fees | One fee per day, the exact sum of that day's charge fees | | Stripe's own fees (billing, Connect) | Fees, with the sales tax Stripe charges as an input tax credit | | A refund | Taken off sales | | A payout | Stripe to your bank. If your bank feed already has it, it's linked, not booked twice. | | A Connect transfer | The account you name for payouts to connected accounts | ## What it checks - **Nothing twice.** Anything already in your books is skipped, from any source. That includes a charge entered by hand and a payout already on your bank feed. - **Fees tie to charges.** Each day, your charges minus their fees must equal what Stripe kept. - **Every charge arrives.** Each day's charges in your books must match Stripe's count and total. - **Stripe ties.** The Stripe account in your books must move by exactly Stripe's net for those days. If any check fails, it tells you why and writes nothing. Charges without a customer go to your sales account. If you've confirmed a charge of the same amount before, it uses that one's category and tax. The older **balance history** export works too. ## Ask Your AI Attach both Stripe files and ask in plain words. It previews first. ### Request ```graphql Import these Stripe exports into Solarium. Show me the preview first. [balance_change_from_activity.csv] [payouts.csv] ``` ### Response ```json Preview: 34 transactions from Stripe, Oct 1 to Oct 15. 2026-10-08 income $ 4.99 Stripe Revenue → Sales 2026-10-08 expense $ 0.88 Stripe Processing Fees → Merchant Fees 2026-10-08 expense $ 4.52 Stripe Fee → Merchant Fees Payout po_1Qa… 1,800.00 is on your bank feed: linked Stripe Oct 1 To Oct 15: Books 562.68, Stripe's Net 562.68, Difference 0.00 It ties. Book it? ``` ## The MCP Tool What your AI calls: import_stripe_balance. Run it with apply false to preview. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `csv` | `String` | Yes | The Balance change from activity export (or the older balance history export). | | `payouts_csv` | `String` | No | The Payouts export, with the Balance summary report. | | `stripe_account_id` | `ID` | Yes | The account that is your Stripe balance. | | `bank_account_id` | `ID` | Yes | The bank Stripe pays out to. | | `revenue_account_id` | `ID` | Yes | Where charges go. | | `fees_account_id` | `ID` | Yes | Where Stripe's fees go. | | `transfers_account_id` | `ID` | No | Connect platforms only: where transfers to connected accounts go. | | `allow_window_mismatch` | `Boolean` | No | Book even if Stripe doesn't tie over the export's days. Only when two exports overlap. | ### Request ```graphql { "name": "import_stripe_balance", "arguments": { "apply": false, "csv": "balance_transaction_id,created,...", "payouts_csv": "balance_transaction_id,effective_at,...", "stripe_account_id": "7b2e…", "bank_account_id": "0c41…", "revenue_account_id": "a9f3…", "fees_account_id": "55d8…" } } ``` ### Response ```json { "summary": "34 From Stripe 2026-10-01 To 2026-10-15; Stripe In The Books Moved 562.68 Against Stripe's 562.68 (Difference 0.00).", "data": { "difference": "0.00", "not_booked": { "on the bank feed": 1, "already booked": 2 } }, "warnings": ["The New Transactions Are In Review: Confirm Them To Post."] } ``` # Venn / Bank CSV Import Import bank transaction CSVs (Venn, TD, or any bank) into Solarium as transactions via the **importBankStatement** API or the CSV import page in the admin UI. The CSV import flow: 1. Upload CSV on the Import page 2. Map columns (date, description, amount) 3. Select payment account 4. Preview rows with duplicate detection 5. Import — each row becomes a LedgerEvent with `needs_review` status | Transaction Type | Event Type | |---|---| | Card payments (expenses) | expense | | Loads / funding | transfer | | Interest / cashback | income | Duplicate detection runs automatically: exact matches (same date + amount + vendor) are flagged as duplicates, partial matches (same date + amount, different vendor) as possible duplicates. ## CSV Import via API Use the importBankStatement mutation to import rows programmatically. ### Request ```graphql mutation { importBankStatement( organizationId: "a1b2c3d4-..." paymentAccountId: "bank-account-uuid" currency: "CAD" rows: [ { transactionDate: "2026-06-19" description: "MVC INC." amount: "-23.55" eventType: "expense" }, { transactionDate: "2026-06-13" description: "Account Funding" amount: "500.00" eventType: "income" } ] ) { importedCount skippedCount success error } } ``` ### Response ```json { "data": { "importBankStatement": { "importedCount": 2, "skippedCount": 0, "success": true, "error": null } } } ``` ## Check Duplicates Before Import Run checkBankStatementDups first to detect duplicates. Returns per-row flags so you can skip or override. ### Request ```graphql mutation { checkBankStatementDups( organizationId: "a1b2c3d4-..." paymentAccountId: "bank-account-uuid" rows: [ { transactionDate: "2026-06-19" description: "MVC INC." amount: "23.55" eventType: "expense" } ] ) { results { rowIndex isDuplicate isPossibleDuplicate matchingEventId } success } } ``` ### Response ```json { "data": { "checkBankStatementDups": { "results": [ { "rowIndex": 0, "isDuplicate": true, "isPossibleDuplicate": false, "matchingEventId": "e5f6g7h8-..." } ], "success": true } } } ``` # Transactions Transactions (LedgerEvents) are the core entity in Solarium. Each transaction flows through a lifecycle: created → classified → confirmed → synced. Use createLedgerEvent for manual entries, or import via bank statement. Classify with updateStagedEntry (set category + payment account), then confirm to post to the books. ## List Transactions Returns a paginated, filterable list of ledger events. Supports status filtering, date ranges, amount ranges, account/category scoping, and full-text search. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `status` | `[String]` | No | Filter by status (needs_review, confirmed, synced, reconciled, pending, ignored) | | `hasDocument` | `Boolean` | No | Filter by whether a receipt/document is attached | | `sinceDays` | `Int` | No | Only return events from the last N days | | `dateFrom` | `String` | No | Start date filter (YYYY-MM-DD) | | `dateTo` | `String` | No | End date filter (YYYY-MM-DD) | | `amountMin` | `String` | No | Minimum amount filter (inclusive) | | `amountMax` | `String` | No | Maximum amount filter (inclusive) | | `accountIds` | `[UUID]` | No | Filter by expense/income account IDs | | `categoryIds` | `[UUID]` | No | Filter by category account IDs | | `search` | `String` | No | Full-text search on merchant name and memo | | `limit` | `Int` | No | Page size (default 25, max 200) | | `offset` | `Int` | No | Number of records to skip (default 0) | | `sortBy` | `String` | No | Field to sort by (e.g. transactionDate, functionalAmount) | | `sortDir` | `String` | No | asc or desc (default desc) | ### Request ```graphql query { listLedgerEvents( organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" status: ["needs_review"] dateFrom: "2026-01-01" dateTo: "2026-06-30" limit: 10 ) { items { id eventType merchantName functionalAmount functionalCurrency transactionDate status hasDocument account { id name } paymentAccount { id name } } totalCount pageSize offset } } ``` ### Response ```json { "data": { "listLedgerEvents": { "items": [ { "id": "b7c8d9e0-f1a2-3456-b789-cdef01234567", "eventType": "expense", "merchantName": "Staples Canada", "functionalAmount": "127.43", "functionalCurrency": "CAD", "transactionDate": "2026-06-18", "status": "needs_review", "hasDocument": true, "account": { "id": "d3e4f5a6-b7c8-9012-def0-123456789abc", "name": "Office Supplies" }, "paymentAccount": { "id": "e4f5a6b7-c8d9-0123-ef01-23456789abcd", "name": "TD Visa *8821" } }, { "id": "c8d9e0f1-a2b3-4567-c890-def012345678", "eventType": "expense", "merchantName": "Tim Hortons", "functionalAmount": "14.85", "functionalCurrency": "CAD", "transactionDate": "2026-06-17", "status": "needs_review", "hasDocument": false, "account": null, "paymentAccount": { "id": "e4f5a6b7-c8d9-0123-ef01-23456789abcd", "name": "TD Visa *8821" } } ], "totalCount": 43, "pageSize": 10, "offset": 0 } } } ``` ## Get a Transaction Returns the full detail for a single ledger event, including attached receipts, journal entry, and classification data. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `eventId` | `UUID!` | Yes | Ledger event ID | ### Request ```graphql query { getLedgerEvent( organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" eventId: "b7c8d9e0-f1a2-3456-b789-cdef01234567" ) { id eventType merchantName functionalAmount functionalCurrency transactionDate status memo hasDocument account { id name accountType } paymentAccount { id name } category { id name } journalEntry { id txnDate totalAmount } receipts { id filename s3Key uploadedAt } } } ``` ### Response ```json { "data": { "getLedgerEvent": { "id": "b7c8d9e0-f1a2-3456-b789-cdef01234567", "eventType": "expense", "merchantName": "Staples Canada", "functionalAmount": "127.43", "functionalCurrency": "CAD", "transactionDate": "2026-06-18", "status": "needs_review", "memo": "Printer paper and toner cartridges", "hasDocument": true, "account": { "id": "d3e4f5a6-b7c8-9012-def0-123456789abc", "name": "Office Supplies", "accountType": "expense" }, "paymentAccount": { "id": "e4f5a6b7-c8d9-0123-ef01-23456789abcd", "name": "TD Visa *8821" }, "category": { "id": "d3e4f5a6-b7c8-9012-def0-123456789abc", "name": "Office Supplies" }, "journalEntry": null, "receipts": [ { "id": "f0a1b2c3-d4e5-6789-a012-bcdef0123456", "filename": "staples-receipt-jun18.pdf", "s3Key": "receipts/a1b2c3d4/2026/06/staples-receipt-jun18.pdf", "uploadedAt": "2026-06-18T14:32:00Z" } ] } } } ``` ## Create a Transaction Creates a new ledger event (manual entry). The event starts in needs_review status and must be classified and confirmed before it posts to the books. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `merchantName` | `String!` | Yes | Vendor / merchant name | | `amount` | `String!` | Yes | Transaction amount (positive decimal string) | | `transactionDate` | `String!` | Yes | Date of transaction (YYYY-MM-DD) | | `eventType` | `String` | No | expense, income, or transfer (default expense) | | `categoryId` | `UUID` | No | Expense/income account to categorize under | | `paymentAccountId` | `UUID` | No | Bank/credit card account used for payment | | `taxAmount` | `String` | No | Tax portion of the amount (single-tax, e.g. HST/GST) | | `taxAccountId` | `UUID` | No | Tax account for the single-tax amount (e.g. HST Payable) | | `taxCode` | `String` | No | Tax code for the single-tax amount (e.g. HST_ON) | | `taxComponents` | `JSONString` | No | Multi-tax entry — array of { sales_tax_code, amount } (e.g. GST + PST in BC). Overrides taxAmount/taxCode; the server resolves each component's account. | ### Request ```graphql mutation { createLedgerEvent( organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" merchantName: "Canada Post" amount: "34.95" transactionDate: "2026-06-20" eventType: "expense" categoryId: "a6b7c8d9-e0f1-2345-6789-abcdef012345" paymentAccountId: "e4f5a6b7-c8d9-0123-ef01-23456789abcd" ) { success eventId } } ``` ### Response ```json { "data": { "createLedgerEvent": { "success": true, "eventId": "d9e0f1a2-b3c4-5678-d901-ef0123456789" } } } ``` ## Update a Transaction Updates mutable fields on an existing ledger event. Only events in needs_review or pending status can be updated. To record a foreign-currency transaction, pass originalCurrency (and optionally chargedAmount) — see below. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `eventId` | `UUID!` | Yes | Ledger event ID | | `transactionDate` | `String` | No | Updated transaction date (YYYY-MM-DD) | | `memo` | `String` | No | Updated memo / description | | `amount` | `String` | No | Updated amount (positive decimal string). In the original currency when originalCurrency is set. | | `eventType` | `String` | No | Updated type: expense, income, or transfer | | `merchantName` | `String` | No | Updated vendor / merchant name | | `originalCurrency` | `String` | No | Original transaction currency (e.g. USD). Alone, the CAD value is derived from the Bank of Canada reference rate for the transaction date. | | `chargedAmount` | `String` | No | The actual functional-currency (CAD) amount charged. Combined with originalCurrency, derives the effective rate including any bank markup (fx_source=manual). | ### Request ```graphql mutation { updateLedgerEvent( organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" eventId: "b7c8d9e0-f1a2-3456-b789-cdef01234567" memo: "Office supplies — printer paper and toner" amount: "127.43" ) { success event { id memo functionalAmount status } } } ``` ### Response ```json { "data": { "updateLedgerEvent": { "success": true, "event": { "id": "b7c8d9e0-f1a2-3456-b789-cdef01234567", "memo": "Office supplies — printer paper and toner", "functionalAmount": "127.43", "status": "needs_review" } } } } ``` ## Confirm a Transaction Confirms a classified ledger event, posting it to the books by creating a journal entry. The event must have a category and payment account assigned before confirming. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `eventId` | `UUID!` | Yes | Ledger event ID to confirm | ### Request ```graphql mutation { confirmLedgerEvent( organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" eventId: "b7c8d9e0-f1a2-3456-b789-cdef01234567" ) { success eventId journalEntryId } } ``` ### Response ```json { "data": { "confirmLedgerEvent": { "success": true, "eventId": "b7c8d9e0-f1a2-3456-b789-cdef01234567", "journalEntryId": "a2b3c4d5-e6f7-8901-ab23-cdef45678901" } } } ``` ## Reject a Transaction Rejects a ledger event, moving it to ignored status. Rejected events are excluded from the books and review queue. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `eventId` | `UUID!` | Yes | Ledger event ID to reject | ### Request ```graphql mutation { rejectLedgerEvent( organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" eventId: "c8d9e0f1-a2b3-4567-c890-def012345678" ) { success eventId } } ``` ### Response ```json { "data": { "rejectLedgerEvent": { "success": true, "eventId": "c8d9e0f1-a2b3-4567-c890-def012345678" } } } ``` ## Classify Transaction Assigns classification data to a staged ledger event. Supports three modes: intent (category + payment, server builds the balanced lines — with optional single or multi-component tax), transfer (two owned accounts, no category), and raw (client sends pre-built double-entry lines). This is the primary way to prepare a transaction for confirmation. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `eventId` | `UUID!` | Yes | Ledger event ID to classify | | `categoryId` | `UUID` | No | Intent mode: expense or income account ID | | `paymentId` | `UUID` | No | Intent mode: bank/credit card payment account ID | | `taxAmount` | `String` | No | Single-tax: tax portion of the amount (e.g. HST/GST) | | `taxAccountId` | `UUID` | No | Single-tax: tax liability account ID (e.g. HST Payable) | | `taxCode` | `String` | No | Single-tax: tax code identifier (e.g. HST_ON, GST, exempt) | | `taxComponents` | `JSONString` | No | Multi-tax: array of { sales_tax_code, amount } (e.g. GST + PST). Amounts are used as-is; the server resolves each component's account. Overrides taxAmount/taxCode. | | `fromAccountId` | `UUID` | No | Transfer mode: the owned account money moves out of | | `toAccountId` | `UUID` | No | Transfer mode: the owned account money moves into | | `accounts` | `JSONString` | No | Raw mode: pre-built balanced lines — array of { account_id, debit, credit, description, sales_tax_code }. Must balance (total debits = total credits). | | `qbObjectType` | `String` | No | Raw mode: the QuickBooks object type the lines represent (e.g. journal_entry, purchase, deposit). | ### Request ```graphql mutation { updateStagedEntry( organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" eventId: "c8d9e0f1-a2b3-4567-c890-def012345678" categoryId: "f5a6b7c8-d9e0-1234-5678-9abcdef01234" paymentId: "e4f5a6b7-c8d9-0123-ef01-23456789abcd" taxAmount: "1.93" taxAccountId: "a0b1c2d3-e4f5-6789-0abc-def123456789" taxCode: "HST_ON" ) { success } } ``` ### Response ```json { "data": { "updateStagedEntry": { "success": true } } } ``` ## Attach Receipt to Transaction Attaches an uploaded receipt (image or PDF) to a ledger event. The file must already be uploaded to S3 — pass the s3Key returned from the upload endpoint. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `eventId` | `UUID!` | Yes | Ledger event ID to attach the receipt to | | `s3Key` | `String!` | Yes | S3 object key of the uploaded receipt file | | `filename` | `String` | No | Original filename for display (e.g. receipt.pdf) | ### Request ```graphql mutation { attachReceiptToEvent( organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" eventId: "b7c8d9e0-f1a2-3456-b789-cdef01234567" s3Key: "receipts/a1b2c3d4/2026/06/canadapost-shipping-label.pdf" filename: "canadapost-shipping-label.pdf" ) { success receiptId } } ``` ### Response ```json { "data": { "attachReceiptToEvent": { "success": true, "receiptId": "b1c2d3e4-f5a6-7890-bcde-f01234567890" } } } ``` # Accounts ## List Accounts Returns a paginated list of accounts in the chart of accounts. Supports search by name and sorting. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `limit` | `Int` | No | Page size (default 25, max 200) | | `offset` | `Int` | No | Number of records to skip | | `search` | `String` | No | Filter by name (case-insensitive) | | `sortBy` | `String` | No | Field to sort by (e.g. name, currentBalance) | | `sortDir` | `String` | No | asc or desc (default asc) | ### Request ```graphql query { listAccounts( organizationId: "a1b2c3d4-..." limit: 10 search: "expense" ) { items { id name accountType classification subType currentBalance active } totalCount pageSize offset } } ``` ### Response ```json { "data": { "listAccounts": { "items": [ { "id": "f7e6d5c4-...", "name": "Office Supplies", "accountType": "expense", "classification": "expense", "subType": "", "currentBalance": 1250.00, "active": true } ], "totalCount": 24, "pageSize": 10, "offset": 0 } } } ``` ## Create an Account Creates a new account in the chart of accounts. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `name` | `String!` | Yes | Account name | | `taxCodeId` | `UUID!` | Yes | CRA Tax Code code ID. Determines account type and classification. Use listTaxCodes to get available codes. | | `lastFour` | `String` | No | Last 4 digits of card/account for payment matching | | `parentId` | `UUID` | No | Parent account ID for hierarchical accounts | ### Request ```graphql mutation { createAccount( organizationId: "a1b2c3d4-..." name: "Office Supplies" taxCodeId: "tax-code-uuid-for-8711" ) { account { id name accountType classification taxCode { code name } } success error } } ``` ### Response ```json { "data": { "createAccount": { "account": { "id": "f7e6d5c4-...", "name": "Office Supplies", "accountType": "expense", "classification": "expense", "taxCode": { "code": "8711", "name": "Office stationery and supplies" } }, "success": true, "error": null } } } ``` ## Add a Card Number Remembers another last-4 for a card (e.g. its Apple Pay number or a reissued card), so receipts showing any of the card's numbers land on it. Any waiting receipts that show this number are filed to the card. A number can belong to only one account. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `accountId` | `UUID!` | Yes | The card or bank account | | `lastFour` | `String!` | Yes | Exactly 4 digits | ### Request ```graphql mutation { addCardNumber(accountId: "c3d4e5f6-...", lastFour: "6659") { success error receiptsResolved account { name cardNumbers { lastFour source } } } } ``` ### Response ```json { "data": { "addCardNumber": { "success": true, "error": null, "receiptsResolved": 1, "account": { "name": "Personal CC", "cardNumbers": [ { "lastFour": "6859", "source": "account" }, { "lastFour": "6659", "source": "manual" } ] } } } } ``` ## Remove a Card Number Forgets a manually added card number. Numbers that come from a connected bank (Plaid) are managed by the bank connection and can't be removed here. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `accountId` | `UUID!` | Yes | The card or bank account | | `lastFour` | `String!` | Yes | The manually added number to remove | ### Request ```graphql mutation { removeCardNumber(accountId: "c3d4e5f6-...", lastFour: "6659") { success error } } ``` ### Response ```json { "data": { "removeCardNumber": { "success": true, "error": null } } } ``` # Contacts ## List Contacts Returns a paginated list of vendors and customers. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `limit` | `Int` | No | Page size (default 25, max 200) | | `offset` | `Int` | No | Number of records to skip | | `search` | `String` | No | Filter by name | ### Request ```graphql query { listContacts( organizationId: "a1b2c3d4-..." limit: 10 search: "uber" ) { items { id contactType displayName email phone balance active } totalCount } } ``` ### Response ```json { "data": { "listContacts": { "items": [ { "id": "b2c3d4e5-...", "contactType": "vendor", "displayName": "Uber", "email": "", "phone": "", "balance": 0, "active": true } ], "totalCount": 1 } } } ``` ## Create a Contact Creates a new vendor or customer. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `contactType` | `String!` | Yes | vendor or customer | | `displayName` | `String!` | Yes | Contact name | | `email` | `String` | No | Email address | | `phone` | `String` | No | Phone number | ### Request ```graphql mutation { createContact( organizationId: "a1b2c3d4-..." contactType: "vendor" displayName: "Staples" email: "billing@staples.com" ) { contact { id displayName contactType } success error } } ``` ### Response ```json { "data": { "createContact": { "contact": { "id": "c3d4e5f6-...", "displayName": "Staples", "contactType": "vendor" }, "success": true, "error": null } } } ``` # Receipts Receipts are documentary evidence attached to transactions. In Solarium V2, receipts don't create transactions directly — instead, you attach a receipt image to an existing transaction using `attachReceiptToEvent` (see [Transactions](#transactions)). Receipts can also arrive automatically via Gmail scanning or SMS/email intake addresses. These are matched to bank transactions automatically when possible, or appear as unmatched receipts for manual review. The `listReceipts` query below returns all receipt records, including their OCR-extracted data, matching status, and linked transaction. ## List Receipts Returns a paginated list of receipts with OCR data, vendor, amount, and linked transaction. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `limit` | `Int` | No | Page size (default 25) | | `offset` | `Int` | No | Number of records to skip | | `search` | `String` | No | Filter by vendor name | | `status` | `String` | No | Filter by status: draft, processed, reviewed, rejected | | `source` | `String` | No | Filter by source: web_upload, email, sms, gmail, google_drive | ### Request ```graphql query { listReceipts( organizationId: "a1b2c3d4-..." limit: 10 status: "draft" ) { items { id vendorName amount receiptDate status source imageUrl event { id merchantName status } } totalCount } } ``` ### Response ```json { "data": { "listReceipts": { "items": [ { "id": "r1a2b3c4-...", "vendorName": "Tim Hortons", "amount": 8.45, "receiptDate": "2026-01-15", "status": "draft", "source": "gmail", "imageUrl": "https://s3.amazonaws.com/...", "event": { "id": "e5f6g7h8-...", "merchantName": "Tim Hortons", "status": "needs_review" } } ], "totalCount": 12 } } } ``` # Reports ## Dashboard Summary High-level financial snapshot: revenue, expenses, net income, receipt counts for a date range. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `startDate` | `Date` | No | Start of range (defaults to start of current month) | | `endDate` | `Date` | No | End of range (defaults to today) | ### Request ```graphql query { dashboardSummary( organizationId: "a1b2c3d4-..." startDate: "2025-01-01" endDate: "2025-03-31" ) { totalRevenue totalExpenses netIncome receiptCount unreviewedCount } } ``` ### Response ```json { "data": { "dashboardSummary": { "totalRevenue": "84500.00", "totalExpenses": "52300.00", "netIncome": "32200.00", "receiptCount": 89, "unreviewedCount": 3 } } } ``` ## Income Statement Full profit & loss report broken down by account. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `startDate` | `Date` | No | Start of range | | `endDate` | `Date` | No | End of range | ### Request ```graphql query { incomeStatement( organizationId: "a1b2c3d4-..." startDate: "2025-01-01" endDate: "2025-03-31" ) { incomeAccounts { accountId accountName total } expenseAccounts { accountId accountName total } totalIncome totalExpenses netIncome } } ``` ### Response ```json { "data": { "incomeStatement": { "incomeAccounts": [ { "accountId": "...", "accountName": "Sales", "total": "84500.00" } ], "expenseAccounts": [ { "accountId": "...", "accountName": "Rent", "total": "18000.00" }, { "accountId": "...", "accountName": "Payroll", "total": "28000.00" }, { "accountId": "...", "accountName": "Office Supplies", "total": "6300.00" } ], "totalIncome": "84500.00", "totalExpenses": "52300.00", "netIncome": "32200.00" } } } ``` ## Profit & Loss By Period Profit & loss with one column per month or quarter. Accounts are grouped into Revenue, Cost of Goods Sold, Operating Expenses and Other Expenses; each line carries its amount per period, its total, and its share of its section. Amounts are natural-signed (revenue and expenses both positive) in the organization's functional currency. A single-period result always equals the Income Statement. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `startDate` | `Date!` | Yes | Start of range (YYYY-MM-DD); the first period is clipped to it | | `endDate` | `Date!` | Yes | End of range (YYYY-MM-DD); the last period is clipped to it | | `granularity` | `String` | No | "month" (default) or "quarter" | ### Request ```graphql query { profitAndLoss( organizationId: "a1b2c3d4-..." startDate: "2026-01-01" endDate: "2026-03-31" granularity: "month" ) { currency periods { key label startDate endDate } sections { key title lines { accountId accountName taxCode amounts total percent } totals total } grossProfit { amounts total } netIncome { amounts total } } } ``` ### Response ```json { "data": { "profitAndLoss": { "currency": "CAD", "periods": [ { "key": "2026-01", "label": "Jan 2026", "startDate": "2026-01-01", "endDate": "2026-01-31" }, { "key": "2026-02", "label": "Feb 2026", "startDate": "2026-02-01", "endDate": "2026-02-28" }, { "key": "2026-03", "label": "Mar 2026", "startDate": "2026-03-01", "endDate": "2026-03-31" } ], "sections": [ { "key": "revenue", "title": "Revenue", "lines": [ { "accountId": "...", "accountName": "Sales", "taxCode": "8000", "amounts": ["26500.00", "27100.00", "30900.00"], "total": "84500.00", "percent": "100.0" } ], "totals": ["26500.00", "27100.00", "30900.00"], "total": "84500.00" }, { "key": "operating_expenses", "title": "Operating Expenses", "lines": [ { "accountId": "...", "accountName": "Rent", "taxCode": "8910", "amounts": ["6000.00", "6000.00", "6000.00"], "total": "18000.00", "percent": "34.4" }, { "accountId": "...", "accountName": "Wages", "taxCode": "9060", "amounts": ["9300.00", "9300.00", "9400.00"], "total": "28000.00", "percent": "53.5" } ], "totals": ["15300.00", "15300.00", "15400.00"], "total": "46000.00" } ], "grossProfit": { "amounts": ["26500.00", "27100.00", "30900.00"], "total": "84500.00" }, "netIncome": { "amounts": ["11200.00", "11800.00", "15500.00"], "total": "38500.00" } } } } ``` ## Spending By Vendor Expenses grouped by vendor, one column per month or quarter. Uses the same posted expense lines as the Profit & Loss (Cost of Goods Sold, Operating and Other Expenses, before tax), so the grand total always equals the P&L's total expenses. A line belongs to its transaction's vendor, else the line's own contact (QuickBooks history), else its known business, else its cleaned bank text; anything left is "No Vendor" (key "none"). Vendors are sorted biggest first. To list one vendor's entries, call accountLines with the returned accountIds and vendor set to the row's key. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `startDate` | `Date!` | Yes | Start of range (YYYY-MM-DD) | | `endDate` | `Date!` | Yes | End of range (YYYY-MM-DD) | | `granularity` | `String` | No | "month" (default) or "quarter" | ### Request ```graphql query { spendingByVendor( organizationId: "a1b2c3d4-..." startDate: "2026-01-01" endDate: "2026-02-28" ) { currency periods { key label startDate endDate } vendors { key name logoUrl amounts total percent count } totals total accountIds } } ``` ### Response ```json { "data": { "spendingByVendor": { "currency": "CAD", "periods": [ { "key": "2026-01", "label": "Jan 2026", "startDate": "2026-01-01", "endDate": "2026-01-31" }, { "key": "2026-02", "label": "Feb 2026", "startDate": "2026-02-01", "endDate": "2026-02-28" } ], "vendors": [ { "key": "c:7f3e...", "name": "Hall Properties", "logoUrl": null, "amounts": ["900.00", "900.00"], "total": "1800.00", "percent": "62.1", "count": 2 }, { "key": "g:41aa...", "name": "Slack", "logoUrl": "https://...", "amounts": ["12.00", "12.00"], "total": "24.00", "percent": "0.8", "count": 2 }, { "key": "none", "name": "No Vendor", "logoUrl": null, "amounts": ["0", "40.00"], "total": "40.00", "percent": "1.4", "count": 1 } ], "totals": ["1430.00", "1470.00"], "total": "2900.00", "accountIds": ["..."] } } } ``` ## Receivables & Payables (Aged) Who owes what as of a date: customers who owe you (side "receivable", A/R accounts) or vendors you owe (side "payable", A/P accounts), grouped by the customer/vendor on each posted line. Aging is first-in, first-out: payments clear the oldest charges first and what remains is aged by its own date into 0–30, 31–60, 61–90 and over 90 days. A contact who has overpaid shows a negative balance in current. Lines with no contact are grouped under key "none". The grand total equals the accounts' balance-sheet balance. To list one row's entries, call accountLines with the returned accountIds, endDate = asOf and contact set to the row's key. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `side` | `String!` | Yes | "receivable" or "payable" | | `asOf` | `Date!` | Yes | Balances as of this date (YYYY-MM-DD) | ### Request ```graphql query { openBalances(organizationId: "a1b2c3d4-...", side: "receivable", asOf: "2026-04-15") { currency rows { key name current days31To60 days61To90 over90 total oldest } totals { current days31To60 days61To90 over90 total } accountIds } } ``` ### Response ```json { "data": { "openBalances": { "currency": "CAD", "rows": [ { "key": "7f3e...", "name": "Buduchnist", "current": "0", "days31To60": "0", "days61To90": "0", "over90": "200.00", "total": "200.00", "oldest": "2026-01-10" }, { "key": "91ab...", "name": "HMECU", "current": "0", "days31To60": "30.00", "days61To90": "0", "over90": "0", "total": "30.00", "oldest": "2026-03-01" } ], "totals": { "current": "0", "days31To60": "30.00", "days61To90": "0", "over90": "200.00", "total": "230.00" }, "accountIds": ["..."] } } } ``` ## Download Reports (Excel / CSV) Every report for a date range in one .xlsx workbook: Profit & Loss by period, Balance Sheet, Spending By Vendor, Receivables and Payables (balances as of endDate). Amounts are numeric cells in the organization's currency format, built from the same services as the queries above, so they always agree. With format "csv", returns just the one report named by report (pnl, balance-sheet, vendors, receivable, payable) as plain rows: amounts to the cent with no symbols, ISO dates. Returns the file name, content type and the file as base64. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `startDate` | `Date!` | Yes | Start of range (YYYY-MM-DD) | | `endDate` | `Date!` | Yes | End of range (YYYY-MM-DD); balance sheets are as of this date | | `granularity` | `String` | No | "month" (default) or "quarter" for the P&L and vendor sheets | | `format` | `String` | No | "xlsx" (default, every report) or "csv" (one report) | | `report` | `String` | No | For csv: pnl, balance-sheet, vendors, receivable or payable | ### Request ```graphql query { reportsExport(organizationId: "a1b2c3d4-...", startDate: "2026-01-01", endDate: "2026-03-31") { filename contentType contentBase64 } } ``` ### Response ```json { "data": { "reportsExport": { "filename": "Vanilla-Bite-Ltd-Reports-2026-01-01-to-2026-03-31.xlsx", "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "contentBase64": "UEsDBBQAAAAIA..." } } } ``` ## Balance Sheet Assets, liabilities and equity as of a date, optionally compared to a second date. Accounts are grouped by type (Current Assets, Fixed Assets, Current Liabilities, Long-Term Liabilities, Equity). Equity includes two computed lines derived from the P&L, the way QuickBooks derives retained earnings: "Prior Years' Earnings" (before Jan 1 of the as-of year, accountId prior_earnings) and "Net Income (Year To Date)" (accountId net_income). outOfBalance is assets minus liabilities and equity, and is zero for balanced books. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `asOf` | `Date!` | Yes | Balance date (YYYY-MM-DD), inclusive | | `compareAsOf` | `Date` | No | Optional second date; fills compareAmount / compareTotal | ### Request ```graphql query { balanceSheet( organizationId: "a1b2c3d4-..." asOf: "2026-03-31" compareAsOf: "2025-12-31" ) { asOf compareAsOf currency sections { key title groups { key title lines { accountId accountName taxCode amount compareAmount computed } total compareTotal } total compareTotal } liabilitiesAndEquity outOfBalance } } ``` ### Response ```json { "data": { "balanceSheet": { "asOf": "2026-03-31", "compareAsOf": "2025-12-31", "currency": "CAD", "sections": [ { "key": "assets", "title": "Assets", "groups": [ { "key": "current_assets", "title": "Current Assets", "lines": [ { "accountId": "...", "accountName": "Chequing", "taxCode": "1001", "amount": "24680.50", "compareAmount": "19450.00", "computed": false } ], "total": "24680.50", "compareTotal": "19450.00" } ], "total": "24680.50", "compareTotal": "19450.00" }, { "key": "equity", "title": "Equity", "groups": [ { "key": "equity", "title": "Equity", "lines": [ { "accountId": "...", "accountName": "Owner Contributions", "taxCode": "3500", "amount": "5000.00", "compareAmount": "5000.00", "computed": false }, { "accountId": "prior_earnings", "accountName": "Prior Years' Earnings", "taxCode": null, "amount": "14450.00", "compareAmount": "0.00", "computed": true }, { "accountId": "net_income", "accountName": "Net Income (Year To Date)", "taxCode": null, "amount": "5230.50", "compareAmount": "14450.00", "computed": true } ], "total": "24680.50", "compareTotal": "19450.00" } ], "total": "24680.50", "compareTotal": "19450.00" } ], "liabilitiesAndEquity": "24680.50", "outOfBalance": "0.00" } } } ``` ## Account Balances Current balance for every account in the chart of accounts. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | ### Request ```graphql query { accountBalances(organizationId: "a1b2c3d4-...") { accountId accountName accountType classification balance } } ``` ### Response ```json { "data": { "accountBalances": [ { "accountId": "...", "accountName": "Checking", "accountType": "bank", "classification": "asset", "balance": "24680.50" }, { "accountId": "...", "accountName": "Accounts Receivable", "accountType": "accounts_receivable", "classification": "asset", "balance": "12400.00" } ] } } ``` ## GST/HST Filing Period Canadian organizations. The organization's GST/HST filing period by its filing frequency (monthly, quarterly or annual — calendar months/quarters/years). offset 0 is the latest period that has already ended (the return you'd file now); -1 is the one before it, 1 is the period in progress. Returns the dates to pass to gstHstReturn plus the due date (null for annual filers, whose deadline depends on the filer). ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `offset` | `Int` | No | Periods from the latest ended one (default 0) | ### Request ```graphql query { filingPeriod(organizationId: "a1b2c3d4-...", offset: 0) { frequency label startDate endDate dueDate offset } } ``` ### Response ```json { "data": { "filingPeriod": { "frequency": "quarterly", "label": "Q3 2026", "startDate": "2026-07-01", "endDate": "2026-09-30", "dueDate": "2026-10-31", "offset": 0 } } } ``` ## GST/HST Return Canadian organizations. GST/HST return summary for a filing period: tax collected on sales, input tax credits (ITCs) claimable on expenses, ITC adjustments (e.g. the 50% meals & entertainment restriction), and the net amount owing or refundable. The province, rate, and filing frequency/deadline are derived from the organization's tax configuration. Each collected/ITC line carries its accountId and revenueAccountIds lists the revenue accounts, so accountLines can list the entries behind any line. Before filing, check unbookedCount / unbookedTotal (transactions in the period still in review — not counted yet) and receiptsMissingTaxCount (receipts in the period with no tax read). Use filingPeriod to get the period dates. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `startDate` | `Date!` | Yes | Start of the filing period (YYYY-MM-DD) | | `endDate` | `Date!` | Yes | End of the filing period (YYYY-MM-DD) | ### Request ```graphql query { gstHstReturn( organizationId: "a1b2c3d4-..." startDate: "2026-01-01" endDate: "2026-03-31" ) { province provinceName taxRate filingFrequency filingDeadline filingDeadlineDate collectedLines { accountId accountName amount } itcLines { accountId accountName amount } itcAdjustmentLines { accountName amount } revenueAccountIds totalRevenue totalCollected totalItcs totalItcAdjustment netClaimableItcs netOwing isRefund hasTaxAccounts unbookedCount unbookedTotal receiptsMissingTaxCount } } ``` ### Response ```json { "data": { "gstHstReturn": { "province": "ON", "provinceName": "Ontario", "taxRate": "13%", "filingFrequency": "quarterly", "filingDeadline": "2026-04-30", "filingDeadlineDate": "2026-04-30", "collectedLines": [ { "accountId": "5c1e...", "accountName": "HST Payable", "amount": "10985.00" } ], "itcLines": [ { "accountId": "9d2a...", "accountName": "GST/HST Receivable", "amount": "4212.55" } ], "revenueAccountIds": ["7f3e..."], "unbookedCount": 3, "unbookedTotal": "412.80", "receiptsMissingTaxCount": 2, "itcAdjustmentLines": [ { "accountName": "Meals & Entertainment (50%)", "amount": "-96.20" } ], "totalRevenue": "84500.00", "totalCollected": "10985.00", "totalItcs": "4212.55", "totalItcAdjustment": "-96.20", "netClaimableItcs": "4116.35", "netOwing": "6868.65", "isRefund": false, "hasTaxAccounts": true } } } ``` ## Provincial Sales Tax Return Provincial sales tax return for PST (BC/SK/MB) and QST (QC), returned as one section per tax component so an organization registered for more than one province files each separately. Each section reports tax collected, input tax credits (QST registrants only — PST is a cost, not recoverable), and the net owing or refundable for that agency. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `startDate` | `Date!` | Yes | Start of the filing period (YYYY-MM-DD) | | `endDate` | `Date!` | Yes | End of the filing period (YYYY-MM-DD) | ### Request ```graphql query { provincialSalesTaxReturn( organizationId: "a1b2c3d4-..." startDate: "2026-01-01" endDate: "2026-03-31" ) { hasAny totalRevenue sections { taxCode label agency collectedLines { accountName amount } totalCollected inputCreditLines { accountName amount } totalInputCredits netOwing isRefund hasInputCredits } } } ``` ### Response ```json { "data": { "provincialSalesTaxReturn": { "hasAny": true, "totalRevenue": "84500.00", "sections": [ { "taxCode": "PST_BC", "label": "PST (British Columbia)", "agency": "BC Ministry of Finance", "collectedLines": [ { "accountName": "PST Payable - BC", "amount": "5915.00" } ], "totalCollected": "5915.00", "inputCreditLines": [], "totalInputCredits": "0.00", "netOwing": "5915.00", "isRefund": false, "hasInputCredits": false } ] } } } ``` ## Tax Collected vs Paid For organizations on their own rates (outside Canada and the US): per rate, tax collected on sales, tax paid on purchases, how much of that is claimed back, and the net owed (collected minus claimed back). Tax that isn't claimed back is part of the expense. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `startDate` | `Date!` | Yes | Start of the period (YYYY-MM-DD) | | `endDate` | `Date!` | Yes | End of the period (YYYY-MM-DD) | ### Request ```graphql query { taxSummary(organizationId: "a1b2c3d4-...", startDate: "2026-07-01", endDate: "2026-09-30") { currency totalCollected totalClaimedBack net lines { code name collected paid claimedBack net } } } ``` ### Response ```json { "data": { "taxSummary": { "currency": "GBP", "totalCollected": "4000.00", "totalClaimedBack": "1250.00", "net": "2750.00", "lines": [ { "code": "vat", "name": "VAT", "collected": "4000.00", "paid": "1250.00", "claimedBack": "1250.00", "net": "2750.00" } ] } } } ``` ## Sales Tax Collected By State US organizations: sales tax collected in the period for each state, a liability summary of what is owed to each state. Each state has its own return and due dates. Sales tax paid on purchases is part of their cost, so it isn't listed. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `startDate` | `Date!` | Yes | Start of the period (YYYY-MM-DD) | | `endDate` | `Date!` | Yes | End of the period (YYYY-MM-DD) | ### Request ```graphql query { salesTaxByState(organizationId: "a1b2c3d4-...", startDate: "2026-07-01", endDate: "2026-09-30") { currency total lines { state name collected } } } ``` ### Response ```json { "data": { "salesTaxByState": { "currency": "USD", "total": "1463.20", "lines": [ { "state": "NY", "name": "New York", "collected": "1180.00" }, { "state": "NJ", "name": "New Jersey", "collected": "283.20" } ] } } } ``` # Batch Operations All batch mutations accept up to **500 items** per call. Failures are per-item — one bad record does not roll back the others. Each item runs in its own database savepoint. For bulk transaction import, use `importBankStatement` (see [Bank Import](#bank-import)) which accepts an array of rows and creates LedgerEvents. ## Batch Create Accounts Create up to 500 accounts in a single call. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `items` | `[AccountInput!]!` | Yes | Array of accounts. Each: name (String!), accountType (String!), classification (String!), subType (String), parentId (UUID) | ### Request ```graphql mutation { batchCreateAccounts( organizationId: "a1b2c3d4-..." items: [ { name: "Office Supplies", accountType: "expense", classification: "expense" }, { name: "Travel", accountType: "expense", classification: "expense" }, { name: "Software", accountType: "expense", classification: "expense" } ] ) { created errors { index error } } } ``` ### Response ```json { "data": { "batchCreateAccounts": { "created": 3, "errors": [] } } } ``` ## Batch Create Contacts Create up to 500 contacts in a single call. ### Arguments | Name | Type | Required | Description | | --- | --- | --- | --- | | `organizationId` | `UUID!` | Yes | Organization ID | | `items` | `[ContactInput!]!` | Yes | Array of contacts. Each: contactType (String!), displayName (String!), email (String), phone (String) | ### Request ```graphql mutation { batchCreateContacts( organizationId: "a1b2c3d4-..." items: [ { contactType: "vendor", displayName: "Staples" }, { contactType: "vendor", displayName: "Amazon" } ] ) { created errors { index error } } } ``` ### Response ```json { "data": { "batchCreateContacts": { "created": 2, "errors": [] } } } ``` # Errors The Solarium API returns errors in a consistent JSON shape. GraphQL requests always return **200 OK** at the HTTP level. Errors appear in the `errors` array of the response body. The only exceptions are **429 Too Many Requests** (rate limit) and **400 Bad Request** (malformed JSON or invalid GraphQL syntax), which return non-200 HTTP status codes. ### Error Object Each error has a `message` string. Some errors include a `path` array indicating which field triggered the error. ### Common Errors | Message | Cause | |---------|-------| | `Authentication Required.` | No token or API key provided | | `API Key Not Authorized For This Organization.` | API key belongs to a different organization | | `Organization Not Found.` | Invalid `organizationId` | | `Not A Member Of This Organization.` | JWT user is not a member of this organization | | `API Keys Cannot Perform Admin Operations.` | API key used for an admin-only mutation (e.g. managing members) | | `Must Be Owner Or Admin.` | Insufficient role for this operation | | `Rate Limit Exceeded. 5,000 Requests/Hour.` | API key exceeded hourly quota (HTTP 429) | ### Mutation Errors Mutations return a `success` boolean and an `error` string instead of throwing. A failed mutation returns `success: false` with a human-readable `error`. The HTTP status is still 200. ## Example ``` # Successful mutation mutation { createAccount( organizationId: "a1b2c3d4-..." name: "Office Supplies" accountType: "expense" classification: "expense" ) { account { id name } success error } } ``` ## Example Response ``` # GraphQL-level error (in errors array) { "errors": [ { "message": "Authentication Required.", "path": ["createAccount"] } ], "data": { "createAccount": null } } # Mutation-level error (success: false) { "data": { "createAccount": { "account": null, "success": false, "error": "Account with this name already exists." } } } # Rate limit error (HTTP 429) { "errors": [ { "message": "Rate Limit Exceeded. 5,000 Requests/Hour." } ] } ``` # Pagination All list endpoints use **offset-based pagination** with a consistent interface. ### Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `limit` | Int | 25 | Number of items per page (max 200) | | `offset` | Int | 0 | Number of items to skip | | `search` | String | — | Case-insensitive text filter (searches relevant fields like name, memo, etc.) | | `sortBy` | String | — | Field to sort by (e.g. `name`, `currentBalance`, `txnDate`) | | `sortDir` | String | `asc` | Sort direction: `asc` or `desc` | ### Response shape Every paginated response includes: - `items`: the array of results - `totalCount`: total number of matching records (before pagination) - `pageSize`: the `limit` that was applied - `offset`: the `offset` that was applied To check if there are more pages: `offset + items.length < totalCount`. Requesting beyond the end of the data returns an empty `items` array, not an error. ## Example ``` # Page 1: first 10 accounts query { listAccounts( organizationId: "a1b2c3d4-..." limit: 10 offset: 0 sortBy: "name" sortDir: "asc" ) { items { id name currentBalance } totalCount pageSize offset } } # Page 2: next 10 query { listAccounts( organizationId: "a1b2c3d4-..." limit: 10 offset: 10 sortBy: "name" sortDir: "asc" ) { items { id name currentBalance } totalCount pageSize offset } } ``` ## Example Response ``` # Page 1 response { "data": { "listAccounts": { "items": [ { "id": "...", "name": "Accounts Payable", "currentBalance": 4200.00 }, { "id": "...", "name": "Accounts Receivable", "currentBalance": 12400.00 } ], "totalCount": 24, "pageSize": 10, "offset": 0 } } } # Last page (fewer items than limit) { "data": { "listAccounts": { "items": [ { "id": "...", "name": "Travel", "currentBalance": 890.00 } ], "totalCount": 24, "pageSize": 10, "offset": 20 } } } ```