Handling Spamova API Errors

Every error response from the Spamova API includes an error field and a message field.

Always use the error value as your machine-readable code in application logic - it is stable across API versions. The message field is human-readable and intended for logging and debugging.

{ "error": "quota_exceeded", "message": "Not enough checks remain for this request." }

Some errors also include a meta object with quota details. See the service_unavailable section below for details.

400 - Bad Request Errors

These errors mean something is wrong with your request payload. Checks are not consumed. Fix the request before retrying.

invalid_json

The request body is not valid JSON, or is valid JSON but not an object.

{ "error": "invalid_json", "message": "..." }

Check that your request sets Content-Type: application/json and that the body is a valid JSON object. Arrays and primitives at the root level are not accepted.

invalid_payload

The request body could not be read.

{ "error": "invalid_payload", "message": "..." }

Retry with a valid JSON request body.

invalid_request

A required field is missing or invalid.

{ "error": "invalid_request", "message": "..." }

For /api/v1/check, the request must include a non-empty email string. For /api/v1/bulk, the request must include a non-empty emails array of strings. Fix the payload before retrying.

bulk_limit_exceeded

The emails array contains more than 100 emails.

{ "error": "bulk_limit_exceeded", "message": "..." }

The request is rejected in full - no subset is processed and no checks are consumed. Split your input into batches of 100 emails or fewer before retrying.

// Split into chunks of 100 before sending (JavaScript example) const chunk = (arr, size) => Array.from({ length: Math.ceil(arr.length / size) }, (_, i) => arr.slice(i * size, i * size + size) ); const batches = chunk(emails, 100);

401 - Unauthorized

unauthorized

The Bearer token is missing or invalid.

{ "error": "unauthorized", "message": "..." }

Check that your request includes the Authorization header in the correct format:

Authorization: Bearer YOUR_API_KEY

If you recently rotated your API key, make sure all instances of your integration are using the new key. No checks are consumed on authentication failures.

402 - Quota Exceeded

quota_exceeded

Not enough checks remain on your plan for this request.

{ "error": "quota_exceeded", "message": "..." }

Checks are not consumed when this error is returned. Your options:

- Wait for your plan to renew at the start of your next billing period

- Upgrade your plan to increase your monthly check limit

- If using bulk, reduce batch sizes to stay within remaining quota

Monitor meta.checks_remaining in bulk responses to track your quota proactively before hitting the limit.

403 - Forbidden Errors

access_denied

The API key belongs to an account whose plan does not include API access.

{ "error": "access_denied", "message": "..." }

Upgrade to an API-enabled plan to resolve this.

bulk_blocked

Bulk checks are disabled for this API key.

{ "error": "bulk_blocked", "message": "..." }

Stop sending requests to /api/v1/bulk for this key. The /api/v1/check endpoint may still be available depending on your plan. Contact support if you believe this is unexpected.

405 - Method Not Allowed

method_not_allowed

The endpoint was called with a method other than POST.

{ "error": "method_not_allowed", "message": "..." }

Both endpoints only accept POST requests with a JSON body. GET, PUT, PATCH, and DELETE are not supported.

413 - Payload Too Large

payload_too_large

The request body exceeds the endpoint payload limit.

{ "error": "payload_too_large", "message": "..." }

Reduce the size of your request body. For bulk requests, keep each batch at 100 emails or fewer. If you are hitting this on a single check, verify that your request is not including unexpected extra data.

429 - Rate Limited

rate_limited

The API key has exceeded its rolling rate limit for this endpoint.

{ "error": "rate_limited", "message": "..." }

No checks are consumed. Back off and retry after a short delay. A simple exponential backoff works well here:

async function checkWithRetry(email, retries = 3) { for (let i = 0; i < retries; i++) { const res = await callApi(email); if (res.status !== 429) return res; await new Promise(r => setTimeout(r, 200 * 2 ** i)); } }

If you are hitting rate limits regularly during high-volume jobs, switch to /api/v1/bulk to reduce the number of requests by up to 100x.

500 - Service Unavailable

service_unavailable

A temporary service problem prevented the request from completing.

{ "error": "service_unavailable", "message": "...", "meta": { "checks_refunded": 3, "checks_remaining": 4997 } }

This error covers two distinct situations:

Quota was never reserved - the failure happened before any checks were consumed. No checks are deducted. Retry with backoff.

Quota was reserved but the check failed - checks were reserved but the email check itself could not complete. In this case the response includes meta.checks_refunded and meta.checks_remaining, confirming the reserved checks have been returned to your account. Retry with backoff.

Check for the presence of meta.checks_refunded in your error handler to distinguish between the two cases:

if (error.error === 'service_unavailable') { if (error.meta?.checks_refunded) { // Checks were reserved and refunded - safe to retry console.log(`Refunded: ${error.meta.checks_refunded} checks`); } // Retry with backoff in both cases }

If service_unavailable errors persist across multiple retries, pause your job and contact support.

General Error Handling Pattern

A robust error handler for any Spamova API call should cover retryable errors separately from errors that require a fix before retrying:

const RETRYABLE = ['rate_limited', 'service_unavailable']; const FIX_REQUIRED = ['invalid_json', 'invalid_payload', 'invalid_request', 'bulk_limit_exceeded', 'method_not_allowed', 'payload_too_large']; const STOP = ['unauthorized', 'quota_exceeded', 'access_denied', 'bulk_blocked']; function handleError(error) { if (RETRYABLE.includes(error.error)) return 'retry'; if (FIX_REQUIRED.includes(error.error)) return 'fix'; if (STOP.includes(error.error)) return 'stop'; }