# Create a new connection
Source: https://docs.qubesync.com/api-reference/connections/create-a-new-connection
/api-reference/openapi.json post /connections
Creates and returns a new connection for the authenticated application. Returns an onboarding URL for the user to complete the QuickBooks setup process.
# Delete a connection
Source: https://docs.qubesync.com/api-reference/connections/delete-a-connection
/api-reference/openapi.json delete /connections/{connection_id}
Deletes a connection by ID. This will also delete all associated queued requests.
# Generate or set password for a connection
Source: https://docs.qubesync.com/api-reference/connections/generate-or-set-password-for-a-connection
/api-reference/openapi.json post /connections/{connection_id}/password
Generates a new password or sets a specific password for a connection
# Get connection by ID
Source: https://docs.qubesync.com/api-reference/connections/get-connection-by-id
/api-reference/openapi.json get /connections/{connection_id}
Returns a single connection by ID
# Get QWC file for a connection
Source: https://docs.qubesync.com/api-reference/connections/get-qwc-file-for-a-connection
/api-reference/openapi.json get /connections/{connection_id}/qwc
Returns a QWC file for the specified connection
# List all connections
Source: https://docs.qubesync.com/api-reference/connections/list-all-connections
/api-reference/openapi.json get /connections
Returns a list of all connections for the authenticated application
# Regenerate onboarding URL for a connection
Source: https://docs.qubesync.com/api-reference/connections/regenerate-onboarding-url-for-a-connection
/api-reference/openapi.json post /connections/{connection_id}/onboarding_url
Generates a new onboarding URL for the specified connection. The previous URL will be invalidated.
# Introduction
Source: https://docs.qubesync.com/api-reference/introduction
The QuBe Sync API lets you talk to QuickBooks Enterprise / Desktop with ease.
## Welcome
The API for QuBe Sync is intentionally small and focused. You **create connections** for your users, **retrieve the QWC File and password** for their web connector, and then **queue requests** for QuickBooks on their behalf. If you provide a `webhook_url` with a request, you'll get the result `POSTed` to that URL.
## Development
If you're using Ruby or Python, you can use the corresponding library to interact with the API, and specify your QuickBooks requests.
* [Ruby SDK (qube\_sync\_rb)](https://github.com/qubeintegrations/qube_sync_rb)
* [Python SDK (qube\_sync\_py)](https://github.com/qubeintegrations/qube_sync_py)
SDKs for other languages are in the works - please send a support request to [support@qubesync.com](mailto:support@qubesync.com) if you'd like your language to be prioritized. You can also use the OpenAPI spec to generate an SDK to fit your needs, or implement the API directly (it's not hard).
[View the OpenAPI specification file](/api-reference/openapi.json)
## Authentication
All API endpoints are authenticated using your Application's API key and Basic Auth. The API key should be entered as the username, and the password left blank.
## Webhooks
To protect yourself from timing attacks and other common API vulnerabilities, it's best practice to follow the [Webhooks Guide](/webhooks). It will show you how to verify the webhooks you're receiving are genuinely from QuBe Sync.
# Create Invoice
Source: https://docs.qubesync.com/api-reference/pre-built-queries/create-invoice
/api-reference/openapi.json post /connections/{connection_id}/invoices
Create an invoice in QuickBooks.
# Query Customers
Source: https://docs.qubesync.com/api-reference/pre-built-queries/query-customers
/api-reference/openapi.json get /connections/{connection_id}/customers
Executes a pre-built query to retrieve customer data from QuickBooks. Supports filtering by name, date range, and active status.
# Query Invoices
Source: https://docs.qubesync.com/api-reference/pre-built-queries/query-invoices
/api-reference/openapi.json get /connections/{connection_id}/invoices
Executes a pre-built query to retrieve invoice data from QuickBooks. Supports filtering by customer, date range, and paid status.
# Query Items
Source: https://docs.qubesync.com/api-reference/pre-built-queries/query-items
/api-reference/openapi.json get /connections/{connection_id}/items
Executes a pre-built query to retrieve item data from QuickBooks. Supports filtering by name, modification date range, and active status.
# Query Sales Orders
Source: https://docs.qubesync.com/api-reference/pre-built-queries/query-sales-orders
/api-reference/openapi.json get /connections/{connection_id}/sales_orders
Retrieves sales orders for the specified connection. Supports iterating and filtering by modified date range or transaction date range.
# Query Sales Tax Codes
Source: https://docs.qubesync.com/api-reference/pre-built-queries/query-sales-tax-codes
/api-reference/openapi.json get /connections/{connection_id}/sales_tax_codes
Executes a pre-built query to retrieve sales tax code data from QuickBooks. Supports filtering by name, list identifiers, modification date range, and active status.
# Query Sales Tax Items
Source: https://docs.qubesync.com/api-reference/pre-built-queries/query-sales-tax-items
/api-reference/openapi.json get /connections/{connection_id}/sales_tax_items
Executes a pre-built query to retrieve item sales tax data from QuickBooks. Supports filtering by name, list identifiers, modification date range, and active status.
# Query Vendors
Source: https://docs.qubesync.com/api-reference/pre-built-queries/query-vendors
/api-reference/openapi.json get /connections/{connection_id}/vendors
Executes a pre-built query to retrieve vendor data from QuickBooks. Supports filtering by name and date range.
# Delete a queued request
Source: https://docs.qubesync.com/api-reference/queued-requests/delete-a-queued-request
/api-reference/openapi.json delete /queued_requests/{request_id}
Permanently deletes a queued request by ID. This operation is idempotent - deleting an already deleted request will return a successful response. Only requests that are in a terminal state (completed, error, or discarded) can be deleted. Active requests must be cancelled first.
# Get queued request by ID
Source: https://docs.qubesync.com/api-reference/queued-requests/get-queued-request-by-id
/api-reference/openapi.json get /queued_requests/{request_id}
Retrieves detailed information about a specific queued request, including its current state, request/response data, and any associated errors. This endpoint can be used to poll for request completion or to check the status of a long-running operation.
# List queued requests for a connection
Source: https://docs.qubesync.com/api-reference/queued-requests/list-queued-requests-for-a-connection
/api-reference/openapi.json get /connections/{connection_id}/queued_requests
Returns a paginated list of queued requests for a specific connection, ordered by most recent first. The response includes metadata about pagination.
# Queue a new request
Source: https://docs.qubesync.com/api-reference/queued-requests/queue-a-new-request
/api-reference/openapi.json post /connections/{connection_id}/queued_requests
Creates a new queued request for processing by the QuickBooks integration. The QuickBooks request itself can be in either XML (`request_xml`) or JSON (`request_json`) format. If a webhook URL is provided, the system will send a POST request to that URL with the result when the request is completed.
# Error Handling
Source: https://docs.qubesync.com/error_handling
Gracefully handle QuickBooks errors.
## Overview
When a queued request fails, a webhook is sent to the `webhook_url` you provided with the request. The webhook contains the request ID and the error that occurred.
```json theme={null}
{
"timestamp": "2023-01-01T12:00:00Z",
"id": "550e8400-e29b-41d4-a716-446655440000",
"page_request_id": "550e8400-e29b-41d4-a716-446655440001",
"page": 1,
"state": "failed",
"request_xml": "...",
"request_json": "...",
...
"error": {
"error_type": "quickbooks_connection_error",
"message": "The raw QuickBooks error message",
"hresult": "the error code from QuickBooks",
"human_message": "A friendly error message for the end-user"
}
}
```
## Error Types
### `quickbooks_connection_error`
This error occurs when the connection to QuickBooks fails. This can happen for a variety of reasons, such as:
* QuickBooks is not running, and the user did not enable "Allow access even when QuickBooks is closed" in the Web Connector
* No company file is open in QuickBooks
* QuickBooks has a modal open, such as a login screen
### `xml_error`
If the QBXML request to QuickBooks was malformed, this error will be returned. This can be caused by:
* Incorrect QBXML structure or tags
* Characters that QuickBooks cannot handle
### `unexpected_error`
All other errors are grouped into this category. Sometimes the user will need to send you the contents of their QuickBooks log file to help you debug the issue.
You can also contact [support@qubesync.com](mailto:support@qubesync.com) for help if you're unable to resolve the issue on your own.
# Introduction
Source: https://docs.qubesync.com/index
Integrating with QuickBooks Enterprise or Desktop? Congratulations — you’ve chosen the hardest API in accounting. Between ancient SOAP servers, cryptic QBXML, and undocumented edge cases, it’s like time-traveling to 2003. QuBe Sync handles all that nonsense so you don’t have to. You write clean code; we wrestle with the dinosaurs.
## Getting Started
The QuBe Sync API is small and focused, so you can get started in minutes.
Make your first request to QB in a few minutes
Dig into the API docs
# Get free QuickBooks Desktop or Enterprise for development
Source: https://docs.qubesync.com/nfr-quickbooks
Intuit offers a not-for-resale licenses for QuickBooks Desktop or Enterprise.
1. Download a trial version of your target QuickBooks version and install it on a Windows machine or VM.
2. Request a [Not-For-Resale version of QuickBooks](https://developer.intuit.com/app/developer/qbdesktop/docs/get-started/not-for-resale-software) so you'll have a valid license when the trial expires.
3. When you open QuickBooks, you will be able to choose a sample company file to prepopulate realistic data.
# Receive Webhooks in Development
Source: https://docs.qubesync.com/ngrok
Use [ngrok](https://ngrok.com), [CloudFlare tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) (our preference), or a similar tool to expose your local development environment to the internet. This is useful for receiving webhooks in development. Every request you queue for QuickBooks can have its own `webhook_url` to receive the result of the request.
# Onboarding Your Customers
Source: https://docs.qubesync.com/onboarding
Getting your customers connected to QuickBooks is as simple as sending them to a URL.
You'll want a button on your site that says "Connect to QuickBooks Desktop" or something similar. This button should do two things:
1. Use the `Connections` API to [create a connection](/api-reference/connections/create-a-new-connection) for the customer, and pass a `redirect_url` to redirect them to once they've completed the setup process.
2. Send them to the `links.onboarding` url returned with the connection you created.
Ask the user to start this process from the Windows computer where they have QuickBooks installed. If they are using a QuickBooks server, we highly suggest the onboarding is performed there. You may wish to warn them they'll be redirected to qubesync.com to complete the setup.
Once they've completed the short setup process, they'll be redirected to the `redirect_url` you provided with a query parameter `state=connected` .
## What if they don't complete the process?
If for some reason they choose to cancel the process, they'll be redirected back to you with `state=cancelled`. You can choose to let them try again, or delete the connection using the [Delete a Connection API](/api-reference/connections/delete-a-connection).
Onboarding links expire after 48 hours, and can be [regenerated using the API](/api-reference/connections/regenerate-onboarding-url-for-a-connection). If a user attempts to use an expired onboarding url, they'll be redirected to the `redirect_url` with `state=expired`.
## Start making requests!
Use the [pre-built requests](/api-reference/pre-built-queries/query-customers) or build your own using the [Queued Requests API](/api-reference/queued-requests/queue-a-new-request).
## Do you want full control over the onboarding experience?
Instead of using the `onboarding` url, once you've created the connection your user will need two essentials to get set up. Use the corresponding connection endpoints to provide them:
1. [Generate a QWC file](/api-reference/connections/get-qwc-file-for-a-connection) for their web connector and let them download it.
2. [Generate a password](/api-reference/connections/generate-or-set-password-for-a-connection) to enter into their web connector once they've added the QWC file.
# Auto-Pagination
Source: https://docs.qubesync.com/pagination
Auto-paginate requests / responses from QuickBooks.
## Overview
QuickBooks frequently needs to paginate responses to avoid timeouts and other issues. Usually this would require tracking iterator metadata and making multiple requests to QuickBooks.
## Auto-pagination to the rescue
QuBe Sync can automatically paginate any QuickBooks request that supports it. Each result page will be sent to the `webhook_url` provided with the original request. Each webhook will contain the `page` and `page_request_id` attributes to help you distinguish between pages.
If you are using [pre-built requests](/api-reference/pre-built-queries/query-customers), you can auto-paginate them by setting the `iterate` parameter to `true`.
```curl curl theme={null}
curl https://qubesync.com/api/v1/connections/$CONN_ID/customers?iterate=true \
--user $QUBE_API_KEY:
```
If you're building custom requests, QuBe Sync will automatically paginate any request you send with `iterator="Start"`.
```xml qbxml theme={null}
100
```
```ruby ruby theme={null}
QubeSync::RequestBuilder.new do |r|
r.QBXML do
r.QBXMLMsgsRq do
r.CustomerQueryRq(iterator: "Start") do
r.MaxReturned(100)
end
end
end
end
```
# Quickstart
Source: https://docs.qubesync.com/quickstart
Send your first request to QuickBooks in 5 minutes.
### Preparing your QuBe Sync account
From the [https://qubesync.com](https://qubesync.com) homepage, click "Register" and create your free account
In the Connections tab, create a new connection. Select your development app and click save.
Usually, this will be done programmatically by posting to the connections endpoint:
`post https://qubesync.com/api/v1/connections`
### Set up the QuickBooks Web Connector
The Web Connector should come pre-installed with QuickBooks, and allows QuickBooks to communicate with web applications.
From your new connection, you can get an onboarding url by clicking "Copy Onboarding URL". You'll need to open this URL from the Windows computer or VM where you have QuickBooks installed and follow the simple instructions!
Check the checkbox next to your app in the Web Connector and click "Update Selected". You should see it authenticate and then finish with "No data exchange required" in green if everything has gone according to plan!
### Make a Test Request
From that same connection screen, you can click "Create Test Request" to fire off a Customer Query - click "Update selected" in your Web Connector to expedite the response and check out the results!
### ...or make a custom request
In the "My Applications" tab, click into the sandbox app. We've generated an API key for you - click to copy it, and have it ready to use in your environment (e.g. `export QUBE_API_KEY="foo..."` )
QuBe Sync gives you the full power of any QBXML request, but also has some prebuilt endpoints for common use cases. Here we'll ask for a list of customers
```bash cURL theme={null}
export QUBE_API_KEY="..." # get this from My Applications tab
export CONN_ID="..." # get this from the Connections tab
curl https://qubesync.com/api/v1/connections/$CONN_ID/customers?max_returned=10 \
--user $QUBE_API_KEY:
```
```ruby ruby theme={null}
ENV["QUBE_API_KEY"]="..."
require 'net/http'
require 'uri'
require 'json'
connection_id = "..."
connection_url = "https://qubesync.com/api/v1/connections/#{connection_id}"
uri = URI.parse("#{connection_url}/customers?max_returned=10")
request = Net::HTTP::Get.new(uri)
request.basic_auth(ENV["QUBE_API_KEY"], "")
request.content_type = "application/json"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
# Check the response
if response.is_a?(Net::HTTPSuccess)
puts "Request was successful!"
puts "Response body: #{response.body}"
# opening the request's page in the browser.
`open #{JSON.parse(response.body)["data"]["links"]["ui"]}`
else
puts "Request failed with status code: #{response.code}"
puts "Response body: #{response.body}"
end
```
The response has a link to the request in the UI (`data.links.ui` ) to watch the request as it's processed by the web connector. Hit "Update Selected" on the Web Connector and see your customers!
# Verbose Web Connector Logs
Source: https://docs.qubesync.com/qwc-logs
Enable verbose logging for the web connector to help debug issues.
[Turn on verbose logging](https://help.developer.intuit.com/s/article/QBWC-Logging) in the QuickBooks Web Connector to see more detailed logs (they are heavily truncated otherwise).
# Webhooks
Source: https://docs.qubesync.com/webhooks
Receive webhooks from QuBe Sync when QuickBooks responds to your requests.
Any time a request is completed or fails, a webhook is sent to the `webhook_url` you provided with the request. The webhook contains the request ID and the result of the request.
```json theme={null}
{
"timestamp": "2023-01-01T12:00:00Z",
"id": "550e8400-e29b-41d4-a716-446655440000",
"page_request_id": "550e8400-e29b-41d4-a716-446655440001",
"page": 1,
"state": "completed",
"response_xml": "80000001-1234567890Sample Customer",
"response_json": [
{
"QBXMLMsgsRs": {
"CustomerQueryRs": {
"@requestID": "32",
"@statusCode": 0,
"@statusSeverity": "Info",
"@statusMessage": "Status OK",
"@iteratorRemainingCount": 3,
"@iteratorID": "{a667c96a-48d0-4e63-866e-d37ce9d607e9}",
"CustomerRet": [
{
"ListID": "80000003-1665678791",
"Name": "Babcock's Music Shop",
"FullName": "Babcock's Music Shop",
"IsActive": true
},
{
"ListID": "80000003-1665678792",
"Name": "John Doe",
"FullName": "John Doe",
"IsActive": true
}
]
}
}
}
],
"error": null
}
```
## Securing your webhook endpoint(s)
To protect yourself from timing attacks, JSON parsing vulnerabilities, etc., it's best practice to verify the webhooks you're receiving are genuinely from QuBe Sync.
If you are using Ruby with the [qube\_sync gem](https://github.com/qubeintegrations/qube_sync_rb/), you can verify a webhook as follows:
```ruby theme={null}
payload = request.body.read
signature = request.headers['X-Qube-Signature']
event = QubeSync.verify_and_build_webhook!(payload, signature)
```
In python with the [qubesync package](https://github.com/qubeintegrations/qube_sync_py/), you can verify a webhook as follows:
```python theme={null}
payload = request.body.decode('utf-8')
signature = request.headers['X-Qube-Signature']
event = QubeSync.verify_and_build_webhook(payload, signature)
```
Please let us know if you would like to see official libraries for other languages.
Otherwise, you can verify a webhook using the following steps:
### Step 1. Get the X-Qube-Signature header
To ensure the webhook is coming from QuBe Sync, you can verify the `X-Qube-Signature` header. This header contains a SHA-256 HMAC of the payload using your application's active secret keys. You can manage your webhook secret keys in the *Application*'s settings.
The header will be in the following format:
Newlines have been added for readability.
```bash theme={null}
X-Qube-Signature:
t=1492774577,
v1=signature1,
v2=signature2
```
#### Step 2. Extract the timestamp and signatures
```ruby ruby theme={null}
def extract_timestamp_and_signatures(header, expected_schemes: ["v1"])
parts = header.split(',').map { |part| part.split('=') }.to_h
timestamp = parts["t"].to_i
signatures = parts.select { |k, _| expected_schemes.include?(k) }
{
timestamp: timestamp,
signatures: signatures.values
}
end
```
```elixir elixir theme={null}
def extract_timestamp_and_signatures(header, expected_schemes: ["v1"] ) do
header
|> String.split(",")
|> Stream.map(&String.split(&1, "="))
|> Enum.into(%{})
|> Kernel.then(fn map ->
signatures =
map
|> Enum.filter(fn {k, _} -> expected_schemes |> Enum.member?(k) end)
|> Enum.values()
%{
timestamp: map["t"],
signatures: signatures
}
end)
end
```
```javascript javascript theme={null}
function extractTimestampAndSignatures(header, expectedSchemes = ["v1"]) {
const parts = header.split(",").map(part => part.split("=")).to_h;
const timestamp = parts["t"].to_i;
const signatures = parts.reject((_, k) => !expectedSchemes.includes(k));
return {
timestamp,
signatures: signatures.values
};
}
```
#### Step 3. Compare the signatures
Compute the expected signature value by calculating the SHA256 HMAC of the timestamp and raw request body (`{{timestamp}}.{{body}}`) using your application's secret key. Then check if that matches any of the signatures in the header.
```ruby ruby theme={null}
def sign_payload(timestamp, body)
OpenSSL::HMAC.hexdigest('sha256', api_secret, "#{timestamp}.#{body}")
end
def verify_and_build_webhook!(body, signature, max_age: 500)
extract_timestamp_and_signatures(signature) => { timestamp:, signatures:}
if timestamp < Time.now.to_i - max_age
raise 'Timestamp diff too high'
end
signatures.detect { |sig| sign_payload(timestamp, body) == sig } or raise 'No matching signature'
JSON.parse(body)
end
payload = request.body.read
signature = request.headers['X-Qube-Signature']
event = verify_and_build_webhook!(payload, signature)
```
```elixir elixir theme={null}
def sign_payload(timestamp, body) do
:crypto.hmac(:sha256, api_secret, "#{timestamp}.#{body}")
end
def verify_and_build_webhook!(body, signature, max_age: 500) do
%{timestamp: timestamp, signatures: signatures} = extract_signatures(signature)
if timestamp < System.system_time(:second) - max_age do
raise 'Timestamp diff too high'
end
Enum.find(signatures, fn sig -> sign_payload(timestamp, body) == sig end) or raise 'No matching signature'
JSON.parse(body)
end
```
```javascript javascript theme={null}
function signPayload(timestamp, body) {
return crypto.createHmac('sha256', api_secret).update(`${timestamp}.${body}`).digest('hex');
}
function verifyAndBuildWebhook!(body, signature, maxAge = 500) {
const { timestamp, signatures } = extractSignatures(signature);
if (timestamp < Date.now() / 1000 - maxAge) {
throw new Error('Timestamp diff too high');
}
signatures.find(sig => signPayload(timestamp, body) === sig) || throw new Error('No matching signature');
JSON.parse(body);
}
```
#### Prevent replay attacks
You can see we're also checking the age of the timestamp to prevent replay attacks. You can choose a `max_age` that makes sense for your application.