Skip to content
Scalekit Docs
Talk to an Engineer Dashboard

Connect AI agents to Xero

Scalekit connector
Open markdown

The Xero connector lets your AI agent act in each user's Xero account. Each user signs in to Xero once, and Scalekit stores and refreshes their tokens, so your agent never handles credentials. It comes with 94 tools.

Tools
94
What they doRead · write · destructive
55 · 34 · 555 read34 write5 destructive
Users sign in with
OAuth app
Scalekit's or your own

What you can do

  • Manage invoices and bills: create, update, void, and email invoices and bills, and get their online links
  • Record payments: apply payments and batch payments to invoices, bills, and credit notes
  • Manage contacts: create and update customers and suppliers and organize them into contact groups
  • Run financial reports: pull profit and loss, balance sheet, trial balance, aged receivables, and aged payables
  • Track bank transactions: create, update, and list spend and receive money transactions, and view bank transfers
  • Manage quotes and purchase orders: create, update, and list quotes, purchase orders, and credit notes

Setup

  1. Install the SDK

    Terminal window
    npm install @scalekit-sdk/node dotenv
  2. Set your credentials

    Add your Scalekit credentials to your .env file. Find values in app.scalekit.com > Developers > API Credentials.

    .env
    SCALEKIT_ENVIRONMENT_URL=<your-environment-url>
    SCALEKIT_CLIENT_ID=<your-client-id>
    SCALEKIT_CLIENT_SECRET=<your-client-secret>
  3. Create the Xero connection

    In AgentKit > Connections, create a Xero connection. The name you give it is the connection_name your code passes. See Configure connections.

    Scalekit credentials are available for Xero, so you don't need to register an OAuth app. To show your own app on the consent screen, use your own credentials instead.

    Use your own OAuth app

    Register your Scalekit environment with the Xero connector so Scalekit handles the OAuth 2.0 flow and token lifecycle for you. The connection name you create is used to identify and invoke the connection in your code.

    1. Set up auth redirects

      • In Scalekit dashboard, go to AgentKit > Connections > Create Connection. Find Xero and click Create. Copy the redirect URI — it looks like https://<SCALEKIT_ENVIRONMENT_URL>/sso/v1/oauth/<CONNECTION_ID>/callback.

      • Log in to developer.xero.com, open your app (or create one under My Apps → New app), and go to Configuration.

      • Paste the redirect URI into the Redirect URIs field and click Save.

    2. Get client credentials

      • In your Xero app, open the Configuration tab.

      • Copy your Client ID and generate a Client Secret.

    3. Add credentials in Scalekit

      • In Scalekit dashboard, go to AgentKit > Connections and open the connection you created.

      • Enter your Xero Client ID and Client Secret, then click Save.

    4. Connect a user account

      Your users must authorize access to their Xero organisation. Generate an authorization link and direct them through the OAuth flow.

      Via dashboard (for testing)

      • Open the connection and click the Connected Accounts tab → Add Account.

      • Fill in Your User’s ID (e.g., user_123) and follow the Xero OAuth prompt.

      Via API (for production)

      const { link } = await scalekit.actions.getAuthorizationLink({
      connectionName: 'xero',
      identifier: 'user_123',
      });
      // Redirect your user to `link` — they complete OAuth on Xero's side
      console.log('Authorize Xero:', link);
  4. Authorize a user and make your first call

    quickstart.mts
    import { ScalekitClient } from '@scalekit-sdk/node'
    import 'dotenv/config'
    import { createInterface } from 'node:readline/promises'
    const scalekit = new ScalekitClient(
    process.env.SCALEKIT_ENVIRONMENT_URL,
    process.env.SCALEKIT_CLIENT_ID,
    process.env.SCALEKIT_CLIENT_SECRET,
    )
    const actions = scalekit.actions
    const connector = 'xero'
    const identifier = 'user_123'
    // Generate an authorization link for the user
    const { link } = await actions.getAuthorizationLink({ connectionName: connector, identifier })
    console.log('Authorize Xero:', link)
    const rl = createInterface({ input: process.stdin, output: process.stdout })
    await rl.question('Press Enter after authorizing...')
    rl.close()
    // Make your first call
    const result = await actions.executeTool({
    connector,
    identifier,
    toolName: 'xero_accounts_list',
    toolInput: {},
    })
    console.log(result)
    Terminal window
    npx tsx quickstart.mts

    Each user signs in once. See Authorize a user for the full flow and statuses.

Void (delete) an invoice

xero_invoice_delete voids an invoice by setting its status to VOIDED. Xero only allows voiding invoices that are in AUTHORISED status — calling it on a DRAFT invoice returns a validation error.

The correct sequence is:

  1. Authorise the invoice with xero_invoice_update, passing Status: "AUTHORISED" and a DueDate.
  2. Call xero_invoice_delete with the same invoice_id.
Node.js example
// Step 1 — authorise the invoice
await actions.executeTool({
connector: connectionName,
identifier,
toolName: 'xero_invoice_update',
toolInput: {
invoice_id: invoiceId,
Status: 'AUTHORISED',
DueDate: '2026-06-30',
},
});
// Step 2 — void it
await actions.executeTool({
connector: connectionName,
identifier,
toolName: 'xero_invoice_delete',
toolInput: { invoice_id: invoiceId },
});
Python example
# Step 1 — authorise the invoice
actions.execute_tool(
connection_name=connection_name,
identifier=identifier,
tool_name="xero_invoice_update",
tool_input={
"invoice_id": invoice_id,
"Status": "AUTHORISED",
"DueDate": "2026-06-30",
},
)
# Step 2 — void it
actions.execute_tool(
connection_name=connection_name,
identifier=identifier,
tool_name="xero_invoice_delete",
tool_input={"invoice_id": invoice_id},
)

Pass Contact and LineItems correctly

Several tools (xero_invoice_create, xero_credit_note_create, xero_purchase_order_create, xero_quote_create) take a Contact field and a LineItems field.

  • Contact — pass as a JSON string: '{"ContactID": "abc123..."}'
  • LineItems — pass as a JSON array (not a string): [{"Description": "...", "Quantity": 1, "UnitAmount": 100, "AccountCode": "200"}]

Include AccountCode in each line item whenever the invoice may later be authorised or voided.

Quotes require a Date

xero_quote_create and xero_quote_update both require a Date field (ISO 8601, e.g. "2026-04-29"). Xero returns a validation error "Date cannot be empty" without it.

xero_quote_update also requires Contact (JSON string) in addition to Date.

Aged reports require a contactID

xero_report_aged_payables and xero_report_aged_receivables require a contactID parameter. The other five report tools (xero_report_balance_sheet, xero_report_profit_and_loss, xero_report_trial_balance, xero_report_bank_summary, xero_report_executive_summary) require no inputs beyond the auto-injected tenant ID.

Update an item

xero_item_update requires Code in the request body (in addition to item_id in the path). Pass the item’s existing code or a new one — Xero uses it to identify the item being updated.

Tools

Pass the exact name to execute_tool
Try in PlaygroundRequest a tool
  • xero_account_getRetrieve a single account by its AccountID.Read-only

    Get Account

    Retrieve a single account by its AccountID.

    Inputs

    account_idstringrequired
    Xero account GUID.
  • xero_accounts_listRetrieve the full chart of accounts for a Xero organisation.Read-only

    List Accounts

    Retrieve the full chart of accounts for a Xero organisation.

    Inputs

    modified_afterstring
    Return records modified after this UTC datetime (ISO 8601).
    orderstring
    Order results (e.g. Name ASC).
    wherestring
    Filter expression (e.g. Type=="BANK").
  • xero_bank_transaction_getRetrieve a single spend or receive money bank transaction by its BankTransactionID.Read-only

    Get Bank Transaction

    Retrieve a single spend or receive money bank transaction by its BankTransactionID.

    Inputs

    bank_transaction_idstringrequired
    Xero bank transaction GUID.
  • xero_bank_transaction_history_getGet the change history and notes trail for a Xero bank transaction.Read-only

    Get Bank Transaction History

    Get the change history and notes trail for a Xero bank transaction.

    Inputs

    bank_transaction_idstringrequired
    Xero bank transaction GUID.
  • xero_bank_transactions_listRetrieve spend or receive money bank transactions from Xero.Read-only

    List Bank Transactions

    Retrieve spend or receive money bank transactions from Xero.

    Inputs

    modified_afterstring
    Modified after UTC datetime.
    orderstring
    Sort order.
    pagenumber
    Page number.
    wherestring
    Filter expression.
  • xero_bank_transfers_listRetrieve bank transfers between accounts in Xero.Read-only

    List Bank Transfers

    Retrieve bank transfers between accounts in Xero.

    Inputs

    modified_afterstring
    Modified after UTC datetime.
    orderstring
    Sort order.
    wherestring
    Filter expression.
  • xero_batch_payment_getRetrieve a specific batch payment using a unique batch payment ID.Read-only

    Get Batch Payment

    Retrieve a specific batch payment using a unique batch payment ID.

    Inputs

    batch_payment_idstringrequired
    Xero batch payment GUID.
  • xero_batch_payments_listRetrieve batch payments from a Xero organisation.Read-only

    List Batch Payments

    Retrieve batch payments from a Xero organisation.

    Inputs

    modified_afterstring
    Modified after UTC datetime.
    orderstring
    Sort order.
    wherestring
    Filter expression.

Workflows

Proxy API call

For raw proxy requests, you must supply the Xero-Tenant-Id header yourself. Trigger any tool call first (e.g. xero_accounts_list) so Scalekit caches the tenant ID, then retrieve it from the connected account’s api_config.path_variables.

const { connectedAccount } = await actions.getConnectedAccount({
connectionName: 'xero',
identifier: 'user_123',
});
const xeroTenantId = connectedAccount.apiConfig?.pathVariables?.xero_tenant_id;
const result = await actions.request({
connectionName: 'xero',
identifier: 'user_123',
path: '/Invoices',
method: 'GET',
headers: { 'Xero-Tenant-Id': xeroTenantId },
});
console.log(result);
Create and authorise an invoice

The Contact field must be a JSON string and LineItems must be a JSON array. Include AccountCode in each line item — Xero requires it when authorising or voiding the invoice.

const contacts = await actions.executeTool({
connector: 'xero',
identifier: 'user_123',
toolName: 'xero_contacts_list',
toolInput: {},
});
const contactId = contacts.Contacts[0].ContactID;
const invoice = await actions.executeTool({
connector: 'xero',
identifier: 'user_123',
toolName: 'xero_invoice_create',
toolInput: {
Type: 'ACCREC',
Contact: JSON.stringify({ ContactID: contactId }),
LineItems: [
{ Description: 'Consulting services', Quantity: 1, UnitAmount: 500, AccountCode: '200' },
],
},
});
const invoiceId = invoice.Invoices[0].InvoiceID;
await actions.executeTool({
connector: 'xero',
identifier: 'user_123',
toolName: 'xero_invoice_update',
toolInput: {
invoice_id: invoiceId,
Status: 'AUTHORISED',
DueDate: '2026-06-30',
},
});
Void an invoice

xero_invoice_delete voids an invoice by setting its status to VOIDED. Xero only permits voiding AUTHORISED invoices — calling it on a DRAFT invoice returns a validation error. Authorise the invoice first (see above), then call delete.

await actions.executeTool({
connector: 'xero',
identifier: 'user_123',
toolName: 'xero_invoice_delete',
toolInput: { invoice_id: invoiceId },
});
Create a quote

xero_quote_create requires Contact (JSON string), LineItems (array), and Date (ISO 8601). Without Date, Xero returns "Date cannot be empty".

const quote = await actions.executeTool({
connector: 'xero',
identifier: 'user_123',
toolName: 'xero_quote_create',
toolInput: {
Contact: JSON.stringify({ ContactID: contactId }),
LineItems: [{ Description: 'Project estimate', Quantity: 1, UnitAmount: 2000 }],
Date: '2026-04-29',
},
});
const quoteId = quote.Quotes[0].QuoteID;
Run aged payables or receivables report

The aged report tools require a contactID parameter. The other reports (Balance Sheet, Profit & Loss, Trial Balance, Bank Summary, Executive Summary) need no inputs beyond the auto-injected tenant ID.

const report = await actions.executeTool({
connector: 'xero',
identifier: 'user_123',
toolName: 'xero_report_aged_receivables',
toolInput: { contactID: contactId },
});