On this page
Quickstart
Create a pd_test_… key in Dashboard → Developer, then send a reserved sandbox address. Test keys never call the verification provider or spend credits.
curl https://peopledat.com/api/v1/verifications \
-H "Authorization: Bearer pd_test_…" \
-H "Idempotency-Key: quickstart-001" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","mode":"power"}'Authentication
Send Authorization: Bearer pd_live_… or pd_test_…. Keys are bound to one workspace, have explicit scopes, can expire or be revoked, and are shown only once at creation. Never embed a secret API key in browser code.
Free workspaces can use test keys with both REST and MCP. A live key—and therefore live REST or MCP work—requires an active subscription or any settled PayGo purchase. Stored live keys resume when that entitlement returns.
Create separate keys for separate systems, grant only the scopes each one needs, lower its read/write limits when practical, and use expiry plus an optional daily credit cap. A workspace can keep up to 20 active, unexpired keys.
Idempotency
Every chargeable POST requires an Idempotency-Key. PeopleDat stores the request digest and resource outcome for 24 hours. An identical retry returns the original outcome; a changed payload returns 409. Concurrent retries create one resource and one charge.
Deterministic sandbox scenarios
deliverable@peopledat.testcatch-all@peopledat.testrole@peopledat.testdisposable@peopledat.testspam-trap@peopledat.testfull@peopledat.testinvalid@peopledat.testdisabled@peopledat.testunknown@peopledat.testUse hyphenated labels exactly as shown. Sandbox results are stable, free, and isolated from provider capacity accounting.
VerificationResultV1
Status is one of deliverable, risky, undeliverable, unknown, or excluded. The advisory action is send, review, remove, retry, or skip. An unperformed check is null, never falsely presented as false.
{
"data": {
"id": "job_01…",
"email": "[email protected]",
"reference": "lead-42",
"result": {
"version": 1,
"status": "risky",
"policy_action": "review",
"reason_code": "catch_all",
"score": 62,
"verification_mode": "power",
"verified_at": "2026-08-27T18:42:00.000Z",
"billed": true,
"credits_used": 1,
"checks": {
"syntax_valid": true,
"domain_accepts_mail": true,
"smtp_connectable": true,
"deliverable": null,
"catch_all": true,
"disposable": false,
"role_account": false,
"free_provider": false,
"inbox_full": false,
"disabled": false,
"spam_trap": false,
"mx_records": ["mx.example.com"]
}
}
}
}A single request may instead return 202 with { "data": { "id": "job_01…", "status": "processing" } }. Poll that job ID rather than submitting the address again.
Verification is a point-in-time technical signal. Policy actions are recommendations and do not guarantee delivery, consent, engagement, or inbox placement.
REST endpoints and scopes
| Method | Path | Purpose | Scope |
|---|---|---|---|
| POST | /verifications | Verify one address synchronously | verifications:write |
| POST | /jobs | Submit up to 50,000 records | jobs:write |
| GET | /jobs/{id} | Read job status and counts | jobs:read |
| GET | /jobs/{id}/results | Page through canonical results | jobs:read |
| POST | /jobs/{id}/cancel | Cancel work before provider execution | jobs:write |
| POST | /jobs/{id}/exports | Create a CSV, XLSX, or TXT export | exports:write |
| GET | /exports/{id} | Read export status | exports:read |
| GET | /exports/{id}/download | Resolve a secured download | exports:read |
| GET | /credits | Read pooled organization credits | credits:read |
| GET | /usage | Read privacy-safe workspace usage | usage:read |
All URLs are relative to https://peopledat.com/api/v1. Resource identifiers outside the key's workspace return 404.
verifications:write covers synchronous single checks. Job creation/cancellation and job reading are intentionally separate. Export creation and export reading are also separate. Balance and usage each have read-only scopes. API keys cannot create keys, register webhooks, or change workspace settings.
Submit, poll, page, and export a bulk job
Bulk work is asynchronous and durable. Submit it once, keep the returned ID, then poll state and page results. The database record is the source of truth; a worker wake-up can be recovered if Redis misses it.
curl https://peopledat.com/api/v1/jobs \
-H "Authorization: Bearer pd_test_…" \
-H "Idempotency-Key: august-cleanup-001" \
-H "Content-Type: application/json" \
-d '{
"name":"August cleanup",
"mode":"power",
"records":[
{"email":"[email protected]","reference":"crm-101"},
{"email":"[email protected]","reference":"crm-102"}
]
}'{
"data": {
"id": "job_01…",
"status": "pending"
}
}- 2. Poll state:
GET /jobs/job_01…untilcompleted,failed, orcancelled. Back off between reads, or use a signed webhook for the terminal event. - 3. Page results:
GET /jobs/job_01…/results?limit=500, then pass the opaquenext_cursoruntilhas_moreis false. - 4. Create an export:
POST /jobs/job_01…/exportswith a new idempotency key and{ "format": "csv" }. Formats arecsv,xlsx,txt_safe, andtxt_all. - 5. Resolve the download: poll
GET /exports/export_01…, then callGET /exports/export_01…/download. An authorized request receives a302to a short-lived signed object-storage URL.
You may call POST /jobs/job_01…/cancel with an idempotency key only while the job is still pending and has not been committed to provider execution.
Errors and cursor pagination
Errors use one stable envelope. Save the response and X-Request-Id for diagnostics, but never log your Bearer key or submitted addresses.
{
"error": {
"code": "IDEMPOTENCY_CONFLICT",
"message": "Idempotency key was already used with a different request",
"request_id": "6e07…"
}
}Results use opaque cursor pagination. Pass the returned next_cursor as ?cursor=…; do not construct or decode cursors. A null cursor means the page is final.
{
"data": [/* canonical result rows */],
"pagination": {
"next_cursor": "eyJ…",
"has_more": true
}
}GET /jobs/{id}/results accepts limit=1…500 (default 100) and an optional cursor up to 128 characters. GET /usage accepts days=1…90 (default 30).
400Invalid body/query or missing/invalid idempotency key.401Missing, malformed, expired, or revoked API key.403Scope denied or live entitlement suspended.404Resource not found in this workspace.409Idempotency conflict/in progress, or job cannot be cancelled.413Bounded request body is too large.429Key or workspace minute bucket is exhausted.503A required result/replay is temporarily unavailable.Rate limits
- Writes are configurable from 1–60 per minute per key; the default and maximum are 60
- Reads are configurable from 1–300 per minute per key; the default and maximum are 300
- Bulk submissions also share a 10-per-minute workspace limit
- Each bulk job is capped at 50,000 records and a 10 MB JSON body
Authenticated responses include X-Request-Id, RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset (UTC epoch seconds). Authentication failures still include a request ID but cannot report a key bucket.
REST and MCP use transactionally reserved PostgreSQL minute buckets. If that reservation cannot complete, the request fails closed before protected work runs.
Webhook verification
Register up to five active HTTPS endpoints per workspace. Supported events are job.completed, job.failed, export.completed, export.failed, and credits.low. Payloads contain identifiers and aggregates, never addresses or source columns.
Read the exact raw request body. Compute lowercase hex HMAC-SHA256 with your one-time signing secret over ${timestamp}.${rawBody}. Compare it in constant time with the value after v1= in PeopleDat-Signature; read the timestamp from PeopleDat-Timestamp and reject stale requests.
Retries occur immediately, then after 1 minute, 5 minutes, 30 minutes, 2 hours, and 12 hours. Endpoints must use HTTPS on port 443 and cannot redirect.
Follow the complete signature, deduplication, retry, and troubleshooting guide →
Hosted MCP endpoint
Connect a Streamable HTTP MCP client to https://peopledat.com/mcp and send the same Bearer API key used by REST. The endpoint is stateless and stays inside the same workspace boundary, scopes, rate limits, exclusions, credit ledger, idempotency records, and privacy-safe audit path.
| Tool | Purpose | Billing |
|---|---|---|
| credit_balance | Read the pooled organization balance | Free |
| usage | Read aggregate workspace usage | Free |
| recent_jobs | List recent workspace jobs | Free |
| job_status | Read one job state | Free |
| job_results | Page through canonical results | Free |
| estimate_bulk_work | Estimate bounded bulk work | Free |
| verify_one | Run one canonical verification | Up to 1 credit |
| create_bulk_job | Create an asynchronous job of up to 50,000 records | Up to max_credits |
Every billable tool requires both an idempotency_key and explicit max_credits. Live calls fail before provider work if that ceiling is too low. Test keys use the deterministic peopledat.test scenarios and consume no credits.
MCP does not provide a privileged data path: API keys cannot create other keys or change webhook settings, and tools never bypass the canonical verification orchestrator.
Follow the complete MCP client, scope, billing, and troubleshooting guide →
Privacy and retention
API request logs keep workspace, key, request, route-template, status, duration, aggregate counts, source, and billed units for 90 days. They do not keep request bodies, raw addresses, raw IPs, or credentials. Idempotency records last 24 hours; webhook delivery diagnostics last 30 days.
See the Privacy Policy and Terms for account, credential, source-data, and acceptable-use responsibilities.
Ready to try it?
Create a free workspace and test every canonical result without a provider call or a credit charge.
Create a test key