Documentation

DMPHub REST API

Single endpoint per verification track. Bearer-token auth, JSON in / JSON out. For interactive testing with your real keys, open the API Explorer inside the dashboard.

Authentication

Bearer token in the Authorization header. Generate keys under Settings → API Keys.

Rate limits

Per-key limit visible in the response headers (X-RateLimit-*). Hitting the limit returns HTTP 429.

Versioning

The current API version is /v1. New versions ship under /v2 etc. — old versions keep working for 6 months after a new version ships.

How-to

Verify your first email in 5 steps

From zero to a graded result. Uses the live /v1/verify/email endpoint — the same one your dashboard uses under the hood.

  1. Step 1

    Generate an API key

    In the dashboard, open API Access → API Keys → Create key. Copy the value — it only shows once. Anyone holding it can verify on your behalf.

  2. Step 2

    Send your first request

    The endpoint is a single POST. Auth is a Bearer header. The body is one JSON field. Replace the email and the key with your own.

    -amber-200">"text-sky-300">curl -X POST https://api.dmphub.com/v1/verify/email \
      -H -amber-200">"Authorization: Bearer dmph_live_…" \
      -H -amber-200">"Content-Type: application/json" \
      -d -amber-200">'{-amber-200">"email":-amber-200">"jane@example.com"}'
  3. Step 3

    Read the result

    A 200 response is a completed verification. The shape is the same JSON the dashboard renders into a result row.

    {
      -200">"text-sky-300">-200">"email": -200">"jane@example.com",
      -200">"text-sky-300">-200">"status": -200">"valid",
      -200">"text-sky-300">-200">"grade": -200">"A",
      -200">"text-sky-300">-200">"score": 96,
      -200">"text-sky-300">-200">"checks": { -200">"text-sky-300">-200">"syntax": true, -200">"text-sky-300">-200">"mx": true, -200">"text-sky-300">-200">"smtp": true }
    }
  4. Step 4

    Run a batch from a CSV

    Use the list endpoint for > 1 email. It accepts a JSON array, returns a job_id, then you poll the job URL until it completes.

    -amber-200">"text-sky-300">curl -X POST https://api.dmphub.com/v1/verify/email/list \
      -H -amber-200">"Authorization: Bearer dmph_live_…" \
      -H -amber-200">"Content-Type: application/json" \
      -d -amber-200">'{-amber-200">"emails":[-amber-200">"a@x.com",-amber-200">"b@x.com",-amber-200">"c@x.com"]}'
  5. Step 5

    Watch the dashboard

    Every API call appears in the dashboard's recent activity feed (color-coded by channel) and counts against your monthly quota. No separate sync step.

Need a working key to test with? Log into the dashboard and open the API Explorer — it has the same endpoints, with a dark-themed code panel and your key auto-populated.

Grading rubric

Every result — quick-verify, batch, API — lands in the same A-F band. The score range below matches what we surface on the at-a-glance table; the meaning column tells you what to do about it. Channels are independent services; one channel’s grade has no influence on another’s.

Email

Combines tier-1 deliverability (syntax, DNS/MX, SMTP) with tier-2 risk signals. Score 0 = best, 100 = worst. The grade is separate from the status: status is valid (passed SMTP), invalid (failed a check), unknown (no SMTP response) or risky (spam trap, screamer, DND, organisation domain, honeypot, role account, disposable or catch-all).

GradeScore bandWhat it means
A+09Clean deliverable address with no risk signals.
A1019Deliverable, very low risk. Safe to send.
B+2029Deliverable but one weak signal (e.g. catch-all domain).
B3039Mostly clean, possibly on suppression-adjacent list.
C4059Mixed signals — review before sending.
D6079Likely deliverability problems. Use cautiously.
F80100Undeliverable, suppressed, or hard-failed.
Signals that feed this channel’s score
  • Syntax (RFC 5322)
  • Domain MX records
  • SMTP handshake (skipped in dev / when port 25 is blocked)
  • Bounce DB match (informational)
  • Spam-trap list match
  • Role-account flag (admin@, support@, …)
  • Disposable / temp-mail domain
  • Organisation domain — government, military, education, large corporate (risky)
  • Screamer / DND / honeypot (tenant suppression reasons)

Phone

Combines carrier lookup (Telnyx), HLR active/inactive flag, line type, and DNC registry presence. Phone has no "Valid/Invalid" string — grade IS the verdict.

GradeScore bandWhat it means
A019Active mobile or landline, no DNC hit, no disposable signal.
B2039Active number with one weak signal (e.g. VoIP).
C4059Mixed signals — could be inactive or DNC-listed.
D6079Likely VoIP / burner / DNC. Do not call.
F80100Inactive, disconnected, or unreachable.
Signals that feed this channel’s score
  • Line type (mobile / landline / VoIP)
  • Carrier (best-effort, ~50% accurate)
  • Active / inactive (HLR lookup)
  • DNC (Do-Not-Call) registry
  • Disposable / burner number flag

Address

Driven by Smarty's US Street API. We forward the raw address string verbatim — Smarty's verdict is what we surface.

GradeScore bandWhat it means
A019Deliverable, occupied address.
B2039Deliverable but with a minor flag (e.g. vacant lot).
C4059Partially matchable — verify components before use.
D6079Known deliverability issue.
F80100Not a recognized USPS deliverable address.
Signals that feed this channel’s score
  • DPV (Delivery Point Validation) match code
  • Vacancy indicator
  • RDI (Residential Delivery Indicator)
  • Standardized address components (street, city, state, ZIP)

Domain

Trust score from the in-process domain-health pipeline. Combines DNS reachability, TLS certificate validity, SPF/DKIM/DMARC posture, and blocklist presence.

GradeScore bandWhat it means
A80100Healthy, authenticated, on no blocklist.
B6079Reachable, minor flag (e.g. SPF missing).
C4059Reachability OK but authentication incomplete.
D2039Multiple failures. Treat as risky.
F019Unreachable or on blocklists.
Signals that feed this channel’s score
  • DNS resolve
  • TLS certificate validity
  • SPF / DKIM / DMARC
  • Email reachability (MX present)
  • Blocklists

Verification pipeline

What happens between POST /v1/verify/... and the row landing in your results table. Each stage short-circuits on definitive failure, so a 500-syntax rejection never costs you a DNS round trip. Open the interactive flow diagram →

Email pipeline

  1. 1. Syntax

    Parses the address with the same RFC 5322 rules mail servers use. Rejects malformed strings before any network call.

    On failure: Returns invalid. No DNS or SMTP attempt — saves a round trip.

  2. 2. DNS / MX

    Looks up the domain's MX records (and A records as fallback). No MX means the domain can't receive mail.

    On failure: Returns invalid. Skips SMTP — there's nothing to talk to.

  3. 3. SMTP handshake

    Opens a TCP connection to the highest-priority MX host and runs a lightweight RCPT TO probe. No mail is sent. Skipped when SMTP_VERIFICATION_MODE=off or in development environments without port 25.

    On failure: Returns unknown — the mailbox could not be checked — unless a risk signal fired, which makes it risky. A 5xx at RCPT TO returns invalid.

  4. 4. Bounce DB

    Checks our internal bounce + complaint ledger. Hard bounces and abuse complaints push the score into the D/F band.

    On failure: n/a — informational, doesn't reject.

  5. 5. Risk signals

    Spam trap, screamer, DND and honeypot (from your suppression lists and our spam-domain feed), organisation domains (government, military, education, large corporate), role accounts, disposable domains and catch-all domains.

    On failure: Any signal returns risky. Doesn't reject outright.

  6. 6. Risk scoring + grade

    Aggregates the tier-1 + tier-2 signals into a 0-100 risk score and maps to A+/A/B+/B/C/D/F via the rubric on this page. The score and grade are shown alongside the status; they do not decide it.

    On failure: n/a — terminal stage.

Phone pipeline

  1. 1. Normalize

    Strips formatting, prepends +1 if missing for US/CA numbers, and re-validates the E.164 shape.

    On failure: Returns invalid.

  2. 2. Telnyx Number Lookup

    Calls Telnyx's number_lookup API to retrieve carrier, line type, and HLR active/inactive.

    On failure: Marks the row as 'Not checked' — we don't claim active or inactive when the lookup is missing.

  3. 3. DNC registry

    Cross-references the US Do-Not-Call registry (where permitted by jurisdiction).

    On failure: n/a — informational.

  4. 4. Score + grade

    Aggregates line-type, active, DNC, and disposable flags into the A-F phone rubric.

    On failure: n/a — terminal stage.

Address pipeline

  1. 1. Passthrough

    We forward the raw address string to Smarty's US Street API verbatim — no parsing, no munging.

    On failure: Smarty returns 0 candidates → we surface 'Not a recognized USPS deliverable address.'

  2. 2. Cache check

    Before hitting Smarty, we hash the address and check the in-process cache (10-min TTL). Hits are free.

    On failure: n/a.

  3. 3. Score + grade

    DPV match code + vacancy + RDI are folded into the A-F address rubric.

    On failure: n/a — terminal stage.

Domain pipeline

  1. 1. DNS

    Resolves the A/AAAA records. Unresolvable domains fail immediately.

    On failure: Returns F.

  2. 2. TLS

    Pulls the certificate chain and verifies it chains to a trusted CA.

    On failure: Subtracts from trust score.

  3. 3. SPF / DKIM / DMARC

    Reads the TXT records at _spf, _dmarc, and common DKIM selectors. Missing DMARC is the most common issue.

    On failure: Subtracts from trust score.

  4. 4. Blocklists

    Cross-references the domain against public blocklists (Spamhaus DBL, etc.).

    On failure: Pushes to F.

  5. 5. Score + grade

    Aggregates the above into the A-F domain rubric.

    On failure: n/a — terminal stage.

Endpoints

Five public endpoints ship in v1. Each example below uses {baseUrl} — substitute your dashboard origin (e.g. https://app.dmphub.com).

POST/api/v1/verify/email

Single Verify (Email)

Verify a single email address in real-time. Returns status, risk score, and the tier-1/tier-2 signal breakdown.

Request

200
-amber-200">"text-sky-300">curl -X POST {baseUrl}/api/v1/verify/email \
  -H -amber-200">"Authorization: Bearer dmph_live_..." \
  -H -amber-200">"Content-Type: application/json" \
  -d -amber-200">'{-amber-200">"email":-amber-200">"user@example.com"}'

Response

{
  -200">"text-sky-300">-200">"email": -200">"user@example.com",
  -200">"text-sky-300">-200">"status": -200">"valid",
  -200">"text-sky-300">-200">"riskReasons": [],
  -200">"text-sky-300">-200">"domainStatus": -200">"valid",
  -200">"text-sky-300">-200">"riskScore": 0.02,
  -200">"text-sky-300">-200">"catchAll": false,
  -200">"text-sky-300">-200">"domainHealth": {
    -200">"text-sky-300">-200">"grade": -200">"A",
    -200">"text-sky-300">-200">"score": 95,
    -200">"text-sky-300">-200">"spf": true,
    -200">"text-sky-300">-200">"dkim": true,
    -200">"text-sky-300">-200">"dmarc": true
  },
  -200">"text-sky-300">-200">"provider": -200">"Google Workspace",
  -200">"text-sky-300">-200">"tier1": {
    -200">"text-sky-300">-200">"syntax": true,
    -200">"text-sky-300">-200">"domain": true,
    -200">"text-sky-300">-200">"mxFound": true,
    -200">"text-sky-300">-200">"smtp": -200">"250 OK",
    -200">"text-sky-300">-200">"bounceDb": false,
    -200">"text-sky-300">-200">"recency": true
  },
  -200">"text-sky-300">-200">"tier2": {
    -200">"text-sky-300">-200">"spamTrap": false,
    -200">"text-sky-300">-200">"roleAccount": false,
    -200">"text-sky-300">-200">"disposable": false,
    -200">"text-sky-300">-200">"complaint": false,
    -200">"text-sky-300">-200">"protected": false,
    -200">"text-sky-300">-200">"optOut": false,
    -200">"text-sky-300">-200">"screamer": false,
    -200">"text-sky-300">-200">"dnd": false,
    -200">"text-sky-300">-200">"honeypot": false
  },
  -200">"text-sky-300">-200">"recommendation": -200">"Safe to send",
  -200">"text-sky-300">-200">"latencyMs": 1842,
  -200">"text-sky-300">-200">"creditsUsed": 1,
  -200">"text-sky-300">-200">"verifiedAt": -200">"2026-07-07T12:00:00.000Z"
}
POST/api/v1/verify/list/start

Batch Verify (Email)

Start a batch verification job. Returns the job id; poll GET /api/v1/jobs/{id} for status.

Request

200
-amber-200">"text-sky-300">curl -X POST {baseUrl}/api/v1/verify/list/start \
  -H -amber-200">"Authorization: Bearer dmph_live_..." \
  -H -amber-200">"Content-Type: application/json" \
  -d -amber-200">'{-amber-200">"jobId":-amber-200">"11111111-1111-1111-1111-111111111111",-amber-200">"columnMapping":{-amber-200">"emailColumnIndex":0}}'

Response

{
  -200">"text-sky-300">-200">"jobId": -200">"11111111-1111-1111-1111-111111111111",
  -200">"text-sky-300">-200">"chunked": true,
  -200">"text-sky-300">-200">"chunks": 2
}
GET/api/v1/jobs

List Jobs

Top-level jobs (parentJobId IS NULL) for the calling tenant, with childCount + grade + coverage. Capped at 20.

Request

200
-amber-200">"text-sky-300">curl {baseUrl}/api/v1/jobs \
  -H -amber-200">"Authorization: Bearer dmph_live_..."

Response

{
  -200">"text-sky-300">-200">"items": [
    {
      -200">"text-sky-300">-200">"id": -200">"11111111-1111-1111-1111-111111111111",
      -200">"text-sky-300">-200">"type": -200">"batch",
      -200">"text-sky-300">-200">"status": -200">"completed",
      -200">"text-sky-300">-200">"totalCount": 3000,
      -200">"text-sky-300">-200">"processedCount": 3000,
      -200">"text-sky-300">-200">"fileName": -200">"june-batch.csv",
      -200">"text-sky-300">-200">"s3InputKey": -200">"tenants/acme/jobs/11111111-1111-1111-1111-111111111111/input.csv",
      -200">"text-sky-300">-200">"s3OutputKey": -200">"tenants/acme/jobs/11111111-1111-1111-1111-111111111111/output.jsonl",
      -200">"text-sky-300">-200">"parentJobId": null,
      -200">"text-sky-300">-200">"chunkOffset": null,
      -200">"text-sky-300">-200">"chunkSize": null,
      -200">"text-sky-300">-200">"errorMessage": null,
      -200">"text-sky-300">-200">"createdAt": -200">"2026-06-02T14:00:00.000Z",
      -200">"text-sky-300">-200">"updatedAt": -200">"2026-06-02T14:08:51.000Z",
      -200">"text-sky-300">-200">"startedAt": null,
      -200">"text-sky-300">-200">"completedAt": -200">"2026-06-02T14:08:51.000Z",
      -200">"text-sky-300">-200">"childCount": 0,
      -200">"text-sky-300">-200">"grade": -200">"A",
      -200">"text-sky-300">-200">"coverage": { -200">"text-sky-300">-200">"processed": 3000, -200">"text-sky-300">-200">"total": 3000, -200">"text-sky-300">-200">"pct": 1 },
      -200">"text-sky-300">-200">"issues": { -200">"text-sky-300">-200">"count": 0, -200">"text-sky-300">-200">"types": [] },
      -200">"text-sky-300">-200">"resultCounts": { -200">"text-sky-300">-200">"valid": 2431, -200">"text-sky-300">-200">"invalid": 389, -200">"text-sky-300">-200">"risky": 180 },
      -200">"text-sky-300">-200">"phoneResultCounts": {}
    }
  ],
  -200">"text-sky-300">-200">"total": 1,
  -200">"text-sky-300">-200">"page": 1,
  -200">"text-sky-300">-200">"pageSize": 20
}
GET/api/v1/jobs/{id}

Job Detail

Single job with children, result breakdown, grade, coverage, and issue counts.

Request

200
-amber-200">"text-sky-300">curl {baseUrl}/api/v1/jobs/11111111-1111-1111-1111-111111111111 \
  -H -amber-200">"Authorization: Bearer dmph_live_..."

Response

{
  -200">"text-sky-300">-200">"id": -200">"11111111-1111-1111-1111-111111111111",
  -200">"text-sky-300">-200">"type": -200">"batch",
  -200">"text-sky-300">-200">"status": -200">"completed",
  -200">"text-sky-300">-200">"totalCount": 3000,
  -200">"text-sky-300">-200">"processedCount": 3000,
  -200">"text-sky-300">-200">"fileName": -200">"june-batch.csv",
  -200">"text-sky-300">-200">"s3InputKey": -200">"tenants/acme/jobs/11111111-1111-1111-1111-111111111111/input.csv",
  -200">"text-sky-300">-200">"s3OutputKey": -200">"tenants/acme/jobs/11111111-1111-1111-1111-111111111111/output.jsonl",
  -200">"text-sky-300">-200">"parentJobId": null,
  -200">"text-sky-300">-200">"errorMessage": null,
  -200">"text-sky-300">-200">"createdAt": -200">"2026-06-02T14:00:00.000Z",
  -200">"text-sky-300">-200">"updatedAt": -200">"2026-06-02T14:08:51.000Z",
  -200">"text-sky-300">-200">"completedAt": -200">"2026-06-02T14:08:51.000Z",
  -200">"text-sky-300">-200">"children": [],
  -200">"text-sky-300">-200">"resultCounts": { -200">"text-sky-300">-200">"valid": 2431, -200">"text-sky-300">-200">"invalid": 389, -200">"text-sky-300">-200">"risky": 180 },
  -200">"text-sky-300">-200">"phoneResultCounts": {},
  -200">"text-sky-300">-200">"grade": -200">"A",
  -200">"text-sky-300">-200">"coverage": { -200">"text-sky-300">-200">"processed": 3000, -200">"text-sky-300">-200">"total": 3000, -200">"text-sky-300">-200">"pct": 1 },
  -200">"text-sky-300">-200">"issues": { -200">"text-sky-300">-200">"count": 0, -200">"text-sky-300">-200">"types": [] }
}
GET/api/v1/suppression-lists

List Suppression Lists

All suppression lists for the calling tenant, each with an entries count.

Request

200
-amber-200">"text-sky-300">curl {baseUrl}/api/v1/suppression-lists \
  -H -amber-200">"Authorization: Bearer dmph_live_..."

Response

[
  {
    -200">"text-sky-300">-200">"id": -200">"22222222-2222-2222-2222-222222222222",
    -200">"text-sky-300">-200">"name": -200">"Hard Bounces",
    -200">"text-sky-300">-200">"entries": 412,
    -200">"text-sky-300">-200">"status": -200">"active",
    -200">"text-sky-300">-200">"dateUploaded": -200">"2026-05-12T10:30:00.000Z",
    -200">"text-sky-300">-200">"lastModified": -200">"2026-06-28T09:14:00.000Z"
  }
]