# 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. SCR-20250704-tdii.png 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! Copy Onboarding Url Pn 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! Create Test Request Pn ### ...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.