---
title: "Exploring the Quickbooks Online Accounting API"
description: "In this article, you'll understand how the API works, how you can connect to it, and how you can actually use it to perform various tasks, such as automatically updating accounts, invoices, bills, and more. Furthermore, you'll learn about the Apideck Unified API platform that simplifies the entire process.
"
author: "Naman Bansal"
published: "2024-10-15T00:00+02:00"
updated: "2025-12-11T21:55:56.359Z"
url: "https://www.apideck.com/blog/exploring-the-quickbooks-online-accounting-api"
tags: ["Unified API", "Accounting", "Guides & Tutorials"]
---

# Exploring the Quickbooks Online Accounting API

# Exploring the QBO API

[QuickBooks](https://quickbooks.intuit.com/) is the leading business accounting software, with a market share [close to 85 percent](https://6sense.com/tech/small-business-accounting/quickbooks-market-share), offering a comprehensive suite of tools, such as invoicing, estimates, instant business loans, and detailed reports.

It's quite powerful on its own, but what if you could integrate it with the rest of your tech stack? Imagine automating financial report generation with data from your sales platform or perfectly synchronized invoices and payment information between your e-commerce store and QuickBooks.

That's precisely where the QuickBooks Online Accounting API or also often referred to as the QBO API comes in, allowing you to programmatically manage your QuickBooks account, create scripts to directly interact with QuickBooks, and integrate with a selection of other services.

In this article, you'll understand how the API works, how you can connect to it, and how you can actually use it to perform various tasks, such as automatically updating accounts, invoices, bills, and more. Furthermore, you'll learn about the [Accounting API](https://www.apideck.com/accounting-api) offered by Apideck that simplifies the entire process.

## QuickBooks Desktop vs. QuickBooks Online APIs

Before diving into the QuickBooks Online API, it's important to understand that Intuit offers two distinct APIs for their different QuickBooks products:

**QuickBooks Desktop API** uses the QuickBooks SDK and QBXML (QuickBooks XML) format for communication. It requires:
- The [QuickBooks Desktop](https://www.apideck.com/connectors/quickbooks-desktop) software to be installed locally
- A connector application or middleware running on the same machine
- XML-based request/response messaging through the SDK
- Different authentication methods (application certificates rather than OAuth)
- Synchronous processing where QuickBooks Desktop must be running

**QuickBooks Online API** (QBO API), on the other hand, is a modern REST API that:
- Works entirely through cloud-based HTTP requests
- Uses JSON for data exchange
- Implements OAuth 2.0 for secure authentication
- Operates independently of any local software installation
- Provides real-time access from anywhere with internet connectivity

The key difference is architectural: Desktop API requires local access to the QuickBooks Desktop application, making it suitable for on-premise integrations, while the Online API is cloud-native, enabling web applications and SaaS platforms to integrate seamlessly without any local dependencies.

## The QuickBooks Online Accounting API

![QuickBooks Online](https://imgur.com/Kj4pjrA.png)

The QuickBooks Online Accounting API uses the REST framework to provide apps with access to the customer-facing features in the software. The API is broken down into several entities that you can use to access different parts of the app. For example, the **Bill** entity has various details and fields about individual bills, such as vendor reference, currency reference, transaction date, amount, and due date.

It utilizes standard HTTP methods (GET, POST, PUT, DELETE) and returns JSON objects, making it compatible with a wide range of languages and frameworks, like Python, Java, TypeScript, and PHP. The API provides unique abilities apart from basic create, read, update, and delete (CRUD) operations, such as the following:

* **Single requests:** The server individually processes and responds to each single request.
* **Query requests:** Use the Intuit SQL-like query language to send a query to the server.
* **Batch operations:** A single batch request can perform multiple actions at once.

The API is free and has no volume limits, but there is a per-minute throttling limit (max 500 requests per minute) for sending requests. It also provides free sandbox accounts. If you want to use it with your own real QuickBooks account, you have to purchase a paid plan to create an account (the API is free regardless). Pricing for QuickBooks Online [varies depending on the plan](https://quickbooks.intuit.com/pricing/?cid=cdist_wblog_id).

### Challenges with the QuickBooks Online Accounting API

It isn't all sunshine and rainbows, though, and there are numerous roadblocks you may face along the way.

One of the biggest challenges with the QuickBooks Online Accounting API is implementing proper authentication via [OAuth 2.0](https://oauth.net/2/). This ensures that your app securely accesses a user's QuickBooks account without compromising their privacy.

The authentication process requires users to grant access to their QuickBooks account, after which QuickBooks sends the developer an access token and a refresh token to get data from the user's account. However, the access token is valid for only one hour, after which you must use the refresh token to reset the access token. On top of that, the refresh token is valid for one hundred days, after which you will have to redo the entire authorization flow. However, this expiry date is rolling and extends each time it’s used to refresh an access token So, if you refresh the token before the end of this period, you won't have to go through the flow again. Still, It can be challenging to build this logic without disrupting the user experience.

Error handling is another crucial aspect that can be quite complex. It provides a variety of error codes and messages that detail why a request may have failed. For example, the following are common HTTP status code responses:

* **`400 Bad Request`** typically signifies incorrect input data, such as missing required fields for an invoice. You must validate your requests to ensure all necessary information is included.
* **`401 Unauthorized`** occurs due to expired access tokens or invalid credentials.
* **`403 Forbidden`** means the URL exists but it's restricted.

Along with these, the QuickBooks response also includes a separate error code that may be mapped to specific issues, as seen in their [documentation](https://developer.intuit.com/app/developer/qbo/docs/develop/troubleshooting/error-codes). You must establish a well-defined error-handling strategy that interprets and captures these error codes and their details.

You must also familiarize yourself with the request and response structures to use the API effectively. It has many different endpoints for each feature, such as invoices, accounts, bills, customers, and employees; and each endpoint has different requirements. Use their [documentation](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/account) to look up the requirements for each one of them and ensure you don't send bad requests.

It can also become difficult to parse the API responses or create the JSON payloads, which often contain many complex nested objects. Since many API operations require asynchronous operations and handling, especially when dealing with long-running operations, a clear understanding of managing promise chains and callbacks is necessary.

Lastly, while the sandbox environment provides a useful platform for testing, it offers only a limited data set. This limitation means that it cannot fully replicate the myriad errors and scenarios you may encounter in a live environment. Real-world testing is essential for developing a comprehensive understanding of how to address various issues that may arise during actual use.

## Authentication and Integration with the QuickBooks Online Accounting API

In this section, you'll learn how to authenticate the QuickBooks Online Accounting API and get your access and refresh tokens.

Head over to [https://developer.intuit.com/](https://developer.intuit.com/app/developer/homepage) and click the **Sign Up** button.

![Intuit Developer **Sign Up** button](https://imgur.com/kAUqXPS.png)

Fill in your account details, and then navigate to the **Sandbox** option from the profile menu. If you don't find a sandbox company already created, click the **Add a sandbox company** button:

![Sandbox companies page](https://imgur.com/a2asyuo.png)

Next, go to the **Dashboard** tab and click **Create an app**. Select **QuickBooks Online and Payments** as the platform you want to develop for:

![Create an app](https://i.imgur.com/I6oiVPF.png)

Give your app a name and tick only the `com.intuit.quickbooks.accounting` option for the scope:

![App scope](https://imgur.com/syhJP7N.png)

Now, navigate to the **Keys & credentials** within the **Development Settings** option and get the **Client ID** and **Client Secret**:

![Client ID and secret](https://imgur.com/JRekJu5.png)

If you're interested, you can find the GitHub repo for the full code [here](https://github.com/namancoderpro/qbo-api). We also have a new guide for getting your [Quickbooks API](https://www.apideck.com/blog/how-to-get-your-quickbooks-api-key)

You'll use the following libraries in your code, so you need to download them with [npm](https://www.npmjs.com/) or another package manager:

* **[Express](https://expressjs.com/):** It's a lightweight framework for building server-side apps in [Node.js](https://nodejs.org/en/). You'll use it to create a server that can handle the routing for OAuth 2.0, like handling the callback with authorization codes.
* **[Axios](https://axios-http.com/):** It's used to make HTTP requests to external APIs, including QuickBooks in this case.
* **[dotenv](https://www.npmjs.com/package/dotenv/v/8.2.0):** It's used to load environment variables from a `.env` file into `process.env` to use in the code.
* **[body-parser](https://www.npmjs.com/package/body-parser):** A middleware for Express that parses incoming request bodies in a middleware before your handlers, making it easier to extract and work with data sent in requests, such as JSON payloads. This enables our application to handle data when interacting with the QuickBooks API.

This tutorial uses [Visual Studio Code](https://code.visualstudio.com/), which you can download from their official [page](https://code.visualstudio.com/download).

You can store sensitive data like the client ID, secret, and redirect URI in a `.env file`, as in the following snippet:

```
# .env
CLIENT_ID="YOUR_CLIENT_SECRET_COPIED_EARLIER"
CLIENT_SECRET="YOUR_CLIENT_SECRET_COPIED_EARLIER"
REDIRECT_URI="http://localhost:3000/callback"
```

Navigate to the **Keys** page in your Intuit Developer account and update the **Redirect URIs > LINK** field with the value `http://localhost:3000/callback`. Ensure that this value exactly matches both your `.env` and your Intuit Developer account:

![Callback URL](https://imgur.com/6MtbzxI.png)

Before getting into the actual code, let's briefly review how the authorization process works:

![Authorization process](https://imgur.com/vMiy5Xi.png)

As seen in this diagram, you first send a GET request to a QuickBooks endpoint where the user authorizes access to their account. Then, that page redirects you to the `redirect_uri` specified in your `.env` file and includes the authorization code.

Next, you send a POST request to another endpoint that allows you to exchange this authorization code with the access and refresh tokens used to access user data.

The first thing you must do is visit the QuickBooks authorization page:

```
app.get('/auth', (req: Request, res: Response) => {
    const redirectUri = encodeURIComponent(process.env.REDIRECT_URI!);
    const authUrl = `https://appcenter.intuit.com/connect/oauth2?client_id=${process.env.CLIENT_ID}&response_type=code&scope=com.intuit.quickbooks.accounting&redirect_uri=${redirectUri}&state=demo-app`;
    res.redirect(authUrl);
});
```

Here, you retrieve the redirect URI from the environment variables and encode it to ensure it's correctly formatted. Next, the `authUrl` is constructed with crucial data, such as the `client_id`, `client_secret`, `response_type`, and `state`, encoded as URL query parameters.

Refer to the [QuickBooks documentation](https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/oauth-2.0) for a primer on what all these terms mean.

Finally, you're instructing the browser to redirect you to the constructed `authUrl`.

The following code handles the callback, retrieves the authorization code, and exchanges it for the access and refresh tokens:

```
# app.ts

app.get('/callback', async (req: Request, res: Response) => {
    const authCode = req.query.code as string;
    const realmId = req.query.realmId as string;
    BASE_URL = `https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}`;

    if (!authCode) {
        return res.status(400).send('No authorization code received');
    }

    const requestBody = new URLSearchParams({
        grant_type: 'authorization_code',
        code: authCode,
        redirect_uri: process.env.REDIRECT_URI!,
    }).toString();

    const response = await axios.post(
        'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer',
        requestBody,
        {
            headers: {
                'Accept': 'application/json',
                'Authorization': `Basic ${Buffer.from(`${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`).toString('base64')}`,
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }
    );

    accessToken = response.data.access_token;
    refreshToken = response.data.refresh_token;

    res.json({ accessToken, refreshToken });
    console.log(accessToken, refreshToken);
});
```

Here, you're making a POST request to the endpoint, passing in the body object and the headers. The `try-catch` block assists with error handling and fixing any possible issues. Similarly, you can implement the refresh token logic to refresh the access code every sixty minutes:

```
async function refreshAccessToken() {
    if (!refreshToken) {
        console.error('No refresh token available to refresh access token');
        return;
    }

    const requestBody = new URLSearchParams({
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
    }).toString();

    const response = await axios.post(
        'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer',
        requestBody,
        {
            headers: {
                'Accept': 'application/json',
                'Authorization': `Basic ${Buffer.from(`${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`).toString('base64')}`,
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }
    );

    // Store the new access and refresh tokens
    accessToken = response.data.access_token;
    refreshToken = response.data.refresh_token;

    console.log('Access token refreshed:', accessToken);
    console.log('Refresh token:', refreshToken);

    // Here, store the new tokens in your database or persistent storage if needed
}

setInterval(refreshAccessToken, 3600000);
```

In this code snippet, you're first checking if a refresh token exists. Then, you're sending a request to the same endpoint as the callback logic with the only difference being that this time, `grant_type` is now `refresh_token` instead of `authorization_code`, as seen in the previous code block.

The `setInterval` function triggers this function every 60 hours or 3,600,000 milliseconds.

## Using the API

Once you're done with the authentication and have the access token, you can use the API to manipulate the sandbox QuickBooks account.

### Create, Get, and Update Accounts

Accounts refer to categories that can track a business's money in and money out. 

To create an account, you send a POST request, as follows:

```
# app.ts

const accountData = {
    "Name": "Test_Account",
    "AccountType": "Accounts Receivable"
};

const response = await axios.post(
    `${BASE_URL}/account`,
    accountData,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(201).json(response.data);
```

![Created account](https://imgur.com/aUbzya7.png)

Now, to retrieve a specific account, you need to send only a GET request to the URL: `BASE_URL/account/account_id`.

The function looks something like this:

```
const response = await axios.get(
    `${BASE_URL}/account/91`,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(200).json(response.data);
```

This code has `91` in the base URL as that was the ID of the account created in the previous step.

Finally, let's cover how to update an account. You need to send a POST request to the URL `${BASE_URL}/account` with a body containing all the fields of the object. Any writable fields omitted in the body are set to `NULL`.

For example, to update the name of the previous account from `Test_Account` to `Test_Account_Updated`, you'd write the following:

```
const accountData = {
    "Name": "Test_Account_Updated",
    "SubAccount": false,
    "FullyQualifiedName": "Test_Account_Updated",
    "Active": true,
    "Classification": "Asset",
    "AccountType": "Accounts Receivable",
    "AccountSubType": "AccountsReceivable",
    "CurrentBalance": 0,
    "CurrentBalanceWithSubAccounts": 0,
    "CurrencyRef": { "value": "USD", "name": "United States Dollar" },
    "domain": "QBO",
    "sparse": false,
    "Id": "91",
    "SyncToken": "0",
    "MetaData": { "CreateTime": "2024-08-12T09:37:38-07:00", "LastUpdatedTime": "2024-08-12T09:37:38-07:00" },
};

const response = await axios.post(
    `${BASE_URL}/account`,
    accountData,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(201).json(response.data);
```

In return, you get this object:

```
{"Account":{"Name":"Test_Account_Updated","SubAccount":false,"FullyQualifiedName":"Test_Account_Updated","Active":true,"Classification":"Asset","AccountType":"Accounts Receivable","AccountSubType":"AccountsReceivable","CurrentBalance":0,"CurrentBalanceWithSubAccounts":0,"CurrencyRef":{"value":"USD","name":"United States Dollar"},"domain":"QBO","sparse":false,"Id":"91","SyncToken":"1","MetaData":{"CreateTime":"2024-08-12T09:37:38-07:00","LastUpdatedTime":"2024-08-12T11:17:56-07:00"}},"time":"2024-08-12T11:17:56.425-07:00"}

```

### Create, Get, and Update Invoices

To create an invoice using the QuickBooks Online Accounting API, you set up an endpoint called `/create-invoice` and send a POST request with the required invoice data. Here's how that works:

```
const invoiceData = {
    "CustomerRef": {
        "value": "1" // Replace with the appropriate customer ID
    },
    "Line": [
        {
            "Amount": 100.00,
            "DetailType": "SalesItemLineDetail",
            "SalesItemLineDetail": {
                "ItemRef": {
                    "value": "1", // Replace with the appropriate item ID
                    "name": "Item Name"
                },
                "UnitPrice": 100.00,
                "Qty": 1
            }
        }
    ],
    "BillAddr": {
        "Line1": "123 Main St",
        "City": "Anytown",
        "CountrySubDivisionCode": "CA",
        "PostalCode": "12345"
    },
    "CurrencyRef": {
        "value": "USD"
    }
};

const response = await axios.post(
    `${BASE_URL}/invoice`,
    invoiceData,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(201).json(response.data);
```

In this code snippet, you define the data for the invoice in the `invoiceData` object, consisting of the following components:

* **`CustomerRef`** identifies the customer for whom the invoice is created. The value field must contain the ID of an existing customer in your QuickBooks account.

* **`Line`** is an array of line items for the invoice. Each object in the array represents an individual item being billed:
    * **`Amount`** is the total amount for this specific line.
    * **`DetailType`** indicates the type for this line item. For sales items, this should be `SalesItemLineDetail`.
    * **`SalesItemLineDetail`** is a nested object that contains details specific to the item:
       * **`ItemRef`** references the item being billed. The `value` field should be the ID of the item, and `name` is the item's name.
       * **`UnitPrice`** is the price of one unit of the item.
       * **`Qty`** is the quantity of the item included in the invoice.

* **`BillAddr`** is an optional object for the billing address details for the customer, helping to clarify where the invoice is to be sent.

* **`CurrencyRef`** identifies the currency in which the invoice is issued. The value `USD` signifies that the invoice is in US dollars.

To read or retrieve a specific invoice, you send a GET request to the URL specified with the invoice ID: `BASE_URL/invoice/invoice_id`. Here's an example of how that might look:

```
 const { invoiceId } = req.params; // Get invoice ID from request parameters

const response = await axios.get(
    `${BASE_URL}/invoice/${invoiceId}`,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);
```

Upon a successful request, you receive the invoice details in JSON format, including all information about the customer, line items, and billing address.

To update an existing invoice, you send a POST request to the URL `BASE_URL/invoice` with a JSON object containing the updates. The `sparse:true` field indicates that it's a sparse update where other fields are left untouched except the ones being updated.

You need to include the invoice's `Id` and `SyncToken` in the request. Here's how you can structure the update:

```
const invoiceData = {
    "Id": "145", // Replace with the invoice ID you want to update
    "SyncToken": "0", // Must be the current SyncToken for the invoice
    "Line": [
        {
            "Amount": 150.00,
            "DetailType": "SalesItemLineDetail",
            "SalesItemLineDetail": {
                "ItemRef": {
                    "value": "1", // ID of the item
                    "name": "Updated Item Name" // Optional updated item name
                },
                "UnitPrice": 150.00,
                "Qty": 1
            }
        }
    ]
};

const response = await axios.post(
    `${BASE_URL}/invoice`,
    invoiceData,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(200).json(response.data);
```

### Create, Get, and Update Bills

To create a bill, set up an endpoint called `/create-bill` and send a POST request with the required bill data. Here's how that works:

```javascript
const billData = {
    "VendorRef": {
        "value": "56"
    },
    "Line": [
        {
            "Amount": 100.00,
            "DetailType": "AccountBasedExpenseLineDetail",
            "AccountBasedExpenseLineDetail": {
                "AccountRef": {
                    "value": "7",
                }
            }
        }
    ],
    "CurrencyRef": {
        "value": "USD"
    }
};

const response = await axios.post(
    `${BASE_URL}/bill`,
    billData,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(201).json(response.data);
```

In this code snippet, you define the data for the bill in the `billData` object, which includes these components:

- **`VendorRef`** identifies the vendor to whom the bill is addressed. The `value` field must contain the ID of an existing vendor in your QuickBooks account.

- **`Line`** is an array of line items for the bill. Each object in the array represents an individual expense being billed:
    - **`Amount`** is the total amount for this specific line.
    - **`DetailType`** indicates the type for this line item. For expense items, this should be `ExpenseLineDetail`.
    - **`ExpenseLineDetail`** is a nested object that contains details specific to the expense:
       - **`AccountRef`** references the account associated with this expense. The `value` field should be the ID of the account, and `name` is the account's name.
       - **`Amount`** is the total amount for this line item.
       - **`Qty`** is the quantity of the item included in the bill.
- **`CurrencyRef`** identifies the currency in which the bill is issued. The value `USD` signifies that the bill is in US dollars.

To read or retrieve a specific bill, you send a GET request to the URL specified with the bill ID: `BASE_URL/bill/bill_id`. Here's an example of how that might look:

```javascript
const response = await axios.get(
    `${BASE_URL}/bill/1`, // Replace 1 with the actual bill ID
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);
```

To update an existing bill, you send a POST request to the URL `${BASE_URL}/bill` with a JSON object containing the updates. Remember to include the bill's `Id` and `SyncToken` in the request.

Also, since it's a full update, any fields not included in the request are changed to `NULL`.

Here's how you can structure the update:

```javascript
const billData = {
    "Id": "146", // Replace with the bill ID you want to update
    "SyncToken": "0",
    "DueDate": "2024-08-12",
    "TotalAmt": 150,
    // Other fields as necessary
};

const response = await axios.post(
    `${BASE_URL}/bill`,
    billData,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(200).json(response.data);

```

### Create, Get, and Update Payments

Payments are essential for tracking monetary transactions made toward invoices or outstanding balances.

QuickBooks has specific features and requirements for payments, which include the following:

* The ability to perform either full or sparse updates.
* The capability to link to multiple invoices or credit memos.
* The option to record a payment without linking it to any specific invoice or credit memo.
* Regardless of whether a full or sparse update is performed, the `Line` field must always contain all relevant line items.

To create a payment, set up an endpoint called `/create-payment` that sends a POST request with the required payment data:

```javascript
const paymentData = {
    "CustomerRef": {
        "value": "1" // Replace with the appropriate customer ID
    },
    "TotalAmt": 100,
    "Line": [
        {
            "Amount": 100.00, // Total payment amount for this line
            "LinkedTxn": [
                {
                    "TxnId": "1", // Replace with the ID of the invoice being paid
                    "TxnType": "Invoice" // The type of transaction
                }
            ]
        }
    ],
    "CurrencyRef": {
        "value": "USD" // Currency code for the payment
    }
};

const response = await axios.post(
    `${BASE_URL}/payment`,
    paymentData,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(201).json(response.data);
```

The code snippet includes the following fields:

- **`CustomerRef`** identifies the customer making the payment.

- **`Line`** contains details about the payment, including the following:
    - **`Amount`** is the total payment amount.
    - **`LinkedTxn`** links the payment to the invoice(s) being paid, requiring the transaction ID (`TxnId`) and transaction type (`TxnType`).

- **`PaymentMethodRef`** identifies how the payment is made (*eg* cash, credit card).

- **`CurrencyRef`** specifies the currency in which the payment is made.

To read or retrieve a specific payment, send a GET request to the URL specified with the payment ID `BASE_URL/payment/payment_id`:

```javascript
const { paymentId } = req.params; // Get payment ID from request parameters

const response = await axios.get(
    `${BASE_URL}/payment/${paymentId}`,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(200).json(response.data);
```

This code gets the payment ID from the request parameters and retrieves the payment details in JSON format.

To update an existing payment, send a POST request to the `${BASE_URL}/payment` URL with a JSON object for the updates. Include the payment's `Id` and `SyncToken`.

### Create, Get, and Update Transfers

Transfers represent the movement of money between bank accounts. To create a transfer, set up an endpoint called `/create-transfer` and send a POST request with the required transfer data:

```javascript
const transferData = {
    "FromAccountRef": {
        "value": "1" // Replace with the source account ID
    },
    "ToAccountRef": {
        "value": "2" // Replace with the destination account ID
    },
    "Amount": 100.00, // Amount to be transferred
    "TxnDate": "2024-01-01" // Date of the transfer
};

const response = await axios.post(
    `${BASE_URL}/transfer`,
    transferData,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(201).json(response.data);

```

Here are the important attributes of the JSON object in this code snippet:

- **`FromAccountRef`** identifies the account from which the money is being transferred.
- **`ToAccountRef`** identifies the account to which the money is being transferred.
- **`Amount`** is the total amount being transferred.
- **`CurrencyRef`** specifies the currency for the transfer.
- **`PaymentMethodRef`** identifies the method used for the transfer (*eg* bank transfer).
- **`Date`** is the date when the transfer occurs.

To read or retrieve a specific transfer, send a GET request to the URL specified with the transfer ID `BASE_URL/transfer/transfer_id`:

```javascript
const { transferId } = req.params; // Get transfer ID from request parameters

const response = await axios.get(
    `${BASE_URL}/transfer/${transferId}`,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(200).json(response.data);
```

This code retrieves the transfer details using the transfer ID provided in the request parameters.

To update an existing transfer, send a POST request to the `${BASE_URL}/transfer` URL with a JSON object for the updates. Include the transfer `Id` and `SyncToken`. Since transfers support sparse updates, you'll see a code snippet for that later, but you may do a full update, too.	

Here's how you could structure the update:

```javascript
const transferData = {
    "Id": "145", // Replace with the transfer ID you want to update
    "SyncToken": "0", // Must be the current SyncToken for the transfer
    "Amount": 150.00, // Updated transfer amount
    "FromAccountRef": {
        "value": "1" // Source account ID
    },
    "ToAccountRef": {
        "value": "2" // Destination account ID
    },
    sparse: true
};

const response = await axios.post(
    `${BASE_URL}/transfer`,
    transferData,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(200).json(response.data);
```

### Create, Get, and Update Vendors

The QuickBooks Online Accounting API has some specific rules for setting up vendors:

* **`DisplayName`** must be unique across all customer, employee, and vendor objects.
* **`PrimaryEmailAddress`** must contain an at (`@`) and dot (`.`) sign.

To create a vendor, set up an endpoint called `/create-vendor` and send a POST request with the required vendor data:

```javascript
const vendorData = {
    "DisplayName": "Vendor Name", // Required: Name of the vendor
    "PrimaryEmailAddr": {
        "Address": "vendor@example.com" // Vendor's email address
    },
    "PrimaryPhone": {
        "FreeFormNumber": "(123) 456-7890" // Vendor's phone number
    },
    "BillAddr": { // Optional billing address details
        "Line1": "123 Vendor St",
        "City": "Vendor City",
        "CountrySubDivisionCode": "CA", // State abbreviation
        "PostalCode": "12345" // Postal code
    },
    "Suffix": "Sr.",
    "Title": "Mr.",
    "GivenName": "Example 1",
    "PrintOnCheckName": "Example Vendor Name"
};

const response = await axios.post(
    `${BASE_URL}/vendor`,
    vendorData,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(201).json(response.data);
```

To read or retrieve a specific vendor, send a GET request to the URL specified with the vendor ID `BASE_URL/vendor/vendor_id`:

```javascript
const { vendorId } = req.params; // Get vendor ID from request parameters

const response = await axios.get(
    `${BASE_URL}/vendor/${vendorId}`,
    {
        headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        },
    }
);

res.status(200).json(response.data);
```

This code retrieves the vendor details using the vendor ID provided in the request parameters.

To update an existing vendor, send a POST request to the `${BASE_URL}/vendor` URL with a JSON object for the updates. Include the vendor's `Id` and `SyncToken`. The structure is the same as the earlier full update requests.

### Webhooks

Webhooks act as instant messengers, delivering information in real-time straight to your app whenever an event occurs in your end user’s QuickBooks account. You can use this data to perform further actions or logic. For example, if your user creates a new invoice, vendor, or customer, you can instantly get a notification, which can be used for actions such as automatically updating your accounting system, sending a confirmation email, or triggering a workflow to approve or review the new entry.

Using webhooks with QuickBooks is quite simple. First, setup OAuth 2.0 authentication for your app using the steps outlined above. Then, you can specify webhook events you want to listen for and the endpoint URL where you want to receive the data in the Intuit Developer dashboard.

Once the webhook's data is validated, you can process it and perform additional logical operations. For more detailed guidance, refer to [the documentation](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks) and explore code samples [here.](https://github.com/IntuitDeveloper/SampleApp-WebhookNotifications-nodejs)

## QuickBooks Online API Pricing

Intuit fundamentally transformed the QuickBooks Online API landscape in 2025 with the introduction of the Intuit App Partner Program, marking the end of free unlimited API access that developers had previously enjoyed. The new tiered pricing model creates a clear distinction between Core API calls (data creation and updates like creating invoices or customers) which remain completely free and unlimited, and CorePlus API calls (data retrieval operations like querying accounts or fetching reports) which are now metered and charged based on usage. The program offers four partner tiers ranging from the free Builder tier with 500,000 monthly CorePlus API credits to the premium Platinum tier at $999/month with up to 75 million credits and enhanced support. While this represents a significant shift for QuickBooks Online integrations, developers can optimize costs by focusing on efficient querying practices, and carefully analyzing their GET versus POST request ratios to select the most appropriate tier for their needs. For a comprehensive breakdown of the pricing structure, API classifications, and migration strategies, developers should consult our detailed analysis of [QuickBooks API pricing and the Intuit App Partner Program](https://www.apideck.com/blog/quickbooks-api-pricing-and-the-intuit-app-partner-program).

## Conclusion

This article covered the fundamental processes involved with the QuickBooks Online Accounting API, such as authorization, account creation, invoices, bills, and payments. By now, you should have a solid understanding of the request and response structure as well as how you can integrate it in your own apps. Want to dive deeper? Check out our follow-up post on [how to integrate with the QuickBooks API](https://www.apideck.com/blog/how-to-integrate-with-quickbooks-api).

To further simplify the process, you should check out [Apideck](https://www.apideck.com/). It's a powerful unified APIs solution that allows business-to-business software-as-a-service (B2B SaaS) companies to scale their integrations effortlessly. The [Apideck Unified API](https://www.apideck.com/unified-apis) enables you to create a single integration that interacts with multiple accounting systems, including QuickBooks, Xero, and Sage, as well as customer relationship management (CRM) and human resources information systems (HRIS), eliminating the need for separate point-to-point integrations. However, its main differentiator is its real-time data access, ensuring minimal delays and robust security with its no-data-storage model and SOC 2 type 2 compliance.

Get started with a [free trial](https://platform.apideck.com/api/auth/login?product=unify&screenHint=signup&connector=quickbooks) today.
