A Node Js App for calculating sales tax using QuickBooks Online Sales Tax API via GraphQL and the Intuit SDK.
- Complete Sales Tax Operations: Full tax calculation and lookup capabilities
- β Calculate: Real-time tax calculations for transactions
- β Customer Integration: Automatic QuickBooks customer lookup and resolution
- GraphQL Integration: Uses QuickBooks Sales Tax GraphQL API for efficient tax operations
- Tax Calculation Mutation:
indirectTaxCalculateSaleTransactionTaxfor real-time calculations - Dynamic Variables: Automatic conversion of REST requests to GraphQL variables
- Schema Compliance: Exact implementation of QuickBooks Sales Tax mutation structure
- Tax Calculation Mutation:
- Dynamic Input Processing: No hardcoded values - all data comes from request input
- Customer ID: Uses provided
customerIdor auto-fetches first available QuickBooks customer - Addresses: Requires
shipFromAddressorbusinessAddressinput for shipping calculations - Item IDs: Accepts QuickBooks-compatible numeric
itemIdvalues or defaults to "1"
- Customer ID: Uses provided
- Intuit Node JS SDK: Built with official Intuit Node JS SDK for QuickBooks API v3 with OAuth 2.0
This API implements OAuth 2.0 as per Intuit Node JS specifications:
The API requires the following OAuth scopes for full functionality:
com.intuit.quickbooks.accounting- Access to QuickBooks accounting data and customer informationindirect-tax.tax-calculation.quickbooks- Required for Sales Tax API accessopenid,profile,email,phone,address- User identity information
Note: The
indirect-tax.tax-calculation.quickbooksscope is essential for accessing QuickBooks Sales Tax GraphQL API endpoints.
- Production:
https://qb.api.intuit.com/graphql - Sandbox:
https://qb-sandbox.api.intuit.com/graphql
- Node.js: Version 12.0.0 or higher (recommended: 14.x or 16.x)
- npm: Comes with Node.js installation
node --version
npm --version-
Install Dependencies
npm install
-
Configure QuickBooks App
- Create a QuickBooks app at https://developer.intuit.com
- Update
.envwith your app credentials
-
Run the Application
node index.js
-
Complete OAuth Authentication
- Navigate to
http://localhost:3000 - Click on the 'Connect to Quickbooks' Button
- Complete the QuickBooks OAuth flow
- It Navigates you back to the Home Page where you can now have access to the Sales Tax UI.
- Navigate to
-
Test the API
- Navigate to
http://localhost:3000for the web interface - Use the UI to perform the various operations.
- Navigate to
Create a .env file with your QuickBooks app credentials:
PORT=3000
CLIENT_ID: PUT_YOUR_CLIENT_ID_HERE
CLIENT_SECRET: PUT_YOUR_CLIENT_SECRET_HERE
ENVIRONMENT: PUT_THE_ENVIRONMENT_HERE
REDIRECT_URI: http://localhost:3000/api/auth/This API requires OAuth 2.0 authentication with QuickBooks Online. The authentication flow includes:
- OAuth Authorization: Visit
/api/auth/loginto start the flow - Scope Configuration: Configurable scopes
- Sales Tax Permissions: Requires
indirect-tax.tax-calculation.quickbooksscope for Sales Tax API access
# 1. Initiate OAuth
curl "http://localhost:3000/api/auth/login"
# 2. Complete authorization in browser, then test APIGET /api/auth/login- Initiate OAuth authorization with QuickBooksGET /api/auth/callback- Handle OAuth callback from QuickBooksGET /api/auth/retrieveToken- Retrieve Token
POST /api/quickbook/salestax/indirect-tax- Calculate tax for a transaction (uses indirectTaxCalculateSaleTransactionTax mutation)GET /api/quickbook/salestax/customers- Get QuickBooks customers for testing and integration
This API implements the exact QuickBooks Sales Tax GraphQL schema:
mutation IndirectTaxCalculateSaleTransactionTax($input: IndirectTax_TaxCalculationInput!) {
indirectTaxCalculateSaleTransactionTax(input: $input) {
taxCalculation {
transactionDate
taxTotals {
totalTaxAmountExcludingShipping {
value
}
}
subject {
customer {
id
}
}
shipping {
shipToAddress {
rawAddress {
... on IndirectTax_FreeFormAddress {
freeformAddressLine
}
}
}
shipFromAddress {
rawAddress {
... on IndirectTax_FreeFormAddress {
freeformAddressLine
}
}
}
}
lineItems {
edges {
node {
numberOfUnits
pricePerUnitExcludingTaxes {
value
}
}
}
nodes {
productVariantTaxability {
product {
id
}
}
}
}
}
}
}{
"input": {
"transactionDate": "2025-06-17",
"subject": {
"qbCustomerId": "1"
},
"shipping": {
"shipFromAddress": {
"freeFormAddressLine": "2600 Marine Way, Mountain View, CA 94043"
},
"shipToAddress": {
"freeFormAddressLine": "2600 Marine Way, Mountain View, CA 94043"
}
},
"lineItems": [
{
"numberOfUnits": 1,
"pricePerUnitExcludingTaxes": {
"value": 10.95
},
"productVariantTaxability": {
"productVariantId": "1"
}
}
]
}
}All APIs now require input data and do not rely on hardcoded values:
- Required:
customerAddress(complete address object) - Required: Either
shipFromAddressORbusinessAddress(complete address object) - Optional:
customerId(auto-fetches first customer if not provided) - Optional:
itemId(accepts numeric strings like "1", "2", "3", etc. - defaults to "1") - Optional:
transactionDate(defaults to current date)
- No input required: Returns all available QuickBooks customers for integration
- Ship-From Address: Must provide either
shipFromAddressORbusinessAddress - Customer ID: If not provided, system auto-fetches first available customer
- Address Completeness: All address fields (city, state, postal code) are required
- Item IDs: Must be numeric strings ("1", "2", "3", etc.) - custom alphanumeric IDs will fail QuickBooks validation
β Dynamic Input Version (No Hardcoded Values):
curl -X POST "http://localhost:3000/api/quickbook/salestax/indirect-tax" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"transaction": {
"transactionDate": "2025-06-17",
"customerId": "1",
"customerAddress": {
"line1": "2600 Marine Way",
"city": "Mountain View",
"state": "CA",
"postalCode": "94043"
},
"shipFromAddress": {
"line1": "123 Business St",
"city": "San Francisco",
"state": "CA",
"postalCode": "94102"
},
"lines": [
{
"amount": 10.95,
"description": "Test Product",
"itemId": "2"
}
]
}
}'Alternative with businessAddress (instead of shipFromAddress):
curl -X POST "http://localhost:3000/api/quickbook/salestax/indirect-tax" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"transaction": {
"transactionDate": "2025-06-17",
"customerAddress": {
"line1": "2600 Marine Way",
"city": "Mountain View",
"state": "CA",
"postalCode": "94043"
},
"businessAddress": {
"line1": "123 Business St",
"city": "San Francisco",
"state": "CA",
"postalCode": "94102"
},
"lines": [
{
"amount": 10.95,
"description": "Test Product"
}
]
}
}'β Valid ItemId Values:
- Numeric strings:
"1","2","3","10", etc. - No itemId provided: Defaults to
"1"
β Invalid ItemId Values:
- Custom alphanumeric:
"CUSTOM-123","PROD-456" - Generated patterns:
"item_1","product_abc"
Note: Either
shipFromAddressORbusinessAddressis required. IfcustomerIdis not provided, the system will automatically use the first available QuickBooks customer. ItemId must be a numeric string to pass QuickBooks validation.
GraphQL Mutation Used:
mutation IndirectTaxCalculateSaleTransactionTax($input: IndirectTax_TaxCalculationInput!) {
indirectTaxCalculateSaleTransactionTax(input: $input) {
taxCalculation {
transactionDate
taxTotals {
totalTaxAmountExcludingShipping {
value
}
}
subject {
customer {
id
}
}
shipping {
shipToAddress {
rawAddress {
... on IndirectTax_FreeFormAddress {
freeformAddressLine
}
}
}
shipFromAddress {
rawAddress {
... on IndirectTax_FreeFormAddress {
freeformAddressLine
}
}
}
}
lineItems {
edges {
node {
numberOfUnits
pricePerUnitExcludingTaxes {
value
}
}
}
nodes {
productVariantTaxability {
product {
id
}
}
}
}
}
}
}Response:
{
"success": true,
"data": {
"totalTaxAmount": 1.0,
"totalAmount": 11.95,
"lines": [
{
"lineNumber": 1,
"amount": 10.95,
"taxAmount": 1.0,
"taxRate": 0.0913242009132420091324200913,
"description": "Test Product",
"taxBreakdown": [
{
"taxType": "Sales Tax",
"taxName": "QuickBooks Sales Tax",
"taxRate": 0.0913242009132420091324200913,
"taxAmount": 1.0,
"jurisdiction": "Mountain View, CA",
"taxableAmount": 10.95
}
]
}
],
"transactionDate": "2025-06-17T00:00:00"
}
}curl -X GET "http://localhost:5038/api/salestax/customers" \
-H "Accept: application/json"Response:
{
"success": true,
"data": [
{
"id": "1",
"name": "",
"companyName": "Amy's Bird Sanctuary",
"active": true
},
{
"id": "2",
"name": "",
"companyName": "Bill's Windsurf Shop",
"active": true
}
]
}βββ graphql/salesTax
β βββ inirectTax.js # GraphQL query and variables for calculating tax
βββ pages/
β βββ index.html # HTML for web UI
β βββ style.css # Styling for the UI
βββ routes/
β βββ oauth=route.js # routes for authentication flow
β βββ sales-tax-route.cs # routes for sales tax
βββ services/
β βββ auth-service.js # service for authentication
β βββ sales-tax-service.js # service for sales tax
βββ env # Configs
βββ index.js # Application startup
This API implements the QuickBooks Sales Tax GraphQL schema with the following operations:
- Purpose: Calculate real-time sales tax for transactions
- Method:
CalculateSaleTransactionTaxAsync() - Endpoint:
POST /api/quickbook/salestax/indirect-tax - GraphQL Type: Mutation with
IndirectTax_TaxCalculationInputinput type - Features: Dynamic customer lookup, address processing, line item support
- Purpose: Retrieve QuickBooks customer data for tax calculations
- Method:
GetCustomersAsync() - Endpoint:
GET /api/quickbook/salestax/customers - Integration: Uses QuickBooks REST API v3 for customer data
- GraphQL.Client - GraphQL client for Node Js (graphql-request)
- IntuitNodeSDK - Official Intuit Node JS SDK for OAuth
- Express - Node Express
Authorization: Uses Intuit Node Js SDK's OAuth2Client with proper environment handling
- Real API Calls: Direct integration with QuickBooks GraphQL endpoint
- Dynamic Variables: Converts REST request to GraphQL variables automatically
- Customer Lookup: Automatically retrieves QuickBooks customer ID for transactions
- Error Handling: Comprehensive error logging and debugging support
- OAuth state parameter validation
- Granular permission scope
- HTTPS redirect URI recommended for production
The application uses:
- Intuit Node JS SDK for OAuth 2.0
- GraphQL.Client for API communication
-
"Cannot find module 'node:events'" Error:
- Root Cause: Using Express v5.x with Node.js < 18.0.0
- Solution: Upgrade to Node.js 18+ or use Express v4.x
- Check Version: Run
node --version(should be 12.0.0+ for this project) - Fix: Delete
node_modulesandpackage-lock.json, then runnpm install
-
"Cannot find package 'graphql'" Error:
- Root Cause: Missing
graphqlpeer dependency forgraphql-request - Solution: Install the missing dependency
- Fix: Run
npm install graphqlornpm install(package.json updated with graphql dependency)
- Root Cause: Missing
-
"Invalid URI or environment" Error:
- Ensure
DiscoveryDocumentis set tohttps://appcenter.intuit.com/api/v1/connection/oauth2 - Verify
Environmentis set to"sandbox"or"production"
- Ensure
-
"Access Denied" GraphQL Error:
- Verify both required scopes are present in
ProjectScopes - Ensure QuickBooks company has sales tax enabled
- Check that OAuth token includes
indirect-tax.tax-calculation.quickbooksscope
- Verify both required scopes are present in
-
"-37109" Application Error:
- Configure sales tax settings in QuickBooks company
- Enable tax agencies and rates for the addresses being tested
- Verify customer exists in QuickBooks (or use hardcoded customer ID "1")
-
"INV-GraphQL expression=Validation failed" Error:
- Root Cause: Invalid
itemIdformat - Solution: Use numeric string itemIds only (
"1","2","3", etc.) - Avoid: Custom alphanumeric itemIds (
"CUSTOM-123","PROD-456") - Default: If no itemId provided, system defaults to
"1"
- Root Cause: Invalid
- Check application logs for detailed GraphQL request/response information
- Use
/api/oauth/retrieveTOkento verify token exists
For production:
- Update
.envwith production credentials - Change
Environmentto"production" - Update
GraphQLEndpointto"https://qb.api.intuit.com/graphql" - Update
BaseUrlto"https://quickbooks.api.intuit.com" - Use HTTPS for redirect URI (required by QuickBooks)
The implementation has been comprehensively tested with real QuickBooks sandbox data:
- Scope Configuration: Both required scopes (
com.intuit.quickbooks.accounting+indirect-tax.tax-calculation.quickbooks) working - Authorization Flow: Complete OAuth flow using Intuit Node JS SDK
- Real API Calls: Direct integration with
https://qb-sandbox.api.intuit.com/graphql - Exact Schema Implementation:
IndirectTaxCalculateSaleTransactionTaxmutation with proper input structure - Customer Integration: Automatic QuickBooks customer lookup and ID resolution
- Dynamic Variables: Request data converted to GraphQL variables correctly
| Endpoint | Status | Input Requirements | Test Results |
|---|---|---|---|
POST /api/quickbook/salestax/indirect-tax |
β Working | Requires customerAddress + (shipFromAddress OR businessAddress) + numeric itemId |
$10.95 β $1.00 tax with itemId="1" |
GET /api/quickbook/salestax/customers |
β Working | No input required | Returns QuickBooks customer list for dynamic lookup |
- Sandbox: Use
qb-sandbox.api.intuit.comendpoints - Production: Use
qb.api.intuit.comendpoints - Port: Default is
3000, configurable inenv
This project is for demonstration purposes and follows QuickBooks API terms of service.