FreshBooks invoicing for Claude β invoices, clients, estimates, and payments
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β or use 1-click editor setup below.
π‘ Paste the JSON block into your client's configuration file under mcpServers, then restart the application.
MCP server for FreshBooks β invoices, clients, estimates and payments, exposed to Claude as typed tools.
This project was developed and is maintained by AI (Claude Code). Use at your own discretion.
FreshBooks is OAuth2 only β there is no API key and no personal access token, so a one-time browser authorization is required.
https://localhost works and never needs to resolve.FRESHBOOKS_CLIENT_ID and
FRESHBOOKS_CLIENT_SECRET, start it, then call freshbooks_auth_url, open
the URL it returns, approve, and pass the URL you land on to
freshbooks_auth_exchange. Those two tools need no refresh token β minting
one is what they are for. This is also the path mcp-host's authFlow
drives, so a hosted connector can do it without you pasting anything.skills/freshbooks-curl.FreshBooks issues a new refresh token on every refresh and immediately invalidates the
old one. This server persists each rotation to FRESHBOOKS_TOKEN_STORE (mode 0600)
before the refresh is considered complete, and prefers the stored token over the
environment value β the stored one has rotated past it.
Two consequences worth knowing:
freshbooks-curl
skill keep separate state files on purpose; sharing one makes them spend each other's
tokens and locks both out.Changing FRESHBOOKS_REFRESH_TOKEN to a freshly bootstrapped value is detected and
adopted, so re-bootstrapping is the supported recovery path.
| Tool | Purpose |
|---|---|
freshbooks_get_identity | Resolve accountId / businessId / businessUuid |
freshbooks_auth_url | Get the consent URL to authorise this connection |
freshbooks_auth_exchange | Exchange the authorization code (or pasted redirect URL) for a refresh token |
freshbooks_healthcheck | Verify the OAuth credential and FreshBooks reachability; distinguishes "no credential" from "rejected" from "FreshBooks is down" |
freshbooks_list_invoices / freshbooks_get_invoice | Browse and fetch invoices |
freshbooks_list_clients / freshbooks_get_client | Browse and fetch clients |
freshbooks_list_estimates / freshbooks_get_estimate | Browse and fetch estimates |
freshbooks_list_payments / freshbooks_get_payment | Browse and fetch payments |
freshbooks_list_items / freshbooks_get_item | Browse and fetch catalogue items |
freshbooks_create_client | Create a client β confirm-gated |
freshbooks_create_invoice | Create an invoice β confirm-gated |
freshbooks_update_invoice | Update an invoice β confirm-gated |
freshbooks_record_payment | Record a payment against an invoice β confirm-gated |
freshbooks_accept_estimate | Accept an estimate (action_accept) β confirm-gated, idempotent |
freshbooks_update_estimate | Update an estimate's lines, notes, terms, presentation β confirm-gated |
freshbooks_send_estimate | Email an estimate to the client (action_email) β confirm-gated |
freshbooks_decline_estimate | Always fails: FreshBooks has no decline. Answers with the alternatives |
freshbooks_list_expenses / freshbooks_get_expense | Browse and fetch expenses |
freshbooks_list_expense_categories | Categories supplying categoryid for new expenses |
freshbooks_create_expense | Record an expense β confirm-gated |
freshbooks_list_projects / freshbooks_get_project | Projects (businessId-keyed) |
freshbooks_create_project | Create a project β confirm-gated |
freshbooks_list_time_entries | Tracked time, with total_logged / total_unbilled |
freshbooks_create_time_entry | Log time in seconds β confirm-gated |
freshbooks_list_services | Billable work types for projects and time entries |
freshbooks_list_records / freshbooks_get_record | Generic accessor for the accounting long tail (taxes, credit notes, invoice profiles, tasks, staff, gateways, bills, bill vendors, bill payments, other income) |
Confirm-gated means the tool makes no network call unless confirm: true is passed;
without it you get a dry-run preview of exactly what would be sent.
Acceptance is an action on the estimate, not a status field: status (int),
display_status and ui_status are computed and read-only, and they disagree with each
other by design (a viewed estimate reads status: 3, display_status: "viewed",
ui_status: "open"). Accepting is PUT estimates/estimates/{id} with
{"estimate": {"action_accept": true}} β see
docs/FRESHBOOKS-API.md for where that shape comes from.
changed: false and no write is sent β acceptance cannot be undone through the API, so
a repeat call must not re-fire it.action_deny, no
estimate.decline webhook. freshbooks_decline_estimate exists only to say so and
point at the alternatives, rather than leave an agent to invent a write that changes
nothing.before / after state and
changed / changedFields, so success is verified against the record rather than
inferred from a 200. changed covers the status fields and the fields that write
actually set, so a successful notes edit reports changed: true even though no status
moves. On freshbooks_send_estimate it describes the record only β emailing an
already-sent estimate moves nothing, and retrying on changed: false would send the
client a second copy.FreshBooks separates the role you hold on a business from the role you hold on an
accounting account. You can own a business that has no accounting account
(account_id: null) while being only a client on the account you can actually see β
in which case reads succeed and every write returns 403 Permission Denied, even though
your OAuth token carries all the :write scopes.
freshbooks_get_identity reports accountRole and businessRole so this is visible up
front. If accountRole is client, the invoicing write tools will not work against that
account β that is an account permission, not a configuration problem.
total counts records you may not be able to read. Expenses reported total: 16
while returning zero rows. List results attach a note when that happens, so it reads
as a permission boundary rather than an empty account.businessId, not accountId, and paginate
under a meta block instead of flat page/pages/total. They also work on a
business with no accounting account at all.FreshBooks hands out three non-interchangeable ids, and using the wrong one returns a bare 404 that reads like a missing record:
| Identifier | Used by |
|---|---|
accountId (alphanumeric) | /accounting/account/β¦, /payments/account/β¦ |
businessId (integer) | /projects/business/β¦, /timetracking/business/β¦ |
businessUuid (UUID) | /accounting/businesses/β¦ |
Call freshbooks_get_identity first. Full API notes, including the four different error
envelopes, are in docs/FRESHBOOKS-API.md.
skills/freshbooks-curl covers the same API from a
shell with curl + jq, including the OAuth bootstrap and rotation-safe token handling.
MIT
No reviews yet β be the first to share how this listing worked for you.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/freshbooks-mcp)<a href="https://allmcps.com/mcp/freshbooks-mcp"><img src="https://allmcps.com/api/badge/freshbooks-mcp?style=directory" alt="Freshbooks MCP on AllMCPs" /></a>