Errors¶
Every exception raised by the SDK derives from Pennylane\Sdk\Exception\PennylaneException, itself a RuntimeException. Catch that base type to handle any SDK failure in one place, or catch a specific subclass to react to one situation.
Exception hierarchy¶
PennylaneException
├── ApiConnectionException
│ └── ApiTimeoutException
├── ApiStatusException
│ ├── BadRequestException (400)
│ ├── AuthenticationException (401)
│ ├── PermissionDeniedException (403)
│ ├── NotFoundException (404)
│ ├── ConflictException (409)
│ ├── ValidationException (422)
│ ├── RateLimitException (429)
│ └── ServerException (5xx)
├── ConfigurationException
├── InvalidRequestBodyException
├── InvalidResponseException
└── WebhookSignatureException
PennylaneException¶
Base class for every exception thrown by the SDK. Carries no properties of its own beyond the standard \RuntimeException message and previous exception.
ApiConnectionException¶
The request failed at the transport level and no HTTP response was received (DNS failure, connection refused, TLS error, and so on).
ApiTimeoutException¶
final class ApiTimeoutException extends ApiConnectionException. The request timed out before a response was received.
ApiStatusException¶
The API answered with an error status code (an unexpected 3xx, or a 4xx/5xx). Error bodies are not consistent across Pennylane endpoints, so every property is parsed defensively and may be null.
Never log the request that produced this exception verbatim: it carries the Authorization header. Log statusCode, errorCode and getMessage() instead. This exception deliberately does not keep a reference to the PSR-7 request or response.
| Property | Type | Description |
|---|---|---|
statusCode |
int |
HTTP status code of the response. |
errorCode |
?string |
Machine readable code, when the body provides one. |
details |
mixed |
Extra context from the error body; the shape varies per endpoint. |
rawBody |
string |
Response body, truncated for safety. |
body |
?array |
Decoded JSON error body, or null when the body is not a JSON array or object. |
BadRequestException¶
HTTP 400: the request is malformed or contains invalid parameters.
AuthenticationException¶
HTTP 401: the API token is missing, invalid or revoked.
PermissionDeniedException¶
HTTP 403: the token lacks the OAuth scope required by this endpoint. Every resource method documents its required scope in a Scope: line of its docblock.
NotFoundException¶
HTTP 404: the record does not exist, or the feature is not enabled on this company.
ConflictException¶
HTTP 409: the request conflicts with the current state of the record.
ValidationException¶
HTTP 422: the payload was understood but rejected by validation rules. For unbalanced ledger entries, details may carry the debit and credit totals reported by the API.
RateLimitException¶
HTTP 429: the per token rate limit was exceeded. The SDK throttles proactively and retries automatically, so this surfaces only after the retry budget is exhausted.
Adds one property on top of ApiStatusException:
| Property | Type | Description |
|---|---|---|
retryAfter |
?float |
Seconds to wait, parsed from the retry-after header when it is present and numeric. null otherwise. |
ServerException¶
HTTP 5xx: Pennylane failed to process the request. The API has no idempotency mechanism, so the SDK never retries a POST after a 5xx: the first attempt may have been processed, and a blind retry could create a duplicate invoice or ledger entry.
ConfigurationException¶
The SDK is not usable as configured. Raised for a missing API token, an insecure base URL (anything other than https, or http outside localhost), or when no PSR-18 HTTP client can be found or built.
InvalidRequestBodyException¶
A request body or filter value cannot be sent safely. The most common cause is a PHP float in a payload: Pennylane expects monetary amounts and quantities as strings, and floats would lose decimal precision on accounting data.
InvalidResponseException¶
The API answered with a success status but the body is not usable JSON. Typical causes are captive portals and misconfigured proxies returning an HTML page with a 200 status.
WebhookSignatureException¶
A webhook payload failed signature verification. Treat the payload as untrusted and discard it.
Example¶
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use Pennylane\Sdk\Exception\NotFoundException;
use Pennylane\Sdk\Exception\RateLimitException;
use Pennylane\Sdk\Exception\ValidationException;
use Pennylane\Sdk\Pennylane;
$client = new Pennylane();
try {
$invoice = $client->customerInvoices->get(999999999);
} catch (NotFoundException $e) {
echo "Invoice not found (HTTP {$e->statusCode}): {$e->getMessage()}\n";
} catch (ValidationException $e) {
echo "Validation failed: {$e->getMessage()}\n";
var_dump($e->details);
} catch (RateLimitException $e) {
echo 'Rate limited, retry after ' . ($e->retryAfter ?? 'unknown') . " seconds\n";
}