Skip to main content

API Reference

Authentication

All API requests use the X-API-Key header over HTTPS. API keys are prefixed am_live_ (production) or am_test_ (sandbox).

curl -X POST https://api.apexmail.ee/v1/messages \
  -H "X-API-Key: am_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"from":"hello@example.com","to":["user@example.com"],"subject":"Welcome","html":"<h1>Hello</h1>"}'

Endpoints

Messages

MethodPathDescriptionAuth
POST/v1/messagesQueue a transactional or campaign sendAPI Key
GET/v1/messagesList recent message activityAPI Key
GET/v1/messages/{id}Get message status and delivery stateAPI Key
POST/v1/messages/{id}/cancelCancel a queued or scheduled messageAPI Key
GET/v1/eventsRetrieve delivery, open, click event historyAPI Key
  • Use idempotency keys when your sender may retry the same write request.
  • Keep API keys server-side; never embed them in browser code.
  • Use the API Explorer to test payloads before wiring them into your application.

API Versioning

All API paths are prefixed with /v1/. The base URL is https://api.apexmail.ee/v1/. Unversioned paths are rejected.

Version Lifecycle

v1 (stable) ──> v2 (released) ──> v1 deprecated ──> v1 sunset
                    │                    │                │
              (announce)           (6mo warning)     (removed)

What Requires a New Version

A new major version (/v2/) is required for backward-incompatible changes:

  • Removing or renaming response fields
  • Changing required parameters to optional (or vice versa)
  • Changing the meaning of an existing field
  • Removing an endpoint
  • Changing authentication requirements
  • Altering error response structure

What Does NOT Require a New Version

These backward-compatible changes may appear within /v1/:

  • Adding new optional fields to request or response bodies
  • Adding new endpoints
  • Extending enumerations with new values
  • Changing response header values (not structure)

Deprecation Policy

PhaseDurationWhat Happens
AnnounceAt version bumpDeprecation: true header added to responses
Warning6 months minimumSunset: Sat, 01 Jan 2027 00:00:00 GMT header on every response, plus Warning: 299 - "v1 is deprecated, migrate to v2"
SunsetAfter warning periodEndpoint returns 410 Gone

Clients should monitor Sunset and Deprecation headers and migrate before the sunset date. Review the API changelog for breaking change history.

Pagination

List endpoints (e.g. GET /v1/messages, GET /v1/events) support two pagination modes:

Page / Limit

Use ?page= (1-indexed) and ?limit= (default 50, max 200). Responses include a Link header with RFC 5988 rel="next", rel="prev", rel="first", and rel="last" URLs.

Example: GET /v1/messages?page=2&limit=50

Cursor-based

Use ?limit= and ?cursor= parameters. Responses include:

  • limit — maximum items per page (default 50, max 200).
  • cursor — opaque token for the next page of results (absent on last page).
  • has_more — boolean indicating whether additional results exist.

Example: GET /v1/events?limit=100&cursor=eyJpZCI6ImV2dF8xMjMifQ%3D%3D

Error Codes

All error responses share a common JSON envelope:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message",
    "details": { "...": "Field-level or contextual detail (optional)" }
  }
}

HTTP Status Codes

CodeMeaningDescription
400Bad RequestInvalid JSON, missing required fields, or parameter validation failure. Body carries code and optional details with field-level messages.
401UnauthorizedMissing, expired, or invalid X-API-Key header. Body: {"error":{"code":"UNAUTHORIZED","message":"..."}}.
403ForbiddenValid credentials but insufficient scopes for the requested operation. Body includes code: "FORBIDDEN" or code: "INSUFFICIENT_SCOPES".
404Not FoundThe requested resource does not exist or belongs to a different tenant. Body: {"error":{"code":"NOT_FOUND"}}.
408Request TimeoutThe request took too long to complete and was terminated by the server. Retry with backoff.
409ConflictResource already exists or the requested mutation conflicts with current state. May indicate an idempotency conflict (differing payload under the same key).
410GoneThe resource has been intentionally removed (e.g. a cancelled message, a sunset API version).
413Payload Too LargeThe request body exceeds the maximum allowed size.
422Unprocessable EntityBusiness rule violation (e.g. unverified sending domain, invalid template render). Retrying without changes will not succeed.
429Too Many RequestsRate limit or quota exceeded. Includes Retry-After header (seconds).
500Internal Server ErrorAn unexpected server-side failure. Retry with exponential backoff.
503Service UnavailableA dependency is temporarily unavailable. Retry with backoff.
504Gateway TimeoutAn upstream service timed out while fulfilling the request. Retry with backoff.

Canonical Error Codes

The error.code field is the programmatic contract. Key codes:

CodeStatusMeaning
UNAUTHORIZED401Authentication missing or invalid
TOKEN_EXPIRED401Access or session token has expired
INVALID_API_KEY401API key is invalid or revoked
FORBIDDEN403Authenticated but not permitted
INSUFFICIENT_SCOPES403Missing required scopes
VALIDATION_ERROR400Field-level validation failure
INVALID_INPUT400Semantically invalid input
NOT_FOUND404Resource does not exist
CONFLICT409Resource conflict
IDEMPOTENCY_CONFLICT409Idempotency key with differing payload
RATE_LIMIT_EXCEEDED429Rate limit threshold exceeded
QUOTA_EXCEEDED429Account or tenant quota exceeded
DOMAIN_NOT_VERIFIED422Sending domain not verified
INVALID_TEMPLATE400Template payload is invalid
INTERNAL_ERROR500Unexpected server failure
SERVICE_UNAVAILABLE503Service temporarily unavailable
GATEWAY_TIMEOUT504Upstream dependency timed out

Retry only transient errors (RATE_LIMIT_EXCEEDED, SERVICE_UNAVAILABLE, GATEWAY_TIMEOUT) with exponential backoff. Match on error.code, not on error.message text.

Rate Limits

Rate limits apply per API key per second. The server returns 429 Too Many Requests when exceeded, with Retry-After header.

PlanRequests/sBatch size
Free10100
Developer100500
Pro100500
Growth5001,000
Business5001,000
EnterpriseCustomCustom

Rate-limit headers returned on every response:

  • X-RateLimit-Limit — requests per second allowed
  • X-RateLimit-Remaining — remaining in current window
  • X-RateLimit-Reset — Unix timestamp when the window resets

Idempotency

Send Idempotency-Key: <unique-value> to safely retry write requests (POST, PUT, PATCH, DELETE). Idempotency is scoped to the authenticated API key — two different keys cannot replay each other’s requests.

Key Format

Use a UUID v4 or a unique string of your choice. Recommended pattern:

Idempotency-Key: 7a8e3b1c-9d4f-4e2a-b6c8-1d2e3f4a5b6c

Retention

Keys are retained for 24 hours. A repeat request with the same key within that window returns the original response (status code, headers, and body) without re-executing the operation. After 24 hours, the key expires and a new request with the same key is treated as a fresh operation.

If two concurrent requests arrive with the same key, the first one to complete determines the cached response; the second receives that same cached response.

Idempotency Conflict

If you reuse a key with a different request payload than the original, the API returns 409 Conflict with code: "IDEMPOTENCY_CONFLICT". This prevents accidental misuse of a key for a different operation.

{
  "error": {
    "code": "IDEMPOTENCY_CONFLICT",
    "message": "Idempotency key already used with a different request body"
  }
}

Webhooks

Webhook payloads are signed with HMAC-SHA256. Verify signatures using X-ApexMail-Signature header:

t=1690000000,v1=hmac_sha256_value

Payload: {timestamp}.{raw_body} signed with your webhook secret. Events include: email.sent, email.delivered, email.opened, email.clicked, email.bounced, email.complained.

SDKs

LanguagePackageInstallMinimum Runtime
Node.js@apexmail/nodenpm install @apexmail/nodeNode.js 20+
Pythonapexmailpip install apexmailPython 3.10+
Goapexmail-gogo get github.com/apexmail/apexmail-goGo 1.21+
PHPapexmail-phpcomposer require apexmail/apexmail-phpPHP 8.2+
Rubyapexmailgem install apexmailRuby 3.0+
Javaapexmail-javaMaven: ee.apexmail:apexmail-java:1.0.0Java 17+

All official SDKs require TLS 1.2+ for API connections. SDK versions follow semantic versioning — pin major versions in production.