PHPackages                             sudiptpa/xero-php-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. sudiptpa/xero-php-sdk

ActiveLibrary[API Development](/categories/api)

sudiptpa/xero-php-sdk
=====================

A fluent, framework-agnostic Xero PHP SDK for PHP 8.2 to 8.5 with rich models and a clean API.

v2.0.0(2mo ago)02.5k↓67.7%[1 PRs](https://github.com/sudiptpa/xero-php-sdk/pulls)MITPHPPHP &gt;=8.2 &lt;8.6CI passing

Since Mar 31Pushed 2w agoCompare

[ Source](https://github.com/sudiptpa/xero-php-sdk)[ Packagist](https://packagist.org/packages/sudiptpa/xero-php-sdk)[ GitHub Sponsors](https://github.com/sudiptpa)[ RSS](/packages/sudiptpa-xero-php-sdk/feed)WikiDiscussions main Synced 4w ago

READMEChangelog (2)Dependencies (4)Versions (3)Used By (0)

Xero PHP SDK
============

[](#xero-php-sdk)

[![PHP 8.2-8.5](https://camo.githubusercontent.com/932ee006933152ba843c99750af6ea84d24f85376df2538715fb728cebf985c6/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322d2d382e352d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://www.php.net/)[![Tests](https://github.com/sudiptpa/xero-php-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/sudiptpa/xero-php-sdk/actions/workflows/ci.yml)[![Latest Version](https://camo.githubusercontent.com/4150d16dd0bc423f36b6264ae69a55126e11a57e0f57e7a68f887440d286a57a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f73756469707470612f7865726f2d7068702d73646b2e737667)](https://packagist.org/packages/sudiptpa/xero-php-sdk)[![Total Downloads](https://camo.githubusercontent.com/3bf2a3dba09447d94c57a7875a655fc125ab28b88737c564127d4080661dd547/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f73756469707470612f7865726f2d7068702d73646b2e737667)](https://packagist.org/packages/sudiptpa/xero-php-sdk)[![Release](https://camo.githubusercontent.com/c8268017e96ae1d0d8f1132974fbb6036773b8af23a3bb3ac75337585c710041/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73756469707470612f7865726f2d7068702d73646b)](https://github.com/sudiptpa/xero-php-sdk/releases)[![Framework Agnostic](https://camo.githubusercontent.com/35363941fb5f598e05929fc995d1c239bd9cd3e84faa719ba781a087ca2efdc5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6672616d65776f726b2d61676e6f737469632d313131383237)](https://github.com/sudiptpa/xero-php-sdk)[![License: MIT](https://camo.githubusercontent.com/8bb50fd2278f18fc326bf71f6e88ca8f884f72f179d3e555e20ed30157190d0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e2e737667)](LICENSE)

---

[![Sponsor](https://camo.githubusercontent.com/d57b8ff0c3e08877e313deccbfe103a9cbcc5121e4d381986c7f56fb19a08b71/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53706f6e736f722d47697448756225323053706f6e736f72732d6561346161613f6c6f676f3d67697468756273706f6e736f7273266c6f676f436f6c6f723d7768697465)](https://github.com/sponsors/sudiptpa)

If this package has been useful to you, GitHub Sponsors is a simple way to support ongoing maintenance, improvements, and future releases.

A fluent, framework-agnostic Xero SDK for PHP 8.2 to 8.5 with rich models, a fluent API, and no runtime dependencies.

- Rich models for reads and writes
- Fluent request flows across Xero families
- Aligned to the official Xero docs

Why We Built It
---------------

[](#why-we-built-it)

- Build Xero integrations with rich models instead of raw payload arrays.
- Use one consistent API across Accounting, Files, Assets, Projects, Payroll, Finance, App Store, Identity, and Webhooks.
- Keep integration code readable in plain PHP or inside any framework.

This package is built for developers who want a modern Xero PHP SDK with a clear API for accounting, payroll, OAuth2, files, webhooks, and tenant-aware integrations.

Installation
------------

[](#installation)

```
composer require sudiptpa/xero-php-sdk
```

Runtime Notes:

- PHP 8.2 to 8.5
- `ext-json` for JSON request and response handling
- `ext-curl` for the built-in native transport

If `ext-curl` is installed, `Xero::withAccessToken(...)` uses the built-in native transport by default.

If `ext-curl` is not installed, requests throw a transport exception when sent. In that case, supply your own transport such as a Guzzle-based transport.

Custom Transport
----------------

[](#custom-transport)

If you want to use a custom transport, pass it explicitly.

### Guzzle Example

[](#guzzle-example)

```
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\GuzzleException;
use Sujip\Xero\Exceptions\TransportException;
use Sujip\Xero\Http\Request;
use Sujip\Xero\Http\Response;
use Sujip\Xero\Http\Transport;
use Sujip\Xero\Xero;

final class GuzzleTransport implements Transport
{
    public function __construct(
        private readonly GuzzleClient $client = new GuzzleClient()
    ) {
    }

    public function send(Request $request): Response
    {
        try {
            $response = $this->client->request($request->method, $request->url(), [
                'headers' => $request->headers,
                'json' => $request->json,
                'body' => $request->body,
            ]);
        } catch (GuzzleException $exception) {
            throw new TransportException($exception->getMessage(), previous: $exception);
        }

        return new Response(
            $response->getStatusCode(),
            array_map(
                static fn (array $values): string => $values[0] ?? '',
                $response->getHeaders()
            ),
            (string) $response->getBody(),
        );
    }
}

$xero = Xero::withAccessToken('token', new GuzzleTransport())
    ->tenant('tenant-id');
```

Quick Start
-----------

[](#quick-start)

Most Xero integrations follow the same path:

1. build an authorization URL
2. exchange the callback code for a token
3. list available tenant connections
4. choose one tenant
5. make your first API call

This is the shortest useful path:

```
use Sujip\Xero\Auth\InMemoryTokenRepository;
use Sujip\Xero\Xero;

$manager = Xero::oauth2(
    clientId: 'client-id',
    clientSecret: 'client-secret',
    redirectUri: 'https://example.com/xero/callback',
)->manager(new InMemoryTokenRepository());

$url = $manager->authorizationUrl(
    scopes: ['openid', 'offline_access', 'accounting.contacts'],
    state: 'csrf-token',
);
```

When Xero redirects back:

```
$token = $manager->exchange($code);
$tenants = $manager->connections();

$connected = $manager->connectTenant($tenants[0]->tenantId);

$contacts = $connected->tenant()
    ->accounting()
    ->contacts()
    ->page(1)
    ->get();
```

Use `tenant()` for the fluent tenant-scoped path. `getClient()` is also available if you prefer a more explicit accessor.

If you already know the tenant id, `exchangeAndConnect()` is the shorter path:

```
$connected = $manager->exchangeAndConnect($code, 'tenant-id');
```

Usage
-----

[](#usage)

```
use Sujip\Xero\Xero;

$xero = Xero::withAccessToken('token')
    ->tenant('tenant-id');

$contacts = $xero->accounting()
    ->contacts()
    ->where('Name.Contains(:name)', name: 'Acme')
    ->orderBy('Name')
    ->page(1)
    ->get();
```

```
$page = $xero->accounting()
    ->contacts()
    ->paginate(page: 2);
```

```
use Sujip\Xero\Accounting\Invoice\Invoice;
use Sujip\Xero\Accounting\Contact\Contact;
use Sujip\Xero\Accounting\Invoice\LineItem;

$invoice = $xero->accounting()
    ->invoices()
    ->create()
    ->using(
        (new Invoice())
            ->setType('ACCREC')
            ->setStatus('DRAFT')
            ->setContact(
                (new Contact())
                    ->setContactID('contact-id')
            )
            ->setReference('PO-1001')
            ->addLineItem(
                (new LineItem())
                    ->setDescription('Consulting')
                    ->setQuantity(2)
                    ->setUnitAmount(150)
            )
    )
    ->save();
```

```
use Sujip\Xero\Accounting\Account\Account;
use Sujip\Xero\Accounting\Payment\Payment;

$payment = $xero->accounting()
    ->payments()
    ->create()
    ->using(
        (new Payment())
            ->setInvoiceID('invoice-id')
            ->setAccount(
                (new Account())
                    ->setAccountID('account-id')
            )
            ->setDate('2026-03-25')
            ->setAmount(150)
            ->setReference('PAY-1001')
    )
    ->save();
```

```
use Sujip\Xero\Accounting\Contact\Contact;

$updated = $xero->accounting()
    ->contacts()
    ->update('contact-id')
    ->using(
        (new Contact())
            ->setContactID('contact-id')
            ->setName('Acme Holdings Pty Ltd')
    )
    ->save();
```

```
$attachment = $xero->accounting()
    ->invoices()
    ->attachments('invoice-id')
    ->upload('invoice.pdf', $pdfBinary)
    ->mimeType('application/pdf')
    ->includeOnline()
    ->save();
```

```
$file = $xero->files()
    ->upload('contract.pdf', $binary)
    ->mimeType('application/pdf')
    ->toFolder('folder-id')
    ->save();

$fileName = $file->getName();
```

```
$folder = $xero->files()
    ->folders()
    ->inbox();

$isInbox = $folder?->getIsInbox();
```

```
$files = $xero->files()
    ->forObject('invoice-id')
    ->get();
```

```
$assets = $xero->assets()
    ->status('registered')
    ->orderBy('AssetName')
    ->filterBy('MacBook')
    ->get();

$assetName = $assets->first()?->getAssetName();
```

```
$project = $xero->projects()
    ->create()
    ->title('Website rebuild')
    ->contact('contact-id')
    ->estimateAmount(1200)
    ->save();

$projectId = $project->getProjectID();
```

```
$entries = $xero->projects()
    ->timeEntries('project-id')
    ->user('user-id')
    ->task('task-id')
    ->states('INPROGRESS')
    ->get();
```

```
$employees = $xero->payroll()
    ->au()
    ->employees()
    ->page(1)
    ->get();
```

```
$leave = $xero->payroll()
    ->au()
    ->leaveApplications()
    ->create()
    ->employee('employee-id')
    ->leaveType('leave-type-id')
    ->title('Annual Leave')
    ->startDate('2026-04-01')
    ->endDate('2026-04-02')
    ->save();
```

```
$timesheet = $xero->payroll()
    ->nz()
    ->timesheets()
    ->create()
    ->employee('employee-id')
    ->startDate('2026-03-23')
    ->endDate('2026-03-29')
    ->status('DRAFT')
    ->save();
```

```
$balances = $xero->payroll()
    ->uk()
    ->employees()
    ->find('employee-id')
    ?->leaveBalances();
```

```
$balanceSheet = $xero->finance()
    ->statements()
    ->balanceSheet(new DateTimeImmutable('2026-03-31'));
```

```
$subscription = $xero->appStore()
    ->subscriptions()
    ->find('subscription-id');
```

```
$connections = Xero::withAccessToken($token)
    ->identity()
    ->connections()
    ->get();
```

```
$verifier = Xero::webhookVerifier($signingKey);

$verifier->assertValid($rawPayload, $signatureHeader);
$webhook = $verifier->parse($rawPayload);
```

Granular Scopes
---------------

[](#granular-scopes)

- Apps created on or after 2 March 2026 use granular scopes
- Apps created before 2 March 2026 can begin requesting granular scopes from April 2026
- Existing apps have until September 2027 to complete migration from broad scopes
- Ask only for the scopes the integration actually uses.
- Prefer `.read` scopes for read-only jobs.
- Expect `401` insufficient-scope responses if an app is missing a required scope.

Identity And Tenants
--------------------

[](#identity-and-tenants)

Use `identity()->connections()` to discover which tenants a token can access. Use `tenant(...)` when you make tenant-scoped API calls such as Accounting, Files, Projects, Assets, Finance, and Payroll requests.

Auth Flow
---------

[](#auth-flow)

```
use Sujip\Xero\Auth\InMemoryTokenRepository;
use Sujip\Xero\Xero;

$manager = Xero::oauth2(
    clientId: 'client-id',
    clientSecret: 'client-secret',
    redirectUri: 'https://example.com/xero/callback',
)->manager(new InMemoryTokenRepository());

$url = $manager->authorizationUrl(
    scopes: ['openid', 'offline_access', 'accounting.contacts'],
    state: 'csrf-token',
);
```

After callback:

```
$manager->exchange($code);
$connected = $manager->connectTenant('tenant-id');

$xero = $connected->tenant();
```

See [Auth](docs/auth.md) for PKCE, refresh, tenant selection, and custom connection flows.

Supported APIs
--------------

[](#supported-apis)

- Accounting
- Files
- Assets
- Projects
- Payroll AU
- Payroll NZ
- Payroll UK
- Finance
- App Store
- Identity
- Webhooks

Documentation
-------------

[](#documentation)

- [Architecture](docs/architecture.md)
- [Auth](docs/auth.md)
- [Accounting](docs/accounting.md)
- [Accounting Coverage](docs/accounting-coverage.md)
- [Files](docs/files.md)
- [Assets](docs/assets.md)
- [Projects](docs/projects.md)
- [Payroll AU](docs/payroll-au.md)
- [Payroll NZ](docs/payroll-nz.md)
- [Payroll UK](docs/payroll-uk.md)
- [Finance](docs/finance.md)
- [App Store](docs/app-store.md)
- [Webhooks](docs/webhooks.md)

Contributing
------------

[](#contributing)

If you want to help, start with:

- [Contributing](CONTRIBUTING.md)
- [Changelog](CHANGELOG.md)

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance91

Actively maintained with recent releases

Popularity22

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~5 days

Total

2

Last Release

85d ago

Major Versions

v1.0.0 → v2.0.02026-04-06

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/7222620?v=4)[Sujip Thapa](/maintainers/sudiptpa)[@sudiptpa](https://github.com/sudiptpa)

---

Top Contributors

[![sudiptpa](https://avatars.githubusercontent.com/u/7222620?v=4)](https://github.com/sudiptpa "sudiptpa (40 commits)")

---

Tags

phpapisdkoauth2webhooksAccountingxeropayrollxero sdkxero php sdkxero php clientxero api php

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/sudiptpa-xero-php-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/sudiptpa-xero-php-sdk/health.svg)](https://phpackages.com/packages/sudiptpa-xero-php-sdk)
```

###  Alternatives

[jstolpe/instagram-graph-api-php-sdk

Instagram Graph API PHP SDK

138106.8k2](/packages/jstolpe-instagram-graph-api-php-sdk)[clever/clever-php

231.6k](/packages/clever-clever-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
