# How to Integrate Retool with FreshBooks

- Tool: Retool
- Difficulty: Beginner
- Time required: 20 minutes
- Last updated: April 2026

## TL;DR

Connect Retool to FreshBooks by creating a REST API Resource using FreshBooks OAuth 2.0 and the account-specific /accounting/account/{accountId}/ base URL. Build invoicing dashboards, time tracking panels, and payment monitoring tools that consolidate freelancer and agency finance data in a single internal interface.

## Build a FreshBooks Finance Management Panel in Retool

FreshBooks is built for individual freelancers managing their own workflow, but agencies and operations teams often need a different view: all outstanding invoices across all clients with days-overdue calculations, payment velocity by month, per-project profitability combining tracked hours with billed amounts, or a bulk invoice-sending tool that processes a batch of clients at once. Retool lets you build these custom views directly against the FreshBooks API.

FreshBooks API v3 provides comprehensive access to invoices, clients, payments, expenses, time entries, projects, and reports. Authentication uses OAuth 2.0 — you exchange an authorization code for an access token, then pass it as a Bearer token in every request. The FreshBooks API is account-specific: each API endpoint includes the accountId, which you can retrieve after authenticating by calling GET /auth/api/v1/users/me.

The most powerful Retool use case for FreshBooks is an agency billing dashboard that tracks invoice aging across all clients — showing which invoices are current, approaching due dates, or overdue — combined with time tracking data to compare budgeted versus actual hours per project. Finance and ops teams can see the complete revenue picture without navigating through FreshBooks' client-by-client interface.

## Before you start

- A FreshBooks account with invoices, clients, or time entries to work with
- A FreshBooks Developer App to obtain OAuth credentials (create at https://my.freshbooks.com/#/developer)
- Your FreshBooks OAuth 2.0 access token and account ID (retrieved via the /auth/api/v1/users/me endpoint)
- A Retool account with permission to create Resources
- Basic familiarity with Retool's query editor and Table component

## Step-by-step guide

### 1. Create a FreshBooks Developer App and obtain OAuth credentials

Navigate to https://my.freshbooks.com/#/developer and click Create App. Give the app a name (e.g., 'Retool Integration'), set the redirect URI to https://login.retool.com/oauthcallback (or your self-hosted Retool URL), and save. FreshBooks will generate a Client ID and Client Secret.

To obtain an access token for initial setup, use the FreshBooks OAuth 2.0 authorization flow. Visit the authorization URL: https://auth.freshbooks.com/oauth/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REDIRECT_URI. After you authorize, FreshBooks redirects to your redirect URI with an authorization code. Exchange this code for an access token via POST to https://api.freshbooks.com/auth/oauth/token with body: { grant_type: 'authorization_code', client_id: YOUR_CLIENT_ID, client_secret: YOUR_CLIENT_SECRET, code: AUTHORIZATION_CODE, redirect_uri: YOUR_REDIRECT_URI }.

The response includes an access_token and refresh_token. Store both in Retool Configuration Variables (Settings → Configuration Variables) as secrets: FRESHBOOKS_ACCESS_TOKEN and FRESHBOOKS_REFRESH_TOKEN.

After obtaining your access token, call GET https://api.freshbooks.com/auth/api/v1/users/me with Authorization: Bearer YOUR_TOKEN. The response includes your accountId under business_memberships[0].business.account_id. This accountId is required in every FreshBooks API endpoint path.

**Expected result:** You have a FreshBooks OAuth access token and your account ID. Both are stored as secret configuration variables in Retool.

### 2. Create the FreshBooks REST API Resource in Retool

In Retool, navigate to the Resources tab and click Add Resource. Select REST API from the connector list.

Name: FreshBooks API

Base URL: https://api.freshbooks.com

Do not include the account-specific path in the base URL — FreshBooks uses different base paths for different API sections (accounting vs. auth vs. timetracking), so keeping the base URL at the domain level gives you the most flexibility.

Authentication: Select Bearer Token. In the Token field, enter {{ retoolContext.configVars.FRESHBOOKS_ACCESS_TOKEN }}. This references the configuration variable you created in the previous step, keeping the token out of the resource configuration itself.

Add a default header: Content-Type: application/json.

Click Create Resource. Test with a query: GET /auth/api/v1/users/me. If the response returns your user profile with email and account information, the authentication is working correctly. If you get 401 Unauthorized, verify the access token is fresh and correctly stored in Configuration Variables.

**Expected result:** The FreshBooks REST API Resource is created. GET /auth/api/v1/users/me returns your user profile with name, email, and account ID.

### 3. Query invoices with status filtering and pagination

Create a getInvoices query. Set method to GET and path to /accounting/account/{{ retoolContext.configVars.FRESHBOOKS_ACCOUNT_ID }}/invoices/invoices. Store your accountId as a configuration variable FRESHBOOKS_ACCOUNT_ID so it can be reused across all queries without hardcoding.

FreshBooks supports filtering invoices via query parameters. Add the following URL parameters to your query:
- search[invoice_status][]: 'unpaid' (filter by status; other values: 'paid', 'overdue', 'draft', 'sent')
- page: {{ Math.floor(table_invoices.pagination.offset / 15) + 1 }} (FreshBooks paginates at 15 results by default)
- per_page: 25
- include[]: 'client'

The include[]=client parameter embeds client name in the invoice response, avoiding separate client lookup queries. FreshBooks returns invoices inside response.result.invoices.

Create a JavaScript transformer to flatten the nested FreshBooks response and compute days overdue for each invoice. FreshBooks invoices include due_date, amount (with code and amount sub-fields), outstanding (current unpaid amount), status, and invoice_number fields.

```
// Transformer for FreshBooks invoices
const invoices = data?.response?.result?.invoices || [];
const today = new Date();

return invoices.map(inv => {
  const dueDate = inv.due_date ? new Date(inv.due_date) : null;
  const daysOverdue = dueDate ? Math.floor((today - dueDate) / 86400000) : 0;
  const totalAmount = parseFloat(inv.amount?.amount || '0');
  const outstanding = parseFloat(inv.outstanding?.amount || '0');

  let agingBucket = 'Current';
  if (daysOverdue > 60) agingBucket = '60+ Days Overdue';
  else if (daysOverdue > 30) agingBucket = '30-60 Days Overdue';
  else if (daysOverdue > 0) agingBucket = '1-30 Days Overdue';

  return {
    id: inv.id,
    invoice_number: inv.invoice_number,
    client_name: inv.current_organization || inv.organization || 'N/A',
    status: inv.v3_status || inv.status,
    amount: `$${totalAmount.toFixed(2)}`,
    outstanding: `$${outstanding.toFixed(2)}`,
    due_date: inv.due_date || 'None',
    days_overdue: daysOverdue > 0 ? daysOverdue : 0,
    aging_bucket: agingBucket,
    created_at: inv.create_date || '',
    currency: inv.amount?.code || 'USD'
  };
});
```

**Expected result:** The getInvoices query returns a flattened array of invoices with aging bucket labels, displayed in a Table with color-coded rows by aging status.

### 4. Fetch clients and payments for filtering and detail views

Create a getClients query: GET /accounting/account/{{ retoolContext.configVars.FRESHBOOKS_ACCOUNT_ID }}/users/clients with page: 1 and per_page: 100. The response contains response.result.clients — an array of client objects with id, organization (company name), fname, lname, email, and outstanding (total outstanding balance across all invoices).

Create a getPayments query: GET /accounting/account/{{ retoolContext.configVars.FRESHBOOKS_ACCOUNT_ID }}/payments/payments with search[invoiceid]: {{ table_invoices.selectedRow.data.id }}. This fetches payments for the currently selected invoice, enabling a payment history detail view.

For the client dropdown filter, transform getClients results into a Select component data source: map each client to { label: client.organization || `${client.fname} ${client.lname}`, value: client.id }.

Create a clientFilter Select component and add search[clientid]: {{ clientFilter.value || '' }} as a conditional URL parameter on the getInvoices query — when set, this filters invoices to a specific client.

For the invoice detail panel, create getInvoiceDetail: GET /accounting/account/{accountId}/invoices/invoices/{{ table_invoices.selectedRow.data.id }}?include[]=lines to fetch the full invoice with line items. Display this in a right-side Container that appears when a row is selected in the main Table.

```
// Transformer for clients dropdown
const clients = data?.response?.result?.clients || [];
return clients.map(client => ({
  label: client.organization || `${client.fname} ${client.lname}`.trim() || `Client ${client.id}`,
  value: client.id,
  email: client.email || '',
  outstanding: parseFloat(client.outstanding?.amount || '0').toFixed(2),
  currency: client.outstanding?.code || 'USD'
}));
```

**Expected result:** Client dropdown is populated and filters the invoice table by client. Selecting an invoice row shows full line item detail and payment history in the right panel.

### 5. Add invoice creation and expense tracking actions

Build invoice creation by adding a Create Invoice button that opens a Modal. Inside the Modal, add form fields: Client Select (bound to getClients), Invoice Date (DatePicker), Due Date (DatePicker), and a dynamic line items section using a Table in edit mode.

Create a createInvoice query: POST /accounting/account/{{ retoolContext.configVars.FRESHBOOKS_ACCOUNT_ID }}/invoices/invoices. The request body must match FreshBooks' nested invoice format.

For expense tracking, create a getExpenses query: GET /accounting/account/{{ retoolContext.configVars.FRESHBOOKS_ACCOUNT_ID }}/expenses/expenses with page: 1 and per_page: 25. The response contains response.result.expenses with fields: id, amount (amount + code), expense_date, notes, category_name, and vendor. Display expenses in a separate Table on an Expenses tab.

For time entries, create a getTimeEntries query: GET /timetracking/business/{{ retoolContext.configVars.FRESHBOOKS_ACCOUNT_ID }}/time_entries. Note: the time tracking API uses a different base path (/timetracking/business/) than the accounting API. The response includes duration (seconds), note, started_at, and is_logged fields.

For complex billing workflows combining FreshBooks invoices, expenses, and time tracking across multiple projects, RapidDev's team can help architect the multi-query Retool solution.

```
// createInvoice request body
{
  "invoice": {
    "customerid": {{ select_client.value }},
    "create_date": "{{ datepicker_invoiceDate.value }}",
    "due_offset_days": {{ net_days.value || 30 }},
    "notes": "{{ textArea_notes.value }}",
    "terms": "{{ textInput_terms.value }}",
    "lines": {{ JSON.stringify(table_lineItems.data.map(row => ({
      "type": 0,
      "name": row.description,
      "qty": parseFloat(row.quantity),
      "unit_cost": { "amount": row.unit_price, "code": "USD" },
      "taxName1": row.tax_name || "",
      "taxAmount1": parseFloat(row.tax_rate || 0)
    }))) }}
  }
}
```

**Expected result:** The Create Invoice Modal allows selecting a client, setting dates, and adding line items. Submitting creates the invoice in FreshBooks and refreshes the invoice table.

### 6. Build the payment velocity and revenue reporting view

Create a dedicated Revenue tab in your Retool app for monthly payment trend reporting. FreshBooks provides a payments API that returns all received payments. Create a getRecentPayments query: GET /accounting/account/{{ retoolContext.configVars.FRESHBOOKS_ACCOUNT_ID }}/payments/payments with search[date_min]: {{ new Date(new Date().setMonth(new Date().getMonth()-6)).toISOString().split('T')[0] }}.

FreshBooks payments include amount (with amount and code), date, type (credit, check, VISA, etc.), and associated invoice details.

Create a JavaScript transformer to group payments by month for a Chart component. Use Retool's Chart component (Plotly.js-powered) with a bar chart showing monthly received payment totals. Add a second series for monthly invoiced amounts to show the gap between revenue billed and revenue collected.

For summary Stat components, compute:
- Total invoiced this month: sum of invoice amounts created in current month
- Total collected this month: sum of payment amounts received in current month
- Outstanding receivables: sum of all unpaid invoice outstanding amounts
- Average days to payment: compute from invoice create_date to payment date for paid invoices

Add a second Chart showing invoice count by status (paid, unpaid, overdue, draft) as a donut chart using the getInvoices data grouped by v3_status.

```
// Transformer: group payments by month for Chart
const payments = data?.response?.result?.payments || [];

const monthly = {};
payments.forEach(payment => {
  const date = new Date(payment.date);
  const key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
  const amount = parseFloat(payment.amount?.amount || '0');
  monthly[key] = (monthly[key] || 0) + amount;
});

const sortedKeys = Object.keys(monthly).sort();
return {
  x: sortedKeys,
  y: sortedKeys.map(k => monthly[k].toFixed(2)),
  total_collected: Object.values(monthly).reduce((a, b) => a + b, 0).toFixed(2),
  month_count: sortedKeys.length
};
```

**Expected result:** A revenue reporting tab with a monthly payment bar chart, invoice status donut chart, and summary stats for total invoiced, collected, and outstanding receivables.

## Best practices

- Store FreshBooks OAuth credentials (access_token, refresh_token, client_id, client_secret, account_id, business_id) in Retool Configuration Variables marked as secret — never hardcode tokens in resource settings.
- Implement token refresh as a scheduled Retool Workflow that runs every 10 hours — FreshBooks access tokens expire and a proactive refresh prevents 401 errors disrupting active dashboards.
- Use the include[]=client parameter on invoice queries to embed client data in a single request rather than making separate client lookup calls for each invoice row.
- Store your FreshBooks accountId and businessId separately as configuration variables — the accounting API uses accountId while the time tracking API uses businessId, and confusing them causes 404 errors.
- Cache getClients and reference data queries for 5 minutes — client lists are stable and caching avoids redundant API calls every time the invoice table auto-refreshes.
- Use FreshBooks' per_page: 100 (maximum) for invoice queries and implement server-side pagination in the Table component — agencies with years of invoice history may have thousands of records.
- Add error handling for 422 Unprocessable Entity responses from the invoice creation endpoint — FreshBooks validates required fields strictly and returns field-level validation errors in response.errors.
- For bulk invoice operations, run FreshBooks API calls sequentially with a short delay rather than in parallel — FreshBooks enforces rate limits and parallel requests from a single account may trigger 429 responses.

## Use cases

### Build an invoice aging and collections dashboard

Create a Retool dashboard showing all FreshBooks invoices with aging buckets: current (0–30 days), overdue 30–60 days, and overdue 60+ days. Finance teams can filter by client, mark invoices as sent, and quickly identify which clients need payment follow-up without navigating client-by-client in FreshBooks.

Prompt example:

```
Build a Retool invoice aging dashboard pulling all FreshBooks invoices. Show invoice number, client name, amount, due date, days overdue, and status. Group invoices into buckets: Current, 30+ Days Overdue, 60+ Days Overdue. Color-code rows: green for current, yellow for 30+, red for 60+. Add a filter by invoice status and client name. Include a Send Reminder button for selected overdue invoices.
```

### Build a project profitability tracker

Build a Retool panel that combines FreshBooks time entries and invoices for each project to compute profitability. Show total hours logged, hours billed, billed amount, cost (hours × hourly rate), and margin percentage per project. Helps agencies identify unprofitable projects and clients before they become a cash flow problem.

Prompt example:

```
Build a Retool project profitability panel. For each FreshBooks project, fetch time entries and invoices. Compute: total hours logged, total billed amount, effective hourly rate (billed / hours), and profit margin. Display in a Table sorted by lowest margin. Add a Chart showing margin trend over last 6 months by project type.
```

### Build a client payment history panel

Create a Retool tool showing each client with their full payment history: total invoiced, total paid, outstanding balance, average days to pay, and last payment date. Useful for account managers tracking client relationships and for finance teams deciding credit terms for new projects.

Prompt example:

```
Build a Retool client payment history panel. Show each FreshBooks client with total invoiced this year, total paid, outstanding balance, number of overdue invoices, and average days to pay. Add a detail section showing payment history when a client row is selected. Include a Chart showing monthly payment trend for the selected client.
```

## Troubleshooting

### 401 Unauthorized on every FreshBooks API request

Cause: The OAuth 2.0 access token has expired. FreshBooks access tokens are short-lived (typically 12 hours) and must be refreshed using the refresh token.

Solution: Exchange your refresh token for a new access token via POST https://api.freshbooks.com/auth/oauth/token with grant_type: 'refresh_token', client_id, client_secret, and refresh_token. Update the FRESHBOOKS_ACCESS_TOKEN configuration variable in Retool (Settings → Configuration Variables) with the new access_token. For production setups, implement automatic token refresh using a Retool Workflow on a schedule.

```
// Retool Workflow block to refresh FreshBooks token
// POST https://api.freshbooks.com/auth/oauth/token
{
  "grant_type": "refresh_token",
  "client_id": "{{ retoolContext.configVars.FRESHBOOKS_CLIENT_ID }}",
  "client_secret": "{{ retoolContext.configVars.FRESHBOOKS_CLIENT_SECRET }}",
  "refresh_token": "{{ retoolContext.configVars.FRESHBOOKS_REFRESH_TOKEN }}"
}
```

### API returns 404 Not Found for invoice or client endpoints

Cause: The account ID in the endpoint path is incorrect, missing, or the endpoint path format is wrong. FreshBooks uses account-specific paths that differ from a typical REST API.

Solution: Verify your account ID by calling GET https://api.freshbooks.com/auth/api/v1/users/me and finding the business_memberships[0].business.account_id value. Ensure the path follows the exact format: /accounting/account/{accountId}/invoices/invoices (note the double 'invoices'). Store the accountId in a Retool configuration variable to avoid typos.

### Invoice list returns only 15 results even though there are hundreds of invoices

Cause: FreshBooks paginates at 15 results per page by default. The per_page parameter must be explicitly set to return more results, up to a maximum of 100.

Solution: Add per_page: 100 to your query URL parameters. Also add page: {{ Math.floor(table_invoices.pagination.offset / 100) + 1 }} for server-side pagination. Check response.result.pages in the response to know the total number of pages and configure your Table component's total row count accordingly.

```
// URL params for paginated invoice query:
// per_page: 100
// page: {{ Math.floor(table_invoices.pagination.offset / 100) + 1 }}
// Total rows for Table: {{ getInvoices.data?.response?.result?.total || 0 }}
```

### Time entries return 404 but accounting endpoints work fine

Cause: FreshBooks time tracking uses a different API base path (/timetracking/business/) compared to accounting endpoints (/accounting/account/). The accountId format also differs — time tracking uses businessId.

Solution: For time tracking queries, use the path /timetracking/business/{businessId}/time_entries. Find your businessId from GET /auth/api/v1/users/me under business_memberships[0].business.id (this is different from the account_id used for accounting). Store this as FRESHBOOKS_BUSINESS_ID in Configuration Variables.

## Frequently asked questions

### Does Retool have a native FreshBooks connector?

No, Retool does not have a dedicated native connector for FreshBooks. You connect via a REST API Resource using FreshBooks OAuth 2.0 Bearer token authentication. The setup takes about 20 minutes and provides access to all FreshBooks API v3 endpoints including invoices, clients, payments, expenses, time entries, and projects.

### How do I handle FreshBooks OAuth token expiration in Retool?

FreshBooks access tokens expire after approximately 12 hours. When a query returns 401 Unauthorized, refresh the token by posting to https://api.freshbooks.com/auth/oauth/token with grant_type: 'refresh_token' and your refresh_token. Then update the FRESHBOOKS_ACCESS_TOKEN configuration variable in Retool Settings → Configuration Variables. For production, create a Retool Workflow on a schedule (every 10 hours) that automatically refreshes the token.

### Can I create and send invoices from Retool?

Yes. Create invoices via POST /accounting/account/{accountId}/invoices/invoices with the required fields: customerid, create_date, and lines (array of line items with name, qty, and unit_cost). To send an invoice by email, follow with PUT /accounting/account/{accountId}/invoices/invoices/{invoiceId} and include the action_email: true field in the update payload. FreshBooks will send the invoice to the client's registered email address.

### What is FreshBooks' API rate limit?

FreshBooks API rate limits depend on your plan, but generally allow around 500 requests per minute for standard accounts. If you exceed the limit, you'll receive a 429 Too Many Requests response. For high-frequency Retool dashboards, enable query caching (2–5 minute cache) for reference data like clients and expense categories to reduce overall API call volume.

### How do I combine FreshBooks invoicing with time tracking data in Retool?

FreshBooks uses two different API base paths: accounting endpoints at /accounting/account/{accountId}/ and time tracking endpoints at /timetracking/business/{businessId}/. Both use the same OAuth token but different ID types. Fetch invoices from the accounting API and time entries from the timetracking API, then join them in a JavaScript transformer using the project_id field that appears in both responses.

---

Source: https://www.rapidevelopers.com/retool-integrations/freshbooks
© RapidDev — https://www.rapidevelopers.com/retool-integrations/freshbooks
