API Errors
Core PHP Framework uses conventional HTTP response codes and provides detailed error information to help you debug issues.
HTTP Status Codes
2xx Success
| Code | Status | Description |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created successfully |
| 202 | Accepted | Request accepted for processing |
| 204 | No Content | Request succeeded, no content to return |
4xx Client Errors
| Code | Status | Description |
|---|---|---|
| 400 | Bad Request | Invalid request format or parameters |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn't exist |
| 405 | Method Not Allowed | HTTP method not supported for endpoint |
| 409 | Conflict | Request conflicts with current state |
| 422 | Unprocessable Entity | Validation failed |
| 429 | Too Many Requests | Rate limit exceeded |
5xx Server Errors
| Code | Status | Description |
|---|---|---|
| 500 | Internal Server Error | Unexpected server error |
| 502 | Bad Gateway | Invalid response from upstream server |
| 503 | Service Unavailable | Server temporarily unavailable |
| 504 | Gateway Timeout | Upstream server timeout |
Error Response Format
All errors return JSON with consistent structure:
{
"message": "Human-readable error message",
"error_code": "MACHINE_READABLE_CODE",
"errors": {
"field": ["Detailed validation errors"]
},
"meta": {
"timestamp": "2026-01-26T12:00:00Z",
"request_id": "req_abc123"
}
}Common Errors
400 Bad Request
Missing Required Parameter:
{
"message": "Missing required parameter: title",
"error_code": "MISSING_PARAMETER",
"errors": {
"title": ["The title field is required."]
}
}Invalid Parameter Type:
{
"message": "Invalid parameter type",
"error_code": "INVALID_TYPE",
"errors": {
"published_at": ["The published at must be a valid date."]
}
}401 Unauthorized
Missing Authentication:
{
"message": "Unauthenticated.",
"error_code": "UNAUTHENTICATED"
}Invalid API Key:
{
"message": "Invalid API key",
"error_code": "INVALID_API_KEY"
}Expired Token:
{
"message": "Token has expired",
"error_code": "TOKEN_EXPIRED",
"meta": {
"expired_at": "2026-01-20T12:00:00Z"
}
}403 Forbidden
Insufficient Permissions:
{
"message": "This action is unauthorized.",
"error_code": "INSUFFICIENT_PERMISSIONS",
"required_scope": "posts:write",
"provided_scopes": ["posts:read"]
}Workspace Suspended:
{
"message": "Workspace is suspended",
"error_code": "WORKSPACE_SUSPENDED",
"meta": {
"suspended_at": "2026-01-25T12:00:00Z",
"reason": "Payment overdue"
}
}Namespace Access Denied:
{
"message": "You do not have access to this namespace",
"error_code": "NAMESPACE_ACCESS_DENIED"
}404 Not Found
Resource Not Found:
{
"message": "Post not found",
"error_code": "RESOURCE_NOT_FOUND",
"resource_type": "Post",
"resource_id": 999
}Endpoint Not Found:
{
"message": "Endpoint not found",
"error_code": "ENDPOINT_NOT_FOUND",
"requested_path": "/v1/nonexistent"
}409 Conflict
Duplicate Resource:
{
"message": "A post with this slug already exists",
"error_code": "DUPLICATE_RESOURCE",
"conflicting_field": "slug",
"existing_resource_id": 123
}State Conflict:
{
"message": "Post is already published",
"error_code": "STATE_CONFLICT",
"current_state": "published",
"requested_action": "publish"
}422 Unprocessable Entity
Validation Failed:
{
"message": "The given data was invalid.",
"error_code": "VALIDATION_FAILED",
"errors": {
"title": [
"The title field is required."
],
"content": [
"The content must be at least 10 characters."
],
"category_id": [
"The selected category is invalid."
]
}
}429 Too Many Requests
Rate Limit Exceeded:
{
"message": "Too many requests",
"error_code": "RATE_LIMIT_EXCEEDED",
"limit": 10000,
"remaining": 0,
"reset_at": "2026-01-26T13:00:00Z",
"retry_after": 3600
}Usage Quota Exceeded:
{
"message": "Monthly usage quota exceeded",
"error_code": "QUOTA_EXCEEDED",
"quota_type": "monthly",
"limit": 50000,
"used": 50000,
"reset_at": "2026-02-01T00:00:00Z"
}500 Internal Server Error
Unexpected Error:
{
"message": "An unexpected error occurred",
"error_code": "INTERNAL_ERROR",
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-01-26T12:00:00Z"
}
}TIP
In production, internal error messages are sanitized. Include the request_id when reporting issues for debugging.
Error Codes
Authentication Errors
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHENTICATED | 401 | No authentication provided |
INVALID_API_KEY | 401 | API key is invalid or revoked |
TOKEN_EXPIRED | 401 | Authentication token has expired |
INVALID_CREDENTIALS | 401 | Username/password incorrect |
INSUFFICIENT_PERMISSIONS | 403 | Missing required permissions/scopes |
Resource Errors
| Code | HTTP Status | Description |
|---|---|---|
RESOURCE_NOT_FOUND | 404 | Requested resource doesn't exist |
DUPLICATE_RESOURCE | 409 | Resource with identifier already exists |
RESOURCE_LOCKED | 409 | Resource is locked by another process |
STATE_CONFLICT | 409 | Action conflicts with current state |
Validation Errors
| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_FAILED | 422 | One or more fields failed validation |
INVALID_TYPE | 400 | Parameter has wrong data type |
MISSING_PARAMETER | 400 | Required parameter not provided |
INVALID_FORMAT | 400 | Parameter format is invalid |
Rate Limiting Errors
| Code | HTTP Status | Description |
|---|---|---|
RATE_LIMIT_EXCEEDED | 429 | Too many requests in time window |
QUOTA_EXCEEDED | 429 | Usage quota exceeded |
CONCURRENT_LIMIT_EXCEEDED | 429 | Too many concurrent requests |
Business Logic Errors
| Code | HTTP Status | Description |
|---|---|---|
ENTITLEMENT_DENIED | 403 | Feature not included in plan |
WORKSPACE_SUSPENDED | 403 | Workspace is suspended |
NAMESPACE_ACCESS_DENIED | 403 | No access to namespace |
PAYMENT_REQUIRED | 402 | Payment required to proceed |
System Errors
| Code | HTTP Status | Description |
|---|---|---|
INTERNAL_ERROR | 500 | Unexpected server error |
SERVICE_UNAVAILABLE | 503 | Service temporarily unavailable |
GATEWAY_TIMEOUT | 504 | Upstream service timeout |
MAINTENANCE_MODE | 503 | System under maintenance |
Handling Errors
JavaScript Example
async function createPost(data) {
try {
const response = await fetch('/api/v1/posts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
const error = await response.json();
switch (response.status) {
case 401:
// Re-authenticate
redirectToLogin();
break;
case 403:
// Show permission error
showError('You do not have permission to create posts');
break;
case 422:
// Show validation errors
showValidationErrors(error.errors);
break;
case 429:
// Show rate limit message
showError(`Rate limited. Retry after ${error.retry_after} seconds`);
break;
default:
// Generic error
showError(error.message);
}
return null;
}
return await response.json();
} catch (err) {
// Network error
showError('Network error. Please check your connection.');
return null;
}
}PHP Example
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$client = new Client(['base_uri' => 'https://api.example.com']);
try {
$response = $client->post('/v1/posts', [
'headers' => [
'Authorization' => "Bearer {$apiKey}",
'Content-Type' => 'application/json',
],
'json' => $data,
]);
$post = json_decode($response->getBody(), true);
} catch (RequestException $e) {
$statusCode = $e->getResponse()->getStatusCode();
$error = json_decode($e->getResponse()->getBody(), true);
switch ($statusCode) {
case 401:
throw new AuthenticationException($error['message']);
case 403:
throw new AuthorizationException($error['message']);
case 422:
throw new ValidationException($error['errors']);
case 429:
throw new RateLimitException($error['retry_after']);
default:
throw new ApiException($error['message']);
}
}Debugging
Request ID
Every response includes a request_id for debugging:
curl -i https://api.example.com/v1/postsResponse headers:
X-Request-ID: req_abc123def456Include this ID when reporting issues.
Debug Mode
In development, enable debug mode for detailed errors:
// .env
APP_DEBUG=trueDebug responses include:
- Full stack traces
- SQL queries
- Exception details
DANGER
Never enable debug mode in production! It exposes sensitive information.
Logging
All errors are logged with context:
[2026-01-26 12:00:00] production.ERROR: Post not found
{
"user_id": 123,
"workspace_id": 456,
"namespace_id": 789,
"post_id": 999,
"request_id": "req_abc123"
}Best Practices
1. Always Check Status Codes
// ✅ Good
if (!response.ok) {
handleError(response);
}
// ❌ Bad - assumes success
const data = await response.json();2. Handle All Error Types
// ✅ Good - specific handling
switch (error.error_code) {
case 'RATE_LIMIT_EXCEEDED':
retryAfter(error.retry_after);
break;
case 'VALIDATION_FAILED':
showValidationErrors(error.errors);
break;
default:
showGenericError(error.message);
}
// ❌ Bad - generic handling
alert(error.message);3. Implement Retry Logic
async function fetchWithRetry(url, options, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
// Rate limited - wait and retry
const retryAfter = parseInt(response.headers.get('Retry-After'));
await sleep(retryAfter * 1000);
continue;
}
return response;
} catch (err) {
if (i === retries - 1) throw err;
await sleep(1000 * Math.pow(2, i)); // Exponential backoff
}
}
}4. Log Error Context
// ✅ Good - log context
console.error('API Error:', {
endpoint: '/v1/posts',
method: 'POST',
status: response.status,
error_code: error.error_code,
request_id: error.meta.request_id
});
// ❌ Bad - no context
console.error(error.message);