Error Handling
All API errors return a consistent JSON format with appropriate HTTP status codes.
Error Response Format
{
"error": "error_code",
"message": "Human-readable description of what went wrong"
}HTTP Status Codes
| Code | Description |
|---|---|
200 | Success |
201 | Created |
204 | No Content (successful delete) |
400 | Bad Request - Invalid input |
401 | Unauthorized - Missing or invalid API key |
403 | Forbidden - Insufficient permissions |
404 | Not Found - Resource doesn't exist |
422 | Unprocessable Entity - Validation failed |
429 | Too Many Requests - Rate limited |
500 | Internal Server Error |
Error Codes
Authentication Errors
| Code | Status | Description |
|---|---|---|
missing_api_key | 401 | No API key provided in request |
invalid_api_key | 401 | API key not found or revoked |
expired_api_key | 401 | API key has expired |
{
"error": "missing_api_key",
"message": "API key is required. Include it in the Authorization header."
}Authorization Errors
| Code | Status | Description |
|---|---|---|
forbidden | 403 | Operation not allowed |
insufficient_scope | 403 | API key lacks required scope |
plan_limit_exceeded | 403 | Plan quota exceeded |
{
"error": "insufficient_scope",
"message": "This operation requires 'monitors:write' scope."
}Validation Errors
| Code | Status | Description |
|---|---|---|
invalid_request | 400 | Malformed request body |
validation_failed | 422 | Field validation failed |
invalid_url | 422 | URL format is invalid |
{
"error": "validation_failed",
"message": "Validation failed",
"details": [
{ "field": "url", "message": "Must be a valid HTTPS URL" },
{ "field": "frequency", "message": "Must be between 60 and 3600" }
]
}Resource Errors
| Code | Status | Description |
|---|---|---|
not_found | 404 | Resource doesn't exist |
already_exists | 409 | Resource already exists |
deleted | 410 | Resource was deleted |
{
"error": "not_found",
"message": "Monitor with ID 'mon_abc123' not found"
}Rate Limit Errors
| Code | Status | Description |
|---|---|---|
rate_limited | 429 | Too many requests |
{
"error": "rate_limited",
"message": "Rate limit exceeded. Try again in 42 seconds.",
"retryAfter": 42
}Server Errors
| Code | Status | Description |
|---|---|---|
internal_error | 500 | Unexpected server error |
service_unavailable | 503 | Service temporarily unavailable |
{
"error": "internal_error",
"message": "An unexpected error occurred. Please try again."
}Handling Errors
JavaScript/TypeScript
async function fetchMonitors() {
const response = await fetch('https://statly.live/api/v1/monitors', {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
if (!response.ok) {
const error = await response.json();
switch (response.status) {
case 401:
throw new Error('Invalid API key');
case 403:
throw new Error(`Permission denied: ${error.message}`);
case 429:
// Wait and retry
await sleep(error.retryAfter * 1000);
return fetchMonitors();
default:
throw new Error(error.message);
}
}
return response.json();
}Python
import requests
import time
def fetch_monitors():
response = requests.get(
'https://statly.live/api/v1/monitors',
headers={'Authorization': f'Bearer {api_key}'}
)
if response.status_code == 429:
retry_after = response.json().get('retryAfter', 60)
time.sleep(retry_after)
return fetch_monitors()
response.raise_for_status()
return response.json()Go
func fetchMonitors() ([]Monitor, error) {
resp, err := http.Get("https://statly.live/api/v1/monitors")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 429 {
var apiErr APIError
json.NewDecoder(resp.Body).Decode(&apiErr)
time.Sleep(time.Duration(apiErr.RetryAfter) * time.Second)
return fetchMonitors()
}
if resp.StatusCode != 200 {
var apiErr APIError
json.NewDecoder(resp.Body).Decode(&apiErr)
return nil, fmt.Errorf("%s: %s", apiErr.Error, apiErr.Message)
}
var monitors []Monitor
json.NewDecoder(resp.Body).Decode(&monitors)
return monitors, nil
}Best Practices
- Always check status codes before parsing response body
- Log error codes for debugging and monitoring
- Implement retry logic for 429 and 5xx errors
- Use exponential backoff to avoid overwhelming the API
- Handle specific errors differently (auth vs validation vs rate limit)