Use QuickBooks integration context for customers, invoices, payments, expenses, vendors, and financial reports. Use after Maverick connects QuickBooks and pr...
Design & media
freshbooks-mcp
Try itQuery FreshBooks (invoices, clients, estimates, payments, expenses, projects, time tracking) from a shell with curl and a rotating OAuth token. Use when you want FreshBooks data without running the freshbooks-mcp server, in a script, or on a machine where the MCP isn't installed.
What it does
Query FreshBooks (invoices, clients, estimates, payments, expenses, projects, time tracking) from a shell with curl and a rotating OAuth token. Use when you want FreshBooks data without running the freshbooks-mcp server, in a script, or on a machine where the MCP isn't installed.
The skill document
FreshBooks from the shell
FreshBooks has a real REST API reachable server-side — no browser bridge, no signed-in
tab, no fpx. Authentication is OAuth2 only: there is no API key and no personal
access token, so a one-time browser authorize flow is unavoidable.
The two things that break naive clients
1. Refresh tokens are single-use and rotate. Every refresh returns a new refresh
token and kills the one you used. If the replacement is not written to disk before the
process ends, the account is locked out and the only recovery is re-running the browser
flow. Never hand-roll the refresh — use fb_access_token from
references/fb-token.sh, which persists the rotation before returning, and never point
two tools at the same state file.
2. Three identifiers that are not interchangeable. accountId (alphanumeric, e.g.
xZNQ1X), businessId (integer), businessUuid. Each URL family takes a different one
and answers a mismatch with a bare 404 that reads like a missing record. Always
resolve first with fb_ids.
| Family | Path | Identifier |
|---|---|---|
| Accounting | /accounting/account/{accountId}/… | accountId |
| Payments | /payments/account/{accountId}/… | accountId |
| Accounting (business) | /accounting/businesses/{businessUuid}/… | businessUuid |
| Projects | /projects/business/{businessId}/… | businessId |
| Time tracking | /timetracking/business/{businessId}/… | businessId |
One-time setup
Register an app at . The redirect URI must be
HTTPS with no query string — https://localhost works and never needs to
resolve. Put FRESHBOOKS_CLIENT_ID and FRESHBOOKS_CLIENT_SECRET somewhere loadable
(e.g. ~/.secrets).
Then run the authorize flow once:
set -a; . ~/.secrets; set +a
# 1. Print the authorize URL, open it, click Allow.
node references/fb-bootstrap.mjs url "$FRESHBOOKS_CLIENT_ID"
# 2. The browser fails to load https://localhost?code=… — that is expected.
# Copy the whole URL from the address bar and exchange it (the code is single-use
# and expires within minutes).
node references/fb-bootstrap.mjs exchange "$FRESHBOOKS_CLIENT_ID" "$FRESHBOOKS_CLIENT_SECRET" \
https://localhost 'https://localhost?code=PASTE_HERE'
Save the printed refresh_token as FRESHBOOKS_REFRESH_TOKEN. It seeds the state file
on first use; after that the state file is the source of truth and the env value is
ignored until you re-bootstrap.
Core call pattern
set -a; . ~/.secrets; set +a
. references/fb-token.sh
fb_ids # resolve accountId / businessId / businessUuid
ACCT=$(fb_account_id)
fb_curl "/accounting/account/$ACCT/invoices/invoices?per_page=5" \
| jq '.response.result.invoices[] | {id, invoice_number, amount, outstanding, status}'
fb_curl [curl args…] attaches the bearer token and Api-Version: alpha;
everything after the path is passed to curl, so writes work too.
Response envelopes differ per family
- Accounting / events →
.response.result., errors at.response.errors[].message - Accounting-business →
.data, errors at.errors.message+.errors.details[].reason - Projects / time tracking / comments → bare object, errors at
.error - Payments → errors at
.errors.details[].field
A single jq path will not work across families — see references/recipes.md.
Writes
Accounting writes wrap the payload in the singular resource name:
fb_curl "/accounting/account/$ACCT/invoices/invoices" \
-X POST -H 'Content-Type: application/json' \
-d '{"invoice":{"customerid":123,"create_date":"2026-08-12","lines":[]}}'
Money is {"amount":"150.00","code":"USD"} — the amount is a string, not a number.
Deletes on the accounting family are frequently soft deletes via vis_state on an
update rather than HTTP DELETE. Confirm per resource before assuming.
A 200 is not proof a write persisted — re-read the record to confirm.
Troubleshooting
| Symptom | Cause |
|---|---|
invalid_client on refresh | The refresh grant needs client_secret and redirect_uri, form-encoded — not JSON |
invalid_grant on refresh | The token was already spent. Re-run the bootstrap |
| 404 on a valid-looking record | Wrong identifier for that URL family — run fb_ids |
| 401 on every call | Access token stale and refresh failing; check the state file |
See references/recipes.md for ready-to-run request bodies and jq recipes.
Related skills
Read a Housecall Pro estimate or invoice your contractor sent you — line items, totals, tax, what is still owed, the company behind it — from a shell with plain curl, instead of running the housecallpro-mcp server. Use when you want the data without the MCP, in a script, or on a machine where the MCP isn't installed. Covers declining an option, and why approving cannot be scripted.
Read HoneyBook client-portal data (contracts, invoices, proposals, payment methods, workspace status) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the honeybook-mcp server — capture a vendor session once via the signed-in browser tab, then curl api.honeybook.com directly. Use when you want HoneyBook data without the MCP, in a script, or on a machine where the MCP isn't installed.
Query eventbrite.com from a shell — your tickets/orders, your organizations' events and attendees via the documented token API (curl), and public event discovery/search via the fpx browser bridge (the search API is WAF-walled and absent from the documented API). Use when you want Eventbrite data without an MCP server, in a script, or one-shot.
Read your Jobber Client Hub — the customer portal a service business (pest control, lawn care, HVAC, cleaning) uses to send you appointments, quotes and invoices — from a shell with the fpx CLI (@fetchproxy/cli), instead of running the jobber-mcp server. Use when you want your Jobber data without the MCP, in a script, or on a machine where the MCP isn't installed.
Safely inspect Stripe payments, customers, invoices, and subscriptions