What you will build

If you send from dedicated IP addresses, their reputation is yours to watch: one blocklisting and a share of your mail goes to spam or bounces. Sender Audit checks each verified IP every hour against the blocklists, and every day for the rest (reverse DNS, SMTP banner, SenderScore). The API lets you:

  • register your IPs automatically when you provision a new server or a new pool at your ESP;
  • pull a reputation score per IP into your monitoring;
  • alert as soon as an address is listed.
Endpoint reference This guide shows which calls to chain and what to read in each response. Every field is documented in the API reference, generated from the API itself.

Before you start

  • An API key created from the dashboard, under Account, Developer, on a plan that includes API access.
  • Public IPv4 addresses with a reverse DNS (PTR): ownership is proved on the hostname the IP points to.
  • Room in your quota. GET /ips/quota returns monitored_ips_used against monitored_ips_limit. The quota counts addresses: a /24 weighs 254.
export SA_KEY="sa_live_your_key"
export SA_API="https://senderaudit.com/api/v1"
import os, requests

API = "https://senderaudit.com/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SA_KEY']}"

An entry can be a single IP (203.0.113.25), a CIDR block up to a /24 (203.0.113.0/24) or a range of up to 256 addresses (203.0.113.10-203.0.113.40). IP endpoints identify an entry by its id, returned when you add it; GET /ips lists them.

Step 1: preview

POST /ips/preview resolves the reverse DNS of the addresses without adding anything, and tells you where the proof can go.

curl -s -X POST "$SA_API/ips/preview" \
  -H "Authorization: Bearer $SA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ip_value": "203.0.113.25"}'
{
  "ip_value": "203.0.113.25",
  "input_type": "single",
  "ip_count": 1,
  "ptr_hostnames": ["mail25.example.com"],
  "root_domains": ["example.com"],
  "dns_targets": { "ptr": ["mail25.example.com"], "root": ["example.com"] },
  "email_targets": { "ptr": { "postmaster": ["postmaster@mail25.example.com"] }, "root": { "postmaster": ["postmaster@example.com"] } }
}

No PTR means no possible proof: set up the reverse DNS at your hosting provider first.

Step 2: add and verify

POST /ips/initiate adds the entry. Like domains, two methods: dns (a TXT record) or email (a link sent to postmaster, abuse, contact, support or admin, given as target).

For a block or a range, scope picks where the proof goes:

  • ptr (default): on every PTR hostname of the block;
  • root: on the root domains those hostnames belong to, usually one. Much simpler for a /24.
ip = session.post(f"{API}/ips/initiate", json={
    "ip_value": "203.0.113.0/24", "method": "dns", "scope": "root",
}).json()
ip_id = ip["id"]
print("Publish on:", ip["verification_hosts"])
print("TXT value:", f'senderaudit_verify:{ip["verification_token"]}')

Publish a TXT record with the value senderaudit_verify: followed by the token on each host listed in verification_hosts, then call POST /ips/{id}/verify:

curl -s -X POST "$SA_API/ips/$IP_ID/verify" -H "Authorization: Bearer $SA_KEY"
AnswerMeaning
{"status": "verified"}Done, the first checks start right away
{"status": "pending", "message": "3/5 host(s) verified"}Some hosts still miss the record. Each call checks every host again, so keep the records already published and add the missing ones
400No PTR to check against, or email method (verification then happens through the link)

Step 3: read the IP’s health

GET /ips/{id}/health-card has the same shape as the domain health card: one score, categories, and actions sorted by what they bring.

card = session.get(f"{API}/ips/{ip_id}/health-card").json()
print(card["ip_value"], card["overall"], "/ 100", "CRISIS" if card["crisis"] else "")
for action in card["actions"][:3]:
    print(f'+{action["gain"]:g}  {action["label"]}: {action["summary"]}')
FieldWhat it is
overallScore out of 100
crisistrue when the IP is on a critical blocklist. Act now
categoriesreputation (blocklists), identity (PTR and forward-confirmed reverse DNS), senderscore, geoip, diagnostics (port 25 and SMTP banner), each with its items and their status pass, warn or fail
actionsEvery item below its maximum, sorted by gain
ptr_mapFor a block or a range, the PTR of each address

Alerting on blocklists

GET /ips/{id}/health-history returns the last 90 days of measures, each with overall, rbl, senderscore and crisis. Comparing the last two points is enough to catch a listing or a SenderScore drop:

history = session.get(f"{API}/ips/{ip_id}/health-history").json()["history"]
if len(history) >= 2:
    before, now = history[-2], history[-1]
    if now["crisis"] and not before["crisis"]:
        print("ALERT: critical blocklisting")
    elif (now["rbl"] or 0) < (before["rbl"] or 0):
        print("Blocklist score dropped:", before["rbl"], "->", now["rbl"])

Sender Audit also emails the account when one of its IPs gets listed. The API is for routing that signal into your own tools.

Pausing and removing

Reserved to admin and owner members: a key created by a plain member gets 403.

  • PATCH /ips/{id}/monitoring pauses or resumes an entry. Resuming checks the quota again: a block only comes back if all of its addresses fit.
  • DELETE /ips/{id} removes the entry and its whole history, immediately and for good.

Limits and errors

SituationAnswer
Block larger than a /24, range over 256 addresses, private or reserved address422
Too many calls on an endpoint429, with a Retry-After header
Entry id unknown on the account404
Write call with a plain member’s key403
Missing or invalid key401
Plan without API access403