Design & media

freshbooks-mcp

Try it

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.

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.

FamilyPathIdentifier
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 stringhttps://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

SymptomCause
invalid_client on refreshThe refresh grant needs client_secret and redirect_uri, form-encoded — not JSON
invalid_grant on refreshThe token was already spent. Re-run the bootstrap
404 on a valid-looking recordWrong identifier for that URL family — run fb_ids
401 on every callAccess token stale and refresh failing; check the state file

See references/recipes.md for ready-to-run request bodies and jq recipes.

Related skills

Use QuickBooks integration context for customers, invoices, payments, expenses, vendors, and financial reports. Use after Maverick connects QuickBooks and pr...

17 installs

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.

1 installs

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.

1 installs

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.

1 installs

Safely inspect Stripe payments, customers, invoices, and subscriptions

21 installs