I was about two years behind on entering business expenses, so I handed the job to Claude, connected straight to my self-hosted Invoice Ninja.
I run a small IT services business from home. I like fixing networks; I do not like typing receipts into an invoicing system. The backlog kept growing, and every month it felt less worth starting.
The goal was simple: drop a photo or PDF of a receipt into a folder, and have an agent read it, create the vendor if needed, file the expense in the right category, attach the receipt, and ask me before it writes anything. This post covers how I set that up, what I had to lock down, and the mistakes it caught along the way.
Choosing an MCP server
Claude talks to other systems through MCP (Model Context Protocol) servers. Invoice Ninja has no official one, but the community has built several. I compared them on one question: can it create expenses, create vendors and attach receipts, without me handing over the keys to everything?
| Server | Expense writes | Receipt upload | Safety on writes | Verdict |
|---|---|---|---|---|
| NightSquawk invoiceninja-mcp-server | Yes, full API | Yes | Per-call approval note, local backup before every change | Picked |
| Marceltov invoiceninja-mcp | Yes, full API | Yes | None, full token access | Strong, but 377 tools and a Docker sidecar |
| aweher invoiceninja-mcp | Yes, generic create/update | No | Read-only until writes are switched on | Good reporting, no uploads |
| a-wiseguy invoiceninja-mcp | Partly | Yes, PDF flow | Preview and approval token | Built for euro invoices with Dutch VAT |
| Fuciuss invoice-ninja-mcp | Create only | No | None | Too thin for expenses |
| ambaloo invoiceninja-mcp | No | No | None | Invoicing and quotes only |
NightSquawk won for three reasons. It covers all 379 Invoice Ninja API operations through just three tools (list, describe, call), which keeps each session light on a Claude Pro plan. Every write must carry an explicit approval flag and a note saying what I approved. And it saves a JSON backup of the request, plus the record as it was, before any change.
Locking it down
The MCP server’s approval check is a guardrail, not a security boundary: the API token is real, and anything holding it can call the API directly. So the real limit lives in Invoice Ninja itself. The token inherits the permissions of the user who created it, so I gave Claude its own user.
| Setting | Value | Why |
|---|---|---|
| Administrator | Off | No settings, users or gateways |
| Disable Emails | On | It can never email a client |
| Reports, View Dashboard | On | Profit and loss, expense reports |
| Expense, Vendor, Recurring Expense, Transaction | Create, view, edit | The actual job |
| Client, Invoice, Payment | View only | Linking rebillable costs, reconciling income |
| Recurring Invoice | View only | It must not change what clients are billed |
One snag: a non-admin user cannot see the API Tokens screen. The workaround was to switch Administrator on, create the token as the Claude user, then switch it off again. Because permissions are checked live on every request, the token drops back to the restricted access.
I checked that rather than trusting it. Listing users with the token and reading the Claude user’s is_admin and permissions fields confirmed is_admin: false and only the permissions I had ticked.
That same check turned up something unrelated: an archived, still-owner admin account left over from the Docker install, with a default email address. If you self-host Invoice Ninja, look for one. Owners do not show in User Management, so query the API for it, then change its email and password and clear IN_PASSWORD from your .env.
Setup on CachyOS
The whole setup runs locally on my CachyOS workstation with a Claude Pro plan. No Docker, no extra server.
- Install Node.js and the Claude desktop app from the CachyOS repo:
sudo pacman -S nodejs npm claude-desktop. Node needs to be version 20 or newer. - Install Claude Code if you don’t have it already, and log in with your Pro account.
- Test the token before involving Claude at all:
curl -s https://YOUR-INVOICENINJA-URL/api/v1/ping \
-H "X-API-TOKEN: YOUR-TOKEN" \
-H "X-Requested-With: XMLHttpRequest"
- Create a working folder with an inbox, a done folder and a log:
mkdir -p ~/nts-books/inbox/statements ~/nts-books/done
echo "date,vendor,amount,currency,category,invoice_ninja_expense_id,source_file" > ~/nts-books/log.csv
- Connect the MCP server from inside that folder. Local scope keeps the token in
~/.claude.json, not in the project folder:
cd ~/nts-books
claude mcp add invoiceninja --scope local \
--env INVOICENINJA_BASE_URL="https://YOUR-INVOICENINJA-URL" \
--env INVOICENINJA_API_TOKEN="YOUR-TOKEN" \
-- npx -y @nightsquawktech/invoiceninja-mcp-server
claude mcp list
- Open Claude Desktop, go to the Code tab, keep it on Local, and pick the folder with the folder button under the message box.
I allowed the read-only tools (list_endpoints, describe_endpoint) permanently and left call_endpoint on “ask each time” for the first few batches. That gives a second approval on top of the preview step.
The rulebook: CLAUDE.md
The MCP server gives Claude access; a CLAUDE.md file in the working folder gives it judgement. Claude Code reads it at the start of every session, so it is the agent’s job description and memory in one.
Mine covers:
- Business facts. Not VAT-registered, so every expense is recorded at the full amount paid. One card is used for personal and business spending, so nothing is assumed to be business.
- The workflow. Read every file in the inbox, check for duplicates, match or create the vendor, pick a category, then show a preview table and stop. Nothing is written until I approve.
- Fields on every expense. Date, amount, vendor, category, a plain description, the invoice number, and the source filename in private notes. The receipt is attached every time.
- Categories. Eleven of them, each with a “use for” and a “not for” column so two categories never compete for the same receipt.
- Foreign currency. USD invoices paid through Wise are recorded at the rand amount actually paid, with the dollar figure in the notes. It never invents an exchange rate.
- Hard limits. Never delete or archive, never send emails, never touch clients, invoices or settings.
| Category | Covers |
|---|---|
| Cost of Goods Sold | Anything bought for a client or rebilled |
| IT and Internet Expenses | Own software, hosting, domains, fibre, VoIP, equipment under R7,000 |
| Repairs & Maintenance | Fixing equipment or the office |
| Bank & Transfer Fees | Bank, Wise, card and EFT fees |
| Professional Fees | Accountant, legal, company registry |
| Capital Improvements | Work on the building itself, such as the server room build |
| Capital Equipment | Movable items over R7,000 per item |
| Home Office | Business share of rent or bond interest, rates, utilities |
| Vehicle & Travel | Business trips |
| Marketing & Advertising | Ads, branding, printing |
| Office & Admin | Stationery, courses, sundries |
Processed receipts are renamed and filed as done/<Vendor>/<Year>/YYYY-MM-DD_Vendor_Amount.pdf, and each expense gets a row in log.csv. When I correct the agent, I tell it to add the correction to CLAUDE.md, so the next session knows it too.
Gotchas worth knowing
VAT on expenses quietly lowered my expenses. I am not VAT-registered, but I had been recording the VAT on each receipt anyway. In South Africa only registered vendors can claim VAT back, so for me it is simply part of the cost. I ran a test month through Invoice Ninja’s reports: the expense list showed the full amounts, but the Profit and Loss report counted expenses “tax exclusive” and listed the VAT separately without deducting it. Profit came out higher than it really was. The fix was to clear the tax fields on old expenses and never fill them in again.
Hand-entered expenses don’t match neatly. I had already typed in some Wise transfer fees, and one turned out to be in twice. Old entries can have a different vendor spelling or a date a day off, so the duplicate check in CLAUDE.md has three tiers: exact match within 3 days is skipped, near matches are flagged for me, and only clean misses are created.
Test the receipt upload once. Another project reported that Invoice Ninja’s upload endpoints don’t behave as its API documentation says. I created one dummy expense, had the agent attach a PDF, and checked the Documents tab before trusting it. With NightSquawk it worked first time.
Start a new session for each batch. The agent’s memory is in files (the rules, the log and Invoice Ninja itself), not in the chat. Long sessions get slower and burn through the Pro allowance faster.
How I use it now
A batch now takes me a few minutes of checking instead of an evening of typing.
- Scan receipts on my phone with FairScan (open source, no account) and send them to the PC with KDE Connect.
- Drop one month of receipts into
inbox/, plus a Wise statement if anything was paid in dollars. - Open a new session in the folder and say “process the inbox”.
- Read the preview table, answer its questions, and approve.
- Spot-check an expense or two in Invoice Ninja.
The parts I would keep if I did this again: a separate restricted user rather than my own token, a preview before every write, and rules that live in a file instead of in my head. The agent asks about anything it isn’t sure of, and each confirmed vendor goes on a list so it asks less next month.
It is not an accountant. It records; my accountant still decides how the home office, vehicle and capital items are treated. But the paperwork I kept putting off now gets done, and every expense has its receipt attached.
