Introduction
SDKs & integrations
Official client libraries for the Puntjes API. They handle the OAuth2 token lifecycle, safe retries with idempotency keys, typed errors, and pagination, so your integration contains business logic, not HTTP plumbing.
Official packages
| Package | For | Requires |
|---|---|---|
puntjes/php-sdk | Any PHP application: WooCommerce/WordPress plugins, POS bridges, cron jobs | PHP 8.1+ |
puntjes/laravel | Laravel applications | PHP 8.2+, Laravel 11–13 |
puntjes/laravel is a thin layer over the core SDK: config, a facade, and a cache-backed token store so your app performs one token grant per hour instead of one per request.
Both packages are on Packagist, follow SemVer, and are stable at v1.0.0. Pin with ^1.0: fixes and new endpoints reach you on their own, and a breaking change waits for 2.0.0.
Registering a customer
The identifiers array accepts email and loyalty_card, and you can leave it out
entirely so Puntjes issues the card itself. That is the shortest correct call and the one
most integrations want.
A loyalty_card value you do send must be a card the vendor has printed and not yet handed
to anyone. See Customer endpoints.
On puntjes/php-sdk below 1.0.0, neither of those works: the card-less call is refused
before a request is made, and the SDK has no loyalty_card identifier type. Upgrade rather
than working around it.
Plain PHP (any framework)
composer require puntjes/php-sdk
The SDK talks PSR-18 and uses whatever HTTP client your project already has. If you have none:
composer require guzzlehttp/guzzle
Create your credentials in the admin portal (see API Client Management). The secret is shown once.
use Puntjes\Puntjes;
use Puntjes\Request\SubmitTransaction;
$puntjes = Puntjes::make(
clientId: getenv('PUNTJES_CLIENT_ID'),
clientSecret: getenv('PUNTJES_CLIENT_SECRET'),
baseUrl: 'https://puntjes.app/api/v1',
);
// A customer scans their card at the till.
$customer = $puntjes->customers->lookup(identifier: $scannedCard);
echo $customer->walletBalance;
// Record the purchase. Amounts are in cents.
$transaction = $puntjes->transactions->submit(new SubmitTransaction(
identifier: $scannedCard,
totalAmount: 4200,
idempotencyKey: 'order-'.$orderId,
));
echo $transaction->pointsEarned;
Authentication is handled for you: a client_credentials token is fetched on the first call, cached until it expires, and silently re-fetched once if the API rejects it.
Laravel
composer require puntjes/laravel
PUNTJES_CLIENT_ID=your-client-id
PUNTJES_CLIENT_SECRET=your-client-secret
PUNTJES_BASE_URL=https://puntjes.app/api/v1
That is the whole setup. The service provider and facade are auto-discovered, and tokens are cached in your application's cache store.
use Puntjes\Laravel\Facades\Puntjes;
use Puntjes\Request\SubmitTransaction;
$customer = Puntjes::customers()->lookup(identifier: $card);
Puntjes::transactions()->submit(new SubmitTransaction(
identifier: $card,
totalAmount: $order->total_cents,
idempotencyKey: 'order-'.$order->id,
));
You can also inject Puntjes\Puntjes from the container instead of using the facade. See the package README for configuration options, custom HTTP clients, and testing guidance.
WordPress / WooCommerce
Build on puntjes/php-sdk directly. It deliberately has no hard dependency on Guzzle or any framework, which keeps it safe to bundle inside a plugin. Two things matter when distributing:
- Scope your dependencies with PHP-Scoper or Strauss, so your vendored libraries cannot collide with other plugins in the shared WordPress process.
- Cache tokens in a transient so every page load does not perform a token grant. The SDK README contains a copy-paste
TransientTokenStoreimplementation and the full guidance.
What the SDK handles for you
- Token lifecycle: fetch, cache, and re-fetch of
client_credentialstokens, with a pluggable token store per platform. - Safe retries: reads and idempotent writes are retried with exponential backoff. Idempotency keys are generated automatically and reused across retries, so a retried
POST /transactionscan never award points twice. Requests without replay protection are never auto-retried. - Typed errors: every error code maps to a typed exception with the machine-readable code, HTTP status, and
request_idattached. Rate limits exposeretryAfter(). - Pagination: paginated endpoints return a lazy iterator that walks pages on demand:
foreach ($puntjes->products->list() as $product) {
// pages are fetched as needed
}
Other languages
Only PHP has an official SDK today. From any other language, integrate against the REST API directly. Every endpoint is plain JSON over OAuth2, and the reference pages in this documentation describe every request and response shape.