Skip to content

Error Handling ​

The MailShield API uses conventional HTTP response codes and returns errors in a consistent JSON format.

Error Response Format ​

All errors follow this structure:

json
{
  "error": {
    "code": "error_code",
    "message": "Human-readable error message",
    "details": []  // Optional: more detail for some errors
  }
}

message says what went wrong and, where it can, what to do about it. details is only present for some errors: a list of problems for validation errors, or the current count and limit when the domain limit is reached.

HTTP Status Codes ​

CodeDescription
200Success
201Created (for POST requests)
400Bad Request: invalid JSON, request body or query parameters
401Unauthorized: invalid or missing authentication
403Forbidden: valid token, but the request isn't allowed
404Not Found: the resource doesn't exist
409Conflict: the resource already exists, or the change conflicts with its state
429Too Many Requests: rate limit exceeded
500Internal Server Error: something went wrong on our end

Error Codes ​

Authentication Errors ​

CodeStatusDescription
unauthorized401Missing or invalid Authorization header
unauthorized401Invalid or revoked API token
unauthorized401API token has expired

Authorization Errors ​

CodeStatusDescription
forbidden403Request from an IP address outside the token's allowlist
forbidden403Read-only token used for a request other than GET
forbidden403The plan doesn't include API access, or a payment is overdue
forbidden403Domain limit reached
forbidden403Hosted MTA-STS needs a verified domain and a paid plan

Resource Errors ​

CodeStatusDescription
not_found404Domain not found (also returned for an ID that isn't a UUID)
not_found404Customer not found (also returned for an ID that isn't a UUID)
not_found404DKIM selector not found
conflict409The domain is already in your organization
conflict409There's already a customer with that name, ignoring case
conflict409The DKIM selector is already registered
conflict409Usage type change on a domain with passive monitoring

Validation Errors ​

CodeStatusDescription
validation_error400Invalid JSON, request body or query parameters

The message names the first problem, starting with the field it's about, and details lists every problem:

json
{
  "error": {
    "code": "validation_error",
    "message": "domain: Not a valid domain name. Send just the name, such as example.com.",
    "details": [
      {
        "code": "custom",
        "path": ["domain"],
        "message": "Not a valid domain name. Send just the name, such as example.com."
      },
      {
        "code": "custom",
        "path": ["testEmailLocalPart"],
        "message": "Required for domains that receive email (sends-and-receives or receives-only)."
      }
    ]
  }
}

A body that isn't valid JSON, or query parameters the API can't use, are rejected the same way, without details:

ProblemExample message
Body isn't valid JSONThe request body isn't valid JSON: Expected property name or '}' in JSON at position 1 (line 1 column 2)
Body is emptyThe request body is empty. Send a JSON object with Content-Type: application/json.
page or limit isn't a whole numberlimit must be a whole number from 1 to 100.
Date that isn't ISO 8601startDate must be an ISO 8601 date or date-time, such as 2026-09-01 or 2026-09-01T00:00:00Z.
startDate after endDatestartDate must be before endDate.
Unknown alert severityseverity must be info, warning or critical.
customerId filter that's neither an ID nor nonecustomerId must be a customer ID, or none for domains without a customer.

A limit above 100 or a page of 0 isn't an error: the value is clamped to the allowed range.

Rate Limiting Errors ​

CodeStatusDescription
rate_limited429Too many requests

Rate limit errors include a Retry-After header with seconds to wait.

Handling Errors ​

Python Example ​

python
import requests

def api_request(url, headers):
    response = requests.get(url, headers=headers)

    if response.ok:
        return response.json()["data"]

    error = response.json()["error"]

    if response.status_code == 401:
        raise AuthenticationError(error["message"])
    elif response.status_code == 403:
        raise PermissionError(error["message"])
    elif response.status_code == 404:
        raise NotFoundError(error["message"])
    elif response.status_code == 429:
        retry_after = response.headers.get("Retry-After", 60)
        raise RateLimitError(f"Rate limited. Retry after {retry_after}s")
    else:
        raise APIError(error["message"])

Node.js Example ​

javascript
async function apiRequest(url, headers) {
  const response = await fetch(url, { headers });
  const body = await response.json();

  if (response.ok) {
    return body.data;
  }

  const { code, message, details } = body.error;

  switch (response.status) {
    case 401:
      throw new AuthenticationError(message);
    case 403:
      throw new PermissionError(message);
    case 404:
      throw new NotFoundError(message);
    case 429: {
      const retryAfter = response.headers.get('Retry-After');
      throw new RateLimitError(message, retryAfter);
    }
    case 400:
      throw new ValidationError(message, details);
    default:
      throw new APIError(message);
  }
}

Debugging Tips ​

  1. Check the error code: it tells you what kind of problem it is

  2. Read the message: it says what went wrong and usually what to do next

  3. Look at details: for validation errors, check every field that failed

  4. Verify your token: most 401 errors are token-related

  5. Check the token's settings: a 403 can mean the request came from an IP outside the token's allowlist, or that a read-only token tried to change something

  6. Review rate limits: 429 means you're making too many requests

Monitor and secure your email domains.