Skip to main content

API reference

Deliverability API

Check a sending domain's DNS authentication and IP reputation, then retrieve checks owned by your account. Send your API key in the X-Api-Key header on every request.

Run a deliverability check

POST/v1/deliverability/check

Inspect MX, SPF, DKIM, DMARC, reverse DNS and supported DNS blocklists. A new check costs 10 credits.

Request body

ParameterTypeRequiredDescription
domainstringRequiredSending domain, such as example.com
sending_ipstringOptionalIPv4 address for PTR and blocklist checks. If omitted, PTR is skipped and blocklists use an IP resolved from the domain's MX records.
refreshbooleanOptionalSet to true to bypass the 24-hour cache and buy a new 10-credit check.

These are the only accepted fields. Unknown fields and a non-boolean refresh return HTTP 400 before any history lookup, DNS work or credit movement. sending_ip accepts IPv4 only; null, blank, hostname and IPv6 values are rejected before charging. Omit the field to use MX discovery.

Request
curl -X POST https://api.kawaa.com/v1/deliverability/check \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "example.com",
    "sending_ip": "203.0.113.10"
  }'
Response
{
  "success": true,
  "data": {
    "domain": "example.com",
    "score": 92,
    "grade": "A",
    "checked_at": "2026-09-14T12:00:00.000Z",
    "check_id": "38c271db-97c4-4c0f-88df-a975027240c4",
    "dns": {
      "mx_records": [{ "priority": 10, "host": "mail.example.com", "ip": null }],
      "has_valid_mx": true,
      "mx_score": 100
    },
    "authentication": {
      "spf": { "status": "pass", "record": "v=spf1 -all", "score": 100, "issues": [] },
      "dkim": { "status": "pass", "selectors_found": ["default"], "score": 100, "issues": [] },
      "dmarc": { "status": "pass", "record": "v=DMARC1; p=reject", "policy": "reject", "score": 100, "issues": [] }
    },
    "reputation": {
      "score": 100,
      "blacklists_checked": 9,
      "blacklists_inconclusive": 3,
      "blacklists_skipped": 0,
      "blacklists_total": 12,
      "blacklists_listed": 0,
      "listed_on": []
    },
    "server": {
      "accepts_mail": null,
      "response_time_ms": null,
      "supports_tls": null,
      "tls_version": null,
      "reverse_dns": "mail.example.com"
    },
    "spam_assessment": { "score": 8, "spam_likelihood": "low", "factors": [] },
    "recommendations": [],
    "credits_used": 10,
    "credits_remaining": 490
  }
}

Cache and charging: a matching domain and sending-IP result from the previous 24 hours returns with cached: true and costs 0 credits, even when the account has fewer than 10 credits. Set refresh: true only when you intend to purchase a new check.

Safe failures: if the result history cannot be read, the API returns 503 before charging. If every DNS lookup times out, it attempts to refund the 10-credit debit and returns 503. Other failures after a debit also attempt a refund and return 500.

Measured scope: the API does not probe an SMTP server today. The server acceptance, response-time and TLS fields are null because they are not measured, not because those checks failed. A reputation score of null likewise means no blocklist returned a verdict.

Safe retries with the Idempotency-Key header

A new check is charged before its DNS work starts, so a request that times out or fails with a 5xx on the way back may already have been charged. To retry it safely, send an optional Idempotency-Key header: any opaque token of 1–255 printable ASCII characters with no spaces (a UUID is fine), unique per logical request, and send the same key again when you retry.

cURL
curl -X POST https://api.kawaa.com/v1/deliverability/check \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Idempotency-Key: 6f1c2a4e-3b7d-4e0a-9c11-2d8f5a7b9e01" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com", "refresh": true}'
  • A repeat after the check was stored answers 200 with the original check_id and result, "idempotent_replay": true, "credits_used": 0 and the credits_remaining recorded right after the original charge (left out if that record cannot be read). If the first attempt was paid but has not stored its result yet — it is still running, or its connection dropped — the repeat runs that paid check again without charging and returns the result, whatever your balance.
  • The debit and the key's claim commit together, so two requests racing with the same key are charged once. A refund and the key's release also commit together: once an attempt is refunded (every DNS lookup timed out, or the result could not be stored), the same key buys a new 10-credit check on its next use, and a refunded attempt is never refunded twice.
  • Reusing a key with a different domain, sending_ip or refresh value returns 409 IDEMPOTENCY_KEY_CONFLICT before anything is charged, and a malformed key returns 400 INVALID_IDEMPOTENCY_KEY. Keys are scoped to your account and to this endpoint, and are remembered for 30 days.
  • The 24-hour cache still applies first: without refresh, a keyed request for a domain you checked recently gets the free cached result, and that key keeps replaying the same result (with cached: true) for its 30 days even after the cache window passes. Without the header the endpoint behaves exactly as before.

List check history

GET/v1/deliverability/checks?limit=20

The rows considered by this request are sorted newest first. limit defaults to 20 and is capped at 100. The service reads at most 20 history pages. When truncated is false, all of your history was considered; when it is true, rows outside that bounded read were not considered and may be newer or older than the rows returned.

Response
{
  "success": true,
  "data": {
    "checks": [
      {
        "check_id": "38c271db-97c4-4c0f-88df-a975027240c4",
        "domain": "example.com",
        "sending_ip": "203.0.113.10",
        "score": 92,
        "grade": "A",
        "checked_at": "2026-09-14T12:00:00.000Z"
      }
    ],
    "count": 1,
    "total": 1,
    "truncated": false
  }
}

Get a check

GET/v1/deliverability/checks/{id}

Return one check with the same detailed result sections shown above. A missing ID, or an ID owned by another account, returns HTTP 404 without revealing whether another account has that record.