Xero Kiwi
A Ruby wrapper for the Xero Accounting API. Xero Kiwi handles the unglamorous parts of integrating with Xero — OAuth2, token refresh, rate limiting, retries — so the rest of your code can focus on the actual business problem.
What's in the box
- Full OAuth2 authorization-code flow with PKCE support, CSRF state helpers, and OIDC ID token verification against Xero's JWKS.
- Automatic token refresh with proactive (before expiry) and reactive (on-401) handling, a callback hook for persisting rotated tokens, and a mutex to dedupe concurrent refreshes from multiple threads.
- Rate-limit-aware retries that honour Xero's
Retry-Afterheader on 429s and back off on transient 5xxs, built onfaraday-retry. - A discoverable client surface with explicit error classes for every failure mode (authentication, rate limit, code exchange, ID token verification, CSRF mismatch).
- Connection management: list and disconnect tenants, with token revocation for "disconnect Xero from my app" flows.
- Accounting resources: fetch contacts, organisations, users, branding themes, and nested objects like addresses, phones, external links, and payment terms — all wrapped in proper value objects.
Installation
Add this line to your application's Gemfile:
gem "xero-kiwi"
Then run bundle install.
Xero Kiwi requires Ruby 3.4.1 or newer.
Quick start
require "xero_kiwi"
# Once you've completed the OAuth flow and have an access token:
client = XeroKiwi::Client.new(access_token: "ya29...")
client.connections.each do |connection|
puts "#{connection.tenant_name} (#{connection.tenant_type})"
end
For the full OAuth flow, refresh handling, and everything else, see the docs below.
Documentation
| Doc | What it covers |
|---|---|
| Getting started | Installation, the mental model, your first end-to-end request |
| Client | XeroKiwi::Client — every constructor option, request lifecycle, configuration |
| Connections | Listing tenants, the XeroKiwi::Connection resource, disconnecting tenants |
| Contacts | Listing and fetching contacts, the XeroKiwi::Accounting::Contact resource, nested ContactPerson |
| Contact Groups | Listing and fetching contact groups, the XeroKiwi::Accounting::ContactGroup resource |
| Organisation | Fetching an organisation, the XeroKiwi::Accounting::Organisation resource, nested objects |
| Users | Listing and fetching users, the XeroKiwi::Accounting::User resource, organisation roles |
| Credit Notes | Listing and fetching credit notes, the XeroKiwi::Accounting::CreditNote resource |
| Invoices | Listing and fetching invoices/bills, the XeroKiwi::Accounting::Invoice resource |
| Payments | Listing and fetching payments, the XeroKiwi::Accounting::Payment resource |
| Overpayments | Listing and fetching overpayments, the XeroKiwi::Accounting::Overpayment resource |
| Prepayments | Listing and fetching prepayments, the XeroKiwi::Accounting::Prepayment resource, LineItem |
| Branding Themes | Listing and fetching branding themes, the XeroKiwi::Accounting::BrandingTheme resource |
| Tokens | The XeroKiwi::Token value object, automatic refresh, revocation, persistence callbacks |
| OAuth | Authorization URL building, code exchange, PKCE, ID token verification, full Rails-style example |
| Errors | The error hierarchy, what to catch and when |
| Retries and rate limits | How Xero Kiwi handles 429s and transient failures, customising the retry policy |
| Throttling | Redis-backed token bucket for proactive rate-limit coordination across multiple workers |
Status
Xero Kiwi is in early development. The API surface for the features documented above is stable, but expect new resource methods to be added over time. Breaking changes will be called out in the changelog.
Development
The gem runs natively via Bundler — nothing is containerised for everyday
development. A docker-compose.yml exists for external services the specs
need, currently just Redis (used by the throttle limiter's Lua-backed specs).
bundle install
docker compose up -d redis # optional; only needed for :redis-tagged specs
bundle exec rspec
Without Redis running, specs tagged :redis are filtered out automatically
(see spec/spec_helper.rb) so the suite still passes.
Override the test Redis URL with TEST_REDIS_URL=... if you don't want the
default redis://127.0.0.1:6379/15.
docker compose down # when you're done
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/douglasgreyling/xero-kiwi.
License
The gem is available as open source under the terms of the MIT License.