Skip to main content

Error response format

All API errors follow a consistent JSON envelope:
{
  "error": {
    "code": "error_code",
    "message": "Human-readable description of what went wrong",
    "details": { },
    "suggestion": "Actionable advice to fix the issue"
  }
}
FieldTypeAlways presentDescription
codestringMachine-readable error identifier
messagestringHuman-readable description
detailsobjectAdditional context (validation errors, etc.)
suggestionstringRecommended fix

Error codes reference

Authentication errors (401)

CodeDescriptionSuggestion
invalid_api_keyAPI key is missing, malformed, or not recognizedCheck your key is correct and includes the full string
expired_api_keyAPI key has passed its expiration dateGenerate a new API key from your dashboard
revoked_api_keyAPI key has been revokedGenerate a new API key from your dashboard

Authorization errors (403)

CodeDescriptionSuggestion
insufficient_permissionsAPI key lacks the required scopeUse a key with the required scope (e.g. email:send)
account_suspendedYour organization account is suspendedContact support

Validation errors (400)

CodeDescriptionSuggestion
invalid_requestRequest body or parameters failed validationCheck the request against the API docs
invalid_emailEmail address format is invalidVerify the email address format
invalid_domainDomain name format is invalidCheck the domain name
missing_required_fieldA required field is missingInclude all required fields
validation_failedBusiness logic validation failedCheck details for specifics

Not found errors (404)

CodeDescription
email_not_foundEmail ID does not exist or is not accessible
domain_not_foundDomain ID does not exist or is not accessible
organization_not_foundOrganization not found
api_key_not_foundAPI key resource not found
resource_not_foundGeneric — the requested resource does not exist

Business logic errors (422)

CodeDescriptionSuggestion
domain_not_verifiedThe sending domain is not yet verifiedVerify your domain first (see Domain Setup)
recipient_suppressedRecipient is on the suppression listCheck Suppressions
invalid_contentEmail content flagged by content filterReview your email content for spam triggers
attachment_too_largeAttachment exceeds the maximum sizeKeep attachments under the size limit
too_many_recipientsMore than 50 recipients in to, cc, or bccReduce recipients per request or use batch send

Rate limiting (429)

CodeDescriptionSuggestion
rate_limit_exceededToo many requests in the current windowWait until X-RateLimit-Reset and retry
quota_exceededAccount quota (e.g. daily sends) exceededUpgrade your plan or wait for the next billing period

Server errors (5xx)

CodeHTTPDescription
internal_error500Unexpected server error
database_error500Database connectivity issue
provider_error502Upstream email provider failure
provider_timeout502Upstream email provider timed out

Validation error details

When code is invalid_request, the details object contains per-field validation errors:
{
  "error": {
    "code": "invalid_request",
    "message": "Request validation failed",
    "details": {
      "validation": {
        "subject": ["Subject is required"],
        "to": ["Must include at least one recipient"],
        "html": ["Either html or text body is required"]
      }
    },
    "suggestion": "Check the request body against the API documentation"
  }
}

Handling errors in code

const response = await fetch("https://api.mail.gorillaa.one/v1/emails", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify(emailData),
});

if (!response.ok) {
  const { error } = await response.json();

  switch (error.code) {
    case "rate_limit_exceeded":
      const resetAt = response.headers.get("X-RateLimit-Reset");
      // Wait and retry
      break;
    case "recipient_suppressed":
      // Remove recipient from your mailing list
      break;
    case "domain_not_verified":
      // Alert admin to verify the domain
      break;
    default:
      console.error(`API error: ${error.code}${error.message}`);
  }
}

Retry strategy

For transient errors (5xx, 429), use exponential backoff with jitter:
async function sendWithRetry(emailData, apiKey, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch("https://api.mail.gorillaa.one/v1/emails", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        "X-Idempotency-Key": emailData.idempotencyKey,
      },
      body: JSON.stringify(emailData),
    });

    if (response.ok) return response.json();

    const status = response.status;
    if (status >= 400 && status < 500 && status !== 429) {
      // Client error — don't retry
      throw new Error(`Client error: ${status}`);
    }

    // Exponential backoff with jitter
    const delay = Math.min(1000 * 2 ** attempt, 30000);
    const jitter = delay * 0.5 * Math.random();
    await new Promise((r) => setTimeout(r, delay + jitter));
  }

  throw new Error("Max retries exceeded");
}
Always include an X-Idempotency-Key when retrying send requests. This ensures retries don’t result in duplicate emails.