Authentication
All API requests require an API key passed in the Authorization header.
API keys are created from your dashboard. Each key is tied to your account and shares its scan quota.
Authorization: Bearer sa_live_your_api_key_here
Security: Keep your API key secret. Do not expose it in client-side code, public repositories, or logs. If compromised, delete it immediately from your dashboard and create a new one.
Rate limits
| Scan creation | 5 requests / minute + daily plan quota |
| Other endpoints | 30 requests / minute |
When a limit is exceeded, the API returns 429 Too Many Requests.
Errors
Errors return a JSON body with a detail field:
{ "detail": "Invalid API key" }
| Code | Meaning |
|---|---|
| 400 | Bad request (missing or invalid parameters) |
| 401 | Invalid or missing API key |
| 404 | Resource not found |
| 429 | Rate limit or daily quota exceeded |
| 500 | Internal server error |
Scans
/scans
Create a new email scan. Returns a unique email address to send your test email to.
The request body can be empty ({}).
Example
curl -X POST https://senderaudit.com/api/v1/scans \
-H "Authorization: Bearer sa_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{}'
Response 201 Created
{
"local_part": "s1a2b3c4",
"email_address": "s1a2b3c4@senderaudit.com",
"status": "pending",
"created_at": "2026-03-25T14:32:00",
"view_count": 0
}
Next step: Send an email to the returned email_address. The scan starts automatically upon reception. Use local_part to check status and fetch results.
/scans/{local_part}
Check the status of a scan. Poll this endpoint until status is completed or failed.
Path parameters
| Param | Description |
|---|---|
| local_part | The local part of the scan email address (e.g. s1a2b3c4) |
Example
curl https://senderaudit.com/api/v1/scans/s1a2b3c4 \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
{
"local_part": "s1a2b3c4",
"email_address": "s1a2b3c4@senderaudit.com",
"status": "completed",
"created_at": "2026-03-25T14:32:00",
"received_at": "2026-03-25T14:32:12",
"processed_at": "2026-03-25T14:32:45",
"processing_time_ms": 33000,
"view_count": 0
}
Status values
| pending | Waiting for the email to arrive |
| processing | Email received, analysis in progress |
| completed | Analysis finished, results ready |
| failed | Analysis failed |
/scans/{local_part}/results
Retrieve the full analysis results for a completed scan.
Example
curl https://senderaudit.com/api/v1/scans/s1a2b3c4/results \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
Returns 202 Accepted if analysis is still in progress.
Top-level fields
| scan_id | string | Local part identifier |
| status | string | "completed" |
| scores | object | Overall score (0-100), spam score, breakdown |
| object | Sender, subject, size | |
| server | object | Sending server IP, hostname, protocol |
| authentication | object | SPF, DKIM, DMARC, BIMI, DNSSEC, MX, PGP |
| security | object | TLS encryption, tracking domains |
| reputation | object | RBL listings, AbuseIPDB score |
| content | object | SpamAssassin, Rspamd, links, images analysis |
scores object
| overall | integer | Composite deliverability score (0-100) |
| spam | float|null | SpamAssassin score (lower is better) |
| breakdown | array | Per-category score details |
authentication object
| spf | object | result, record, domain, dns_lookups |
| dkim | object | result, signatures[], selector, domain |
| dmarc | object | result, policy, record, alignment |
| bimi | object|null | BIMI record, logo URL, VMC certificate |
| dnssec | object|null | enabled, domain |
| mx | object|null | MX records for sender domain |
reputation.rbl object
| listed_count | integer | Number of blocklists listing this IP |
| total_checked | integer | Total blocklists checked |
| listings | array | Details of each listing |
Tip: The response is extensive. For a quick integration, focus on scores.overall (0-100 deliverability score), authentication.spf.result, authentication.dkim.result, and authentication.dmarc.result.
/scans/{local_part}/optimized-images
Download a ZIP archive containing WebP-optimized versions of all images found in the email. Available only for a limited time after analysis.
Path parameters
| Param | Description |
|---|---|
| local_part | The local part of the scan email address (e.g. s1a2b3c4) |
Example
curl -OJ https://senderaudit.com/api/v1/scans/s1a2b3c4/optimized-images \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
Returns a binary application/zip file containing optimized images in WebP format.
The Content-Disposition header provides the suggested filename: senderaudit-{local_part}-optimized.zip
Note: Optimized images are cached temporarily after analysis. If the bundle has expired, this endpoint returns 404.
Account
/auth/me
Retrieve the authenticated user's profile.
Example
curl https://senderaudit.com/api/v1/auth/me \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
{
"id": 1,
"email": "user@example.com",
"is_verified": true,
"active_account_id": 1,
"memberships": [
{ "account_id": 1, "role": "owner" }
],
"created_at": "2026-01-15T10:00:00"
}
/auth/me/scan-usage
Check your daily scan quota and current usage.
Example
curl https://senderaudit.com/api/v1/auth/me/scan-usage \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
{
"scans_used_today": 3,
"scans_limit": 20,
"scans_remaining": 17
}
| Field | Type | Description |
|---|---|---|
| scans_used_today | integer | Scans consumed today (UTC) |
| scans_limit | integer | Maximum scans per day for your plan |
| scans_remaining | integer | Remaining scans today (-1 = unlimited) |
/auth/me/scans
List all scans for your account with pagination and optional domain filtering.
Query parameters
| Param | Type | Description |
|---|---|---|
| limit | integer | Number of results (1-100, default 50) |
| offset | integer | Pagination offset (default 0) |
| domain | string | Optional. Filter scans by sender domain (e.g. example.com) |
| status | string | Optional. Filter by status: pending, processing, completed, failed, or all. Default: excludes pending. |
Example
curl "https://senderaudit.com/api/v1/auth/me/scans?limit=10&offset=0" \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
{
"scans": [
{
"email_address": "s1a2b3c4@senderaudit.com",
"status": "completed",
"overall_score": 87,
"from_header": "noreply@example.com",
"subject": "Welcome!",
"spam_score": 1.2,
"created_at": "2026-03-25T14:32:00",
"processed_at": "2026-03-25T14:32:45"
}
],
"total": 42
}
| Field | Type | Description |
|---|---|---|
| scans | array | List of scan objects |
| total | integer | Total number of matching scans (for pagination) |
| overall_score | integer | null | Deliverability score (0-100) |
| from_header | string | null | Sender address from email header |
| spam_score | number | null | SpamAssassin score |
API keys
Manage API keys for your account. Maximum 5 keys per account.
/auth/me/api-keys
Create a new API key. The raw key is returned only once.
Request body
{ "name": "My integration" }
Response 201
{
"id": 1,
"name": "My integration",
"key": "sa_live_abc123...",
"key_hint": "c123",
"created_at": "2026-03-25T14:00:00"
}
Important: The key field is only returned at creation time. Store it securely.
/auth/me/api-keys
List all API keys for your account. Full keys are never exposed.
Response 200
{
"keys": [
{
"id": 1,
"name": "My integration",
"key_hint": "c123",
"is_active": true,
"created_at": "2026-03-25T14:00:00",
"last_used_at": "2026-04-05T09:15:00"
}
]
}
/auth/me/api-keys/{key_id}
Permanently delete an API key. This action is irreversible.
Response 200
{ "deleted": true }
Seed addresses
Seed addresses are persistent scan addresses that can receive multiple test emails over time, making it easy to track improvements.
/auth/me/seed-addresses
Create a new seed address.
Request body
{ "name": "Production newsletter" }
Response 201
{
"id": 1,
"name": "Production newsletter",
"email": "seed-x7k9m2@senderaudit.com",
"created_at": "2026-03-25T14:00:00"
}
/auth/me/seed-addresses
List all seed addresses for your account.
Response 200
{
"addresses": [
{
"id": 1,
"name": "Production newsletter",
"email": "seed-x7k9m2@senderaudit.com",
"is_active": true,
"created_at": "2026-03-25T14:00:00",
"last_used_at": "2026-04-01T08:30:00"
}
],
"limit": 10
}
/auth/me/seed-addresses/{addr_id}
Delete a seed address.
Response 200
{ "deleted": true }
Domains
/domains
List all domains registered on your account.
Example
curl https://senderaudit.com/api/v1/domains \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
[
{
"id": "a1b2c3d4-...",
"domain": "example.com",
"verification_method": "dns",
"is_verified": true,
"verified_at": "2026-03-20T10:00:00",
"created_at": "2026-03-20T09:50:00",
"monitored_hosts_count": 3
}
]
| Field | Type | Description |
|---|---|---|
| id | string | Public UUID identifier |
| domain | string | Domain name |
| verification_method | string | "dns" or "email" |
| is_verified | boolean | Whether domain ownership is confirmed |
| monitored_hosts_count | integer | Number of monitored hosts for this domain |
/domains/initiate
Start domain ownership verification. Choose between DNS (TXT record) or email-based verification.
Request body
| Field | Type | Description |
|---|---|---|
| domain | string | Domain to verify (e.g. example.com) |
| method | string | "dns" or "email" |
| target | string | null | Required if method="email": "postmaster", "abuse", "contact", "support", or "admin" |
| lang | string | Verification email language: "en" (default), "fr" |
Example
curl -X POST https://senderaudit.com/api/v1/domains/initiate \
-H "Authorization: Bearer sa_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "domain": "example.com", "method": "dns" }'
Response 201 Created
{
"id": "a1b2c3d4-...",
"domain": "example.com",
"verification_method": "dns",
"verification_token": "senderaudit_verify:abc123def456",
"is_verified": false,
"created_at": "2026-03-25T14:00:00",
"monitored_hosts_count": 0
}
DNS method: Add a TXT record with the verification_token value to your domain, then call POST /domains/{id}/verify.
/domains/{domain_id}/verify
Attempt to verify domain ownership. For DNS verification, checks that the TXT record is present.
Path parameters
| Param | Description |
|---|---|
| domain_id | The public UUID of the domain |
Example
curl -X POST https://senderaudit.com/api/v1/domains/a1b2c3d4-.../verify \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
{
"status": "verified"
}
| Field | Type | Description |
|---|---|---|
| status | string | "verified" or "conflict" |
| existing_owner_email | string | null | Obfuscated email of existing owner (if conflict) |
/domains/{domain_id}
Remove a domain and all its associated monitoring data.
Example
curl -X DELETE https://senderaudit.com/api/v1/domains/a1b2c3d4-... \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 204 No Content
Empty response body.
/domains/quota
Check your domain and monitored hosts quota.
Example
curl https://senderaudit.com/api/v1/domains/quota \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
{
"plan": "pro",
"monitored_hosts_used": 5,
"monitored_hosts_total": 8,
"monitored_hosts_limit": 20
}
| Field | Type | Description |
|---|---|---|
| plan | string | Current plan name |
| monitored_hosts_used | integer | Active monitored hosts |
| monitored_hosts_total | integer | Total hosts (including inactive) |
| monitored_hosts_limit | integer | Maximum hosts allowed by plan |
Monitoring
/domains/{domain_id}/monitoring
Retrieve current DNS records and change timeline for a verified domain.
Path parameters
| Param | Description |
|---|---|
| domain_id | The public UUID of the domain |
Example
curl https://senderaudit.com/api/v1/domains/a1b2c3d4-.../monitoring \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
{
"records": [
{
"id": 1,
"record_type": "SPF",
"record_key": "example.com",
"record_value": "v=spf1 include:_spf.google.com ~all",
"first_seen_at": "2026-03-20T10:00:00",
"last_seen_at": "2026-04-05T06:00:00",
"removed_at": null
}
],
"timeline": [
{
"record_type": "DMARC",
"record_key": "_dmarc.example.com",
"record_value": "v=DMARC1; p=reject;",
"first_seen_at": "2026-03-22T10:00:00",
"removed_at": "2026-04-01T06:00:00"
}
],
"building": false
}
/domains/{domain_id}/monitored-items
List all monitored hosts (IPs, domains) discovered from scan results for this domain.
Example
curl https://senderaudit.com/api/v1/domains/a1b2c3d4-.../monitored-items \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
{
"hosts": [
{
"id": 1,
"host_value": "example.com",
"is_active": true,
"types": [
{ "host_type": "header_from", "discovered_by": "scan" }
],
"created_at": "2026-03-20T10:00:00"
},
{
"id": 2,
"host_value": "198.51.100.25",
"is_active": true,
"types": [
{ "host_type": "sending_ip", "discovered_by": "scan" }
],
"created_at": "2026-03-20T10:00:00"
}
]
}
/domains/{domain_id}/monitored-items/{item_id}
Toggle the active state of a monitored host. Deactivated hosts stop being monitored and free up quota.
Path parameters
| Param | Description |
|---|---|
| domain_id | The public UUID of the domain |
| item_id | The monitored host ID |
Example
curl -X PATCH https://senderaudit.com/api/v1/domains/a1b2c3d4-.../monitored-items/2 \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
{
"id": 2,
"is_active": false
}
Note: The main domain host (header_from) cannot be deactivated. Reactivation is subject to your monitored hosts quota.
/domains/{domain_id}/score-summary
Get the average deliverability score, percentile ranking, and certificate eligibility for a domain.
Example
curl https://senderaudit.com/api/v1/domains/a1b2c3d4-.../score-summary \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
{
"avg_score": 92,
"scan_count": 8,
"percentile": 87,
"eligible_certificate": true,
"certificate": {
"token": "cert_abc123",
"avg_score": 92,
"updated_at": "2026-04-05T06:00:00"
}
}
| Field | Type | Description |
|---|---|---|
| avg_score | integer | null | Average score across recent scans (null if <3 scans) |
| scan_count | integer | Number of recent scans used for scoring |
| percentile | integer | null | Percentage of domains scoring below yours |
| eligible_certificate | boolean | True if score >= 80 and scan_count >= 3 |
| certificate | object | null | Certificate details (token, avg_score, updated_at) |
IPs
/ips
List all IPs registered on your account.
Example
curl https://senderaudit.com/api/v1/ips \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
[
{
"id": "a1b2c3d4-...",
"ip_value": "51.178.83.230",
"input_type": "single",
"ip_count": 1,
"root_domains": ["example.com"],
"verification_method": "dns",
"is_verified": true,
"verified_at": "2026-04-05T10:00:00",
"created_at": "2026-04-05T09:50:00"
}
]
| Field | Type | Description |
|---|---|---|
| id | string | Public UUID identifier |
| ip_value | string | IP address, CIDR or range |
| input_type | string | "single", "cidr" or "range" |
| ip_count | integer | Number of individual IPs in the group |
| root_domains | array | null | Root domains extracted from PTR records |
| verification_method | string | "dns" or "email" |
| is_verified | boolean | Whether IP ownership is confirmed |
| verified_at | string | null | ISO timestamp of verification |
/ips/preview
Resolve PTR records for an IP and return available verification targets. Use this before /ips/initiate to preview which hostnames and email addresses are available for verification.
Request body
| Field | Type | Description |
|---|---|---|
| ip_value | string | IP address, CIDR (max /24) or range (max 256) |
Example
curl -X POST https://senderaudit.com/api/v1/ips/preview \
-H "Authorization: Bearer sa_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "ip_value": "51.178.83.230" }'
Response 200 OK
{
"ip_value": "51.178.83.230",
"input_type": "single",
"ip_count": 1,
"ptr_hostnames": ["mail.example.com"],
"root_domains": ["example.com"],
"ptr_map": { "51.178.83.230": "mail.example.com" },
"is_multi": false,
"dns_targets": {
"ptr": ["mail.example.com"],
"root": ["example.com"]
},
"email_targets": {
"ptr": {
"postmaster": ["postmaster@mail.example.com"],
"abuse": ["abuse@mail.example.com"],
"contact": ["contact@mail.example.com"],
"support": ["support@mail.example.com"],
"admin": ["admin@mail.example.com"]
},
"root": {
"postmaster": ["postmaster@example.com"],
"abuse": ["abuse@example.com"],
"contact": ["contact@example.com"],
"support": ["support@example.com"],
"admin": ["admin@example.com"]
}
}
}
| Field | Type | Description |
|---|---|---|
| ptr_hostnames | array | PTR hostnames resolved for the IP(s) |
| root_domains | array | Root domains extracted from PTR hostnames |
| ptr_map | object | Mapping of each IP to its PTR hostname |
| dns_targets | object | DNS verification hostnames grouped by scope (ptr / root) |
| email_targets | object | Email verification addresses grouped by scope and target role |
/ips/initiate
Start IP ownership verification. Choose between DNS (TXT record on the PTR hostname) or email-based verification.
Request body
| Field | Type | Description |
|---|---|---|
| ip_value | string | IP address, CIDR (max /24) or range (max 256) |
| method | string | "dns" or "email" |
| target | string | null | Required for email: "postmaster", "abuse", "contact", "support", or "admin" |
| scope | string | "ptr" (default) - verify on PTR hostname, or "root" - verify on root domain. Single IPs always use "ptr". |
| lang | string | Verification email language: "en" (default), "fr", "de", "es" |
Example
curl -X POST https://senderaudit.com/api/v1/ips/initiate \
-H "Authorization: Bearer sa_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "ip_value": "51.178.83.230", "method": "dns", "scope": "ptr" }'
Response 201 Created
{
"id": "a1b2c3d4-...",
"ip_value": "51.178.83.230",
"input_type": "single",
"ip_count": 1,
"root_domains": ["example.com"],
"ptr_map": { "51.178.83.230": "mail.example.com" },
"verification_method": "dns",
"verification_token": "senderaudit_verify:abc123def456",
"verification_hosts": ["mail.example.com"],
"is_verified": false,
"verified_at": null,
"created_at": "2026-04-05T09:50:00"
}
DNS method: Add a TXT record with the verification_token value on each host listed in verification_hosts, then call POST /ips/{ip_id}/verify.
/ips/{ip_id}/verify
Attempt to verify IP ownership via DNS TXT record. Checks that the TXT record is present on each verification host.
Path parameters
| Param | Description |
|---|---|
| ip_id | The public UUID of the IP |
Example
curl -X POST https://senderaudit.com/api/v1/ips/a1b2c3d4-.../verify \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
{
"status": "verified"
}
| Field | Type | Description |
|---|---|---|
| status | string | "verified", "pending" or "failed" |
| message | string | null | Progress details when status is "pending" (e.g. "1/2 host(s) verified") |
Email method: For email-based verification, the user receives a link at GET /ips/confirm/{token}. No manual call to this endpoint is needed.
/ips/{ip_id}/health-card
Retrieve the structured health card for a verified IP. Returns a 5-category score breakdown out of 100.
Path parameters
| Param | Description |
|---|---|
| ip_id | The public UUID of the IP |
Example
curl https://senderaudit.com/api/v1/ips/a1b2c3d4-.../health-card \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 200 OK
{
"overall": 85,
"max": 100,
"crisis": false,
"categories": [
{
"key": "reputation",
"label": "Reputation",
"score": 30,
"max": 30,
"items": [
{
"key": "rbl",
"label": "Blocklists (RBL)",
"status": "pass",
"score": 30,
"max": 30,
"value": "0 / 45 - Clean",
"summary": "Not listed on any of 45 monitored blocklists",
"details": { "listings": [...], "crisis": false }
}
]
},
{ "key": "identity", "label": "Identity", "score": 20, "max": 20, "items": [...] },
{ "key": "connectivity", "label": "Connectivity", "score": 15, "max": 20, "items": [...] },
{ "key": "senderscore", "label": "SenderScore", "score": 16, "max": 20, "items": [...] },
{ "key": "geoip", "label": "GeoIP", "score": 10, "max": 10, "items": [...] }
],
"actions": [
{
"category": "connectivity",
"item": "smtp_banner",
"label": "SMTP Banner",
"status": "fail",
"gain": 10,
"summary": "No SMTP banner"
}
],
"sparkline": [80, 82, 85, 85],
"ip_value": "51.178.83.230",
"input_type": "single",
"ip_count": 1,
"ptr_map": { "51.178.83.230": "mail.example.com" },
"root_domains": ["example.com"]
}
Categories (max 100)
| reputation | 30 pts | RBL blocklist presence (weighted by provider impact) |
| identity | 20 pts | PTR record (10) + Forward-Confirmed rDNS (10) |
| connectivity | 20 pts | Port 25 reachability (10) + SMTP banner (10) |
| senderscore | 20 pts | SenderScore reputation (0-100 mapped to 0-20). N/A if unavailable. |
| geoip | 10 pts | GeoIP and ASN data availability |
Item status values
| pass | Check passed (score >= 90% of max) |
| warn | Partial pass (score >= 50% of max) |
| fail | Check failed (score < 50% of max) |
| na | Not applicable (e.g. SenderScore unavailable) |
Crisis mode: If the IP is listed on a critical blocklist (e.g. Spamhaus), crisis is true and overall is forced to 0.
SenderScore N/A: When SenderScore data is not available for an IP, the category max is set to 0 and the overall score is normalized to /80 instead of /100. It does not penalize the score.
/ips/{ip_id}
Remove an IP and all its associated monitoring data (DNS records, health scores).
Example
curl -X DELETE https://senderaudit.com/api/v1/ips/a1b2c3d4-... \
-H "Authorization: Bearer sa_live_YOUR_KEY"
Response 204 No Content
Empty response body.
Typical flow
Create a scan
POST /scans - get back the target email address.
Send your email
Send a test email to the returned address using your mail infrastructure.
Poll for completion
GET /scans/{local_part} - poll every 5 seconds until status is completed.
Fetch results
GET /scans/{local_part}/results - retrieve the full deliverability report.
Complete example (bash)
#!/bin/bash
API_KEY="sa_live_YOUR_KEY"
BASE="https://senderaudit.com/api/v1"
# 1. Create scan
RESPONSE=$(curl -s -X POST "$BASE/scans" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{}')
LOCAL=$(echo "$RESPONSE" | jq -r '.local_part')
EMAIL=$(echo "$RESPONSE" | jq -r '.email_address')
echo "Send your email to: $EMAIL"
echo "Waiting for analysis..."
# 2. Send your email (via your SMTP server)
# echo "Test" | mail -s "Deliverability check" "$EMAIL"
# 3. Poll until completed
while true; do
STATUS=$(curl -s "$BASE/scans/$LOCAL" \
-H "Authorization: Bearer $API_KEY" | jq -r '.status')
[ "$STATUS" = "completed" ] && break
[ "$STATUS" = "failed" ] && echo "Scan failed" && exit 1
sleep 5
done
# 4. Get results
curl -s "$BASE/scans/$LOCAL/results" \
-H "Authorization: Bearer $API_KEY" | jq '.scores'