Errors & Exceptions
All API errors are surfaced as typed PHP exceptions. Every exception carries the HTTP status code and raw response body.
Exception Class Hierarchy
\RuntimeException (PHP built-in)
└── Web3ApiException — base for all API errors
├── AuthException — HTTP 401 (unrecoverable auth failure)
└── InsufficientCreditsException — HTTP 402 (not enough credits)
All three live in the Web3Sdk\Laravel\Exceptions namespace.
Web3ApiException
The base exception. Thrown for all non-401, non-402 HTTP errors (400, 403, 404, 409, 422, 500, etc.) and for network transport failures.
Properties
| Property | Type | Description |
|---|---|---|
$message | string | Human-readable error description (from detail field in API response) |
$statusCode | int | HTTP status code returned by the API (readonly) |
$responseBody | array | Full decoded JSON response body (readonly) |
$previous | \Throwable|null | Wrapped Guzzle exception on transport errors |
<?php
use Web3Sdk\Laravel\Exceptions\Web3ApiException;
try {
$result = Web3Api::balance()->getNative(address: '0xINVALID');
} catch (Web3ApiException $e) {
$e->getMessage(); // "Address is not a valid EVM address"
$e->statusCode; // 400
$e->responseBody; // ['detail' => 'Address is not a valid EVM address']
}
AuthException
Thrown when authentication fails (HTTP 401) and cannot be automatically recovered. Specifically raised when:
- A request returns 401 and no refresh token is configured, or
- The token refresh attempt itself returns a non-200 response.
If a refresh token is available, the client automatically calls
POST /auth/refresh on the first 401. Only if that also fails is AuthException thrown. This means your code typically only sees this exception on genuine session expiry or invalid credentials.Properties
| Property | Type | Description |
|---|---|---|
$message | string | Auth failure detail, defaults to "Authentication failed." |
$statusCode | int | Always 401 |
$responseBody | array | API response body |
<?php
use Web3Sdk\Laravel\Exceptions\AuthException;
use Web3Sdk\Laravel\Facades\Web3Api;
try {
$me = Web3Api::auth()->me();
} catch (AuthException $e) {
// Token expired and refresh failed — force re-login
session()->forget(['access_token', 'refresh_token']);
return redirect()->route('login')->with('error', 'Session expired. Please log in again.');
}
InsufficientCreditsException
Thrown when the API returns HTTP 402. The request was not executed — no credits were deducted. The exception parses the detail message to extract the required and available credit counts.
Properties
| Property | Type | Description |
|---|---|---|
$message | string | Full error message, e.g. "Insufficient credits. Need 10, have 5." |
$statusCode | int | Always 402 |
$cost | int | Credits required by the endpoint (readonly, parsed from message) |
$balance | int | Current credit balance of the account (readonly, parsed from message) |
$responseBody | array | Raw API response body |
<?php
use Web3Sdk\Laravel\Exceptions\InsufficientCreditsException;
use Web3Sdk\Laravel\Facades\Web3Api;
try {
$tx = Web3Api::transfer()->native(
fromAddress: '0xSENDER...',
privateKey: '0xKEY...',
toAddress: '0xRECEIVER...',
amount: '1.0',
);
} catch (InsufficientCreditsException $e) {
$e->cost; // 10 — this endpoint costs 10 credits
$e->balance; // 5 — current account balance
$e->getMessage(); // "Insufficient credits. Need 10, have 5."
// Redirect user to top-up page
return redirect()->route('credits.packages')
->with('warning', "You need {$e->cost} credits but only have {$e->balance}.");
}
Full try/catch Pattern
The recommended pattern catches all exception types in order from most specific to most general:
<?php
use Web3Sdk\Laravel\Facades\Web3Api;
use Web3Sdk\Laravel\Exceptions\AuthException;
use Web3Sdk\Laravel\Exceptions\InsufficientCreditsException;
use Web3Sdk\Laravel\Exceptions\Web3ApiException;
try {
$result = Web3Api::transfer()->token(
tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
fromAddress: '0xSENDER...',
privateKey: '0xKEY...',
toAddress: '0xRECEIVER...',
amount: '100.0',
decimals: 6,
network: 'ethereum',
);
// Success — $result['tx_hash'] available
logger()->info('Transfer submitted', ['tx_hash' => $result['tx_hash']]);
} catch (InsufficientCreditsException $e) {
// HTTP 402 — no credits deducted; prompt user to top up
logger()->warning('Not enough credits', ['cost' => $e->cost, 'balance' => $e->balance]);
return response()->json([
'error' => 'insufficient_credits',
'needed' => $e->cost,
'balance' => $e->balance,
], 402);
} catch (AuthException $e) {
// HTTP 401 — session expired and auto-refresh failed
logger()->error('Auth failure', ['status' => $e->statusCode]);
return response()->json(['error' => 'unauthenticated'], 401);
} catch (Web3ApiException $e) {
// Any other API error (400, 403, 404, 422, 500, transport error)
logger()->error('Web3 API error', [
'status' => $e->statusCode,
'message' => $e->getMessage(),
'body' => $e->responseBody,
]);
return response()->json([
'error' => 'api_error',
'message' => $e->getMessage(),
'status' => $e->statusCode,
], 500);
}
HTTP Status Code Reference
| Status | Exception Thrown | Meaning |
|---|---|---|
200–299 | none — success | Request succeeded; response returned as array |
400 | Web3ApiException | Validation error or malformed request |
401 | AuthException | Missing or invalid auth token (after auto-refresh attempt) |
402 | InsufficientCreditsException | Account has insufficient credits for this endpoint |
403 | Web3ApiException | Forbidden — wrong owner, inactive API key, or insufficient permissions |
404 | Web3ApiException | Resource not found (wallet, transaction, webhook, etc.) |
409 | Web3ApiException | Conflict — resource already exists (duplicate email, duplicate key name) |
422 | Web3ApiException | Pydantic schema validation failed — check required fields |
429 | Web3ApiException | Rate limit exceeded — wait and retry |
500 | Web3ApiException | Internal server error |
| network error | Web3ApiException | Guzzle transport exception wrapped as $previous |
Credits are only deducted on success (HTTP 2xx). If the API returns any error status, no credits are consumed — including 402.