ScamVerify™

Error Handling

Error response format, error codes, and retry strategies for the ScamVerify™ API.

The ScamVerify™ API uses standard HTTP status codes and returns structured error responses. This guide covers the error format, all possible error codes, and recommended retry strategies.

Error Response Format

All errors follow a consistent JSON structure:

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Try again in 12 seconds.",
    "details": null,
    "retry_after": 12,
    "upgrade_url": null
  }
}
FieldTypeDescription
codestringMachine-readable error code (see table below)
messagestringHuman-readable error description
detailsobject or nullAdditional context for certain error types
retry_afternumber or nullSeconds to wait before retrying (only for rate limits)
upgrade_urlstring or nullLink to upgrade page (only for quota errors)

Error Codes

CodeHTTP StatusDescriptionRetryable
missing_api_key401No API key provided in the Authorization headerNo
invalid_api_key401The API key is invalid, revoked, or expiredNo
validation_error400The request body is malformed or missing required fieldsNo
rate_limit_exceeded429You have exceeded your plan's RPM limitYes
quota_exhausted402Your monthly quota for this channel is used upNo
internal_error500An unexpected server error occurredYes

Validation Errors

When the request body fails validation, the details field contains an issues array describing each problem:

{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": {
      "issues": [
        {
          "field": "phone_number",
          "message": "Phone number must be in E.164 format (e.g., +12025551234)"
        },
        {
          "field": "force_refresh",
          "message": "Expected boolean, received string"
        }
      ]
    },
    "retry_after": null,
    "upgrade_url": null
  }
}

Each issue in the array has:

FieldDescription
fieldThe request body field that failed validation
messageA description of what is wrong and how to fix it

Retry Strategy

Not all errors should be retried. Here is how to handle each type:

StatusAction
400 (Validation)Fix the request and resubmit. Do not retry automatically.
401 (Auth)Check your API key. Do not retry automatically.
402 (Quota)Upgrade your plan or wait for quota reset. Do not retry.
429 (Rate Limit)Wait for the retry_after duration, then retry.
500 (Server Error)Retry with exponential backoff.

Never retry 400, 401, or 402 errors. These indicate a problem with your request or account, not a transient issue. Retrying them wastes resources and may trigger additional rate limiting.

Exponential Backoff with Jitter

For retryable errors (429 and 500), use exponential backoff with random jitter. This prevents multiple clients from retrying at the exact same time.

import time
import random
import requests

def scamverify_request(method, endpoint, api_key, payload=None, max_retries=5):
    url = f"https://scamverify.ai/api/v1{endpoint}"
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    }

    for attempt in range(max_retries):
        response = requests.request(method, url, headers=headers, json=payload)

        # Success
        if response.status_code < 400:
            return response.json()

        error_data = response.json().get("error", {})
        status = response.status_code

        # Non-retryable errors
        if status in (400, 401, 402):
            raise ApiError(
                code=error_data.get("code"),
                message=error_data.get("message"),
                status=status,
            )

        # Rate limited: respect Retry-After
        if status == 429:
            retry_after = error_data.get("retry_after", 5)
            jitter = random.uniform(0, retry_after * 0.25)
            wait = retry_after + jitter
            print(f"Rate limited. Waiting {wait:.1f}s (attempt {attempt + 1})")
            time.sleep(wait)
            continue

        # Server error: exponential backoff
        if status >= 500:
            base_delay = 2 ** attempt  # 1, 2, 4, 8, 16
            jitter = random.uniform(0, base_delay * 0.5)
            wait = base_delay + jitter
            print(f"Server error. Retrying in {wait:.1f}s (attempt {attempt + 1})")
            time.sleep(wait)
            continue

    raise ApiError(code="max_retries", message="Max retries exceeded", status=0)


class ApiError(Exception):
    def __init__(self, code, message, status):
        self.code = code
        self.message = message
        self.status = status
        super().__init__(f"[{status}] {code}: {message}")


# Usage
try:
    result = scamverify_request(
        "POST",
        "/phone/lookup",
        api_key="sv_live_abc123...",
        payload={"phone_number": "+12025551234"},
    )
    print(f"Verdict: {result['verdict']}")
except ApiError as e:
    if e.code == "quota_exhausted":
        print("Quota used up. Upgrade at:", e.message)
    elif e.code == "invalid_api_key":
        print("Check your API key")
    else:
        print(f"Error: {e}")
class ApiError extends Error {
  constructor(code, message, status) {
    super(`[${status}] ${code}: ${message}`);
    this.code = code;
    this.status = status;
  }
}

async function scamverifyRequest(method, endpoint, apiKey, payload, maxRetries = 5) {
  const url = `https://scamverify.ai/api/v1${endpoint}`;
  const headers = {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  };

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, {
      method,
      headers,
      body: payload ? JSON.stringify(payload) : undefined,
    });

    // Success
    if (response.ok) {
      return await response.json();
    }

    const errorData = (await response.json()).error || {};
    const status = response.status;

    // Non-retryable errors
    if ([400, 401, 402].includes(status)) {
      throw new ApiError(errorData.code, errorData.message, status);
    }

    // Rate limited: respect Retry-After
    if (status === 429) {
      const retryAfter = errorData.retry_after || 5;
      const jitter = Math.random() * retryAfter * 0.25;
      const wait = retryAfter + jitter;
      console.log(`Rate limited. Waiting ${wait.toFixed(1)}s (attempt ${attempt + 1})`);
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }

    // Server error: exponential backoff
    if (status >= 500) {
      const baseDelay = 2 ** attempt; // 1, 2, 4, 8, 16
      const jitter = Math.random() * baseDelay * 0.5;
      const wait = baseDelay + jitter;
      console.log(`Server error. Retrying in ${wait.toFixed(1)}s (attempt ${attempt + 1})`);
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }
  }

  throw new ApiError("max_retries", "Max retries exceeded", 0);
}

// Usage
try {
  const result = await scamverifyRequest(
    "POST",
    "/phone/lookup",
    "sv_live_abc123...",
    { phone_number: "+12025551234" }
  );
  console.log(`Verdict: ${result.verdict}`);
} catch (err) {
  if (err.code === "quota_exhausted") {
    console.log("Quota used up. Time to upgrade.");
  } else if (err.code === "invalid_api_key") {
    console.log("Check your API key");
  } else {
    console.error("Error:", err.message);
  }
}

Best Practices

  1. Always parse the error response. Do not rely on HTTP status codes alone. The code field gives you the specific reason for the failure.

  2. Log errors with context. Include the code, message, request endpoint, and timestamp in your logs for debugging.

  3. Handle quota exhaustion gracefully. Show a meaningful message to your users rather than a generic error. The upgrade_url field links directly to the upgrade page.

  4. Set a retry budget. Limit the total number of retries (we recommend 5) and the total time spent retrying (we recommend 60 seconds) to avoid blocking your application indefinitely.

  5. Use circuit breakers for 500 errors. If you receive multiple consecutive 500 errors, stop retrying and alert your operations team. Continued retries during an outage add unnecessary load.

On this page