Skip to main content

Error Handling Guide

Learn how to handle errors gracefully when integrating with the Fuuffy API.

Error Response Format

All API errors follow a consistent format:

{
"error": {
"code": "validation_error",
"message": "Invalid request parameters",
"details": [
{
"field": "sender.phone",
"message": "Phone number is required"
}
]
}
}

HTTP Status Codes

Status CodeDescriptionCommon Causes
400 Bad RequestInvalid requestMissing required fields, invalid data format
401 UnauthorizedAuthentication failedInvalid or expired access token
403 ForbiddenInsufficient permissionsAccessing resources you don't have access to
404 Not FoundResource not foundInvalid order ID or tracking number
409 ConflictResource conflictDuplicate order, conflicting operation
422 Unprocessable EntityValidation failedBusiness logic validation errors
429 Too Many RequestsRate limit exceededToo many requests in a short time
500 Internal Server ErrorServer errorUnexpected server-side error
503 Service UnavailableService unavailableMaintenance or temporary outage

Error Codes

Authentication Errors

CodeDescriptionSolution
invalid_tokenAccess token is invalid or expiredRequest a new access token
invalid_clientClient credentials are invalidVerify your client ID and secret
unauthorized_clientClient not authorizedCheck your account permissions

Validation Errors

CodeDescriptionSolution
validation_errorRequest validation failedCheck the details field for specific errors
missing_fieldRequired field is missingInclude all required fields
invalid_formatField format is invalidCheck field format requirements

Resource Errors

CodeDescriptionSolution
order_not_foundOrder not foundVerify the order ID
tracking_not_foundTracking info not foundVerify the tracking number
cannot_cancelCannot cancel orderCheck order status
cannot_updateCannot update orderCheck if order is still pending

Rate Limiting

CodeDescriptionSolution
rate_limit_exceededToo many requestsWait and retry, implement exponential backoff

Error Handling in PHP

Basic Error Handling

<?php
function makeApiRequest($endpoint, $method = 'GET', $data = null, $access_token) {
$ch = curl_init("${API_URL}/v1{$endpoint}");

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $access_token,
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

if ($data) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$result = json_decode($response, true);

// Handle errors
if ($http_code >= 400) {
handleApiError($http_code, $result);
return null;
}

return $result;
}

function handleApiError($http_code, $error_data) {
$error = $error_data['error'] ?? [];
$code = $error['code'] ?? 'unknown_error';
$message = $error['message'] ?? 'An error occurred';

switch ($http_code) {
case 400:
error_log("Bad Request: {$message}");
if (isset($error['details'])) {
foreach ($error['details'] as $detail) {
error_log(" - {$detail['field']}: {$detail['message']}");
}
}
break;

case 401:
error_log("Unauthorized: {$message}");
// Refresh access token
break;

case 404:
error_log("Not Found: {$message}");
break;

case 429:
error_log("Rate Limited: {$message}");
// Implement retry with backoff
break;

case 500:
case 503:
error_log("Server Error: {$message}");
// Retry the request
break;

default:
error_log("API Error ({$http_code}): {$message}");
}
}
?>

Advanced Error Handling with Retry Logic

<?php
class FuuffyApiClient {
private $access_token;
private $max_retries = 3;

public function request($endpoint, $method = 'GET', $data = null) {
$attempt = 0;

while ($attempt < $this->max_retries) {
try {
$result = $this->makeRequest($endpoint, $method, $data);
return $result;
} catch (RateLimitException $e) {
$attempt++;
$wait_time = $this->calculateBackoff($attempt);
error_log("Rate limited. Retrying in {$wait_time}s...");
sleep($wait_time);
} catch (ServerErrorException $e) {
$attempt++;
if ($attempt >= $this->max_retries) {
throw $e;
}
$wait_time = $this->calculateBackoff($attempt);
error_log("Server error. Retrying in {$wait_time}s...");
sleep($wait_time);
} catch (UnauthorizedException $e) {
// Refresh token and retry once
$this->refreshAccessToken();
return $this->makeRequest($endpoint, $method, $data);
}
}

throw new Exception("Max retries exceeded");
}

private function makeRequest($endpoint, $method, $data) {
$ch = curl_init("${API_URL}/v1{$endpoint}");

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $this->access_token,
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

if ($data) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$result = json_decode($response, true);

// Throw appropriate exceptions
if ($http_code === 401) {
throw new UnauthorizedException($result['error']['message']);
} elseif ($http_code === 429) {
throw new RateLimitException($result['error']['message']);
} elseif ($http_code >= 500) {
throw new ServerErrorException($result['error']['message']);
} elseif ($http_code >= 400) {
throw new ApiException($result['error']['message'], $http_code);
}

return $result;
}

private function calculateBackoff($attempt) {
// Exponential backoff: 2^attempt seconds
return min(pow(2, $attempt), 60); // Max 60 seconds
}

private function refreshAccessToken() {
// Implement token refresh logic
}
}

// Custom exceptions
class ApiException extends Exception {}
class UnauthorizedException extends ApiException {}
class RateLimitException extends ApiException {}
class ServerErrorException extends ApiException {}
?>

Best Practices

1. Always Check HTTP Status Codes

<?php
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($http_code !== 200 && $http_code !== 201) {
// Handle error
}
?>

2. Implement Exponential Backoff

<?php
function retryWithBackoff($callable, $max_attempts = 3) {
for ($attempt = 1; $attempt <= $max_attempts; $attempt++) {
try {
return $callable();
} catch (Exception $e) {
if ($attempt === $max_attempts) {
throw $e;
}
$wait = pow(2, $attempt);
sleep($wait);
}
}
}
?>

3. Log All Errors

<?php
error_log(sprintf(
"API Error: %s - %s (HTTP %d)",
$error['code'],
$error['message'],
$http_code
));
?>

4. Handle Token Expiration

<?php
if ($http_code === 401) {
$this->refreshAccessToken();
// Retry the request
}
?>

5. Validate Before Sending

<?php
function validateOrderData($data) {
$required = ['sender', 'recipient', 'package'];

foreach ($required as $field) {
if (!isset($data[$field])) {
throw new ValidationException("Missing required field: {$field}");
}
}
}
?>

Next Steps