FreshBooks NodeJS SDK
The FreshBooks NodeJS SDK allows you to more easily utilize the FreshBooks API.
Installation
Use your favorite package manager to install any of the packages and save to your package.json
:
$ npm install @freshbooks/api
# Or, if you prefer yarn
$ yarn add @freshbooks/api
Usage
See the full documentation.
Configuring the API client
Your app will interact with the REST API using the Client
object, available from the @freshbooks/api
package.
The client may be instantiated with a valid OAuth token or provided with a client secret and redirect URI which may
then be used to obtain an access token.
Using a pre-generated access token
import { Client } from '@freshbooks/api'
const clientId = process.env.FRESHBOOKS_APPLICATION_CLIENT_ID
const token = process.env.FRESHBOOKS_TOKEN
const client = new Client(clientId, {
accessToken: token,
})
Using a client secret and redirect URI
import { Client } from '@freshbooks/api'
const clientId = process.env.FRESHBOOKS_APPLICATION_CLIENT_ID
const clientSecret = process.env.FRESHBOOKS_APPLICATION_CLIENT_SECRET
const client = new Client(clientId, {
clientSecret,
redirectUri: 'https://your-redirect-uri.com/'
})
const authUrl = client.getAuthRequestUrl()
const code = ...
const tokens = client.getAccessToken(code)
Get/set data from REST API
All REST API methods return a response in the shape of:
{
ok: boolean
data?: T
error?: Error
}
Example API client call:
try {
const { data } = await client.users.me()
console.log(`Hello, ${data.id}`)
} catch ({ statusCode, message }) {
console.error(`Error fetching user: ${statusCode} - ${message}`)
}
Errors
If an API error occurs, the response object contains an error
object, with the following shape:
{
name: string
message: string
statusCode?: string
errors?: APIError[]
}
Not all API calls return a list of specific errors, but if they do, they will be in the form of:
{
message: string
errorCode?: number
field?: string
object?: string
value?: string
}
Examples:
clientData = {}
try {
const client = await fbClient.clients.single(accountId, 00000)
console.log('Not called')
} catch (err) {
console.log(err.name)
console.log(err.message)
console.log(err.statusCode)
console.log(err.errors)
}
clientData = {}
try {
const client = await fbClient.clients.create(clientData, accountId)
console.log('Not called')
} catch (err) {
console.log(err.name)
console.log(err.message)
console.log(err.statusCode)
console.log(err.errors)
}
Pagination, Filters, and Includes
If an endpoint supports searching or custom inclusions via query parameters, these parameters
can be specified using a QueryBuilderType
. See FreshBooks API - Parameters
documentation.
type QueryBuilderType = PaginationQueryBuilder | IncludesQueryBuilder | SearchQueryBuilder
An appropriate method on the API client will support an array of builders:
public readonly invoices = {
list: (accountId: string, queryBuilders?: QueryBuilderType[]) => Promise<Result<{
invoices: Invoice[];
pages: Pagination;
}>>;
}
Pagination results are included in list
responses. The data
object will contain a pages
property, with the following:
{
page: number
pages: number
total: number
size: number
}
To make a paginated call, first create a PaginationQueryBuilder
object set using the page
and perPage
functions
and pass it in the list
call.
import { PaginationQueryBuilder } from '@freshbooks/api/dist/models/builders/PaginationQueryBuilder'
const paginator = new PaginationQueryBuilder()
paginator.page(1).perPage(10)
let page = 1
let totalPages = 1
while (page <= totalPages) {
let response = await fbClient.clients.list(accountId, [paginator])
let { clients, pages } = response.data
console.log(clients)
console.log(`Page ${pages.page} of ${pages.pages} pages`)
console.log(`Showing ${pages.size} per page for ${pages.total} total clients`)
page++
totalPages = pages.pages
paginator.page(page)
console.log('Clients:')
clients.map((client) => console.log(client.organization))
}
Search Filters
To filter which results are return by list
method calls, construct a SearchQueryBuilder
and pass and pass it in
the list
call.
The SearchQueryBuilder
supports the patterns: equals
, in
, like
and between
.
s = SearchQueryBuilder()
s.in("email", "api@freshbooks.com")
console.log(s.build())
s = SearchQueryBuilder()
s.in("clientids", [123, 456])
console.log(s.build())
s = SearchQueryBuilder()
s.like("email_like", "@freshbooks.com")
console.log(s.build())
s = SearchQueryBuilder()
s.between("amount", 1, 10)
console.log(s.build())
s = SearchQueryBuilder()
s.between("start_date", date.today())
console.log(s.build())
Example API client call with SearchQueryBuilder
:
import { SearchQueryBuilder } from '@freshbooks/api/dist/models/builders/SearchQueryBuilder'
const searchQueryBuilder = new SearchQueryBuilder()
.like('address_like', '200 King Street')
.between('date', { min: new Date('2010-05-06'), max: new Date('2019-11-10') })
try {
const { data } = await client.invoices.list(accountId, [searchQueryBuilder])
console.log('Invoices: ', data)
} catch ({ statusCode, message }) {
console.error(`Error fetching user: ${statusCode} - ${message}`)
}
Includes
To include additional relationships, sub-resources, or data in a response an IncludesQueryBuilder
can be constructed
which can then be passed into list
, single
, create
, and update
calls.
Example API client call with IncludesQueryBuilder
:
import { IncludesQueryBuilder } from '@freshbooks/api/dist/models/builders/IncludesQueryBuilder'
const includesQueryBuilder = new IncludesQueryBuilder().includes('lines')
console.log(includesQueryBuilder.build())
try {
const { data } = await client.invoices.list(accountId, [includesQueryBuilder])
console.log('Invoices: ', data)
} catch ({ statusCode, message }) {
console.error(`Error fetching user: ${statusCode} - ${message}`)
}
Data Field Notes
Dates and Times
For historical reasons, many resources in the FreshBooks API (mostly accounting-releated) return date/times in
"US/Eastern" timezone. Some effort is taken to convert these in the models to return Date
objects normalized to UTC.
Monetary Values
The FreshBooks API returns most monetary values as a money object containing the amount and a currency code:
export default interface Money {
amount?: string
code?: string
}
The amount is returned and stored as a string to prevent floating point precision issues. If you are doing
calculations on these values, it is recommended to use a library for decimal arithmetic such as
decimal.js, big.js,
or bignumber.js