# PHP SDK

The **AlsendoOne SDK** is the official PHP client for the Apaczka API v2 and
the **recommended way to integrate** — it handles request signing, `expires`
timestamps and error mapping for you, and exposes every API method through
typed request/response objects.

- Source: [github.com/Alsendo/AlsendoOneSDK](https://github.com/Alsendo/AlsendoOneSDK) (MIT)
- Package: [`alsendo/alsendo-one-sdk`](https://packagist.org/packages/alsendo/alsendo-one-sdk) on Packagist
- Requires PHP ≥ 7.4 and `ext-json`

## Installation

```bash
composer require alsendo/alsendo-one-sdk
```

The SDK ships a Guzzle adapter as its default HTTP client — install Guzzle
alongside it (or [bring your own client](#custom-http-client-retries-and-logging)):

```bash
composer require guzzlehttp/guzzle guzzlehttp/psr7
```

## Getting started

Get your `app_id` and `app_secret` in the
[Apaczka panel](https://panel.apaczka.pl) under **Settings → API**, then:

```php
<?php
require __DIR__ . '/vendor/autoload.php';

use AlsendoOne\SDK\AlsendoClient;

$client = new AlsendoClient('your_app_id', 'your_app_secret');

// Fetch the services available on your account
$structure = $client->getServiceStructure();

foreach ($structure->getServices() as $service) {
    echo $service['name'] . ' (ID: ' . $service['service_id'] . ')' . PHP_EOL;
}
```

Authentication is fully automatic — the SDK computes the
[HMAC-SHA256 signature](/legacy/hmac-signing) and the `expires` timestamp
for every request.

Every request also carries a `User-Agent: AlsendoOneSDK/<version> PHP/<version>`
header, so Apaczka can tell which SDK release your integration runs — keep
the header if you [bring your own HTTP client](#custom-http-client-retries-and-logging).

:::tip

Type-hint `AlsendoOne\SDK\AlsendoClientInterface` (not the concrete
`AlsendoClient`) in your application code — it keeps the client mockable in
tests.

:::

## Available methods

| Method | Description |
|---|---|
| `getServiceStructure()` | Available services, options, package and pickup types |
| `getPoints($supplier, $countryCode, $subtype)` | Pickup/drop-off points for a carrier (e.g. InPost lockers) |
| `getValuation($orderData)` | Price quote — prices are returned **in groszy** (1 PLN = 100 gr) |
| `sendOrder($orderData)` | Create a shipment order |
| `getOrder($orderId)` / `getOrders($page, $limit)` | Order detail / paginated list (max 25 per page) |
| `cancelOrder($orderId)` | Cancel an order |
| `getPickupHours($postalCode, $service)` | Available courier pickup windows for a postal code |
| `schedulePickup(...)` / `scheduleBatchPickup(...)` | Schedule courier pickup for one / many orders |
| `getTracking($waybillNumber)` | Tracking events for a waybill |
| `getWaybill($orderId)` | Shipping label as base64-encoded PDF |
| `getTurnIn($orderIds)` | Batch turn-in confirmation PDF |
| `getDispatchCode($orderId)` | Carrier dispatch/return code |
| `request($route, $params)` | Raw signed request to any API endpoint |

Privileged partner applications additionally get `registerCustomer()` and
`checkData()` (VAT id validation, rate-limited to 100 calls/day).

Requests can be built as plain arrays (matching the
[API reference](/reference/v2-legacy) payloads) or with typed builders —
`OrderRequest`, `Address`, `ShipmentRequest`, `PickupRequest` and friends.
See the [order lifecycle guide](/guides/order-lifecycle) for a full example.

## Error handling

All SDK exceptions extend `AlsendoOne\SDK\Exception\AlsendoException`:

| Exception | Meaning |
|---|---|
| `ApiException` | The API returned an error envelope — see [Error semantics](/legacy/error-semantics) |
| `ConnectionException` | The HTTP request itself failed (timeout, DNS, non-200 HTTP status) |
| `WebhookVerificationException` | An incoming [push-tracking webhook](/guides/tracking#push-tracking-webhooks) failed signature verification |

```php
use AlsendoOne\SDK\Exception\ApiException;
use AlsendoOne\SDK\Exception\ConnectionException;

try {
    $order = $client->getOrder(123456);
} catch (ApiException $e) {
    // e.g. "Order not found." — full envelope via $e->getResponseData()
    echo 'API error ' . $e->getCode() . ': ' . $e->getMessage();
} catch (ConnectionException $e) {
    echo 'Connection error: ' . $e->getMessage();
}
```

The API does not distinguish authentication or validation failures by status
code (errors are in practice always `400`) — inspect the exception message
when you need to tell them apart.

## Custom HTTP client, retries and logging

Any HTTP client can replace the bundled Guzzle adapter by implementing
`HttpClientInterface`. Two optional decorators wrap any implementation:

```php
use AlsendoOne\SDK\Http\GuzzleHttpClient;
use AlsendoOne\SDK\Http\LoggingHttpClient;
use AlsendoOne\SDK\Http\RetryingHttpClient;

$httpClient = new LoggingHttpClient(
    new RetryingHttpClient(new GuzzleHttpClient(), maxRetries: 2, delayMs: 500),
    $psrLogger
);

$client = new AlsendoClient('app_id', 'app_secret', $httpClient);
```

`RetryingHttpClient` retries **network failures only**, never API error
envelopes — and beware that a network failure does not guarantee the request
never reached the server; retrying non-idempotent operations such as
`sendOrder()` may create duplicate orders. `LoggingHttpClient` logs URL,
statuses and duration through any PSR-3 logger and never logs credentials or
signed payloads.

## Sandbox

Pass `AlsendoClient::SANDBOX_URL` as the base URL to test against the
sandbox environment — see [Testing your integration](/guides/testing):

```php
$client = new AlsendoClient(
    'app_id',
    'app_secret',
    null,                        // default Guzzle client
    AlsendoClient::SANDBOX_URL
);
```

## Integrating from another language?

The SDK is PHP-only. From any other language, call the API directly — the
whole contract is a signed `POST` envelope described in
[Request signing (HMAC)](/legacy/hmac-signing), with copy-paste cURL
examples in [Implementation examples](/legacy/implementation-examples).
