Table of contents

Add hundreds of integrations to your product through Merge’s Unified API
Get a demo

A guide to integrating with the FreshBooks API

Thinus Swart
@Merge

FreshBooks is a cloud-based accounting solution designed to simplify invoicing, expense tracking, and financial reporting for small businesses. 

While FreshBooks is powerful on its own, it's even more valuable when it’s integrated directly into your product or with the other applications your teams use internally. 

To that end, we’ll walk you through everything you need to know to integrate with FreshBooks’ endpoints.

Steps for integrating with the Freshbooks API

FreshBooks offers a modern REST API that makes it possible to programmatically access and manage everything, from clients and invoices to expenses and time tracking. 

The API is designed to be developer-friendly, but successfully integrating with the API requires a clear understanding of its strengths and quirks. 

To deliver a reliable, production-ready integration, you need to become familiar with its authentication model, key endpoints, and approach to handling things like pagination, rate limits, and webhooks.

Prerequisites

If you want to follow along with the examples here, you need a FreshBooks account. You also need to connect or create an application that can connect to your FreshBooks account. This allows you to query and update the information contained in your FreshBooks instance.

Create a custom application

From the main FreshBooks dashboard, you can navigate to Apps, which allows you to connect many existing online applications to your FreshBooks account:

Connect existing applications to FreshBooks account

This example uses a custom application to demonstrate the different API calls that are available to you. Navigate to the FreshBooks Developer Hub and click the Create New App button in the top right corner:

**Create new App** button in the Developer Hub

Once you click the button, you'll be given a form where you can input information about your application:

New app information

Note that you need an application that can handle an OAuth 2.0 workflow. Creating this application is beyond the scope of this article, but you can read more about it in this authentication tutorial. As a reminder, FreshBooks also offers SDKs for popular languages, like Python and Node.js, that can help you set up your application.

Something to take note of is the Scope portion of the form. This is where you give your application permission to read from different sections of your FreshBooks account. In this example, a few scopes have been added in addition to the default scope, which gives the application permission to read from a list of clients and expenses. You can read more about scopes in the FreshBooks API documentation.

Once you've completed the form, make sure to save it. This creates your application and lists it in your custom applications list:

New app created

Now it's time to retrieve the credentials for your application so that you can test some API endpoints. Click on your newly created application and scroll down to see the Client ID and Client Secret:

Client ID and Secret

Because you'll be authorizing your application to access the FreshBooks API, update your Python or Node.js application to use your Client ID and Client Secret.

Authorize your custom application

Scroll a bit further down and find the Go to authentication page link on your Application Settings page:

App authentication page

Once you click that, you'll start the flow where you give your application access to the FreshBooks API.

Notice how the application authorization page is using your client ID:

App using client ID

You also see that the scopes you've decided on are reflected on this page as well:

App with scope

Click Allow to allow your custom application to connect to the FreshBooks API.

Depending on how your application handles the OAuth 2.0 workflow, you should get an authorization code that authorizes your application to query the FreshBooks API. In this example, the code is available via the query string:

Retrieve auth code

Now, you can use this authorization code to retrieve a bearer token to query the API further.

Get Your bearer and refresh tokens

You can use an API client tool like Postman or Bruno to test these calls. You can also use a command line tool, like curl, to replicate the examples used here.

The following curl command retrieves your bearer and refresh tokens:

curl -X POST https://api.freshbooks.com/auth/oauth/token \
  -d 'grant_type=authorization_code' \
  -d 'client_id=58f1...' \
  -d 'client_secret=c4a8...' \
  -d 'code=8749...' \
  -d 'redirect_uri=https://awesomeonlinestore.example/redirect'
  

Replace all the variables starting with the values retrieved from your application listing as well as the authorization code that you got when you authorized your application.

If it all went well, you should now receive a bearer token that you can use for testing your other calls:

{
  "access_token":"eyJra...",
  "token_type":"Bearer",
  "expires_in":43200,
  "refresh_token":"818c...",
  "scope":"user:profile:read user:clients:read user:expenses:read","created_at":1747105792,
  "direct_buy_tokens":{}
}

As discussed, the bearer token has a twelve-hour lifespan, so your application needs to refresh the bearer token at least once every twelve hours to retrieve a new token. You can do this by hitting the same /token endpoint but, this time, also supplying your refresh token:

curl -X POST https://api.freshbooks.com/auth/oauth/token \
  -d 'grant_type=refresh_token' \
  -d 'client_id=58f1...' \
  -d 'client_secret=c4a8...' \
  -d 'refresh_token=818c...' \
  -d 'redirect_uri=https://awesomeonlinestore.example/redirect'
  

You receive a new bearer token and a refresh token again, resetting the twelve-hour lifespan:

{
  "access_token":"eyJra...",
  "token_type":"Bearer",
  "expires_in":43200,
  "refresh_token":"757d...",
  "scope":"user:profile:read user:clients:read user:expenses:read","created_at":1747106356,
  "direct_buy_tokens":{}
}

Keep in mind that if you want your application to have a continuous connection to the FreshBooks API, you need to manage this token lifecycle.

Now, you can use your (latest) bearer token to retrieve some information from the API. Try retrieving some information about your own profile as a test:

curl -X GET https://api.freshbooks.com/auth/api/v1/users/me \
-H 'Authorization: Bearer eyJra...' 
-H 'Content-Type: application/json'

Your response should look like this:

{
  "response": {
    "id": 134...,
    "identity_id": 134...,
    "identity_uuid": "df26...",
    "first_name": "Thinus",
    "last_name": "Swart",
...
}

This indicates that your bearer token works. Now it's time to look at some other calls you can make using your token.

https://www.merge.dev/blog/accounting-integration?blog-related=image

Key endpoints and data models

The FreshBooks API provides a few endpoints that allow developers to interact with various aspects of the platform, from client management to invoicing and expense tracking. Understanding these endpoints and their associated data models is important for building and maintaining integrations.

Before diving into specific endpoints, you should also understand what a data model is in the context of an API. A data model defines the structure of the information you are sending to or receiving from FreshBooks, including field names, data types, and relationships between different objects (like invoices, clients, or expenses). Having a solid grasp of these models ensures your integration can correctly parse, validate, and map data back into your system.

Clients endpoint

The <code class="blog_inline-code">clients</code> endpoint enables you to manage client information, including creating, retrieving, updating, and deleting client records. Each client object contains fields such as <code class="blog_inline-code">fname</code>, <code class="blog_inline-code">lname</code>, <code class="blog_inline-code">email</code>, and <code class="blog_inline-code">organization</code>:

curl -X GET https://api.freshbooks.com/accounting/account/{account_id}/users/clients \
  -H 'Authorization: Bearer eyJra...' \
  -H 'Content-Type: application/json'

You can retrieve your <code class="blog_inline-code">{account_id}</code> from the previous output from the call to the "users/me" endpoint. If you have clients loaded on the system, the response looks like this:

{
  "response": {
    "result": {
      "clients": [
        {
          ...
          "fname": "Calvin",
          "lname": "Hobbs",
          "organization": "Imagination Inc.",
          ...
        }
      ],
      "page": 1,
      "pages": 1,
      "per_page": 15,
      "total": 1
    }
  }
}

For more details, refer to the Clients API documentation.

Expenses endpoint

The <code class="blog_inline-code">expenses</code> endpoint facilitates tracking of business expenses. Expense objects can include fields such as <code class="blog_inline-code">amount</code>, <code class="blog_inline-code">categoryid</code>, <code class="blog_inline-code">vendor</code>, and <code class="blog_inline-code">notes</code>:

curl -X GET https://api.freshbooks.com/accounting/account/{account_id}/expenses/expenses \
  -H 'Authorization: Bearer eyJra...' \
  -H 'Content-Type: application/json'

If you have some expenses saved to your FreshBooks account, your response looks like this:

{
  "response": {
    "result": {
      "expenses": [
        {
          ...
          "amount": {
            "amount": "25.00",
            "code": "USD"
          },
          "categoryid": 8659909,
          "notes": "100x Black Pens\n100x Blue Pens",
          "updated": "2025-05-13 21:29:40",
          "vendor": "Stationary King",
          ...
        }
      ],
      "page": 1,
      "pages": 1,
      "per_page": 15,
      "total": 1
    }
  }
}

More information about the <code class="blog_inline-code">expenses</code> endpoint can be found in the documentation.

Invoices endpoint

The <code class="blog_inline-code">invoices</code> endpoint allows you or your clients to create and manage invoices. Invoice objects include fields like <code class="blog_inline-code">customerid</code>, <code class="blog_inline-code">create_date</code>, <code class="blog_inline-code">due_date</code>, and<code class="blog_inline-code">lines</code> (which represent individual line items).

Don't forget that the custom application you created in the Developer Hub does not have the correct scope to be able to read invoices, so the next section is an example of what you might expect if it had read access:

curl -X GET https://api.freshbooks.com/accounting/account/{account_id}/invoices/invoices \
  -H 'Authorization: Bearer eyJra...' \
  -H 'Content-Type: application/json'

Your response looks like this:

{
    "response": {
        "result": {
            "invoices": [
                {
                  ...
                    "accountid": "LJA...",
                    "accounting_systemid": "LJA...",
                    "address": "",
                    "amount": {
                        "amount": "5000.00",
                        "code": "USD"
                    },
                    "auto_bill": false,
                    "autobill_status": null,
                    "basecampid": 0,
                    "city": "",
                    "code": "",
                    "country": "Canada",
                    "create_date": "2025-05-05",
                    "created_at": "2025-05-05 07:46:40",
                    "currency_code": "USD",
                    "due_date": "2025-06-15 00:00:00",
                    ...
                }
            ],
            "page": 1,
            "pages": 1,
            "per_page": 15,
            "total": 1
        }
    }
}

For more information on retrieving, creating, and amending invoices using the API, check out this Invoices documentation.

Pagination and webhooks

When you're interacting with the FreshBooks API, it's important to understand how it handles data retrieval, paging limits, and real-time notifications. These features ensure efficient data management, prevent overuse, and keep your application synchronized with FreshBooks events.

Pagination

You may have noticed that almost all the endpoints returned some statistics at the end of the JSON response:

...
            "page": 1,
            "pages": 1,
            "per_page": 15,
            "total": 1
        }
    }
}

When you start dealing with large amounts of information, like hundreds of expenses, the API breaks up the result set using pages. You can control the number of results and navigate through pages using the <code class="blog_inline-code">per_page</code> and <code class="blog_inline-code">page</code> query parameters:

curl -X GET "https://api.freshbooks.com/accounting/account/{account_id}/expenses/expenses?per_page=50&page=2" \
  -H 'Authorization: Bearer eyJra...' \
  -H 'Content-Type: application/json'

This call returns the fifty results that are found on page two of your initial result set.

Keep in mind that the FreshBooks API silently caps the <code class="blog_inline-code">per_page</code> parameter to one hundred results, even if you set the parameter with a higher value.

You can find more information about paging, searching, and sorting your result sets on the Search, Paging and Includes documentation.

Webhooks

Webhooks are a mechanism for APIs to notify each other of changes that have occurred. In a typical scenario, you might receive payment for an invoice in cash, which means you would need to manually input that information in FreshBooks. However, if your application needs to do something in response to that payment (like sending a receipt), your application needs to be aware of this change.

A webhook can be created that contacts your application when a certain event occurs, like an invoice being updated to reflect its paid status.

The Webhook Callbacks documentation page has a lot of information for creating webhooks to communicate between the APIs of FreshBooks and your application.

https://www.merge.dev/blog/erp-api-integration?blog-related=image

Challenges of integrating with the FreshBooks API

While FreshBooks offers a well-documented and modern REST API, teams building production-ready integrations often run into a range of challenges, especially when supporting multiple customers or accounting platforms. Here are some common issues developers face:

Addressing schema inconsistencies and mapping issues

FreshBooks has evolved significantly over time, and its data models can change with it. Even now, some endpoints are in the beta phase (like bills and bill_vendors), which could mean that their fields could change in the future.

These inconsistencies can make it difficult to create predictable data mappings, especially if you're trying to normalize data for downstream systems. Developers often need to write custom transformation logic or build schema validators to ensure stability.

Managing authentication and credential expiry

FreshBooks uses OAuth 2.0, which is a secure industry standard, but it does add some complexity. Access tokens expire, and your application needs to handle refresh token flows gracefully. If you're building an integration for many users (e.g., each of your customers connects their own FreshBooks account), securely managing these tokens at scale becomes a serious operational concern. Expired or revoked tokens can silently break sync if not handled properly.

Supporting webhooks and sync reliability

Webhooks are great for real-time sync, but in practice, they come with reliability challenges. You need to verify webhook payloads, handle retries from FreshBooks, and build processing logic on the application side to avoid duplicate data. There's also no guarantee that every webhook will be delivered, so you typically need a hybrid approach that combines polling with webhook support to ensure data integrity.

Scaling to support multiple accounting platforms

If your product integrates with FreshBooks and QuickBooks, Xero, or others, you'll quickly face the burden of maintaining separate data models, auth flows, rate limits, and webhook formats. Each API behaves differently; FreshBooks might return a field as  <code class="blog_inline-code">clientid</code>, while another platform might call it  <code class="blog_inline-code">customer_id</code>. Normalizing these differences requires a significant investment in abstraction and integration engineering.

Handling API changes and versioning

Any developer who routinely works with APIs will tell you that API providers occasionally roll out changes that may not be fully backward-compatible, like renamed fields, altered response formats, or deprecated endpoints. If your integration depends on undocumented behaviors or edge-case scenarios, these changes can introduce breaking bugs.

Staying up-to-date with API changelogs and building defensive code that fails gracefully is essential over time.

Best practices for integrating with the FreshBooks API

Despite the challenges of working with accounting APIs like FreshBooks, many teams have found ways to streamline their integration efforts and reduce long-term maintenance. The key is to plan for complexity early and adopt practices that make your system more resilient, observable, and scalable.

Normalize data internally

Even if you're integrating only with FreshBooks for now, it's wise to design your system to work with a normalized internal schema. By creating a consistent internal representation of entities, like invoices, clients, or expenses, you insulate your business logic from external API quirks. This also lays the groundwork for supporting other accounting platforms in the future.

Build for failures and retry logic from day one

APIs can and will fail due to rate limits, credential issues, or schema mismatches. Your integration should treat API calls as unreliable by default and have a robust strategy for retrying failed requests. This includes deduplication to avoid duplicate records, and alerts when failures persist. 

Prioritize observability and support tooling

When something goes wrong with a customer's integration, like a missing invoice or a failed sync, your support team needs tools to debug it quickly. 

To that end, invest in logging, request tracing, and dashboards that can expose API request history and webhook events. The faster you can surface and diagnose an issue, the more trust you build with customers relying on your application.

https://www.merge.dev/blog/financial-api-integration?blog-related=image

Integrate FreshBooks and any other accounting application with your product via Merge

As you can tell, integrating with Freshbooks can be complex. 

This complexity only gets magnified when you need to integrate your product with additional accounting solutions, like Xero, QuickBooks, and NetSuite.

To help you connect to any accounting application without straining your developer resources, you can leverage Merge—the leading unified API solution.

Using Merge, you can simply build to its Accounting Unified API once to unlock more than a dozen accounting integrations.

Merge's integrations with accounting solutions
A snapshot of the accounting integrations Merge supports

Merge also provides a full suite of integration observability tooling to help your customer-facing teams manage your integrations; post-sales support to help your team take the integrations to market successfully; advanced features to sync any financial custom data—and much more.

Learn more about Merge by scheduling a demo with an integration expert.

“It was the same process, go talk to their team, figure out their API. It was taking a lot of time. And then before we knew it, there was a laundry list of HR integrations being requested for our prospects and customers.”

Name
Position
Position
Thinus Swart
@Merge

Read more

API vs AI: how to understand their relationship

AI

A guide to integrating with the FreshBooks API

Software scalability: design, strategies and best practices

Insights

Subscribe to the Merge Blog

Get stories from Merge straight to your inbox

Subscribe

But Merge isn’t just a Unified 
API product. Merge is an integration platform to also manage customer integrations.  gradient text
But Merge isn’t just a Unified 
API product. Merge is an integration platform to also manage customer integrations.  gradient text
But Merge isn’t just a Unified 
API product. Merge is an integration platform to also manage customer integrations.  gradient text
But Merge isn’t just a Unified 
API product. Merge is an integration platform to also manage customer integrations.  gradient text