PHPackages                             thijsdejong/exact-php-client - 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. thijsdejong/exact-php-client

ActiveLibrary[API Development](/categories/api)

thijsdejong/exact-php-client
============================

A PHP Client for the Exact API

3.24.1(6y ago)025MITPHPPHP &gt;=5.5.0

Since Nov 4Pushed 6y ago1 watchersCompare

[ Source](https://github.com/thijssnowcountry/exact-php-client)[ Packagist](https://packagist.org/packages/thijsdejong/exact-php-client)[ Docs](https://github.com/thijssnowcountry/exact-php-client)[ RSS](/packages/thijsdejong-exact-php-client/feed)WikiDiscussions master Synced today

READMEChangelog (1)Dependencies (2)Versions (64)Used By (0)

exact-php-client
================

[](#exact-php-client)

[![Build Status](https://camo.githubusercontent.com/11e3b845edc5abaf6cf49438605a358bce7498d70e7a160c65a9bb5dc8586d04/68747470733a2f2f7472617669732d63692e6f72672f7069637165722f65786163742d7068702d636c69656e742e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/picqer/exact-php-client)

PHP client library to use the Exact Online API.

Note: For Guzzle 6 use v3, for Guzzle 3 use v1.

Direct link to Exact Online docs
--------------------------------

[](#direct-link-to-exact-online-docs)

Composer install
----------------

[](#composer-install)

Installing this Exact client for PHP can be done through Composer.

```
composer require picqer/exact-php-client

```

Usage
-----

[](#usage)

1. Set up app at Exact App Center to retrieve credentials
2. Authorize the integration from your app
3. Parse callback and finish connection set up
4. Use the library to do stuff

Steps 1 - 3 are only required once on set up.

### Set up app at Exact App Center to retrieve credentials

[](#set-up-app-at-exact-app-center-to-retrieve-credentials)

Set up an App at the Exact App Center to retrieve your `Client ID` and `Client Secret`. You will also need to set the correct `Callback URL` for the oAuth dance to work.

### Authorize the integration from your app

[](#authorize-the-integration-from-your-app)

The code below is an example `authorize()` function.

```
$connection = new \Picqer\Financials\Exact\Connection();
$connection->setRedirectUrl('CALLBACK_URL'); // Same as entered online in the App Center
$connection->setExactClientId('CLIENT_ID');
$connection->setExactClientSecret('CLIENT_SECRET');
$connection->redirectForAuthorization();
```

This will redirect the user to Exact to login and authorize your integration with their account.

### Parse callback and finish connection set up

[](#parse-callback-and-finish-connection-set-up)

Exact will redirect back to the `callback url` you provided. The callback will receive a `code` param. This is the authorization code for oAuth. Store this code.

Make a new connection to Exact so the library can exchange codes and fetch the `accesstoken` and `refreshtoken`. The `accesstoken` is a temporary token which allows for communication between your app and Exact. The `refreshtoken` is a token which is used to get a new `accesstoken` which also refreshes the `refreshtoken`. The library will settle all of this for you. The code below could be an general `connect()` function, which returns the api connection.

```
$connection = new \Picqer\Financials\Exact\Connection();
$connection->setRedirectUrl('CALLBACK_URL');
$connection->setExactClientId('CLIENT_ID');
$connection->setExactClientSecret('CLIENT_SECRET');

if (getValue('authorizationcode')) // Retrieves authorizationcode from database
	$connection->setAuthorizationCode(getValue('authorizationcode'));

if (getValue('accesstoken')) // Retrieves accesstoken from database
	$connection->setAccessToken(unserialize(getValue('accesstoken')));

if (getValue('refreshtoken')) // Retrieves refreshtoken from database
	$connection->setRefreshToken(getValue('refreshtoken'));

if (getValue('expires_in'))  // Retrieves expires timestamp from database
	$connection->setTokenExpires(getValue('expires_in'));

// Make the client connect and exchange tokens
try {
	$connection->connect();
} catch (\Exception $e)
{
	throw new Exception('Could not connect to Exact: ' . $e->getMessage());
}

// Save the new tokens for next connections
setValue('accesstoken', serialize($connection->getAccessToken()));
setValue('refreshtoken', $connection->getRefreshToken());

// Optionally, save the expiry-timestamp. This prevents exchanging valid tokens (ie. saves you some requests)
setValue('expires_in', $connection->getTokenExpires());

// Optionally, set the lock and unlock callbacks to prevent multiple request for acquiring a new refresh token with the same refresh token.
$connection->setAcquireAccessTokenLockCallback('CALLBACK_FUNCTION');
$connection->setAcquireAccessTokenUnlockCallback('CALLBACK_FUNCTION');
```

### About divisions (administrations)

[](#about-divisions-administrations)

By default the library will use the default administration of the user. This means that when the user switches administrations in Exact Online. The library will also start working with this administration.

### Rate limits

[](#rate-limits)

Exact uses a minutely and daily rate limit. There are a maximum number of calls per day you can do per company, and to prevent bursting they have also implemented a limit per minute. This PR stores this information in the `Connection` and adds methods to read the rate limits so you can handle these as appropriate for your app. Exact documentation on rate limits is found here:

If you hit a rate limit, an `ApiException` will be thrown with code 429. At that point you can determine whether you've hit the minutely or the daily limit. If you've hit the minutely limit, try again after 60 seconds. If you've hit the daily limit, try again after the daily reset.

You can use the following methods on the `Connection`, which will return the limits after your first API call (based on the headers from Exact).

```
$connection->getDailyLimit(); // Retrieve your daily limit
$connection->getDailyLimitRemaining(); // Retrieve the remaining amount of API calls for this day
$connection->getDailyLimitReset(); // Retrieve the timestamp for when the limit will reset
$connection->getMinutelyLimit(); // Retrieve your limit per minute
$connection->getMinutelyLimitRemaining(); // Retrieve the amount of API calls remaining for this minute
```

### Use the library to do stuff (examples)

[](#use-the-library-to-do-stuff-examples)

```
// Optionally set administration, otherwise use the current administration of the user
$connection->setDivision(123456);

// Create a new account
$account = new Account($connection);
$account->AddressLine1 = $customer['address'];
$account->AddressLine2 = $customer['address2'];
$account->City = $customer['city'];
$account->Code = $customer['customerid'];
$account->Country = $customer['country'];
$account->IsSales = 'true';
$account->Name = $customer['name'];
$account->Postcode = $customer['zipcode'];
$account->Status = 'C';
$account->save();

// Add a product in Exact
$item = new Item($connection);
$item->Code = $productcode;
$item->CostPriceStandard = $costprice;
$item->Description = $name;
$item->IsSalesItem = true;
$item->SalesVatCode = 'VH';
$item->save();

// Retrieve an item
$item = new Item($connection);
$item->find(ID);

// List items
$item = new Item($connection);
$item->get();

// List items with filter (using a filter always returns a collection)
$item = new Item($connection);
$items = $item->filter("Code eq '$productcode'"); // Uses filters as described in Exact API docs (odata filters)

// Create new invoice with invoice lines
$invoiceLines[] = [
	'Item'      => $itemId,
	'Quantity'  => $orderproduct['amount'],
	'UnitPrice' => $orderproduct['price']
];

$salesInvoice = new SalesInvoice($connection);
$salesInvoice->InvoiceTo = $customer_code;
$salesInvoice->OrderedBy = $customer_code;
$salesInvoice->YourRef = $orderId;
$salesInvoice->SalesInvoiceLines = $invoiceLines;
$salesInvoice->save();

// Print and email the invoice
$printedInvoice = new PrintedSalesInvoice($connection);
$printedInvoice->InvoiceID = $salesInvoice->InvoiceID;
$printedInvoice->SendEmailToCustomer = true;
$printedInvoice->SenderEmailAddress = "from@example.com";
$printedInvoice->DocumentLayout = "401f3020-35cd-49a2-843a-d904df0c09ff";
$printedInvoice->ExtraText = "Some additional text";
$printedInvoice->save();
```

Connect to other Exact country than NL
--------------------------------------

[](#connect-to-other-exact-country-than-nl)

Choose the right base URL according to [Exact developers guide](https://developers.exactonline.com/#Exact%20Online%20sites.html)

```
$connection = new \Picqer\Financials\Exact\Connection();
$connection->setRedirectUrl('CALLBACK_URL');
$connection->setExactClientId('CLIENT_ID');
$connection->setExactClientSecret('CLIENT_SECRET');
$connection->setBaseUrl('https://start.exactonline.de');
```

Check [src/Picqer/Financials/Exact](src/Picqer/Financials/Exact) for all available entities.

Webhooks
--------

[](#webhooks)

Managaging webhook subscriptions is possible through the [WebhookSubscription](src/Picqer/Financials/Exact/WebhookSubscription.php) entitiy.

For authenticating incoming webhook calls you can use the [Authenticatable](src/Picqer/Financials/Exact/Webhook/Authenticatable.php) trait. Supply the authenticate method with the full JSON request and your Webhook secret supplied by Exact, it will return true or false.

Troubleshooting
---------------

[](#troubleshooting)

> 'Picqer\\Financials\\Exact\\ApiException' with message 'Error 400: Please add a $select or a $top=1 statement to the query string.'

In specific instances, sadly not documented in the API documentation of Exact this is a requirement. Probably to prevent overflooding requests. What you have to do when encountering this error is adding a select or top. The select is used to provide a list of fields you want to extract, the $top=1 limits the results to one item.

Examples:

Return only the EntryID and FinancialYear.

```
$test = new GeneralJournalEntry($connection);
var_dump($test->filter('', '', 'EntryID, FinancialYear'));
```

The $top=1 is added like this:

```
$test = new GeneralJournalEntry($connection);
var_dump($test->filter('', '', '', ['$top'=> 1]));
```

### Authentication error

[](#authentication-error)

> 'Fatal error: Uncaught Exception: Could not connect to Exact: Client error:POST  resulted in a 400 Bad Request response: Bad Request in /var/www/html/oauth\_call\_connect.php:61 Stack trace: #0 {main} thrown in /var/www/html/oauth\_call\_connect.php on line 61`'

This error occurs because the code you get in your redirect URL is only valid for one call. When you call the authentication-process again with a "used" code. You get this error. Make sure you use the provided code by Exact Online only once to get your access token.

Code example
------------

[](#code-example)

See for example: [example/example.php](example/example.php)

TODO
----

[](#todo)

- Current entities do not contain all available properties. Feel free to submit a PR with added or extended entities if you require them. Use the `userscript.js` in greasemonkey or tampermonkey to generate entities consistently and completely.

###  Health Score

31

—

LowBetter than 68% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity6

Limited adoption so far

Community20

Small or concentrated contributor base

Maturity72

Established project with proven stability

 Bus Factor1

Top contributor holds 51.1% 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 ~31 days

Recently: every ~52 days

Total

63

Last Release

2245d ago

Major Versions

v1.0.8 → v2.0.02015-10-27

v1.0.9 → v2.1.32015-11-09

v2.15.0 → v3.0.02016-10-24

PHP version history (2 changes)v1.0.0PHP &gt;=5.4.0

v2.0.0PHP &gt;=5.5.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/f91a2fa11264c811cbe2a198e72f940e51eb2ed2fdce3cb2f3acc703c68dd15c?d=identicon)[thijsdejong](/maintainers/thijsdejong)

---

Top Contributors

[![stephangroen](https://avatars.githubusercontent.com/u/1607643?v=4)](https://github.com/stephangroen "stephangroen (268 commits)")[![markjongkind](https://avatars.githubusercontent.com/u/7794593?v=4)](https://github.com/markjongkind "markjongkind (28 commits)")[![jeff-99](https://avatars.githubusercontent.com/u/3385333?v=4)](https://github.com/jeff-99 "jeff-99 (26 commits)")[![DannyvdSluijs](https://avatars.githubusercontent.com/u/618940?v=4)](https://github.com/DannyvdSluijs "DannyvdSluijs (18 commits)")[![kvij](https://avatars.githubusercontent.com/u/19838921?v=4)](https://github.com/kvij "kvij (18 commits)")[![it-can](https://avatars.githubusercontent.com/u/644288?v=4)](https://github.com/it-can "it-can (13 commits)")[![tomcoonen](https://avatars.githubusercontent.com/u/988013?v=4)](https://github.com/tomcoonen "tomcoonen (12 commits)")[![yohancreemers](https://avatars.githubusercontent.com/u/4000329?v=4)](https://github.com/yohancreemers "yohancreemers (10 commits)")[![remkobrenters](https://avatars.githubusercontent.com/u/4686406?v=4)](https://github.com/remkobrenters "remkobrenters (10 commits)")[![ThijsdejongSNC](https://avatars.githubusercontent.com/u/12978537?v=4)](https://github.com/ThijsdejongSNC "ThijsdejongSNC (10 commits)")[![marcelfw](https://avatars.githubusercontent.com/u/638082?v=4)](https://github.com/marcelfw "marcelfw (9 commits)")[![eMaurits](https://avatars.githubusercontent.com/u/6850780?v=4)](https://github.com/eMaurits "eMaurits (8 commits)")[![monkie](https://avatars.githubusercontent.com/u/633782?v=4)](https://github.com/monkie "monkie (7 commits)")[![WietseWind](https://avatars.githubusercontent.com/u/4756161?v=4)](https://github.com/WietseWind "WietseWind (7 commits)")[![pdroge84](https://avatars.githubusercontent.com/u/18680053?v=4)](https://github.com/pdroge84 "pdroge84 (6 commits)")[![paneidos](https://avatars.githubusercontent.com/u/125766?v=4)](https://github.com/paneidos "paneidos (6 commits)")[![ttaelman](https://avatars.githubusercontent.com/u/68073284?v=4)](https://github.com/ttaelman "ttaelman (6 commits)")[![ameenross](https://avatars.githubusercontent.com/u/859737?v=4)](https://github.com/ameenross "ameenross (5 commits)")[![Spaggel](https://avatars.githubusercontent.com/u/1824950?v=4)](https://github.com/Spaggel "Spaggel (5 commits)")[![davidvandertuijn](https://avatars.githubusercontent.com/u/11010507?v=4)](https://github.com/davidvandertuijn "davidvandertuijn (5 commits)")

---

Tags

phpapiexact

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/thijsdejong-exact-php-client/health.svg)

```
[![Health](https://phpackages.com/badges/thijsdejong-exact-php-client/health.svg)](https://phpackages.com/packages/thijsdejong-exact-php-client)
```

###  Alternatives

[openai-php/laravel

OpenAI PHP for Laravel is a supercharged PHP API client that allows you to interact with the Open AI API

3.7k7.6M74](/packages/openai-php-laravel)[hubspot/api-client

Hubspot API client

23414.2M16](/packages/hubspot-api-client)[picqer/exact-php-client

A PHP Client for the Exact API

1773.0M6](/packages/picqer-exact-php-client)[theodo-group/llphant

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

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

Resend PHP library.

564.7M21](/packages/resend-resend-php)[checkout/checkout-sdk-php

Checkout.com SDK for PHP

553.3M7](/packages/checkout-checkout-sdk-php)

PHPackages © 2026

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