Invoices

Create and manage customer invoices

Overview

Invoices track billing for services rendered. They can contain multiple line items, apply taxes, and integrate with Stripe and QuickBooks for billing and accounting. Invoices support various payment statuses and can be created as local invoices or synced with external providers.

List Invoices (Cursor-Based)

Retrieve a cursor-based paginated list of invoices with filtering and sorting. Requires read:invoice permission.

query InfiniteInvoices(
  $first: Int!
  $after: String
  $selector: InvoicesSelector
  $sort: [InvoicesSort!]
) {
  infiniteInvoices(
    first: $first
    after: $after
    selector: $selector
    sort: $sort
  ) {
    edges {
      node {
        id
        invoiceNumber
        status
        type
        subtotal
        taxes
        total
        amountPaid
        issueDate
        dueDate
        customer {
          id
          firstName
          lastName
        }
        lines {
          id
          description
          quantity
          price
          type
        }
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

# Variables
{
  "first": 20,
  "selector": {
    "filters": {
      "status": { "in": ["OPEN", "PAST_DUE"] },
      "dueDate": { "lte": "2024-02-01T00:00:00Z" }
    },
    "search": "Smith"
  },
  "sort": [{ "field": "dueDate", "direction": "ASC" }]
}

Response:

{
  "data": {
    "infiniteInvoices": {
      "edges": [
        {
          "node": {
            "id": "inv_001",
            "invoiceNumber": "INV-2024-0001",
            "status": "OPEN",
            "type": "STRIPE",
            "subtotal": 137.61,
            "taxes": 12.39,
            "total": 150.00,
            "amountPaid": 0,
            "issueDate": "2024-01-15T00:00:00Z",
            "dueDate": "2024-01-31T00:00:00Z",
            "customer": {
              "id": "cust_456",
              "firstName": "John",
              "lastName": "Smith"
            },
            "lines": [
              {
                "id": "li_001",
                "description": "Weekly Pool Cleaning - January",
                "quantity": 2,
                "price": 75.00,
                "type": "SERVICE_TYPE"
              }
            ]
          },
          "cursor": "eyJpZCI6Imludl8wMDEifQ=="
        }
      ],
      "pageInfo": {
        "hasNextPage": true,
        "endCursor": "eyJpZCI6Imludl8wMDEifQ=="
      }
    }
  }
}

List Invoices (Offset-Based)

Retrieve an offset-based paginated list of invoices. Maximum 100 per page. Requires read:invoice permission.

query PaginatedInvoices(
  $limit: Int!
  $page: Int!
  $selector: InvoicesSelector
  $sort: [InvoicesSort!]
) {
  paginatedInvoices(
    limit: $limit
    page: $page
    selector: $selector
    sort: $sort
  ) {
    items {
      id
      invoiceNumber
      status
      total
      amountPaid
      issueDate
      dueDate
      customer {
        id
        firstName
        lastName
      }
    }
    totalCount
    page
    limit
  }
}

# Variables
{
  "limit": 25,
  "page": 1,
  "selector": {
    "filters": {
      "status": { "equals": "PAID" },
      "issueDate": { "gte": "2024-01-01T00:00:00Z" }
    }
  },
  "sort": [{ "field": "issueDate", "direction": "DESC" }]
}

Response:

{
  "data": {
    "paginatedInvoices": {
      "items": [
        {
          "id": "inv_003",
          "invoiceNumber": "INV-2024-0003",
          "status": "PAID",
          "total": 200.00,
          "amountPaid": 200.00,
          "issueDate": "2024-01-20T00:00:00Z",
          "dueDate": "2024-02-03T00:00:00Z"
        }
      ],
      "totalCount": 48,
      "page": 1,
      "limit": 25
    }
  }
}

Preview Invoice

Preview an invoice before creating it. Requires read:invoice permission.

query PreviewInvoice($input: PreviewInvoiceInput!) {
  previewInvoice(input: $input) {
    subtotal
    taxes
    total
    lines {
      description
      price
      quantity
      type
    }
  }
}

# Variables
{
  "input": {
    "customerId": "cust_456",
    "serviceIds": ["svc_001", "svc_002"]
  }
}

Response:

{
  "data": {
    "previewInvoice": {
      "subtotal": 250.00,
      "taxes": 22.50,
      "total": 272.50,
      "lines": [
        {
          "description": "Weekly Pool Cleaning",
          "price": 150.00,
          "quantity": 1,
          "type": "SERVICE_TYPE"
        },
        {
          "description": "Filter Replacement",
          "price": 100.00,
          "quantity": 1,
          "type": "INVENTORY_ITEM"
        }
      ]
    }
  }
}

Create Invoice

Create invoices for services. Returns an array of invoices (multiple if separateInvoicePerService is true). Requires write:invoice permission.

mutation CreateInvoice($options: CreateInvoiceOptions!) {
  createInvoice(options: $options) {
    id
    invoiceNumber
    status
    type
    total
    issueDate
    dueDate
    createdAt
  }
}

# Variables
{
  "options": {
    "issueDate": "2024-01-20T00:00:00Z",
    "daysUntilDue": 15,
    "separateInvoicePerService": false,
    "includeSurcharge": false,
    "allowOnlineAchPayment": true,
    "allowOnlineCreditCardPayment": true,
    "sendInvoices": true,
    "provider": "STRIPE",
    "messageOnInvoice": "Thank you for your business!",
    "customLines": [
      {
        "description": "Pool Repair - Filter Replacement",
        "quantity": 1,
        "price": 250.00
      }
    ]
  }
}

Response:

{
  "data": {
    "createInvoice": [
      {
        "id": "inv_002",
        "invoiceNumber": "INV-2024-0002",
        "status": "OPEN",
        "type": "STRIPE",
        "total": 271.25,
        "issueDate": "2024-01-20T00:00:00Z",
        "dueDate": "2024-02-04T00:00:00Z",
        "createdAt": "2024-01-20T09:00:00Z"
      }
    ]
  }
}

Create Local Invoice

Create a local invoice for a specific customer with custom line items. Requires non-empty custom lines. Requires write:invoice permission.

mutation CreateLocalInvoice($customerId: ID!, $input: CreateInvoiceOptions!) {
  createLocalInvoice(customerId: $customerId, input: $input) {
    id
    invoiceNumber
    status
    type
    total
    issueDate
    dueDate
    lines {
      id
      description
      price
      quantity
      type
    }
  }
}

# Variables
{
  "customerId": "cust_456",
  "input": {
    "issueDate": "2024-01-20T00:00:00Z",
    "daysUntilDue": 30,
    "separateInvoicePerService": false,
    "includeSurcharge": false,
    "allowOnlineAchPayment": false,
    "allowOnlineCreditCardPayment": false,
    "sendInvoices": false,
    "customLines": [
      {
        "description": "Emergency Pool Pump Repair",
        "quantity": 1,
        "price": 350.00
      },
      {
        "description": "Replacement Parts",
        "quantity": 3,
        "price": 45.00
      }
    ]
  }
}

Response:

{
  "data": {
    "createLocalInvoice": {
      "id": "inv_005",
      "invoiceNumber": "INV-2024-0005",
      "status": "DRAFT",
      "type": "LOCAL",
      "total": 485.00,
      "issueDate": "2024-01-20T00:00:00Z",
      "dueDate": "2024-02-19T00:00:00Z",
      "lines": [
        {
          "id": "li_010",
          "description": "Emergency Pool Pump Repair",
          "price": 350.00,
          "quantity": 1,
          "type": "SERVICE_CALL_FEE"
        },
        {
          "id": "li_011",
          "description": "Replacement Parts",
          "price": 45.00,
          "quantity": 3,
          "type": "INVENTORY_ITEM"
        }
      ]
    }
  }
}

Update Invoice

Update an existing invoice. Requires write:invoice permission.

mutation UpdateInvoice($id: ID!, $input: UpdateInvoiceInput!) {
  updateInvoice(id: $id, input: $input) {
    id
    invoiceNumber
    status
    total
    dueDate
    notes
  }
}

# Variables
{
  "id": "inv_001",
  "input": {
    "notes": "Updated payment terms",
    "messageOnInvoice": "Payment due upon receipt"
  }
}

Response:

{
  "data": {
    "updateInvoice": {
      "id": "inv_001",
      "invoiceNumber": "INV-2024-0001",
      "status": "OPEN",
      "total": 150.00,
      "dueDate": "2024-01-31T00:00:00Z",
      "notes": "Updated payment terms"
    }
  }
}

Delete Invoice

Delete an invoice. Requires delete:invoice permission.

mutation DeleteInvoice($id: ID!) {
  deleteInvoice(id: $id)
}

# Variables
{
  "id": "inv_001"
}

Response:

{
  "data": {
    "deleteInvoice": true
  }
}

Pay Invoice

Record a payment against an invoice. Requires write:invoice permission.

mutation PayInvoice($id: ID!, $paymentSourceId: String!, $provider: InvoiceType!) {
  payInvoice(id: $id, paymentSourceId: $paymentSourceId, provider: $provider) {
    id
    status
    amountPaid
    total
    paidDate
  }
}

# Variables
{
  "id": "inv_001",
  "paymentSourceId": "pm_abc123",
  "provider": "STRIPE"
}

Response:

{
  "data": {
    "payInvoice": {
      "id": "inv_001",
      "status": "PAID",
      "amountPaid": 150.00,
      "total": 150.00,
      "paidDate": "2024-01-20T15:00:00Z"
    }
  }
}

Resend Invoice

Resend an invoice notification to the customer. Requires write:invoice permission.

mutation ResendInvoice($id: ID!, $sendService: InvoiceService) {
  resendInvoice(id: $id, sendService: $sendService) {
    id
    invoiceNumber
    status
  }
}

# Variables
{
  "id": "inv_001",
  "sendService": "EMAIL"
}

Response:

{
  "data": {
    "resendInvoice": {
      "id": "inv_001",
      "invoiceNumber": "INV-2024-0001",
      "status": "OPEN"
    }
  }
}

Bulk & Export Operations

Creating, updating, or exporting many invoices at once is handled asynchronously through the Bulk Operations API. These jobs run in the background and report progress you can poll. See the Bulk Operations guide for the full workflow.

Filtering and Sorting

Both infiniteInvoices and paginatedInvoices accept an InvoicesSelector with the following filter options:

InvoiceFilter fields

  • id - IdFilter
  • customerId - IdFilter
  • status - InvoiceStatusFilter (equals, in, notIn)
  • type - InvoiceTypeEnum (STRIPE, QUICKBOOKS, LOCAL)
  • total - NumberFilter
  • issueDate - DateTimeFilter
  • dueDate - DateTimeFilter
  • customerTags - IdFilter

Sortable fields

  • id
  • invoiceNumber
  • status
  • total
  • issueDate
  • dueDate
  • customer___firstName
  • customer___lastName

The selector also supports a search field (max 600 characters) for free-text search across invoices.

Invoice Field Reference

FieldTypeDescription
idID!Unique identifier
invoiceNumberString(nullable)Invoice number
customerIdID!Customer ID
customerCustomer!Customer being billed
statusInvoiceStatusEnum!Status (DRAFT, OPEN, PAID, PAST_DUE, EXPORTED, QUICKBOOKS, STRIPE)
typeInvoiceTypeEnum!Invoice provider type (STRIPE, QUICKBOOKS, LOCAL)
subtotalFloat!Subtotal before tax
taxesFloat!Tax amount
totalFloat!Total amount due
amountPaidFloat!Amount paid
issueDateDateTime!Date invoice was issued
dueDateDateTime(nullable)Payment due date
paidDateDateTime(nullable)When invoice was paid
notesString(nullable)Invoice notes
stripeInvoiceIdString(nullable)Stripe invoice ID
quickbooksIdString(nullable)QuickBooks invoice ID
editInvoiceUrlString(nullable)URL to edit the invoice in the provider
invoicePdfString(nullable)URL to the invoice PDF
hostedInvoiceUrlString(nullable)URL to the hosted invoice page
canPayBoolean(nullable)Whether the invoice can be paid
canChangeStatusBoolean(nullable)Whether the invoice status can be changed
surchargeEnabledBoolean!Whether surcharge is enabled
surchargePercentFloat(nullable)Surcharge percentage
messageOnInvoiceString(nullable)Custom message displayed on the invoice
messageOnStatementString(nullable)Custom message displayed on statements
createdAtDateTime!Creation timestamp
lines[InvoiceLine!](nullable)Invoice line items

InvoiceLine Field Reference

FieldTypeDescription
idID!Unique identifier
lineDateDateTime!Date for this line item
descriptionString!Line item description
detailString(nullable)Additional detail
priceNumber!Unit price
quantityNumber!Quantity
typeString!Line type (SERVICE_TYPE, SERVICE_CALL_FEE, INVENTORY_ITEM)
isTaxableBoolean(nullable)Whether this line is taxable
metadataInvoiceLineMetadata(nullable)Additional metadata for this line