Skip to content

Xero Accounting

Overview

The Xero Accounting task integrates BaseCloud CRM with Xero (cloud accounting platform), enabling OAuth-authenticated contact and invoice management across multiple organizations with automatic token refresh.

Key Features:

  • 8 OAuth Actions: Full CRUD for contacts and invoices
  • Multi-Tenant Support: Manage multiple Xero organizations
  • Auto Token Refresh: Automatic OAuth token renewal
  • Tracking Categories: Two-level project/department tracking
  • Contact Auto-Create: Automatic contact creation in invoices
  • Global Coverage: 180+ countries, 160+ currencies
  • Pagination: Handle large datasets efficiently
  • Advanced Filtering: OData-style where clauses

Use Cases:

  • Multi-organization invoicing from single CRM
  • Automated contact synchronization
  • Project-based billing with tracking
  • Invoice status monitoring and reconciliation
  • Bulk operations with pagination

Prerequisites

1. Xero Account & App

Xero Requirements:

  • Active Xero subscription (any plan)
  • Xero Developer account: https://developer.xero.com
  • OAuth 2.0 app created in Xero Developer Portal

App Setup:

  1. Create app at https://developer.xero.com/app/manage
  2. Note Client ID and Client Secret
  3. Add redirect URI: https://your-basecloud-domain.com/oauth/xero/callback
  4. Scopes required:
  5. accounting.contacts (read/write contacts)
  6. accounting.transactions (read/write invoices)
  7. offline_access (refresh token support)

2. BaseCloud App Connection

Configure Xero Connection:

  1. Navigate to Settings Integrations Xero in BaseCloud
  2. Click Connect Xero Account
  3. Authorize with Xero (OAuth flow)
  4. Select organization(s) to connect
  5. Note app_connection_id for use in tasks

Multi-Tenant:

  • Can connect multiple Xero organizations
  • Each organization has unique xero_tenant_id
  • Specify tenant in each task call

3. Tracking Categories (Optional)

Xero Tracking Setup:

  1. Log into Xero Settings Tracking Categories
  2. Create categories (e.g., "Department", "Project")
  3. Add options (e.g., Department: Sales, Marketing, Support)
  4. Note exact option names for use in invoices

Configuration

Action 1: create_contact

Create new contact in Xero (never updates existing).

Field Required Description Example
xero_action Yes Must be "create_contact" create_contact
app_connection_id Yes BaseCloud Xero connection ID 45
xero_tenant_id Yes Xero organization ID {{client_xero_tenant_id}}
Contact Name Yes Contact/company name {{client_company_name}}
Contact Number No Display number (# in Xero) C{{client_id}}
Account Number No Account/customer number {{client_account_number}}
First Name No Contact first name {{client_first_name}}
Last Name No Contact last name {{client_last_name}}
Email Address No Contact email {{client_email}}
Bank Account Details No Bank account info {{client_bank_details}}

Output Variables:

task_31001_xero_contact_id        // Xero contact UUID
task_31001_xero_contact_name      // Contact name
task_31001_xero_account_number    // Account number
task_31001_xero_first_name        // First name
task_31001_xero_last_name         // Last name
task_31001_xero_email_address     // Email
task_31001_xero_message           // "Contact created successfully"
task_31001_xero_existing          // true if duplicate found, false if new

Duplicate Handling:

  • If contact name exists: Returns existing contact with existing=true
  • If account number exists: Returns existing contact
  • No update performed - use update_contact for changes

Action 2: update_contact

Update existing Xero contact.

Field Required Description Example
xero_action Yes Must be "update_contact" update_contact
app_connection_id Yes BaseCloud connection ID 45
xero_tenant_id Yes Xero organization ID {{xero_tenant}}
Contact ID Yes Xero contact UUID {{xero_contact_id}}
Contact Name No Updated name {{new_company_name}}
Contact Number No Updated display number C-NEW-{{client_id}}
Account Number No Updated account number {{new_account_number}}
First Name No Updated first name {{new_first_name}}
Last Name No Updated last name {{new_last_name}}
Email Address No Updated email {{new_email}}

Output: Same structure as create_contact

Notes:

  • Only provided fields are updated
  • Empty fields are ignored (not cleared)
  • Requires valid Contact ID UUID

Action 3: get_contact

Retrieve single contact by ID, number, or name.

Field Required Description Example
xero_action Yes Must be "get_contact" get_contact
app_connection_id Yes BaseCloud connection ID 45
xero_tenant_id Yes Xero organization ID {{xero_tenant}}
Contact ID Conditional Xero UUID (direct fetch) {{contact_uuid}}
Contact Number Conditional Display number search C123
Account Number Conditional Account number search ACC-001
Contact Name Conditional Exact name match Acme Corporation

At least one search field required

Output:

task_31001_xero_contact = {
  contactID: "uuid",
  name: "Acme Corporation",
  accountNumber: "ACC-001",
  emailAddress: "billing@acme.com",
  firstName: "John",
  lastName: "Doe",
  // ... full Xero contact object
}

Action 4: get_contacts

Retrieve multiple contacts with filtering and pagination.

Field Required Description Example
xero_action Yes Must be "get_contacts" get_contacts
app_connection_id Yes BaseCloud connection ID 45
xero_tenant_id Yes Xero organization ID {{xero_tenant}}
Page No Page number (100 per page) 1
Where Filter No OData where clause Name.Contains("Acme")
Order By No Sort expression Name ASC
Include Archived No "true" or "false" false

Output:

task_31001_xero_contacts = [...]  // Array of contact objects
task_31001_xero_pagination = {
  page: 1,
  pageSize: 100,
  totalCount: 45
}

Where Filter Examples:

  • Name.Contains("Corp") - Name contains "Corp"
  • EmailAddress=="billing@acme.com" - Exact email match
  • AccountNumber.StartsWith("C") - Account starts with C
  • UpdatedDateUTC>=DateTime(2024,1,1) - Updated since date

Action 5: create_invoice

Create invoice with line items and tracking.

Field Required Description Example
xero_action Yes Must be "create_invoice" create_invoice
app_connection_id Yes BaseCloud connection ID 45
xero_tenant_id Yes Xero organization ID {{xero_tenant}}
Contact Name Yes Contact (auto-creates if not found) {{client_company_name}}
Date Yes Invoice date (DD/MM/YYYY HH:mm) 15/01/2024 10:00
Due Date Yes Payment due date 30/01/2024 17:00
Status Yes DRAFT / SUBMITTED / AUTHORISED AUTHORISED
line_items_tax_type Yes Exclusive / Inclusive / NoTax Exclusive
Reference No Invoice reference CRM-{{client_id}}
Invoice Number No Custom number (checks duplicates) INV-{{timestamp}}
Item Code Yes Pipe-delimited item codes CONSULT\|DESIGN
Item Description Yes Pipe-delimited descriptions Consulting\|Design Work
Item Quantity Yes Pipe-delimited quantities 10\|5
Unit Price Yes Pipe-delimited prices 150.00\|200.00
Discount % No Pipe-delimited discounts 0\|10
Account Code No Pipe-delimited GL codes (default "200") 200\|200
Tracking Category 1 Value No Pipe-delimited TC1 values Sales\|Marketing
Tracking Category 2 Value No Pipe-delimited TC2 values Project A\|Project A

Output:

task_31001_xero_invoice_id         // Invoice UUID
task_31001_xero_invoice_number     // Invoice number
task_31001_xero_invoice_status     // DRAFT / AUTHORISED / PAID
task_31001_xero_invoice_total      // Total amount
task_31001_xero_invoice_sub_total  // Subtotal
task_31001_xero_invoice_total_tax  // Tax amount
task_31001_xero_contact_id         // Contact UUID

Line Amount Types:

  • Exclusive: Prices exclude tax (tax added on top)
  • Inclusive: Prices include tax (tax within price)
  • NoTax: No tax applied

Invoice Statuses:

  • DRAFT: Editable draft
  • SUBMITTED: Submitted for approval
  • AUTHORISED: Approved and locked
  • PAID: Fully paid (set automatically by Xero)

Action 6: get_invoice

Retrieve single invoice by ID or number.

Field Required Description Example
xero_action Yes Must be "get_invoice" get_invoice
app_connection_id Yes BaseCloud connection ID 45
xero_tenant_id Yes Xero organization ID {{xero_tenant}}
Invoice ID Conditional Invoice UUID (direct) {{invoice_uuid}}
Invoice Number Conditional Invoice number search INV-001

At least one field required

Output:

task_31001_xero_invoice = {
  invoiceID: "uuid",
  invoiceNumber: "INV-001",
  total: 1150.00,
  amountDue: 1150.00,
  amountPaid: 0,
  status: "AUTHORISED",
  lineItems: [...],
  contact: {...},
  // ... full invoice object
}

Action 7: get_invoices

Retrieve multiple invoices with filtering.

Field Required Description Example
xero_action Yes Must be "get_invoices" get_invoices
app_connection_id Yes BaseCloud connection ID 45
xero_tenant_id Yes Xero organization ID {{xero_tenant}}
Page No Page number 1
Where Filter No OData where clause Status=="AUTHORISED"
Order By No Sort expression Date DESC
Statuses No Comma-separated statuses AUTHORISED,PAID
Contact IDs No Comma-separated contact UUIDs {{uuid1}},{{uuid2}}

Output:

task_31001_xero_invoices = [...]  // Array of invoices
task_31001_xero_pagination = {
  page: 1,
  totalCount: 23
}

Action 8: update_invoice

Update existing invoice.

Field Required Description Example
xero_action Yes Must be "update_invoice" update_invoice
app_connection_id Yes BaseCloud connection ID 45
xero_tenant_id Yes Xero organization ID {{xero_tenant}}
Invoice ID Yes Invoice UUID {{invoice_uuid}}
Reference No Updated reference PAID-{{date}}
Status No Updated status AUTHORISED
Date No Updated date 20/01/2024 10:00
Due Date No Updated due date 05/02/2024 17:00
Line Items No Update all line items (same format as create) See create_invoice

Notes:

  • Only provided fields updated
  • Line items replace all existing (not additive)
  • Status changes have restrictions (DRAFTAUTHORISED, cannot unpay PAID)

Real-World Examples

Example 1: Multi-Organization Invoicing

Scenario: Single CRM managing invoices for multiple business entities in Xero.

Workflow:

  1. Trigger: Manual Button - Create Invoice

  2. Task: Variable - Select Organization

    // Based on client property or dropdown
    xero_tenant_id: {{client_xero_org_id}}
    xero_org_name: {{client_xero_org_name}}
    

  3. Task: Xero - Get/Create Contact

    xero_action: get_contact
    app_connection_id: 45
    xero_tenant_id: {{xero_tenant_id}}
    Contact Name: {{client_company_name}}
    

  4. Task: If Condition - Contact Not Found

    Condition: {{task_31001_xero_contact.contactID}} is empty
    

  5. Task (If True): Xero - Create Contact

    xero_action: create_contact
    xero_tenant_id: {{xero_tenant_id}}
    Contact Name: {{client_company_name}}
    Email Address: {{client_email}}
    Account Number: {{client_id}}
    

  6. Task: Xero - Create Invoice

    xero_action: create_invoice
    app_connection_id: 45
    xero_tenant_id: {{xero_tenant_id}}
    Contact Name: {{client_company_name}}
    Date: {{invoice_date}}
    Due Date: {{due_date}}
    Status: AUTHORISED
    line_items_tax_type: Exclusive
    Reference: CRM-{{client_id}}-{{timestamp}}
    Item Code: CONSULT|DESIGN
    Item Description: {{service_descriptions}}
    Item Quantity: {{service_quantities}}
    Unit Price: {{service_prices}}
    Account Code: 200|200
    Tracking Category 1 Value: {{department}}|{{department}}
    

  7. Task: Edit Client - Store Invoice Details

    xero_invoice_id: {{task_31001_xero_invoice_id}}
    xero_invoice_number: {{task_31001_xero_invoice_number}}
    xero_org: {{xero_org_name}}
    

  8. Task: Workflow Note

    Note: Invoice {{task_31001_xero_invoice_number}} created in {{xero_org_name}}
          Total: ${{task_31001_xero_invoice_total}}
    

Result: Invoices routed to correct Xero organization based on client configuration.


Example 2: Contact Sync Automation

Scenario: Bi-directional contact sync between CRM and Xero.

Workflow Part 1: CRM Xero

  1. Trigger: CRM Trigger - Client Created/Updated

  2. Task: If Condition - Has Xero Contact ID

    Condition: {{client_xero_contact_id}} is not empty
    

  3. Task (If True): Xero - Update Contact

    xero_action: update_contact
    xero_tenant_id: {{default_xero_tenant}}
    Contact ID: {{client_xero_contact_id}}
    Contact Name: {{client_company_name}}
    Email Address: {{client_email}}
    First Name: {{client_first_name}}
    Last Name: {{client_last_name}}
    

  4. Task (If False): Xero - Create Contact

    xero_action: create_contact
    xero_tenant_id: {{default_xero_tenant}}
    Contact Name: {{client_company_name}}
    Account Number: C{{client_id}}
    Email Address: {{client_email}}
    

  5. Task (If False): Edit Client - Store ID

    xero_contact_id: {{task_31001_xero_contact_id}}
    

Workflow Part 2: Xero CRM (Query)

  1. Trigger: Timer - Daily at 02:00

  2. Task: Xero - Get Contacts

    xero_action: get_contacts
    Where Filter: UpdatedDateUTC>=DateTime({{yesterday_date}})
    Order By: UpdatedDateUTC DESC
    

  3. Task: Loop - Process Updated Contacts

  4. Task (In Loop): MySQL - Find CRM Client

    SELECT client_id FROM clients
    WHERE xero_contact_id = '{{loop_item_contactID}}'
    

  5. Task (In Loop): If - Client Found, Update

    Edit Client based on Xero data
    


Example 3: Project Billing with Tracking

Scenario: Generate invoices with departmental and project tracking for financial reporting.

Workflow:

  1. Trigger: Manual Button - Bill Project

  2. Task: MySQL - Get Project Time Entries

    SELECT 
      description,
      SUM(hours) as total_hours,
      hourly_rate,
      department,
      project_name
    FROM time_entries
    WHERE project_id = {{project_id}}
      AND billable = 1
      AND invoiced = 0
    GROUP BY description, hourly_rate, department, project_name
    

  3. Task: Formatter - Build Line Items

    item_descriptions: {{grouped_descriptions}}  (pipe-delimited)
    item_quantities: {{grouped_hours}}
    item_prices: {{grouped_rates}}
    tracking_1: {{grouped_departments}}
    tracking_2: {{grouped_projects}}
    

  4. Task: Xero - Create Invoice

    xero_action: create_invoice
    xero_tenant_id: {{xero_tenant}}
    Contact Name: {{client_company_name}}
    Date: {{today}}
    Due Date: {{due_date}}
    Status: AUTHORISED
    line_items_tax_type: Exclusive
    Reference: Project-{{project_id}}
    Item Description: {{item_descriptions}}
    Item Quantity: {{item_quantities}}
    Unit Price: {{item_prices}}
    Account Code: 200|200|200
    Tracking Category 1 Value: {{tracking_1}}
    Tracking Category 2 Value: {{tracking_2}}
    

  5. Task: MySQL - Mark Entries Invoiced

    UPDATE time_entries
    SET invoiced = 1,
        xero_invoice_id = '{{task_31001_xero_invoice_id}}'
    WHERE project_id = {{project_id}} AND billable = 1
    

  6. Task: Email - Send Invoice Notice

    Subject: Invoice {{task_31001_xero_invoice_number}} - {{project_name}}
    Body: Project invoice created.
          Hours: {{total_hours}}
          Amount: ${{task_31001_xero_invoice_total}}
    

Result: Time tracking automatically converts to invoices with dual tracking (department + project) for detailed financial reporting.


Example 4: Invoice Status Monitoring

Scenario: Check Xero daily for paid invoices and update CRM.

Workflow:

  1. Trigger: Timer - Daily at 09:00

  2. Task: MySQL - Get Unpaid CRM Invoices

    SELECT client_id, xero_invoice_id, xero_invoice_number
    FROM clients
    WHERE xero_invoice_id IS NOT NULL
      AND invoice_status != 'PAID'
    

  3. Task: Loop - Check Each Invoice

  4. Task (In Loop): Xero - Get Invoice

    xero_action: get_invoice
    xero_tenant_id: {{default_xero_tenant}}
    Invoice ID: {{loop_item_xero_invoice_id}}
    

  5. Task (In Loop): If Condition - Status Changed to Paid

    Condition: {{task_31001_xero_invoice.status}} equals "PAID"
    

  6. Task (In Loop, If True): Edit Client - Update Status

    invoice_status: PAID
    paid_date: {{task_31001_xero_invoice.fullyPaidOnDate}}
    amount_paid: {{task_31001_xero_invoice.amountPaid}}
    

  7. Task (In Loop, If True): Workflow Note

    Note: Invoice {{loop_item_xero_invoice_number}} marked PAID in Xero
          Amount: ${{task_31001_xero_invoice.amountPaid}}
    

  8. Task (In Loop, If True): Email - Payment Confirmation

    To: {{client_email}}
    Subject: Payment Received - {{loop_item_xero_invoice_number}}
    Body: Thank you! Payment received for invoice {{loop_item_xero_invoice_number}}.
    

Result: Automated payment reconciliation keeps CRM synchronized with Xero payment status.


Example 5: Bulk Contact Export

Scenario: Export all Xero contacts to CRM for reporting/analysis.

Workflow:

  1. Trigger: Manual Button - Import Xero Contacts

  2. Task: Variable - Initialize

    page: 1
    total_imported: 0
    has_more: true
    

  3. Task: Loop - While Has More Pages

    Condition: {{has_more}} equals true
    Max Iterations: 50  // Safety limit
    

  4. Task (In Loop): Xero - Get Contacts

    xero_action: get_contacts
    xero_tenant_id: {{xero_tenant}}
    Page: {{page}}
    Order By: Name ASC
    

  5. Task (In Loop): Loop - Process Contacts

    Loop through {{task_31001_xero_contacts}}
    

  6. Task (Nested Loop): MySQL - Check if Exists

    SELECT client_id FROM clients
    WHERE xero_contact_id = '{{loop_item_contactID}}'
    

  7. Task (Nested Loop): If - Not Exists, Create

    If {{mysql_result}} is empty:
      New Client:
        company_name: {{loop_item_name}}
        email: {{loop_item_emailAddress}}
        xero_contact_id: {{loop_item_contactID}}
        xero_account_number: {{loop_item_accountNumber}}
    

  8. Task (In Loop): Variable - Update Counters

    page: {{page + 1}}
    total_imported: {{total_imported + contact_count}}
    has_more: {{task_31001_xero_pagination.totalCount > page * 100}}
    

  9. Task: Email - Import Summary

    Subject: Xero Contact Import Complete
    Body: Imported {{total_imported}} contacts from Xero.
          Pages processed: {{page}}
    

Result: Bulk import of all Xero contacts with pagination handling and duplicate prevention.


Troubleshooting

OAuth Token Expired

Cause: Access token invalid or expired

Solution: Automatic - task auto-refreshes tokens. If persistent:

  1. Reconnect Xero in BaseCloud Settings
  2. Check refresh token hasn't expired (90 days)
  3. Verify app scopes unchanged

Contact Duplicate Detection

Cause: create_contact finds existing contact

Solution: This is normal behavior:

  • Check {{task_31001_xero_existing}}
  • If true, use returned contact ID
  • Use update_contact for changes

Tracking Category Not Found

Cause: Tracking value doesn't match Xero exactly

Solutions:

  1. Check exact spelling in Xero Settings
  2. Case-sensitive match required
  3. Use "null" to skip tracking (not empty string)
  4. Pre-fetch tracking categories to validate

Invoice Number Already Exists

Cause: Xero prevents duplicate invoice numbers per contact

Solutions:

  1. Use unique numbering: INV-{{timestamp}}-{{client_id}}
  2. Check existing invoices first with get_invoices
  3. Leave Invoice Number empty for Xero auto-numbering

Line Items Calculation Wrong

Cause: Tax type mismatch with line_items_tax_type

Solutions:

  • Exclusive: Unit prices don't include tax tax added on top
  • Inclusive: Unit prices include tax tax extracted from price
  • Verify which format your prices use

Pagination Not Working

Cause: Not iterating through pages correctly

Solutions:

  1. Check {{task_31001_xero_pagination.totalCount}}
  2. Calculate: hasMore = totalCount > (page * 100)
  3. Increment page number in loop
  4. Add safety max iterations

Multi-Tenant Confusion

Cause: Wrong tenant ID used for operation

Solutions:

  1. Store tenant ID per client: {{client_xero_tenant_id}}
  2. List available tenants: Use xeroService getTenants
  3. Validate tenant access before operations
  4. Use separate app connections per tenant if needed

Best Practices

OAuth Management

  1. Token Storage: BaseCloud handles token storage/refresh automatically
  2. App Scopes: Request only required scopes
  3. Reconnection: Prompt users to reconnect before 90-day refresh expiration
  4. Error Handling: Check for 401/403 errors indicating auth issues

Contact Management

  1. Auto-Create in Invoices: create_invoice auto-creates missing contacts
  2. Unique Account Numbers: Use CRM client IDs: C{{client_id}}
  3. Sync Strategy: Decide on sync direction (CRMXero, XeroCRM, bi-directional)
  4. Duplicate Prevention: Use get_contact before create_contact

Invoice Creation

  1. Status Workflow:
  2. DRAFT: For review/approval workflows
  3. AUTHORISED: For immediate dispatch
  4. Never set to PAID manually (Xero manages)

  5. Tracking Categories:

  6. Always use both levels for detailed reporting
  7. Standardize category values across CRM
  8. Validate values exist before invoice creation

  9. Line Items:

  10. Use meaningful item codes
  11. Consistent account codes (200=Sales, 400=Sales Discounts)
  12. Calculate totals in CRM for validation

Performance

  1. Pagination: Always use for get_contacts and get_invoices (100 per page)
  2. Filters: Use where clauses to reduce data transfer
  3. Caching: Cache tenant IDs and tracking categories
  4. Rate Limits: Xero has rate limits - add delays in bulk operations

Error Handling

  1. Validation Errors: Parse {{task_31001_xero_error}} for details
  2. Retry Logic: Implement retry for transient failures
  3. Logging: Always log Xero operations with IDs/numbers
  4. Alerts: Notify on critical failures (invoice creation, sync)

FAQ

Q: Can I use Xero with multiple organizations simultaneously?

A: Yes, specify different xero_tenant_id per operation. Store tenant IDs in client records or use tenant selector.

Q: How do I get my tenant ID?

A: Use xeroService getTenants() or check app connection details in BaseCloud after OAuth authorization.

Q: Can I update invoice line items after creation?

A: Yes, with update_invoice. Note: Replaces all existing line items (not additive).

Q: What happens if I create invoice with existing invoice number?

A: Xero returns error. Use get_invoice to check first or leave Invoice Number blank for auto-numbering.

Q: How do I handle refunds or credit notes?

A: Create separate credit note in Xero (not currently supported via task). Use Xero UI or request feature addition.

Q: Can I attach files to invoices?

A: Not via task action. Use Xero API directly or Xero UI for attachments.

Q: How long do access tokens last?

A: Access tokens: 30 minutes, Refresh tokens: 90 days. Auto-refresh handled by task.

Q: Can I get invoice PDFs?

A: Not directly. Xero generates PDFs separately. Use Xero API or UI to download.

Q: What's the rate limit?

A: Xero: 60 requests per minute per tenant. Add delays in bulk operations.

Q: How do I query tracking category reports?

A: Use get_invoices with where filter or query Xero Reports API separately.

Q: Can I create quotes instead of invoices?

A: Xero separates quotes from invoices. This task handles invoices only.

Q: How do I handle multiple currencies?

A: Xero supports 160+ currencies per contact. Specify currency when creating invoices.

Q: Can I mark invoices as paid from CRM?

A: No - Xero manages payment status based on payment entry. Use Xero UI or payment API.

Q: What if contact auto-create fails?

A: Task returns error. Create contact manually first with create_contact action.



Technical Details

  • Type ID: 31
  • Function: taskXero() in automationService.js (lines 7803-8400)
  • Service: xeroService.js (1367 lines)
  • Library: xero-node (XeroClient)
  • Authentication: OAuth 2.0 with auto token refresh
  • Output Prefix: task_31001_* or task_31001_xero_*
  • Pagination: 100 results per page
  • Rate Limit: 60 requests/minute/tenant