Error Handling

Common error responses and status codes.

All error responses follow a consistent format:

{
  "statusCode": 400,
  "message": "Description of what went wrong",
  "error": "Bad Request"
}

Common status codes:

• 400 Bad Request — Invalid request body or parameters
• 401 Unauthorized — Missing or invalid API key
• 403 Forbidden — API key doesn't have access to this resource (see scope errors below)
• 404 Not Found — Resource doesn't exist or doesn't belong to you
• 429 Too Many Requests — Rate limit exceeded
• 500 Internal Server Error — Something went wrong on our end

For 429 errors, check the X-RateLimit-Reset header for when you can retry.

Insufficient scope errors (403):

When an API key lacks the required scope for an endpoint, you get a structured 403 response:

{
  "statusCode": 403,
  "error": "insufficient_scope",
  "message": "This API key does not have permission to access POST /v1/send. Required scope: sending.",
  "required_scopes": ["sending"],
  "key_scopes": ["reading"],
  "hint": "Create a new API key with \"sending\" or \"full_access\" scope in the Posthawk dashboard under API Keys.",
  "scope_reference": {
    "sending": "Send emails, render templates, schedule/cancel/reschedule emails"
  }
}

The response tells you exactly which scope is needed and what your key currently has. To fix this, create a new API key with the correct scope in the dashboard.

Scope requirements by endpoint:

• POST /v1/send, POST /v1/render → sending
• GET /v1/send/:jobId, GET /v1/queue/stats → reading
• POST /scheduled, DELETE /scheduled/:id, PATCH /scheduled/:id/reschedule → sending
• GET /scheduled, GET /scheduled/:id → reading
• full_access keys can access all endpoints

Domain sending limits (cloud edition):

When per-domain sending limits are configured, exceeding them returns an error:

• "Daily sending limit reached for example.com (1,000 emails/day)."
• "Monthly sending limit reached for example.com (25,000 emails/month)."

These limits apply to both the REST API (/v1/send) and SMTP relay (port 587). Configure them via PATCH /v1/domains/:id/limits or the dashboard.