Overview
A client-mode connector for the Xero Accounting API (api.xro/2.0). Use it to connect the shop floor to accounting: raise a sales invoice when a batch ships, keep the item catalogue in step with your MES, and read item stock levels, open invoices or customers into MQTT.
The file holds two connectors. XeroAccountingRestApi talks to https://api.xero.com (accounting calls and tenant discovery), and XeroOAuth gets access tokens from Xero’s identity host.
Every accounting call sends
Accept: application/json (Xero answers XML otherwise) and the xero-tenant-id header. Topics use finance/xero/<object>/<verb>; replies land on <topic>/response.
See the base guide: REST API Connectors.
Auth: Xero uses OAuth 2.0, in one of two ways.
- Web app (authorization code). Do the one-time consent outside the broker, asking for
offline_accessplus the accounting scopes you need, to get a refresh token. Then call GetConnections once to find the organisation’stenantId. After that the broker can stay connected on its own: publish to RefreshToken to get a fresh access token (valid 30 minutes). - Custom connection (client credentials). A custom connection is linked to one organisation and has no refresh token. Publish to ClientCredentialsToken with the scopes to get a 30-minute access token. Custom connections are a paid Xero option, but you can try one for free with the Demo Company.
access_token and tenant_id from the payload. Keep the client id, client secret and refresh token in Hub secrets (suggested names XERO_CLIENT_ID, XERO_CLIENT_SECRET, XERO_BASIC_AUTH, XERO_REFRESH_TOKEN, XERO_TENANT_ID). Never commit them into the .lot.Basic Syntax
MQTT examples
Control fields (access_token, tenant_id, ids, where, page) only fill the URL and headers. Only body is sent as the request body.
RefreshToken (finance/xero/oauth/refresh). basic_auth is the Base64 of client_id:client_secret. Expect 200:
finance/xero/oauth/token), custom connections only. The scope list is space-separated, so URL-encode it:
finance/xero/connections/get) needs only the token. Response:
finance/xero/item/list). The where filter is placed in the URL, so URL-encode it. This asks for tracked inventory items:
Code=="PUMP-SEAL" becomes Code%3D%3D%22PUMP-SEAL%22.
UpdateOrCreateItems (finance/xero/item/upsert). Creates the item, or updates it when the Code already exists. Expect 200 with the item:
QuantityOnHand is read only: stock goes up with approved purchase bills (ACCPAY) and down with approved sales invoices (ACCREC). Use CreateItems (PUT) when a repeated Code should fail instead of update.
CreateInvoices (finance/xero/invoice/create). Bills a customer for two seal kits from batch 4711. Expect 200 with the new invoice (InvoiceID, InvoiceNumber, Total):
Status (or send DRAFT) to create a draft for review, then approve it later with UpdateOrCreateInvoices: {"Invoices":[{"InvoiceID":"<id>","Status":"AUTHORISED"}]}.
EmailInvoice (finance/xero/invoice/email) takes invoice_id. The invoice must be an approved sales invoice and the contact needs an email address. Xero answers 204 with an empty body.
GetInvoices and GetContacts take where and page (start at 1), for example "where": "Status%3D%3D%22AUTHORISED%22", "page": 1.
Keeping the token
A pair of small actions can refresh the token from Hub secrets and keep the latest values when the reply arrives:AuthenticationUnsuccessful. Restrict who can read the finance/xero/# topics with a rule, because these payloads carry tokens and client credentials.
Webhooks (companion, not packaged)
Xero webhooksPOST events (for example invoice or contact created or updated) to an HTTPS URL. Each request carries an x-xero-signature header, an HMAC-SHA256 of the body with your webhook key, and Xero first checks the URL with an “intent to receive” handshake. Receiving them needs a separate ENABLE_SERVER REST API connector on a publicly reachable HTTPS address. That companion connector is not part of this template. Polling GetInvoices with an UpdatedDateUTC filter is the alternative.
Notes
- Limits per organisation: 5 calls at a time, 60 calls per minute and 5,000 calls per day (1,000 per day on Xero’s free Starter developer tier). Across all organisations an app can make 10,000 calls per minute. Above that Xero answers 429 with a
Retry-Afterheader. Each reply carriesX-MinLimit-RemainingandX-DayLimit-Remaining. PUTandPOSTtake up to 50 records per call. By default one invalid record fails the whole request with 400.- Common errors: 401
AuthenticationUnsuccessful(expired or invalid token, or a missing scope), 403 (the token is not connected to thattenant_id), 400ValidationExceptionwith the reason underElements[].ValidationErrors, 404 (unknown id), 429 (rate limit). - Add an
Idempotency-Keyheader to a write event if the broker may retry it; Xero then ignores repeats of the same request. - Not covered: invoice PDF download (binary), attachments, the Payroll, Projects and Assets APIs, disconnecting a tenant (
DELETE /connections/{id}) and token revoke (POST https://identity.xero.com/connect/revocation). - Suggested branches:
feat/rest-api-route-xero,feat/docs-rest-api-xero(Hugo pushes).

