QuickBooks Online Integration Skill

Use when integrating with QuickBooks Online for invoicing, estimates, customers, and payments.

by hewi333·MIT license·★ 122 Stars on the repo·GitHub ↗

Use now

Inside this skill

SKILL.md · the author's own words
# QuickBooks Online Integration Skill

## Overview
Integrates a small business with QuickBooks Online for customer management, estimates, invoices, and payment tracking. Uses Intuit's OAuth…

## When to Use
- **Create/send estimates** from estimator-engine output
- **Convert estimates to invoices** when job is booked
- **Record payments** (Stripe webhook → QB payment)
- **Sync customers** between CRM and QB
- **Pull reports** for weekly owner summary (AR aging, revenue, outstanding estimates)

## Don't Use For
- Complex inventory/assembly tracking
- Multi-currency or multi-entity accounting
- Payroll or tax filing

Word-for-word excerpt, not a result. Read the full SKILL.md ↓

Files of QuickBooks Online Integration Skill

hewi333/main1 file shown
SKILL.md
Show the full text217 lines

QuickBooks Online Integration Skill

Overview

Integrates a small business with QuickBooks Online for customer management, estimates, invoices, and payment tracking. Uses Intuit's OAuth 2.0 flow with automatic token refresh. Designed for agent-mediated use: owners text commands, agent executes QB API calls.

When to Use

  • Create/send estimates from estimator-engine output
  • Convert estimates to invoices when job is booked
  • Record payments (Stripe webhook → QB payment)
  • Sync customers between CRM and QB
  • Pull reports for weekly owner summary (AR aging, revenue, outstanding estimates)

Don't Use For

  • Complex inventory/assembly tracking
  • Multi-currency or multi-entity accounting
  • Payroll or tax filing

Authentication (OAuth 2.0)

Required Credentials (Environment Variables)
QBO_CLIENT_ID=*** From Intuit Developer Dashboard
QBO_CLIENT_SECRET= # From Intuit Developer Dashboard
QBO_REDIRECT_URI= # e.g., https://your-domain.com/qbo/callback
QBO_ENVIRONMENT= # "sandbox" or "production"
QBO_COMPANY_ID= # Realm ID (set after first auth)
Token Storage
  • Store access_token + refresh_token + realm_id in ~/.hermes/qbo_tokens.json
  • Auto-refresh: access_token expires in 1 hour; refresh_token expires in 101 days
  • On 401: refresh token, retry once, then force re-auth if refresh fails
Auth Flow
  1. User visits: https://appcenter.intuit.com/connect/oauth2?client_id={id}&redirect_uri={uri}&scope=com.intuit.quickbooks.accounting&response_type=code&state={random}
  2. Callback receives code + realmId
  3. Exchange code for tokens: POST https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer
  4. Store tokens + realm_id

Core API Operations

Customers
# Create customer (from CRM lead)
POST /v3/company/{realmId}/customer
{
  "DisplayName": "John Smith",
  "PrimaryEmailAddr": {"Address": "[email protected]"},
  "PrimaryPhone": {"FreeFormNumber": "555-555-0123"},
  "BillAddr": {"Line1": "123 Main St", "City": "[CITY]", "CountrySubDivisionCode": "[STATE]", "PostalCode": "[ZIP]"},
  "Notes": "Lead from referral"
}

# Query customer
GET /v3/company/{realmId}/query?query=SELECT * FROM Customer WHERE DisplayName='John Smith'
Estimates
# Create estimate (from estimator-engine output)
POST /v3/company/{realmId}/estimate
{
  "CustomerRef": {"value": "123"},
  "TxnDate": "2026-01-15",
  "ExpirationDate": "2026-02-14",
  "Line": [
    {
      "DetailType": "SalesItemLineDetail",
      "Amount": 3125.00,
      "Description": "Service - 2500 sqft, vaulted ceilings, moderate tobacco",
      "SalesItemLineDetail": {"ItemRef": {"value": "1", "name": "Base Treatment"}, "Qty": 2500, "UnitPrice": 1.25}
    },
    {"DetailType": "SalesItemLineDetail", "Amount": 350.00, "Description": "Cigarette >5yrs extended treatment", "SalesItemLineDetail": {"ItemRef": {"value": "2"}, "Qty": 1, "UnitPrice": 350}},
    {"DetailType": "SalesItemLineDetail", "Amount": 75.00, "Description": "Travel surcharge - Premium Region", "SalesItemLineDetail": {"ItemRef": {"value": "3"}, "Qty": 1, "UnitPrice": 75}}
  ],
  "CustomerMemo": {"value": "Valid 30 days. 50% deposit to schedule."}
}

# Send estimate email
POST /v3/company/{realmId}/estimate/{estimateId}/send?sendTo={email}
Invoices
# Create invoice from accepted estimate
POST /v3/company/{realmId}/invoice
{
  "CustomerRef": {"value": "123"},
  "TxnDate": "2026-01-20",
  "DueDate": "2026-02-03",
  "Line": [...same line items...],
  "Deposit": 1562.50,  # 50% deposit collected via Stripe
  "CustomerMemo": {"value": "Balance due upon completion"}
}

# Get invoice PDF
GET /v3/company/{realmId}/invoice/{invoiceId}/pdf
Payments
# Record payment (from Stripe webhook)
POST /v3/company/{realmId}/payment
{
  "CustomerRef": {"value": "123"},
  "TotalAmt": 1562.50,
  "PaymentRefNum": "pi_3StripePaymentIntentId",
  "PaymentMethodRef": {"value": "5"},  # Credit Card
  "DepositToAccountRef": {"value": "35"},  # Undeposited Funds
  "Line": [{"Amount": 1562.50, "LinkedTxn": [{"TxnId": "456", "TxnType": "Invoice"}]}]
}
Items (Products/Services)
# Ensure service items exist (run once on setup)
# Base Treatment (per sqft)
# Cigarette Extended Treatment (each)
# Travel Surcharge (each)
# HVAC Duct Treatment (each)
# High Rise Surcharge (each)
# Region Surcharge (each)

Agent Integration Patterns

"Create estimate for John Smith at 123 Main St..."
# 1. Look up or create customer in QB
# 2. Call estimator-engine with property details
# 3. Transform estimator output → QB estimate line items
# 4. Create estimate in QB
# 5. Email PDF to customer
# 6. Create CRM estimate record (status=sent)
# 7. Create follow-up task (3 days)
"Customer accepted estimate #EST-0001-0042"
# 1. Update CRM estimate status=accepted
# 2. Create invoice in QB (copy line items)
# 3. Generate Stripe Payment Link for 50% deposit
# 4. Text customer deposit link
# 5. Create job scheduled task for the owner
"Record payment from Stripe webhook"
# 1. Verify webhook signature
# 2. Find invoice by payment_intent metadata
# 3. Create QB Payment linked to invoice
# 4. Update CRM invoice status=paid
# 5. Notify owners via Telegram

Common Pitfalls

  1. Token expiry — Always wrap API calls in try/except 401 → refresh → retry
  2. Sandbox vs production — Different base URLs, different realm IDs, different client credentials
  3. Minor version — Always send minorversion=75 header for latest API
  4. Idempotency — Use RequestId header for estimate/invoice creation to avoid duplicates
  5. Rate limits — 500 requests/minute; implement exponential backoff
  6. Custom fields — QB custom fields need definition before use; use Notes field for flexibility
  7. Tax — Many services are non-taxable; ensure items are marked non-taxable per your local tax rules

Verification Checklist

  • OAuth flow completes and stores tokens
  • Token refresh works (wait 1hr or force expiry)
  • Create customer → appears in QB UI
  • Create estimate with line items → PDF renders correctly
  • Send estimate email → customer receives it
  • Convert estimate to invoice → amounts match
  • Record payment → invoice shows paid
  • Weekly report pulls: outstanding estimates, AR aging, revenue MTD

One-Shot Recipes

"Sync new CRM lead to QuickBooks"
customer = qbo.upsert_customer(
    name="Jane Doe",
    email="[email protected]",
    phone="555-555-0199",
    address="456 Palm Ave, [CITY], [STATE] [ZIP]",
    notes="Referral - Example Realty"
)
"Push estimator output to QB estimate"
estimate = qbo.create_estimate(
    customer_id=customer.id,
    line_items=estimator_output.line_items,  # [{desc, qty, unit_price, total}]
    txn_date="2026-01-15",
    expiration_date="2026-02-14",
    memo="Valid 30 days. 50% deposit to schedule."
)
qbo.send_estimate(estimate.id, customer.email)
"Weekly owner report data"
report = qbo.get_weekly_report(realm_id)
# Returns: {outstanding_estimates: [...], ar_aging: {...}, revenue_mtd: 45000, payments_this_week: [...]}
1---
2name: quickbooks-online
3description: "Use when integrating with QuickBooks Online for invoicing, estimates, customers, and payments. Handles OAuth 2.0 auth, token refresh, and sandbox/production environments."
4version: "1.0.0"
5author: Hermes Agent
6license: MIT
7metadata:
8 hermes:
9 tags: [quickbooks, intuit, accounting, invoicing, small-business]
10 related_skills: [crm-lite, estimator-engine, stripe-payments]
11---
12 
13# QuickBooks Online Integration Skill
14 
15## Overview
16Integrates a small business with QuickBooks Online for customer management, estimates, invoices, and payment tracking. Uses Intuit's OAuth 2.0 flow with automatic token refresh. Designed for agent-mediated use: owners text commands, agent executes QB API calls.
17 
18## When to Use
19- **Create/send estimates** from estimator-engine output
20- **Convert estimates to invoices** when job is booked
21- **Record payments** (Stripe webhook → QB payment)
22- **Sync customers** between CRM and QB
23- **Pull reports** for weekly owner summary (AR aging, revenue, outstanding estimates)
24 
25## Don't Use For
26- Complex inventory/assembly tracking
27- Multi-currency or multi-entity accounting
28- Payroll or tax filing
29 
30## Authentication (OAuth 2.0)
31 
32### Required Credentials (Environment Variables)
33```
34QBO_CLIENT_ID=*** From Intuit Developer Dashboard
35QBO_CLIENT_SECRET= # From Intuit Developer Dashboard
36QBO_REDIRECT_URI= # e.g., https://your-domain.com/qbo/callback
37QBO_ENVIRONMENT= # "sandbox" or "production"
38QBO_COMPANY_ID= # Realm ID (set after first auth)
39```
40 
41### Token Storage
42- Store access_token + refresh_token + realm_id in `~/.hermes/qbo_tokens.json`
43- Auto-refresh: access_token expires in 1 hour; refresh_token expires in 101 days
44- On 401: refresh token, retry once, then force re-auth if refresh fails
45 
46### Auth Flow
471. User visits: `https://appcenter.intuit.com/connect/oauth2?client_id={id}&redirect_uri={uri}&scope=com.intuit.quickbooks.accounting&response_type=code&state={random}`
482. Callback receives `code` + `realmId`
493. Exchange code for tokens: `POST https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer`
504. Store tokens + realm_id
51 
52## Core API Operations
53 
54### Customers
55```python
56# Create customer (from CRM lead)
57POST /v3/company/{realmId}/customer
58{
59 "DisplayName": "John Smith",
60 "PrimaryEmailAddr": {"Address": "[email protected]"},
61 "PrimaryPhone": {"FreeFormNumber": "555-555-0123"},
62 "BillAddr": {"Line1": "123 Main St", "City": "[CITY]", "CountrySubDivisionCode": "[STATE]", "PostalCode": "[ZIP]"},
63 "Notes": "Lead from referral"
64}
65 
66# Query customer
67GET /v3/company/{realmId}/query?query=SELECT * FROM Customer WHERE DisplayName='John Smith'
68```
69 
70### Estimates
71```python
72# Create estimate (from estimator-engine output)
73POST /v3/company/{realmId}/estimate
74{
75 "CustomerRef": {"value": "123"},
76 "TxnDate": "2026-01-15",
77 "ExpirationDate": "2026-02-14",
78 "Line": [
79 {
80 "DetailType": "SalesItemLineDetail",
81 "Amount": 3125.00,
82 "Description": "Service - 2500 sqft, vaulted ceilings, moderate tobacco",
83 "SalesItemLineDetail": {"ItemRef": {"value": "1", "name": "Base Treatment"}, "Qty": 2500, "UnitPrice": 1.25}
84 },
85 {"DetailType": "SalesItemLineDetail", "Amount": 350.00, "Description": "Cigarette >5yrs extended treatment", "SalesItemLineDetail": {"ItemRef": {"value": "2"}, "Qty": 1, "UnitPrice": 350}},
86 {"DetailType": "SalesItemLineDetail", "Amount": 75.00, "Description": "Travel surcharge - Premium Region", "SalesItemLineDetail": {"ItemRef": {"value": "3"}, "Qty": 1, "UnitPrice": 75}}
87 ],
88 "CustomerMemo": {"value": "Valid 30 days. 50% deposit to schedule."}
89}
90 
91# Send estimate email
92POST /v3/company/{realmId}/estimate/{estimateId}/send?sendTo={email}
93```
94 
95### Invoices
96```python
97# Create invoice from accepted estimate
98POST /v3/company/{realmId}/invoice
99{
100 "CustomerRef": {"value": "123"},
101 "TxnDate": "2026-01-20",
102 "DueDate": "2026-02-03",
103 "Line": [...same line items...],
104 "Deposit": 1562.50, # 50% deposit collected via Stripe
105 "CustomerMemo": {"value": "Balance due upon completion"}
106}
107 
108# Get invoice PDF
109GET /v3/company/{realmId}/invoice/{invoiceId}/pdf
110```
111 
112### Payments
113```python
114# Record payment (from Stripe webhook)
115POST /v3/company/{realmId}/payment
116{
117 "CustomerRef": {"value": "123"},
118 "TotalAmt": 1562.50,
119 "PaymentRefNum": "pi_3StripePaymentIntentId",
120 "PaymentMethodRef": {"value": "5"}, # Credit Card
121 "DepositToAccountRef": {"value": "35"}, # Undeposited Funds
122 "Line": [{"Amount": 1562.50, "LinkedTxn": [{"TxnId": "456", "TxnType": "Invoice"}]}]
123}
124```
125 
126### Items (Products/Services)
127```python
128# Ensure service items exist (run once on setup)
129# Base Treatment (per sqft)
130# Cigarette Extended Treatment (each)
131# Travel Surcharge (each)
132# HVAC Duct Treatment (each)
133# High Rise Surcharge (each)
134# Region Surcharge (each)
135```
136 
137## Agent Integration Patterns
138 
139### "Create estimate for John Smith at 123 Main St..."
140```python
141# 1. Look up or create customer in QB
142# 2. Call estimator-engine with property details
143# 3. Transform estimator output → QB estimate line items
144# 4. Create estimate in QB
145# 5. Email PDF to customer
146# 6. Create CRM estimate record (status=sent)
147# 7. Create follow-up task (3 days)
148```
149 
150### "Customer accepted estimate #EST-0001-0042"
151```python
152# 1. Update CRM estimate status=accepted
153# 2. Create invoice in QB (copy line items)
154# 3. Generate Stripe Payment Link for 50% deposit
155# 4. Text customer deposit link
156# 5. Create job scheduled task for the owner
157```
158 
159### "Record payment from Stripe webhook"
160```python
161# 1. Verify webhook signature
162# 2. Find invoice by payment_intent metadata
163# 3. Create QB Payment linked to invoice
164# 4. Update CRM invoice status=paid
165# 5. Notify owners via Telegram
166```
167 
168## Common Pitfalls
169 
1701. **Token expiry** — Always wrap API calls in try/except 401 → refresh → retry
1712. **Sandbox vs production** — Different base URLs, different realm IDs, different client credentials
1723. **Minor version** — Always send `minorversion=75` header for latest API
1734. **Idempotency** — Use `RequestId` header for estimate/invoice creation to avoid duplicates
1745. **Rate limits** — 500 requests/minute; implement exponential backoff
1756. **Custom fields** — QB custom fields need definition before use; use `Notes` field for flexibility
1767. **Tax** — Many services are non-taxable; ensure items are marked non-taxable per your local tax rules
177 
178## Verification Checklist
179- [ ] OAuth flow completes and stores tokens
180- [ ] Token refresh works (wait 1hr or force expiry)
181- [ ] Create customer → appears in QB UI
182- [ ] Create estimate with line items → PDF renders correctly
183- [ ] Send estimate email → customer receives it
184- [ ] Convert estimate to invoice → amounts match
185- [ ] Record payment → invoice shows paid
186- [ ] Weekly report pulls: outstanding estimates, AR aging, revenue MTD
187 
188## One-Shot Recipes
189 
190### "Sync new CRM lead to QuickBooks"
191```python
192customer = qbo.upsert_customer(
193 name="Jane Doe",
194 email="[email protected]",
195 phone="555-555-0199",
196 address="456 Palm Ave, [CITY], [STATE] [ZIP]",
197 notes="Referral - Example Realty"
198)
199```
200 
201### "Push estimator output to QB estimate"
202```python
203estimate = qbo.create_estimate(
204 customer_id=customer.id,
205 line_items=estimator_output.line_items, # [{desc, qty, unit_price, total}]
206 txn_date="2026-01-15",
207 expiration_date="2026-02-14",
208 memo="Valid 30 days. 50% deposit to schedule."
209)
210qbo.send_estimate(estimate.id, customer.email)
211```
212 
213### "Weekly owner report data"
214```python
215report = qbo.get_weekly_report(realm_id)
216# Returns: {outstanding_estimates: [...], ar_aging: {...}, revenue_mtd: 45000, payments_this_week: [...]}
217```

Discussion

Alternatives