Symptom → fix.
For the person who runs SideQuest for a distributor. Type a symptom in the box, hit the matching card. End users should check the FAQ first.
Quick reference — commands you'll run most
sidequest doctorhealth checksidequest reauth-qbrefresh QB tokensidequest setupinstall wizardsidequest demoverify installsidequest flush-usagesend usage eventstail ~/.qb-distributor-mcp/auth.logtoken timeline
Health + setup
Nothing seems to be happening. Claude can't see SideQuest at all.
- Cmd+Q Claude Desktop (not just close the window).
- Open Terminal and confirm the binary resolves:
Should return a venv path. If empty, open a fresh Terminal window orwhich sidequestsource ~/.zshrc. - Reopen Claude Desktop. Look for SideQuest Automation in the MCP tool list at the bottom of the compose box.
- Still not there? Run
sidequest doctorand follow the output.
Why this happens
Claude Desktop spawns the MCP server on stdio at launch. If the claude_desktop_config.json file is malformed or the connector binary isn't on PATH, the server never starts and Claude has no tools to call. The legacy binary name was qb-distributor-mcp (pre-v0.15.15); both names still work as aliases.
Need to run a self-diagnostic.
sidequest doctor
Prints license tier, env vars, file presence, license check, demo-mode call, and the QB refresh-token chain probe. Output is safe to paste into a support ticket — license key and tokens are redacted automatically.
QuickBooks auth
"QuickBooks token needs reseeding" / error 3200 / 401 / Token expired
sidequest reauth-qb
Walks the Intuit OAuth dance via the hosted callback at sidequestautomation.com/qb/callback, writes the fresh refresh token to .env, and auto-reinjects into Claude Desktop's config. Then Cmd+Q Claude Desktop and reopen.
From v0.15.13 the token rotates itself going forward. This is a one-time reseed.
Before reseeding, check the audit log
v0.15.27+ writes every QB refresh attempt to ~/.qb-distributor-mcp/auth.log. Token tails are redacted. Run tail -20 ~/.qb-distributor-mcp/auth.log to see whether the chain died because Intuit rejected a rotation or because a stale token was retried after a process restart. The two scenarios have different fixes.
Want to see the QB refresh history.
tail -20 ~/.qb-distributor-mcp/auth.log
v0.15.27+ writes every refresh attempt with ISO timestamp, event name (refresh_start, refresh_success, refresh_fail, persist_write), and redacted token tail (...4XYZ (17 chars)). File rotates at 1 MB to auth.log.1.bak.
QB error 2020 — Permissions error on Estimates.
sidequest reauth-qb
The OAuth scope on Estimates got revoked. The reauth flow re-issues the grant with com.intuit.quickbooks.accounting scope. Cmd+Q Claude Desktop and reopen after.
Error 3200 keeps coming back even after reauth.
On v0.15.27+ this is rare. Open ~/.qb-distributor-mcp/auth.log — the timeline tells the story. Three remaining causes:
- The grant was revoked in QuickBooks (admin removed the app, password reset, trial company deleted).
QB_ENVIRONMENTwas swapped (sandbox ↔ production) without re-authing.- Multiple connector instances are racing each other to refresh.
Re-run sidequest reauth-qb once more. If 3200 returns within an hour, send auth.log + sidequest doctor output to support.
QuickBooks submit + writes
Submit returned freight_unconfigured.
- In QuickBooks: Sales → Products and services → New → Service. Name it Shipping. Save.
- Open the item again. The URL contains the item id (the number after
/item/). Copy it. - Add to
~/.qb-distributor-mcp/.env:SIDEQUEST_FREIGHT_ITEM_ID=<the-id> - Run
sidequest reauth-qb(which also reinjects). Cmd+Q Claude Desktop and reopen.
Resubmit the draft. Freight now lands as a real SalesItemLineDetail referencing the Shipping item.
Draft has both a doc discount AND freight. What does QB store?
On v0.15.29 or later: products get discounted, freight stays at full carrier cost. Local and QB totals agree to the penny.
How it gets there: the connector pre-computes the discount as a fixed dollar amount (lines_subtotal × pct ÷ 100) and submits a DiscountLineDetail with PercentBased: false and that dollar amount filled in. QB stores it as-is and doesn't recalculate, so freight never gets swept into the discount.
What you'll see in the QuickBooks UI: the Estimate shows the discount as a dollar amount (e.g. $36.45 off), not 5% off. The percent intent is written into the discount line's Description ("5% off products ($36.45)"), so the printed Estimate the customer sees still calls out the 5%. Operators still set the discount as a percent in Claude — the percent-to-dollars conversion happens only at QB submit time.
Why the v0.15.25 / v0.15.28 fixes didn't take
v0.15.25 reordered the payload to products → discount → freight; v0.15.28 stamped LineNum on every line. Both assumed QB would apply a percent discount only to lines preceding the discount entry. It doesn't — when PercentBased: true, QuickBooks Online always applies the percent to the entire document subtotal, regardless of line position or LineNum. Four consecutive sandbox Estimates (1010-1013) reproduced the $716.30 vs $717.55 drift with both attempts in place. v0.15.29's fixed-dollar serialization is what actually fixed it; the next Estimate (1014) stored $717.55 to the penny with no qb_total_mismatch. The LineNum stamps stayed in — harmless, useful for audit — but they aren't the fix.
Why "freight outside discount" is the right default
Freight is a pass-through cost. NetSuite, Acumatica, SAP Business One, and Fishbowl all default the same way. If a customer needs freight discounted on a specific Estimate (rare), edit the Estimate in QB after submit — the connector won't fight the override.
Submit response includes a qb_total_mismatch warning.
The QB-stored total disagrees with the locally-computed total by more than a penny. v0.15.29 ended the combined-discount-and-freight case for good — v0.15.25 had claimed the same and turned out not to be the fix (see the combined-discount card above for what actually happened). Remaining causes today:
- A tax line QB injected that the draft didn't account for.
- A rounding gap on a percentage discount (rare — the fixed-dollar serialization avoids most rounding paths).
- The draft was edited between preview and submit.
Open the QB Estimate, find the line that doesn't agree, send draft_id + both totals + QB Estimate id to support. On v0.15.29+ the warning is rare; when it does surface, it's a real bug worth tracing.
QB error 5010 — Object not found on submit.
The customer name on the PO doesn't match any QB Customer. Either:
- Create the customer in QB first, then resubmit.
- Have Claude reassign the draft to an existing customer before submitting.
QB error 6240 — Duplicate transaction.
This PO number already has an Estimate in QB. Look up the existing one before resubmitting.
Matching + cross-references
SideQuest matched to the wrong SKU.
Add a row to ~/.qb-distributor-mcp/cross_reference.csv:
customer_id,customer_part,internal_sku,notes
ACME,ACME-EL12,BR-ELB-050-NPT,Acme uses their own codes
Then either Cmd+Q Claude Desktop and reopen, OR ask Claude to call refresh_catalog for a no-restart pickup.
Other causes
Description ambiguity — the customer's description matches multiple QB SKUs. Tighten by adding more detail to the QB item description.
Stale catalog cache — the connector caches QBO items at first call. If you added a SKU 30 min ago and Claude Desktop has been open all day, ask Claude to call refresh_catalog.
All matches come back low-confidence.
The cross-reference table is probably undersized. Check the row count:
wc -l ~/.qb-distributor-mcp/cross_reference.csv
Most distributors need at least 100 mappings before match quality stabilizes. If you have a customer who sends weird POs weekly, ship their last 6 months of POs as a CSV/ZIP to support — we'll extract the implicit cross-references in batch.
Customer field reads "Corporate AP Office" or similar — not the real buyer.
v0.15.16 added an AP-office fallback. When the parser detects an AP-system identifier in the Bill To, it walks: sender's email domain stem → vendor block → sender's display name. The draft gets tagged with customer_source="ap_fallback_domain" | "ap_fallback_vendor" | "ap_fallback_sender" so you can see how it was inferred.
If the fallback picks the wrong customer, override on the draft and add a cross-reference row so the next PO from that AP office anchors correctly.
Consumer-mail domain protection
Gmail, Yahoo, Outlook, Hotmail, AOL, iCloud, and Proton are explicitly denied from the domain-stem fallback. You'll never get a draft anchored to customer="Gmail" because the buyer used a personal address. The vendor and sender-name fallbacks still run.
Pricing
SideQuest flagged a price as wrong but it's actually right.
The QuickBooks list price for that item is stale. The connector compares PO line price against QB Item UnitPrice. Two fixes:
- Update the QB Item's
UnitPriceto match the real list. - Set up a customer-specific price level in QB — the connector will use it automatically.
Email + labeling
A PO was labeled in Gmail but Claude can't find it.
- Confirm the label name matches exactly. Default is
purchase-orders. Open.envand checkGMAIL_PO_LABEL. - Nested label? Include the slash:
POs/Incoming. - Cmd+Q Claude Desktop and reopen so the connector re-reads
.env. - Still missing? Have Claude call
parse_po_from_emailwith the message_id directly. Ifheuristic_linesis empty andraw_contentis gibberish, the PDF isn't text-extractable — send to support and we'll add that format.
Claude keeps drafting the same PO over and over.
Ask Claude to call mark_email_processed on that message ID. The connector remembers processed message IDs in ~/.qb-distributor-mcp/drafts.sqlite.
A marketing/contest email got labeled as a PO.
v0.15.26+ added a promo filter. Emails with promotional emoji (🔥💰🎁🎯🎉), contest language ("Win a/the X", "sweepstake", "giveaway"), sales markers ("N% off", "Black Friday", "flash sale", "limited time"), newsletter hooks ("unsubscribe", "view in browser"), or known bulk-mail sender domains (mailchimp.com, sendgrid.net, klaviyo.com, etc.) get rejected pre-classification.
If a promo is still slipping through, send the message_id to support and we'll tighten the matcher.
Gmail token expired.
Rerun the Gmail OAuth dance directly:
~/.qb-distributor-mcp/venv/bin/python -c "from pathlib import Path; from qb_distributor_mcp.gmail_client import GmailClient; h=Path.home()/'.qb-distributor-mcp'; GmailClient(h/'google_client_secret.json', h/'google_token.json')"
If it fails with KeyError: 'client_secret', your google_client_secret.json is the incomplete first download. Go to Google Cloud → your OAuth client → + Add secret, download the second one, replace the file, rerun.
Reports + insights
Local reports come back empty or very thin.
Expected on recent installs. Local reports read ~/.qb-distributor-mcp/drafts.sqlite and usage.sqlite — only data from POs the connector processed since install.
For deeper history, use the QB pass-through reports: report_qb_top_items, report_qb_top_customers. They pull full QBO history.
report_pos_processed dollar value looks wrong.
v0.15.13+ splits the response into two source blocks. Pick the right one:
from_usage_log— append-only billing counters. Source of truth for "what was I billed for?"from_drafts_store— current SQLite state. Reflects edits/deletions. Answers "what's on my board now?"
If they disagree, the usage log is the billing source of truth. The drafts store can drift if a draft was deleted post-submit or the local store got rebuilt.
report_match_quality shows a high flag rate.
Look at the top review reasons:
description_fuzzyorcustomer_part_not_founddominate → add cross-reference rows for that customer.short_descriptiondominates → customer's PO format isn't including line descriptions. Matcher needs the SKU column to be present.
Billing
"You've hit your monthly limit."
You're past your tier's monthly PO quota. Upgrade at sidequestautomation.com/pricing or email support for a custom plan.
Card on file got declined.
Stripe sends a dunning email automatically. Your service stays active for 7 days of grace. Update the card via the Stripe customer portal link in your most recent receipt email.
After 7 days of failed retries, your account drops to free tier (still functional, just rate-limited at 20 POs/month).
QuickBooks Desktop bridge (Windows beta)
"Couldn't reach QuickBooks Desktop" error.
Three causes. Check in order:
- QBD isn't open. The bridge needs QuickBooks Desktop running with the company file loaded.
- The bridge isn't running. Look for the
start-bridge.batcommand window on the taskbar. If closed, double-clickstart-bridge.batin the connector folder. - Trust dialog was rejected. QuickBooks → Edit → Preferences → Integrated Applications → tick "Allow this application to access your company file" for SideQuest Connector.
"QuickBooks Desktop is busy" / 409 from the bridge.
A modal dialog is open in QBD. Bring QBD to the front, close any open wizards, retry. The bridge auto-retries 3× with backoff before surfacing.
Bridge crashed and the command window closed.
QBSDK COM connections crash periodically — this is what the bridge model protects against. Just re-double-click start-bridge.bat. The MCP server stays alive across bridge restarts.
If it happens more than once a day, copy the last 50 lines of the bridge log window and email support.
Customer wants to switch QBO ↔ QBD.
Change QB_BACKEND=online or QB_BACKEND=desktop in .env. Cmd+Q Claude Desktop and reopen. No reinstall needed — local stores (drafts.sqlite, usage.sqlite, cross_reference.csv) are backend-agnostic.
No symptoms match. Try fewer words, or email support.
sidequest doctor (safe to share — secrets are redacted), and if it's a PO issue, the sample PO. Response target during business hours (Mon-Fri, 9am-5pm ET): 2 hours for Scale tier, 1 business day for everyone else.