PHPackages                             zhartaunik/xero-php - 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. zhartaunik/xero-php

ActiveLibrary[API Development](/categories/api)

zhartaunik/xero-php
===================

A client implementation of the Xero API, with a cleaner OAuth interface and ORM-like abstraction.

v2.5.1.2(3y ago)016MITPHPPHP &gt;=5.5.0

Since Jan 27Pushed 3y agoCompare

[ Source](https://github.com/zhartaunik/xero-php)[ Packagist](https://packagist.org/packages/zhartaunik/xero-php)[ Docs](https://github.com/calcinai/xero-php)[ RSS](/packages/zhartaunik-xero-php/feed)WikiDiscussions master Synced 6d ago

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

XeroPHP
-------

[](#xerophp)

[![Build Status](https://camo.githubusercontent.com/1ffa726382f5b34219e4e753bd725ee37a1b4ae0c190dca7d18446066afd76fc/68747470733a2f2f7472617669732d63692e6f72672f63616c63696e61692f7865726f2d7068702e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/calcinai/xero-php)[![Latest Stable Version](https://camo.githubusercontent.com/ed939fe9f09a4f9fa591f033f2982a21bb732755806c9b72050947c2e0bcf60e/68747470733a2f2f706f7365722e707567782e6f72672f63616c63696e61692f7865726f2d7068702f762f737461626c65)](https://packagist.org/packages/calcinai/xero-php)[![Total Downloads](https://camo.githubusercontent.com/67f29fd22d0f9eccb6586e67feff18c471dcdde32b6755f124e6e38a5468112b/68747470733a2f2f706f7365722e707567782e6f72672f63616c63696e61692f7865726f2d7068702f646f776e6c6f616473)](https://packagist.org/packages/calcinai/xero-php)

A client library for the [Xero API](http://developer.xero.com), wrapping Guzzle and ORM-like models.

This library was developed for the traditional Private, Public and Partner applications, but is now based on OAuth 2 scopes.

Requirements
------------

[](#requirements)

- PHP 5.6+

Setup
-----

[](#setup)

Using composer:

```
composer require calcinai/xero-php
```

Migration from 1.x/OAuth 1a
---------------------------

[](#migration-from-1xoauth-1a)

There is now only one flow for all applications, which is most similar to the legacy *Public* application. All applications now require the OAuth 2 authorisation flow and specific organisations to be authorised at runtime, rather than creating certificates during app creation.

As there is now only one type of application, you now create a generic `XeroPHP\Application` with your access token and tenantId, from this point onward, all your code should remain the same.

Usage
-----

[](#usage)

Before resource requests can be made, the application must be authorised. The authorisation flow will give you an access token and a refresh token. The access token can be used to retrieve a list of tenants (Xero organisations) which the app is authorised to query, then, in conjunction with the desired tenantId, you can instantiate a `XeroPHP\Application` to query the API relating to a specific organisation.

For applications that require long-lived access to organisations, the refresh flow will need to be built in to catch and expired access token and refresh it.

### Authorization Code Flow

[](#authorization-code-flow)

Usage is the same as The League's OAuth client, using `\Calcinai\OAuth2\Client\Provider\Xero` as the provider.

```
session_start();

$provider = new \Calcinai\OAuth2\Client\Provider\Xero([
    'clientId'          => '{xero-client-id}',
    'clientSecret'      => '{xero-client-secret}',
    'redirectUri'       => 'https://example.com/callback-url',
]);

if (!isset($_GET['code'])) {

    // If we don't have an authorization code then get one
    // Additional scopes may be required depending on your application
    // additional common scopes are:
    // Add/edit contacts: accounting.contacts
    // Add/edit attachments accounting.attachments
    // Refresh tokens for non-interactive re-authorisation: offline_access
    // See all Xero Scopes https://developer.xero.com/documentation/guides/oauth2/scopes/
    $authUrl = $provider->getAuthorizationUrl([
        'scope' => 'openid email profile accounting.transactions'
    ]);

    $_SESSION['oauth2state'] = $provider->getState();
    header('Location: ' . $authUrl);
    exit;

// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {

    unset($_SESSION['oauth2state']);
    exit('Invalid state');

} else {

    // Try to get an access token (using the authorization code grant)
    $token = $provider->getAccessToken('authorization_code', [
        'code' => $_GET['code']
    ]);

    //If you added the openid/profile scopes you can access the authorizing user's identity.
    $identity = $provider->getResourceOwner($token);
    print_r($identity);

    //Get the tenants that this user is authorized to access
    $tenants = $provider->getTenants($token);
    print_r($tenants);
}
```

You can then store the token and use it to make requests against the api to the desired tenants

### Scopes

[](#scopes)

OAuth scopes, indicating which parts of the Xero organisation you'd like your app to be able to access. The complete list of scopes can be found [here](https://developer.xero.com/documentation/oauth2/scopes).

```
$authUrl = $provider->getAuthorizationUrl([
   'scope' => 'bankfeeds accounting.transactions'
]);
```

### Refreshing a token

[](#refreshing-a-token)

```
// Requires scope offline_access
$newAccessToken = $provider->getAccessToken('refresh_token', [
    'refresh_token' => $existingAccessToken->getRefreshToken()
]);
```

### Client credentials flow (custom connections)

[](#client-credentials-flow-custom-connections)

You can utilise the client credentials grant type by creating a ["Custom Connection"](https://developer.xero.com/documentation/guides/oauth2/custom-connections/). Once you have your client credentials, usage is the same as The League's OAuth client. You can select your scopes when configuring your Custom Connection.

```
$provider = new \Calcinai\OAuth2\Client\Provider\Xero([
    'clientId'          => '{xero-client-id}',
    'clientSecret'      => '{xero-client-secret}',
]);
$token = $provider->getAccessToken('client_credentials');
$tenants = $provider->getTenants($token);
```

Interacting with the API
------------------------

[](#interacting-with-the-api)

Once you've got a valid access token and tenantId, you can instantiate a `XeroPHP\Application`. All the examples below refer to models in the `XeroPHP\Models\Accounting` namespace. Additionally, there are models for `PayrollAU`, `PayrollUS`, `Files`, and `Assets`.

Refer to the [examples](examples) for more complex usage and nested/related objects.

### Instantiate an Application

[](#instantiate-an-application)

Create a XeroPHP instance (sample config included):

```
$xero = new \XeroPHP\Application($accessToken, $tenantId);
```

### Load a collection

[](#load-a-collection)

```
$contacts = $xero->load(Contact::class)->execute();

foreach ($contacts as $contact) {
    print_r($contact);
}
```

### Load a collection with pagination

[](#load-a-collection-with-pagination)

Load collection of objects, for a single page, and loop through them [(Why?)](https://developer.xero.com/documentation/auth-and-limits/xero-api-limits#Systemlimits)

```
$contacts = $xero->load(Contact::class)->page(1)->execute();

foreach ($contacts as $contact) {
    print_r($contact);
}
```

### Load a collection with WHERE filtering

[](#load-a-collection-with-where-filtering)

Search for objects meeting [certain criteria](https://developer.xero.com/documentation/api/invoices#optimised-parameters)

```
$xero->load(Invoice::class)
    ->where('Status', Invoice::INVOICE_STATUS_AUTHORISED)
    ->where('Type', Invoice::INVOICE_TYPE_ACCREC)
    ->where('Date', 'DateTime(2020,11,25)')
    ->execute();

$xero->load(Invoice::class)
    ->where('Date >= DateTime(2020,11,25)')
    ->where('Date < DateTime(2020,12,25)')
    ->execute();
```

### Load a specific resource

[](#load-a-specific-resource)

Load resources by their GUID

```
$contact = $xero->loadByGUID(Contact::class, $guid);
```

### Create a new resource

[](#create-a-new-resource)

Populate resource parameters with their setters

```
$contact = new Contact($xero);

$contact->setName('Test Contact')
    ->setFirstName('Test')
    ->setLastName('Contact')
    ->setEmailAddress('test@example.com');
```

### Saving resources

[](#saving-resources)

```
// Requires scope accounting.contacts to add/edit contacts
$contact->save();
```

If you have created a number of objects of the same type, you can save them all in a batch by passing an array to `$xero->saveAll()`.

From v1.2.0+, Xero context can be injected directly when creating the objects themselves, which then exposes the `->save()` method. This is necessary for the objects to maintain state with their relations.

### Saving related models

[](#saving-related-models)

If you are saving several models at once, by default additional model attributes are not updated. This means if you are saving an invoice with a new contact, the contacts `ContactID` is not updated. If you want the related models attributes to be updated you can pass a boolean flag with `true` to the save method.

```
$invoice = $xero->loadByGUID(Invoice::class, '[GUID]');
$invoice->setContact($contact);
$xero->save($invoice, true);
```

### Attachments

[](#attachments)

```
$attachments = $invoice->getAttachments();
foreach ($attachment as $attachment) {
    //Do something with them
    file_put_contents($attachment->getFileName(), $attachment->getContent());
}

//You can also upload attachemnts
// Requires scope accounting.attachments
$attachment = Attachment::createFromLocalFile('/path/to/image.jpg');
$invoice->addAttachment($attachment);
```

To set the `IncludeOnline` flag on the attachment, pass `true` as the second parameter for `->addAttachment()`.

### PDFs

[](#pdfs)

Models that support PDF export will inherit a `->getPDF()` method, which returns the raw content of the PDF. Currently this is limited to Invoices and CreditNotes.

### Unit price precision

[](#unit-price-precision)

The [unit price decimal place precision](https://developer.xero.com/documentation/api-guides/rounding-in-xero) (the `unitdp` parameter) is set via a config option:

```
$xero->setConfigOption('xero', 'unitdp', 3);
```

Practice Manager
----------------

[](#practice-manager)

If requiring the "practicemanager" scope please query models using the following syntax

```
$clients = $xero->load(\XeroPHP\Models\PracticeManager\Client::class)
            ->setParameter('detailed', true)
            ->setParameter('modifiedsince', date('Y-m-d\TH:i:s', strtotime('- 1 week')))
            ->execute();

foreach ($clients as $client) {
    $name = $client->getName();
}
```

Webhooks
--------

[](#webhooks)

If you are receiving webhooks from Xero there is `Webhook` class that can help with handling the request and parsing the associated event list.

```
// Configure the webhook signing key on the application
$application->setConfig(['webhook' => ['signing_key' => 'xyz123']]);
$webhook = new Webhook($application, $request->getContent());

/**
 * @return int
 */
$webhook->getFirstEventSequence();

/**
 * @return int
 */
$webhook->getLastEventSequence();

/**
 * @return \XeroPHP\Webhook\Event[]
 */
$webhook->getEvents();
```

See: [Webhooks documentation](https://developer.xero.com/documentation/webhooks/overview)

### Validating Webhooks

[](#validating-webhooks)

To ensure the webhooks are coming from Xero you must validate the incoming request header that Xero provides.

```
if (! $webhook->validate($request->headers->get('x-xero-signature'))) {
    throw new Exception('This request did not come from Xero');
}
```

See: [Signature documentation](https://developer.xero.com/documentation/webhooks/configuring-your-server#intent)

Handling Errors
---------------

[](#handling-errors)

Your request to Xero may cause an error which you will want to handle. You might run into errors such as:

- `HTTP 400 Bad Request` by sending invalid data, like a malformed email address.
- `HTTP 503 Rate Limit Exceeded` by hitting the API to quickly in a short period of time.
- `HTTP 400 Bad Request` by requesting a resource that does not exist.

These are just a couple of examples and you should read the official documentation to find out more about the possible errors.

### Thrown exceptions

[](#thrown-exceptions)

This library will parse the response Xero returns and throw an exception when it hits one of these errors. Below is a table showing the response code and corresponding exception that is thrown:

HTTP CodeException400 Bad Request`\XeroPHP\Remote\Exception\BadRequestException`401 Unauthorized`\XeroPHP\Remote\Exception\UnauthorizedException`403 Forbidden`\XeroPHP\Remote\Exception\ForbiddenException`403 ReportPermissionMissingException`\XeroPHP\Remote\Exception\ReportPermissionMissingException`404 Not Found`\XeroPHP\Remote\Exception\NotFoundException`500 Internal Error`\XeroPHP\Remote\Exception\InternalErrorException`501 Not Implemented`\XeroPHP\Remote\Exception\NotImplementedException`503 Rate Limit Exceeded`\XeroPHP\Remote\Exception\RateLimitExceededException`503 Not Available`\XeroPHP\Remote\Exception\NotAvailableException`503 Organisation offline`\XeroPHP\Remote\Exception\OrganisationOfflineException`See: [Response codes and errors documentation](https://developer.xero.com/documentation/api/http-response-codes)

### Handling exceptions

[](#handling-exceptions)

To catch and handle these exceptions you can wrap the request in a try / catch block and deal with each exception as needed.

```
try {
    $xero->save($invoice);
} catch (NotFoundException $exception) {
    // handle not found error
} catch (RateLimitExceededException $exception) {
    // handle rate limit error
}
```

See: [Working with exceptions](https://secure.php.net/manual/en/language.exceptions.php)

###  Health Score

32

—

LowBetter than 72% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity6

Limited adoption so far

Community19

Small or concentrated contributor base

Maturity73

Established project with proven stability

 Bus Factor1

Top contributor holds 61.6% 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 ~40 days

Total

67

Last Release

1434d ago

Major Versions

v1.9.1 → v2.0.02020-02-06

v1.x-dev → v2.0.22020-03-31

PHP version history (3 changes)v1.2.3PHP &gt;=5.4.0

v1.3.2PHP &gt;=5.5.0

v1.9.0PHP &gt;=5.6.0

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/11556652?v=4)[Alexandr Skrashuk](/maintainers/zhartaunik)[@zhartaunik](https://github.com/zhartaunik)

---

Top Contributors

[![calcinai](https://avatars.githubusercontent.com/u/2415868?v=4)](https://github.com/calcinai "calcinai (506 commits)")[![timacdonald](https://avatars.githubusercontent.com/u/24803032?v=4)](https://github.com/timacdonald "timacdonald (93 commits)")[![Healyhatman](https://avatars.githubusercontent.com/u/13272508?v=4)](https://github.com/Healyhatman "Healyhatman (35 commits)")[![Josh-G](https://avatars.githubusercontent.com/u/487384?v=4)](https://github.com/Josh-G "Josh-G (31 commits)")[![dextermb](https://avatars.githubusercontent.com/u/6137789?v=4)](https://github.com/dextermb "dextermb (25 commits)")[![coop182](https://avatars.githubusercontent.com/u/1443208?v=4)](https://github.com/coop182 "coop182 (16 commits)")[![M1ke](https://avatars.githubusercontent.com/u/1226123?v=4)](https://github.com/M1ke "M1ke (14 commits)")[![bretto36](https://avatars.githubusercontent.com/u/6217994?v=4)](https://github.com/bretto36 "bretto36 (10 commits)")[![Synchro](https://avatars.githubusercontent.com/u/81561?v=4)](https://github.com/Synchro "Synchro (10 commits)")[![mhlavac](https://avatars.githubusercontent.com/u/743421?v=4)](https://github.com/mhlavac "mhlavac (7 commits)")[![davidwindell](https://avatars.githubusercontent.com/u/1720090?v=4)](https://github.com/davidwindell "davidwindell (6 commits)")[![sketchthat](https://avatars.githubusercontent.com/u/5913727?v=4)](https://github.com/sketchthat "sketchthat (5 commits)")[![wbercx](https://avatars.githubusercontent.com/u/2244710?v=4)](https://github.com/wbercx "wbercx (5 commits)")[![lunchboffin](https://avatars.githubusercontent.com/u/3990871?v=4)](https://github.com/lunchboffin "lunchboffin (5 commits)")[![direvus](https://avatars.githubusercontent.com/u/312229?v=4)](https://github.com/direvus "direvus (4 commits)")[![SidneyAllen](https://avatars.githubusercontent.com/u/595967?v=4)](https://github.com/SidneyAllen "SidneyAllen (4 commits)")[![theshelf](https://avatars.githubusercontent.com/u/5120570?v=4)](https://github.com/theshelf "theshelf (4 commits)")[![wobinb](https://avatars.githubusercontent.com/u/15114287?v=4)](https://github.com/wobinb "wobinb (4 commits)")[![iveoles](https://avatars.githubusercontent.com/u/1750634?v=4)](https://github.com/iveoles "iveoles (4 commits)")[![jeremyj11](https://avatars.githubusercontent.com/u/15713461?v=4)](https://github.com/jeremyj11 "jeremyj11 (4 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

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

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

###  Alternatives

[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.4k5.6M651](/packages/sylius-sylius)[saloonphp/saloon

Build beautiful API integrations and SDKs with Saloon

2.4k9.6M468](/packages/saloonphp-saloon)[irazasyed/telegram-bot-sdk

The Unofficial Telegram Bot API PHP SDK

3.3k4.5M84](/packages/irazasyed-telegram-bot-sdk)[googleads/googleads-php-lib

Google Ad Manager SOAP API Client Library for PHP

67410.3M25](/packages/googleads-googleads-php-lib)[hubspot/api-client

Hubspot API client

23414.2M16](/packages/hubspot-api-client)[theodo-group/llphant

LLPhant is a library to help you build Generative AI applications.

1.5k311.5k5](/packages/theodo-group-llphant)

PHPackages © 2026

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