Error Handling

Understand API error responses, status codes, and error codes. Handle errors gracefully in your integration.Last updated: 2026-02-14

All API errors return a consistent JSON structure, making it easy to handle failures programmatically.

Error Response Format

{
	"error": {
		"code": "ERROR_CODE",
		"message": "Human readable description"
	}
}

The code field contains a machine-readable identifier for programmatic handling, while message provides a human-readable explanation.

HTTP Status Codes

StatusCodeDescription
400VALIDATION_ERRORRequest body validation failed
400LIMIT_EXCEEDEDResource limit reached (e.g., max 10 alerts)
401UNAUTHORIZEDMissing or invalid API key
403FORBIDDENAPI key doesn't have required scope
404NOT_FOUNDResource not found
405METHOD_NOT_ALLOWEDHTTP method not supported
409CONFLICTResource state conflict (e.g., cron job disabled)
429RATE_LIMITEDToo many requests
500INTERNAL_ERRORServer error

Validation Errors

When a request fails validation, the response includes field-level details:

{
	"error": {
		"code": "VALIDATION_ERROR",
		"message": "Request validation failed",
		"details": [
			{
				"field": "url",
				"message": "Must be a valid URL"
			},
			{
				"field": "interval",
				"message": "Must be at least 60 seconds"
			}
		]
	}
}

Use the details array to display specific feedback for each invalid field in your UI.

Best Practices

Always Check Status Codes

let response = await fetch("/api/monitors", {
	method: "POST",
	headers: { Authorization: `Bearer ${apiKey}` },
	body: JSON.stringify(data),
});

if (!response.ok) {
	let error = await response.json();
	// Handle based on error.error.code
}

Log Error Codes for Debugging

Store the code value in your logs to quickly identify issues:

if (!response.ok) {
	let { error } = await response.json();
	console.error(`API Error: ${error.code} - ${error.message}`);
}

Show User-Friendly Messages

Map error codes to user-friendly messages instead of displaying raw API responses:

let userMessages: Record<string, string> = {
	VALIDATION_ERROR: "Please check your input and try again.",
	LIMIT_EXCEEDED: "You've reached the maximum number of resources.",
	UNAUTHORIZED: "Please sign in to continue.",
	FORBIDDEN: "You don't have permission to perform this action.",
	NOT_FOUND: "The requested resource could not be found.",
	RATE_LIMITED: "Too many requests. Please wait a moment.",
	INTERNAL_ERROR: "Something went wrong. Please try again later.",
};

Implement Retry Logic

For transient errors (429 and 5xx), implement exponential backoff:

async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) {
	for (let attempt = 0; attempt < maxRetries; attempt++) {
		let response = await fetch(url, options);

		if (response.ok) return response;

		// Retry on rate limit or server errors
		if (response.status === 429 || response.status >= 500) {
			let delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
			await new Promise((resolve) => setTimeout(resolve, delay));
			continue;
		}

		// Don't retry client errors (4xx except 429)
		throw new Error(`API error: ${response.status}`);
	}

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

For 429 responses, check the Retry-After header if present to determine the optimal wait time.