Production-ready patterns for error handling, performance optimization, and monitoring.
The API uses standard HTTP status codes. Handle these codes in your integration for a robust experience.
| Status | Meaning | Action |
|---|---|---|
| 200 | Success (sync conversion complete) | Download the result from download_url |
| 202 | Accepted (async processing started) | Poll status_url until status is done |
| 401 | Unauthorized โ invalid or missing token | Check your Authorization header. Generate a new token if needed. |
| 402 | Insufficient credits | Top up credits. Response includes credits_needed and credits_available. |
| 404 | Workflow or execution not found | Check workflow_id via GET /api/v2/user/workflows |
| 422 | Validation error | Check the errors object for field-specific messages |
| 429 | Rate limit exceeded | Wait retry_after seconds, then retry |
| 503 | Conversion service temporarily unavailable | Wait retry_after seconds. The service will recover automatically. |
<?php
$ch = curl_init('https://pdfen.com/api/v2/convert');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer YOUR_API_TOKEN'],
CURLOPT_POSTFIELDS => [
'files[]' => new CURLFile('document.docx'),
'workflow_id' => 1,
'async' => 'false',
],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
switch ($httpCode) {
case 200:
case 202:
// Success โ process the result
break;
case 401:
throw new Exception('Invalid API token');
case 402:
throw new Exception("Insufficient credits: need {$result['credits_needed']}, have {$result['credits_available']}");
case 422:
$errors = implode(', ', array_map('implode', $result['errors'] ?? []));
throw new Exception("Validation failed: $errors");
case 429:
// Rate limited โ wait and retry
sleep($result['retry_after'] ?? 60);
break;
case 503:
// Service temporarily down โ wait and retry
sleep($result['retry_after'] ?? 300);
break;
default:
throw new Exception("API error ($httpCode): " . ($result['error'] ?? 'Unknown error'));
}
For production applications, implement exponential backoff to handle transient errors gracefully. Only retry on 429, 503, and network errors โ never on 401, 402, or 422.
<?php
function apiRequestWithRetry(string $url, array $options, int $maxRetries = 3): array
{
$attempt = 0;
while ($attempt <= $maxRetries) {
$ch = curl_init($url);
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
// Network error
if ($curlError) {
$attempt++;
if ($attempt > $maxRetries) {
throw new Exception("Network error after $maxRetries retries: $curlError");
}
sleep(pow(2, $attempt)); // 2, 4, 8 seconds
continue;
}
$result = json_decode($response, true);
// Retryable status codes
if (in_array($httpCode, [429, 503])) {
$waitSeconds = $result['retry_after'] ?? pow(2, $attempt + 1);
$attempt++;
if ($attempt > $maxRetries) {
throw new Exception("API returned $httpCode after $maxRetries retries");
}
sleep($waitSeconds);
continue;
}
// Non-retryable errors
if ($httpCode >= 400) {
throw new Exception("API error ($httpCode): " . ($result['error'] ?? $result['message'] ?? 'Unknown'));
}
return $result;
}
throw new Exception('Max retries exceeded');
}
Do not retry 4xx errors (except 429)
Retrying 401, 402, or 422 will always fail โ fix the issue first (invalid token, insufficient credits, invalid parameters).
Choose the right processing strategy for your use case.
| Mode | Best for | Behavior |
|---|---|---|
async=false |
Small files, single documents, quick conversions | Request blocks until conversion completes. Returns 200 with download URL. |
async=true |
Large files, batch processing, long conversions | Returns 202 immediately. Poll status_url or use webhooks. |
For async conversions, you can either poll the status endpoint or register a webhook to receive notifications.
async=false avoids polling overhead.GET /api/v2/workflows/{'{id}'}/options once to discover available options, not on every request.Monitor your API usage and credit balance to avoid interruptions.
Rate limited responses (429) include a retry_after field indicating how many seconds to wait before retrying.
// 429 response body
{
"error": "Rate limit exceeded",
"retry_after": 60
}
Check your credit balance regularly to avoid failed conversions. The credits endpoint shows both personal and organization credits.
curl https://pdfen.com/api/v2/user/credits \
-H "Authorization: Bearer YOUR_API_TOKEN"
<?php
function checkCredits(string $token, int $threshold = 10): void
{
$ch = curl_init('https://pdfen.com/api/v2/user/credits');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"],
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
$available = $result['total_available'] ?? 0;
if ($available < $threshold) {
// Trigger your alerting system
error_log("PDFen credit balance low: $available credits remaining");
}
}
Each conversion response includes credits_charged and credits_remaining. Use these to track usage without extra API calls.
// Successful conversion response
{
"execution_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "done",
"credits_charged": 3,
"credits_remaining": 42,
"download_url": "https://pdfen.com/api/v2/executions/550e8400.../download"
}