Errors

The API uses standard HTTP status codes and a consistent JSON error envelope.

Envelope

{
  "error": {
    "code": "validation_error",
    "message": "Request failed validation.",
    "details": { "issues": [{ "path": ["recipient", "email"], "message": "Invalid email address" }] }
  }
}
  • code — a stable machine-readable string. Switch on this in your integration.
  • message — human-readable explanation. Safe to log; do not switch on this.
  • details — optional structured data (e.g. Zod issues, quota info).

Status / code table

| HTTP | code | Meaning | |---|---|---| | 401 | unauthorized | Missing Authorization header. | | 401 | invalid_api_key | Key not found, malformed, or revoked. | | 402 | quota_exceeded | Monthly certificate quota exhausted. details includes remaining, limit, resetAt. | | 403 | insufficient_scope | Key lacks the scope this endpoint requires. | | 404 | not_found | Resource does not exist or is not yours. | | 409 | conflict | Resource state conflicts (reserved for future use). | | 422 | validation_error | Request body/query failed schema validation. details.issues lists the fields. | | 429 | rate_limited | Too many requests. See rate limits. | | 500 | internal_error | Something went wrong on our side. Safe to retry with backoff. |

Best practices

  • Log code, not message. Messages may change; codes are contractual.
  • Retry only idempotent failures. 500 and 429 are retry-safe; 402/404/422 are not.
  • Surface details to your users. For validation errors, the details.issues array maps directly to form fields.