7.1 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| Syncro billing rules — products, rates, taxes, attribution, emergency, warranty | How to bill a Syncro ticket correctly — fetch live rates, use real product names, pick the right labor type for the delivery channel, set taxable=false on labor (AZ), warranty product 1049360, emergency ×1.5 (branch by prepay_hours), preserve original tech's user_id on corrections, estimate hardware uses product 32252. |
|
Rules only. Incident detail, verbatim Mike quotes, ticket numbers, dates, the tech user_id table, and the labor-product table all live in feedback_syncro_history — read on-demand when judging an edge case. API mechanics: feedback_syncro_api. Workflow: feedback_syncro_workflow.
.claude/commands/syncro.md is the authoritative live product table.
1. Bill with add_line_item directly — never the timer workflow
POST /tickets/{id}/add_line_item is the billing path for ALL work (labor, warranty, internal, hardware). The timer workflow (timer_entry → charge_timer_entry) is not used.
Payload: product_id, quantity (decimal hours), price_retail (fetched live), name (the product's REAL name — see §3), description (free-text work narrative), taxable: false for labor.
2. ALWAYS fetch the rate live
Fetch price_retail from GET /products/<id> → .product.price_retail before billing. The product-ID table in .claude/commands/syncro.md is valid for IDs but not dollar amounts — rates vary by contract and change.
RATE=$(curl -s "$BASE/products/$PRODUCT_ID?api_key=$API_KEY" | jq -r '.product.price_retail')
Use $RATE for drafts, the user preview, and the price_retail field.
3. NEVER invent or rename labor line items
Every labor line MUST be an existing Syncro product, billed under its REAL name (from GET /products/<id> → .product.name, verbatim). Work-specific narrative goes in description, never the name.
Why: invented names break the Syncro → QuickBooks sync. QB maps each labor line to an existing item; a fabricated name has no QB match and messes up the accounting. If no existing product fits, STOP and ask Mike — never invent one.
Product table lives in feedback_syncro_history (and .claude/commands/syncro.md).
4. Labor type must match delivery channel — never "Prepaid project labor"
Pick the labor product matching how work was delivered: remote (most common), onsite, in-shop, or web. Resolve product_id via GET /products?search=remote+labor etc.
Never default to "Prepaid project labor" — it is exempt and does NOT consume hours from a customer's prepaid block. Block accounting silently drifts.
Verify: after billing a prepay-block customer, confirm the block balance dropped by the expected hours. If it didn't, the labor type was wrong.
5. Labor is NEVER taxable in Arizona
Pass "taxable": false explicitly on every labor line. The product config has taxable: false, but add_line_item does not inherit it — posts as taxable: true regardless. Applies to remote, onsite, in-shop, emergency, warranty, prepaid.
6. Warranty / no-charge → product 1049360, never patch the price
Warranty work uses product_id: 1049360 ("Labor- Warranty work", $0/hr, non-taxable). The line generates at $0 from price_retail × quantity — no need to flag or patch anything.
Do NOT pick a regular labor product and try to neutralize it with billable: false or by patching price_retail to 0. Prices are set by selecting the correct product. If you reach for update_line_item to drop a price, that's the signal to back up and pick a different product_id.
The only legitimate update_line_item price_retail use is the Syncro auto-gen-zero recovery case (auto-line came in at $0 instead of the product's rate).
7. Emergency / after-hours — ×1.5 applied ONCE; branch by prepay_hours
GET /customers/<id> and read prepay_hours BEFORE adding any emergency line. Emergency = time-and-a-half, applied ONCE. Never bill a separate regular line + emergency line for the same hours.
No prepaid block (prepay_hours == 0):
- product
26184, quantity = actual hours (do NOT also ×1.5 the quantity) price_retailby delivery channel (the 1.5× lives in the dollars):- Onsite emergency =
$262.50(175 × 1.5; 26184's default). - Remote / In-Shop emergency =
$225(150 × 1.5) → overrideprice_retailto225.
- Onsite emergency =
Prepaid block (prepay_hours > 0):
- product
26184, quantity = actual hours × 1.5 (premium goes in the QUANTITY) - Delivery channel / dollar rate is irrelevant; prepaid blocks debit by quantity. Invoice nets to $0; block debits hrs×1.5.
- e.g. 1.5 emergency hrs →
26184@2.25.
Always set price_retail explicitly — the rate doesn't auto-populate and the line posts $0 if omitted. Verify after: .invoice.total (non-prepaid) or block decrement (prepaid).
8. API key follows the BILLING TECH — always
Attribution is determined by which API key you use. Every add_line_item / remove_line_item call is logged as the owner of that key. user_id in the payload does NOT override this.
Common-sense defaults (confirmed by Howard 2026-06-23):
- Howard asks for billing → use Howard's key (he's billing himself)
- Mike asks for billing → use Mike's key
- Told "put X hours in for [tech]" → use that tech's key, regardless of who is asking
- Split ticket ("2 hrs for Mike, 1 hr for Howard") → two separate
add_line_itemcalls, each with the correct tech's key
Vault paths:
- Howard →
msp-tools/syncro-howard.sops.yaml→credentials.credential - Mike →
msp-tools/syncro.sops.yaml→credentials.credential
HOWARD_KEY=$(bash .claude/scripts/vault.sh get-field msp-tools/syncro-howard credentials.credential)
MIKE_KEY=$(bash .claude/scripts/vault.sh get-field msp-tools/syncro credentials.credential)
# Each line item call uses the BILLING TECH's key as a query param:
curl -s -X POST "https://computerguru.syncromsp.com/api/v1/tickets/{id}/add_line_item?api_key=${HOWARD_KEY}" ...
curl -s -X POST "https://computerguru.syncromsp.com/api/v1/tickets/{id}/add_line_item?api_key=${MIKE_KEY}" ...
Auth note: ?api_key= is the attribution mechanism. The Authorization: <key> header works for reads but does NOT control line-item attribution — always use ?api_key= for billing writes.
Corrections: wrong key used → remove_line_item with any key (doesn't matter), then re-add_line_item with the correct tech's key. update_line_item does NOT fix user_id.
Ticket ownership: adding notes or labor does NOT change .ticket.user_id. Multiple techs routinely work the same ticket. Only change ticket ownership when explicitly asked.
Tech user_id table → feedback_syncro_history.
9. Estimate hardware → product 32252
All hardware on estimates uses one generic product: product_id: 32252 ("Hardware", price_retail: 0.0). Differentiate via name ("Dell OptiPlex 7010") and price_retail (actual cost). Hardware is typically taxable: true. Never look up individual hardware product IDs — there's only one.