Documentation
Error Handling
Error format, HTTP status codes, and retry guidance for the SimuTest API.
Error Format
All API errors return a JSON body with a top-level error object containing three fields:
| Field | Description |
|---|---|
| error.code | Machine-readable snake_case error identifier |
| error.message | Human-readable description of the error |
| error.details | Optional object with additional context specific to the error type |
{
"error": {
"code": "url_unreachable",
"message": "The provided URL could not be reached by our test runners.",
"details": {
"url": "https://example.com/checkout",
"http_status": 503,
"retried": true
}
}
}HTTP Status Codes
| Status | Meaning | When it occurs |
|---|---|---|
| 400 | Bad Request | Missing required fields, invalid parameter values, or malformed JSON |
| 401 | Unauthorized | Missing, invalid, or revoked API key |
| 403 | Forbidden | Valid key but insufficient scope for the requested operation |
| 404 | Not Found | Requested resource does not exist or is not accessible with your key |
| 409 | Conflict | Resource already exists or is in a conflicting state (e.g., test already running) |
| 422 | Unprocessable | Request is well-formed but semantically invalid (e.g., URL is unreachable) |
| 429 | Too Many Requests | Rate limit or session quota exceeded; check Retry-After header |
| 500 | Server Error | Unexpected internal error; safe to retry with backoff |
| 503 | Service Unavailable | API temporarily unavailable; retry after the indicated delay |
Common Errors
| Error Code | Status | Description |
|---|---|---|
| url_unreachable | 422 | Test runner could not load the target URL; check the URL is publicly accessible |
| invalid_auth_config | 422 | Authentication configuration is invalid or credentials are incorrect |
| session_timeout | 422 | A session exceeded the maximum allowed duration; page may be unresponsive |
| quota_exceeded | 429 | Monthly session quota exhausted; upgrade plan or wait for quota reset |
| rate_limited | 429 | API request rate limit exceeded; retry after the value in Retry-After |
| invalid_api_key | 401 | API key is missing, malformed, or has been revoked |
| insufficient_scope | 403 | Key does not have the required scope for this operation |
| test_not_found | 404 | Test ID does not exist or belongs to a different organization |
SDK Exception Mapping
The Node.js and Python SDKs map API errors to typed exceptions for easier handling:
| Exception | HTTP Status | Trigger |
|---|---|---|
| AuthError | 401, 403 | Invalid key or insufficient scope |
| NotFoundError | 404 | Resource not found |
| ValidationError | 400, 422 | Invalid request parameters |
| RateLimitError | 429 (rate) | Request rate limit exceeded; .retryAfter gives seconds to wait |
| QuotaError | 429 (quota) | Monthly session quota exhausted |
| ServerError | 500, 503 | Internal server error; retryable |
| SimuTestError | Any | Base class for all SDK errors |
import { SimuTest, SimuTestError, AuthError, QuotaError, RateLimitError } from '@simutest/node';
const client = new SimuTest({ apiKey: process.env.SIMUTEST_API_KEY });
try {
const test = await client.tests.create({
url: 'https://example.com',
task: 'Find the pricing page',
sessions: 100,
});
} catch (err) {
if (err instanceof AuthError) {
// Invalid or expired API key — check your key
console.error('Authentication failed:', err.message);
} else if (err instanceof QuotaError) {
// Monthly session quota exceeded
console.error('Quota exceeded:', err.details.quota_resets_at);
} else if (err instanceof RateLimitError) {
// Too many requests — retry after the indicated delay
const retryAfter = err.retryAfter; // seconds
await sleep(retryAfter * 1000);
} else if (err instanceof SimuTestError) {
// All other API errors
console.error(`[${err.code}] ${err.message}`);
} else {
throw err; // re-throw unexpected errors
}
}Retry Guidance
Not all errors should be retried. Use the following table to decide:
| Status / Error | Retryable? | Notes |
|---|---|---|
| 400, 422 | No | Fix the request payload |
| 401, 403 | No | Check API key and scopes |
| 404 | No | Resource does not exist |
| 429 (rate) | Yes | Wait for Retry-After seconds |
| 429 (quota) | No | Upgrade plan or wait for quota reset |
| 500 | Yes | Retry with exponential backoff, max 3 attempts |
| 503 | Yes | Wait for Retry-After then retry |
async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 3): Promise<T> {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (err) {
const isRetryable =
err instanceof RateLimitError ||
(err instanceof SimuTestError && err.retryable);
if (!isRetryable || attempt === maxAttempts) throw err;
const delay = err instanceof RateLimitError
? err.retryAfter * 1000
: Math.min(1000 * 2 ** (attempt - 1), 10000);
await sleep(delay);
}
}
throw new Error('Unreachable');
}On this page