Skip to main content
Defendis API uses standard HTTP status codes and a JSON error envelope. This page explains how to handle errors safely in production, including which scenarios are retryable.

Error response format

Most error responses use this shape:
{
  "error": "string",
  "message": "string (optional)"
}
Implementation principles:

Treat errors as text

Treat error as a human-readable reason, not a stable enum.

Stay forward-compatible

Ignore unknown response fields for forward compatibility.

Protect secrets in logs

Never log secrets, especially the Authorization header.

Status codes and retry actions

Status codeMeaningRetry strategyRecommended action
400 Bad RequestMissing or invalid parameters/JSON bodyNoFix request shape or required inputs
401 UnauthorizedMissing, invalid, or revoked API keyNoValidate credentials and replace/re-enable key
403 ForbiddenAuthenticated but blocked by enterprise access policyNoVerify enterprise scope/eligibility and enterprise billing state
409 ConflictWorkspace quota reached on write operationsNoReduce usage or increase workspace quotas, then retry
404 Not FoundUnknown endpoint or resourceNoConfirm endpoint path, method, and resource identifiers
429 Too Many RequestsRate limit exceededYesRetry with exponential backoff + jitter
500/502/503/504Transient server-side failureConditionalRetry safe requests (GET) with backoff

Common examples

ScenarioHTTP statusExample error body
Missing API key401 Unauthorized{ "error": "Missing API key" }
Out-of-scope request403 Forbidden{ "error": "You don't have permission to view this" }
Enterprise billing access denied403 Forbidden{ "error": "Enterprise billing access denied", "reason": "payment_unpaid", "status": "unpaid" }
Watchlist quota reached409 Conflict{ "error": "quota_domains_reached", "metric": "domains", "used": 10, "limit": 10, "requestedIncrement": 1 }
Rate limit exceeded429 Too Many Requests{ "error": "Rate limit exceeded" }

Backoff snippet

JavaScript backoff snippet
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function fetchGetWithBackoff(url, options = {}, { maxRetries = 5, baseDelayMs = 500 } = {}) {
  const retryableStatuses = new Set([429, 500, 502, 503, 504]);

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const res = await fetch(url, { ...options, method: "GET" });
      if (!retryableStatuses.has(res.status)) return res;

      if (attempt === maxRetries) return res;

      const delay = Math.min(30_000, baseDelayMs * 2 ** attempt);
      const jitter = Math.floor(Math.random() * 250);
      await sleep(delay + jitter);
    } catch (error) {
      if (attempt === maxRetries) throw error;

      const delay = Math.min(30_000, baseDelayMs * 2 ** attempt);
      const jitter = Math.floor(Math.random() * 250);
      await sleep(delay + jitter);
    }
  }
}
Python backoff snippet
import random
import time
import requests

def get_with_backoff(url, headers, max_retries=5, base_delay_s=0.5, timeout_s=30):
    retryable_statuses = {429, 500, 502, 503, 504}

    for attempt in range(max_retries + 1):
        try:
            resp = requests.get(url, headers=headers, timeout=timeout_s)
        except requests.RequestException:
            if attempt == max_retries:
                raise
            resp = None

        if resp is not None and resp.status_code not in retryable_statuses:
            return resp

        if attempt == max_retries:
            return resp

        delay = min(30.0, base_delay_s * (2 ** attempt))
        jitter = random.uniform(0, 0.25)
        time.sleep(delay + jitter)

Troubleshooting checklist

When contacting Support, include:
1

Request path

Include the request path example: /api/v1/dataleaks/stats
2

Request timing and correlation

Include the UTC timestamp and your client request ID.
3

Error details

Include the HTTP status code and the error response body.
4

Request parameters

Include the parameters you sent, with sensitive values redacted.