ZevSend Docs
Sign up

API Reference

Errors

The error envelope, common codes, and how to handle them.

Every error response from the API uses the same shape. One handler, three channels.

The error envelope

{
  "error": {
    "code": "invalid_phone",
    "message": "Recipient must be a phone number in international format, e.g. +2348012345678.",
    "param": "to",
    "type": "validation_error",
    "request_id": "req_01HXYZ..."
  }
}
FieldMeaning
codeStable, machine-readable identifier. Switch on this in your code.
messageHuman-readable explanation. Safe to show to developers.
paramThe request field at fault, when applicable.
typeHigh-level category: validation_error, auth_error, etc.
request_idUnique id for this request. Include it when you contact support.

Status codes

StatusTypeExample codes
400validation_errorinvalid_phone, invalid_email, body_too_long
401auth_errormissing_api_key, invalid_api_key
403permission_errorkey_domain_scope, team_suspended
404not_foundtemplate_not_found, domain_not_found
409conflicttemplate_channel_mismatch
422validation_errormissing_variable, unknown_variable
429rate_limitedrate_limited
500internal_errorinternal_error

Common error codes

invalid_phone

The to field isn’t a valid phone number in E.164 format. E.164 means international form with a leading + and country code, no spaces or dashes: +2348012345678.

invalid_email

The to field isn’t a valid email address.

template_not_found

The template_id you passed doesn’t exist on this team, or isn’t enabled. Check that the template hasn’t been archived.

wrong_template_channel

You sent a template through the wrong endpoint — e.g. an SMS template via POST /v1/emails. Send it through the matching /v1/{channel} endpoint.

missing_variable / unknown_variable

Your variables object doesn’t match the template’s variable manifest. The param field names the offending key.

no_approved_brand_domain

You’re in live mode but no verified domain on the team is approved yet. Add a verified domain and complete the brand profile.

rate_limited

You exceeded the rate limit. The response includes a Retry-After header (seconds) telling you when to retry. See Rate limits.

internal_error

Something went wrong on our side. Include the request_id when you contact support. These errors are safe to retry with exponential backoff.

Handling errors

A reasonable client retries 429 and 5xx with exponential backoff and surfaces everything else to the developer:

async function send(payload: SendInput, attempt = 0): Promise<Sent> {
  const res = await fetch('https://api.zevsend.com/v1/emails', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.ZEVSEND_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(payload),
  });

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

  const body = await res.json();
  const retryable = res.status === 429 || res.status >= 500;
  if (retryable && attempt < 5) {
    const wait =
      res.status === 429
        ? Number(res.headers.get('Retry-After') ?? 1) * 1000
        : 2 ** attempt * 250;
    await new Promise((r) => setTimeout(r, wait));
    return send(payload, attempt + 1);
  }
  throw new Error(`${body.error.code}: ${body.error.message}`);
}

Updated at, Thursday, October 1, 2026