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 information
- indirect-tax.tax-calculation.quickbooks- Required for Sales Tax API access
- openid,- 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 API- GET /api/auth/login- Initiate OAuth authorization with QuickBooks
- GET /api/auth/callback- Handle OAuth callback from QuickBooks
- GET /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+ (shipFromAddressORbusinessAddress) + numericitemId | $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.