# QuickBooks Online Expenses

> QuickBooks Online expense entities: Purchase (check, cash, credit card), Bill for accounts payable, BillPayment, and Vendor. Covers account-based vs item-based expense lines, per-client cost allocation via CustomerRef and BillableStatus, expense categorization, query syntax, error codes, and MSP profitability analysis patterns.

- Skill: `wyre-ai/quickbooks-online-expenses` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add wyre-ai/quickbooks-online-expenses`
- Raw SKILL.md: https://api.skillmd.com/api/skills/wyre-ai/quickbooks-online-expenses/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: WYRE-AI (https://skillmd.com/u/wyre-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/wyre-ai/quickbooks-online-expenses

---


# QuickBooks Online Expense Management

## Overview

Expenses in QuickBooks Online are tracked through two primary entities: **Purchase** (for direct expenses like checks, credit card charges, and cash payments) and **Bill** (for accounts payable -- vendor invoices you owe). For MSPs, expense tracking is critical for per-client profitability analysis: tracking software licenses, hardware costs, subcontractor fees, and third-party service costs against the revenue each client generates.

## Anti-triggers

- **Money coming in** — this skill is accounts payable. Client billing and
  cash receipts are `qbo-invoices` and `qbo-payments`.
  "Bill" is the trap: a QBO Bill is something the MSP owes, while a PSA
  "bill" is usually something a client owes.
- **The books are in Xero, not QuickBooks** — use `xero-accounts` and
  `xero-invoices`.
- **The distributor's own charge detail** — Pax8 and Sherweb hold the
  line-level cost of cloud licences before it is entered as a bill here.
  Use `pax8-invoices` or `sherweb-billing`.
- **Technician time as a cost** — labour cost is not a Purchase or Bill;
  time originates in the PSA (`autotask-time-entries`,
  `connectwise-psa-time-entries`) and lands here only as payroll.
- **The profitability answer itself** — per-client margin is a report over
  these records, not a query against them; use `qbo-reports`.

## Key Concepts

### Expense Types (Purchase Entity)

The Purchase entity covers direct expenses paid immediately or via credit card:

| PaymentType | Description | MSP Example |
|-------------|-------------|-------------|
| `Cash` | Cash/bank payment | Petty cash for supplies |
| `Check` | Check payment | Vendor payment by check |
| `CreditCard` | Credit card charge | Software subscription charge |

### Bills vs Purchases

| Entity | When to Use | Payment Timing |
|--------|-------------|----------------|
| **Purchase** | Expense already paid | Immediate (check, cash, credit card) |
| **Bill** | Vendor invoice received | Deferred (pay later via BillPayment) |

### MSP Expense Categories

| Category | Description | Examples |
|----------|-------------|---------|
| Software Licenses | Per-seat or per-client licenses | Microsoft 365, antivirus, RMM seats |
| Hardware | Equipment for clients | Servers, firewalls, workstations |
| Subcontractors | Outsourced labor | Cabling, specialized consulting |
| Cloud Services | Hosted services | Azure, AWS, backup storage |
| Telecom | Communication services | Internet, VoIP, SIP trunks |
| Training | Staff certifications | Vendor certs, training courses |

### Per-Client Cost Tracking

QBO supports assigning expenses to customers, enabling per-client profitability analysis. Use the `CustomerRef` field on line items to allocate costs to specific MSP clients.

### Key Fields

A Purchase requires `PaymentType`, `AccountRef.value` (the bank or credit card
account it was paid from), and at least one `Line`. A Bill requires
`VendorRef.value` and `Line`. Per-client allocation lives on the line detail, not
the header: `AccountBasedExpenseLineDetail.CustomerRef.value` plus
`BillableStatus` ("Billable", "NotBillable", "HasBeenBilled"). `TotalAmt` and
`Balance` are read-only; `SyncToken` is required on updates.

See [references/fields.md](references/fields.md) for the complete Purchase, Bill,
and Vendor field reference.

## API Patterns

### Query Purchases (Expenses)

```http
GET /v3/company/{realmId}/query?query=SELECT * FROM Purchase WHERE PaymentType = 'CreditCard' AND TxnDate >= '2026-01-01'&minorversion=73
Authorization: Bearer {access_token}
Accept: application/json
```

**Common Queries:**

```sql
-- All credit card expenses in a date range
SELECT * FROM Purchase WHERE PaymentType = 'CreditCard' AND TxnDate >= '2026-01-01' AND TxnDate <= '2026-01-31'

-- Expenses for a specific vendor
SELECT * FROM Purchase WHERE EntityRef = '42'

-- All expenses in a month
SELECT * FROM Purchase WHERE TxnDate >= '2026-02-01' AND TxnDate <= '2026-02-28' ORDERBY TxnDate DESC

-- All bills (accounts payable)
SELECT * FROM Bill WHERE Balance > '0' ORDERBY DueDate ASC

-- Bills for a specific vendor
SELECT * FROM Bill WHERE VendorRef = '42' AND Balance > '0'

-- All vendors
SELECT * FROM Vendor WHERE Active = true ORDERBY DisplayName
```

### Create Purchase (Credit Card Expense)

```http
POST /v3/company/{realmId}/purchase?minorversion=73
Content-Type: application/json
Authorization: Bearer {access_token}
```

Software license expense allocated to a client — note `AccountRef` on the header
is the funding account (credit card), while `AccountRef` inside the line detail
is the expense account:

```json
{
  "PaymentType": "CreditCard",
  "AccountRef": { "value": "41" },
  "EntityRef": { "value": "42", "type": "Vendor" },
  "TxnDate": "2026-02-15",
  "Line": [{
    "Amount": 450.00,
    "Description": "Microsoft 365 Business Premium - 30 seats - Acme Corp - February 2026",
    "DetailType": "AccountBasedExpenseLineDetail",
    "AccountBasedExpenseLineDetail": {
      "AccountRef": { "value": "60" },
      "CustomerRef": { "value": "123" },
      "BillableStatus": "Billable"
    }
  }],
  "PrivateNote": "Monthly software licenses for Acme Corp"
}
```

See [references/examples.md](references/examples.md) for the multi-line request
body, the curl equivalent, and a Create Vendor payload.

### Create Bill (Vendor Invoice)

```json
{
  "VendorRef": {
    "value": "42"
  },
  "TxnDate": "2026-02-10",
  "DueDate": "2026-03-12",
  "Line": [
    {
      "Amount": 2400.00,
      "Description": "Cabling project - Acme Corp new office build-out",
      "DetailType": "AccountBasedExpenseLineDetail",
      "AccountBasedExpenseLineDetail": {
        "AccountRef": { "value": "62" },
        "CustomerRef": { "value": "123" },
        "BillableStatus": "Billable"
      }
    }
  ],
  "PrivateNote": "Subcontractor cabling for Acme Corp office expansion"
}
```

### Pay a Bill (BillPayment)

```json
{
  "VendorRef": {
    "value": "42"
  },
  "PayType": "Check",
  "CheckPayment": {
    "BankAccountRef": { "value": "35" }
  },
  "TotalAmt": 2400.00,
  "Line": [
    {
      "Amount": 2400.00,
      "LinkedTxn": [
        {
          "TxnId": "789",
          "TxnType": "Bill"
        }
      ]
    }
  ]
}
```

## Common Workflows

### Per-Client Expense Report

```javascript
async function getClientExpenses(customerId, startDate, endDate) {
  // Get all purchases with line items allocated to this customer
  const purchases = await queryAll('Purchase',
    `TxnDate >= '${startDate}' AND TxnDate <= '${endDate}'`
  );

  const clientExpenses = [];

  for (const purchase of purchases) {
    for (const line of purchase.Line || []) {
      const detail = line.AccountBasedExpenseLineDetail || line.ItemBasedExpenseLineDetail;
      if (detail?.CustomerRef?.value === customerId) {
        clientExpenses.push({
          date: purchase.TxnDate,
          vendor: purchase.EntityRef?.name || 'Unknown',
          description: line.Description,
          amount: line.Amount,
          billable: detail.BillableStatus === 'Billable',
          category: detail.AccountRef?.name
        });
      }
    }
  }

  return {
    customer: customerId,
    period: `${startDate} to ${endDate}`,
    expenses: clientExpenses,
    total: clientExpenses.reduce((sum, e) => sum + e.Amount, 0)
  };
}
```

### Monthly Software License Tracking

```javascript
async function recordMonthlyLicenses(month, licenseData) {
  const results = [];

  for (const license of licenseData) {
    const purchase = await createPurchase({
      PaymentType: 'CreditCard',
      AccountRef: { value: license.creditCardAccountId },
      EntityRef: { value: license.vendorId, type: 'Vendor' },
      TxnDate: `${month}-15`,
      Line: [{
        Amount: license.seats * license.perSeatCost,
        Description: `${license.productName} - ${license.seats} seats - ${license.customerName} - ${month}`,
        DetailType: 'AccountBasedExpenseLineDetail',
        AccountBasedExpenseLineDetail: {
          AccountRef: { value: license.expenseAccountId },
          CustomerRef: { value: license.customerId },
          BillableStatus: license.billable ? 'Billable' : 'NotBillable'
        }
      }],
      PrivateNote: `Auto-recorded: ${license.productName} for ${license.customerName}`
    });

    results.push({
      customer: license.customerName,
      product: license.productName,
      amount: license.seats * license.perSeatCost,
      purchaseId: purchase.Id
    });
  }

  return results;
}
```

### Profitability Analysis

```javascript
async function clientProfitability(customerId, startDate, endDate) {
  // Revenue: sum of invoice line items for this customer
  const invoices = await qboQuery(
    `SELECT * FROM Invoice WHERE CustomerRef = '${customerId}' AND TxnDate >= '${startDate}' AND TxnDate <= '${endDate}'`
  );
  const revenue = (invoices.QueryResponse.Invoice || [])
    .reduce((sum, inv) => sum + inv.TotalAmt, 0);

  // Costs: sum of expense line items allocated to this customer
  const expenses = await getClientExpenses(customerId, startDate, endDate);

  return {
    customerId,
    period: `${startDate} to ${endDate}`,
    revenue,
    costs: expenses.total,
    profit: revenue - expenses.total,
    margin: revenue > 0 ? ((revenue - expenses.total) / revenue * 100).toFixed(1) + '%' : 'N/A'
  };
}
```

### Outstanding Bills Summary

```javascript
async function getOutstandingBills() {
  const result = await qboQuery(
    "SELECT * FROM Bill WHERE Balance > '0' ORDERBY DueDate ASC"
  );
  const bills = result.QueryResponse.Bill || [];
  const today = new Date().toISOString().split('T')[0];

  return bills.map(bill => ({
    vendor: bill.VendorRef.name,
    amount: bill.TotalAmt,
    balance: bill.Balance,
    dueDate: bill.DueDate,
    overdue: bill.DueDate < today
  }));
}
```

## Error Handling

- **Customer allocation is per line, not per transaction.** There is no
  header-level `CustomerRef` on Purchase or Bill — a report that reads only the
  header will show zero client cost.
- **610 Object Not Found** on a Purchase create is usually a stale or inactive
  `AccountRef`/`EntityRef`; the fault Detail names the field, the message does not.
- **Inactive references fail silently in intent.** Deactivating a vendor or
  account leaves existing transactions intact but rejects new ones referencing it.
- **6240 Duplicate Name** applies to Vendor `DisplayName`, which shares a
  namespace with Customer and Employee names in QBO.

See [references/errors.md](references/errors.md) for the full error-code and
validation tables plus a recovery pattern.

## Best Practices

1. **Allocate to customers** - Always set CustomerRef on expense lines for per-client tracking
2. **Mark billable expenses** - Use BillableStatus "Billable" for costs to re-invoice to clients
3. **Use consistent accounts** - Map expense categories to standard QBO accounts
4. **Track by vendor** - Create Vendor records for all suppliers and service providers
5. **Use Bills for deferred payment** - Record vendor invoices as Bills, then pay with BillPayment
6. **Record software licenses monthly** - Track per-seat costs monthly for accurate reporting
7. **Tag with PrivateNote** - Store PSA references and internal project codes
8. **Review billable expenses** - Regularly check for uninvoiced billable expenses

See [references/api.md](references/api.md) for the complete endpoint reference.

## Related Skills

- [QBO Customers](../customers/SKILL.md) - Customer allocation targets
- [QBO Invoices](../invoices/SKILL.md) - Re-invoicing billable expenses
- [QBO Payments](../payments/SKILL.md) - Bill payments
- [QBO Reports](../reports/SKILL.md) - P&L and expense reports
- [QBO API Patterns](../api-patterns/SKILL.md) - API reference

