From xero
Use this skill when working with Xero invoices - creating, searching, updating, voiding, and managing sales invoices (ACCREC) and supplier bills (ACCPAY). Covers invoice lifecycle, line items, tax handling, recurring managed services billing, and MSP invoice workflows.
How this skill is triggered — by the user, by Claude, or both
Slash command
/xero:invoicesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Invoices are the core transaction entity in Xero for billing and accounts payable. For MSPs, invoices represent two primary flows: sales invoices (ACCREC) for billing managed services clients, and supplier bills (ACCPAY) for vendor costs like software licenses, hardware purchases, and ISP charges.
Invoices are the core transaction entity in Xero for billing and accounts payable. For MSPs, invoices represent two primary flows: sales invoices (ACCREC) for billing managed services clients, and supplier bills (ACCPAY) for vendor costs like software licenses, hardware purchases, and ISP charges.
| Type | Code | Description | MSP Use Case |
|---|---|---|---|
| Sales Invoice | ACCREC | Accounts Receivable - you bill a customer | Monthly managed services, project work |
| Supplier Bill | ACCPAY | Accounts Payable - a vendor bills you | Software licenses, hardware, ISP bills |
DRAFT --> SUBMITTED --> AUTHORISED --> PAID
\--> VOIDED
\--> DELETED (draft only)
| Status | Description | Editable | Can Pay |
|---|---|---|---|
DRAFT | Created but not submitted | Yes | No |
SUBMITTED | Submitted for approval | Limited | No |
AUTHORISED | Approved and sent to client | No | Yes |
PAID | Fully paid | No | N/A |
VOIDED | Cancelled/voided | No | No |
DELETED | Removed (draft only) | N/A | N/A |
Xero auto-generates sequential invoice numbers (e.g., INV-0001, INV-0002) or you can set custom numbers using the InvoiceNumber field. MSPs often use prefixes like MS- for managed services or PJ- for project invoices.
| Field | Type | Required | Description |
|---|---|---|---|
InvoiceID | string (UUID) | System | Auto-generated unique identifier |
InvoiceNumber | string | No | Invoice number (auto-generated if blank) |
Type | string | Yes | ACCREC (sales) or ACCPAY (bill) |
Contact | object | Yes | Contact object with ContactID |
Date | string | No | Invoice date (YYYY-MM-DDT00:00:00) |
DueDate | string | No | Payment due date |
Status | string | No | DRAFT, SUBMITTED, AUTHORISED, PAID, VOIDED |
LineAmountTypes | string | No | Exclusive, Inclusive, or NoTax |
Reference | string | No | Reference text (e.g., PO number) |
Url | string | No | URL link for the invoice |
CurrencyCode | string | No | Currency code (e.g., USD, AUD) |
BrandingThemeID | string | No | Invoice template/theme ID |
| Field | Type | Required | Description |
|---|---|---|---|
Description | string | Yes | Line item description |
Quantity | decimal | No | Quantity (default 1) |
UnitAmount | decimal | Yes* | Price per unit |
AccountCode | string | Yes* | GL account code |
TaxType | string | No | Tax type code |
ItemCode | string | No | Inventory item code |
LineAmount | decimal | No | Total line amount (calculated) |
DiscountRate | decimal | No | Discount percentage |
Tracking | array | No | Tracking category assignments |
*Required for AUTHORISED status.
| Field | Type | Description |
|---|---|---|
SubTotal | decimal | Sum of line items before tax |
TotalTax | decimal | Total tax amount |
Total | decimal | Total including tax |
AmountDue | decimal | Remaining unpaid amount |
AmountPaid | decimal | Amount already paid |
AmountCredited | decimal | Amount from credit notes |
HasAttachments | boolean | Whether attachments exist |
Payments | array | Associated payment records |
curl -s -X GET "https://api.xero.com/api.xro/2.0/Invoices" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Accept: application/json"
With Filters:
# Sales invoices only
curl -s -X GET "https://api.xero.com/api.xro/2.0/Invoices?where=Type==%22ACCREC%22" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Accept: application/json"
# Outstanding invoices
curl -s -X GET "https://api.xero.com/api.xro/2.0/Invoices?where=Type==%22ACCREC%22&&AmountDue>0" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Accept: application/json"
# Invoices for a specific contact
curl -s -X GET "https://api.xero.com/api.xro/2.0/Invoices?where=Contact.ContactID==guid(%22${CONTACT_ID}%22)" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Accept: application/json"
# Invoices by date range
curl -s -X GET "https://api.xero.com/api.xro/2.0/Invoices?where=Date>=DateTime(2026,3,1)&&Date<=DateTime(2026,3,31)" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Accept: application/json"
# Overdue invoices
curl -s -X GET "https://api.xero.com/api.xro/2.0/Invoices?where=Type==%22ACCREC%22&&Status==%22AUTHORISED%22&&DueDate<DateTime(2026,2,23)" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Accept: application/json"
curl -s -X GET "https://api.xero.com/api.xro/2.0/Invoices/${INVOICE_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Accept: application/json"
curl -s -X POST "https://api.xero.com/api.xro/2.0/Invoices" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Content-Type: application/json" \
-d '{
"Type": "ACCREC",
"Contact": {
"ContactID": "'${CONTACT_ID}'"
},
"Date": "2026-03-01T00:00:00",
"DueDate": "2026-03-31T00:00:00",
"LineAmountTypes": "Exclusive",
"Reference": "March 2026 Managed Services",
"LineItems": [
{
"Description": "Monthly Managed Services - Acme Corp (25 endpoints)",
"Quantity": 1,
"UnitAmount": 2500.00,
"AccountCode": "200",
"TaxType": "OUTPUT"
},
{
"Description": "Microsoft 365 Business Premium Licenses (25 users)",
"Quantity": 25,
"UnitAmount": 22.00,
"AccountCode": "200",
"TaxType": "OUTPUT"
},
{
"Description": "Backup & Disaster Recovery - 500GB",
"Quantity": 1,
"UnitAmount": 350.00,
"AccountCode": "200",
"TaxType": "OUTPUT"
}
],
"Status": "DRAFT"
}'
curl -s -X POST "https://api.xero.com/api.xro/2.0/Invoices" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Content-Type: application/json" \
-d '{
"Type": "ACCPAY",
"Contact": {
"ContactID": "'${VENDOR_CONTACT_ID}'"
},
"Date": "2026-03-01T00:00:00",
"DueDate": "2026-03-31T00:00:00",
"InvoiceNumber": "VENDOR-INV-2026-03",
"Reference": "Monthly software licenses",
"LineAmountTypes": "Exclusive",
"LineItems": [
{
"Description": "RMM Platform - 150 endpoints",
"Quantity": 150,
"UnitAmount": 3.50,
"AccountCode": "400",
"TaxType": "INPUT"
}
],
"Status": "DRAFT"
}'
curl -s -X POST "https://api.xero.com/api.xro/2.0/Invoices/${INVOICE_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Content-Type: application/json" \
-d '{
"InvoiceID": "'${INVOICE_ID}'",
"Reference": "Updated reference",
"Status": "AUTHORISED"
}'
curl -s -X POST "https://api.xero.com/api.xro/2.0/Invoices/${INVOICE_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Content-Type: application/json" \
-d '{
"InvoiceID": "'${INVOICE_ID}'",
"Status": "VOIDED"
}'
curl -s -X POST "https://api.xero.com/api.xro/2.0/Invoices?summarizeErrors=false" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Content-Type: application/json" \
-d '{
"Invoices": [
{
"Type": "ACCREC",
"Contact": { "ContactID": "abc-123" },
"Date": "2026-03-01T00:00:00",
"DueDate": "2026-03-31T00:00:00",
"LineItems": [{ "Description": "Managed Services - March 2026", "Quantity": 1, "UnitAmount": 2500.00, "AccountCode": "200" }]
},
{
"Type": "ACCREC",
"Contact": { "ContactID": "def-456" },
"Date": "2026-03-01T00:00:00",
"DueDate": "2026-03-31T00:00:00",
"LineItems": [{ "Description": "Managed Services - March 2026", "Quantity": 1, "UnitAmount": 1800.00, "AccountCode": "200" }]
}
]
}'
The typical MSP monthly billing workflow:
async function generateMonthlyInvoices(clients, billingMonth) {
const invoices = clients.map(client => ({
Type: 'ACCREC',
Contact: { ContactID: client.xeroContactId },
Date: `${billingMonth}-01T00:00:00`,
DueDate: `${billingMonth}-28T00:00:00`,
Reference: `${billingMonth} Managed Services`,
LineAmountTypes: 'Exclusive',
LineItems: client.services.map(service => ({
Description: `${service.name} - ${billingMonth}`,
Quantity: service.quantity,
UnitAmount: service.unitPrice,
AccountCode: service.accountCode || '200'
})),
Status: 'DRAFT'
}));
const token = await auth.getToken();
const response = await fetch(
'https://api.xero.com/api.xro/2.0/Invoices?summarizeErrors=false',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'xero-tenant-id': process.env.XERO_TENANT_ID,
'Content-Type': 'application/json'
},
body: JSON.stringify({ Invoices: invoices })
}
);
return await response.json();
}
Use tracking categories for department or project reporting:
{
"Type": "ACCREC",
"Contact": { "ContactID": "abc-123" },
"LineItems": [
{
"Description": "Managed Services - March 2026",
"Quantity": 1,
"UnitAmount": 2500.00,
"AccountCode": "200",
"Tracking": [
{
"Name": "Department",
"Option": "Managed Services"
},
{
"Name": "Region",
"Option": "East"
}
]
}
]
}
When a client needs a partial credit:
curl -s -X POST "https://api.xero.com/api.xro/2.0/CreditNotes" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Content-Type: application/json" \
-d '{
"Type": "ACCRECCREDIT",
"Contact": { "ContactID": "'${CONTACT_ID}'" },
"Date": "2026-03-15T00:00:00",
"LineItems": [
{
"Description": "Service credit - 3 day outage adjustment",
"Quantity": 1,
"UnitAmount": 250.00,
"AccountCode": "200"
}
],
"Status": "AUTHORISED"
}'
async function findUnbilledClients(billingMonth) {
const allContacts = await fetchActiveCustomers();
const invoices = await fetchInvoicesByDateRange(
`${billingMonth}-01`,
`${billingMonth}-28`
);
const billedContactIds = new Set(
invoices
.filter(inv => inv.Type === 'ACCREC')
.map(inv => inv.Contact.ContactID)
);
return allContacts.filter(
contact => !billedContactIds.has(contact.ContactID)
);
}
| Code | Message | Resolution |
|---|---|---|
| 400 | Account code is not valid | Verify AccountCode exists in chart of accounts |
| 400 | A Contact is required | Provide Contact with valid ContactID |
| 400 | At least one line item is required | Add LineItems array with entries |
| 400 | Invoice number already used | Use unique InvoiceNumber or omit for auto |
| 400 | Cannot void a paid invoice | Reverse payment first, then void |
| 401 | Unauthorized | Refresh access token |
| 404 | Invoice not found | Verify InvoiceID |
Xero returns validation errors inline with the invoice object:
{
"Invoices": [
{
"InvoiceID": "00000000-0000-0000-0000-000000000000",
"HasErrors": true,
"ValidationErrors": [
{ "Message": "Account code '999' is not a valid code for this document." }
]
}
]
}
async function safeCreateInvoice(invoiceData) {
const result = await createInvoice(invoiceData);
const invoice = result.Invoices?.[0];
if (invoice?.HasErrors) {
const errors = invoice.ValidationErrors.map(e => e.Message);
console.error('Invoice validation failed:', errors);
// Common recovery: invalid account code
if (errors.some(e => e.includes('Account code'))) {
console.log('Check chart of accounts for valid codes.');
}
// Common recovery: duplicate invoice number
if (errors.some(e => e.includes('already used'))) {
delete invoiceData.InvoiceNumber;
return await createInvoice(invoiceData);
}
throw new Error(`Validation errors: ${errors.join('; ')}`);
}
return invoice;
}
| Endpoint | Method | Description |
|---|---|---|
/Invoices | GET | List invoices (paginated, filterable) |
/Invoices | POST | Create or update invoices (single or batch) |
/Invoices/{InvoiceID} | GET | Get single invoice with full detail |
/Invoices/{InvoiceID} | POST | Update an invoice |
/Invoices/{InvoiceID}/Attachments | GET | List invoice attachments |
/Invoices/{InvoiceID}/Attachments/{FileName} | PUT | Upload attachment |
/Invoices/{InvoiceID}/OnlineInvoice | GET | Get online invoice URL |
/Invoices/{InvoiceID}/Email | POST | Email invoice to contact |
npx claudepluginhub p/drewrox2009-xero-msp-claude-plugins-xero-xeroGuides collaborative design exploration before implementation: explores context, asks clarifying questions, proposes approaches, and writes a design doc for user approval.
Creates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.
Provides Slack GIF creation utilities with dimension/FPS/color constraints and Python PIL-based frame generation. Use for animated Slack emoji or message GIFs.