scrub

Scruubb

Reference

AuthenticationValidationIntegrationsHistoryAPI KeysWebhooks

API Reference

Scrub validates email addresses via REST. Integrate with your product using an API key — no OAuth flow required.

Authentication

All endpoints require authentication. Create an API key from your API Keys page and pass it in the header.

X-API-Key: sk_your_key_here

API keys are scoped to your account. All data returned — history, results — belongs exclusively to the key owner.

Validation

POST

/v1/validate/single

Validate a single email address. Checks syntax and DNS MX records.

Query parameters

email

string

required

Email address to validate.

Example request

curl -X POST "https://api.thescrub.app/v1/validate/[email protected]" \
  -H "X-API-Key: sk_your_key_here"

Response

{
  "email": "[email protected]",
  "status": "deliverable",
  "reason": null,
  "quality_score": 95,
  "checks": {
    "syntax": true,
    "mx": true,
    "disposable": false,
    "role_based": false
  },
  "attributes": {
    "domain": "example.com",
    "is_free_provider": false
  }
}

Status values

deliverable

Syntax valid, MX record found.

undeliverable

Syntax valid but no MX record — email will bounce.

risky

Valid but flagged — disposable domain, role-based address, etc.

invalid_syntax

Failed RFC syntax check.

POST

/v1/validate/bulk

Validate up to 30,000 email addresses in a single request.

Request body

emails

string[]

required

Array of email addresses. Max 30,000.

response_mode

enum

"all" (default) | "invalid_only" | "summary_only"

dedupe

boolean

Remove duplicates before validating. Default false.

Example request

curl -X POST "https://api.thescrub.app/v1/validate/bulk" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": ["[email protected]", "[email protected]"],
    "response_mode": "all",
    "dedupe": true
  }'

Response

{
  "summary": {
    "total": 2,
    "processed": 2,
    "valid": 1,
    "invalid": 1,
    "errors": 0,
    "duplicates_removed": 0,
    "duration_ms": 280,
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
  },
  "results": [
    { "email": "[email protected]", "valid": true, "status": "deliverable" },
    { "email": "[email protected]", "valid": false, "status": "undeliverable", "reason": "no_mx" }
  ]
}

The request_id in the summary can be used with GET /v1/history/bulk/{request_id} to retrieve results later.

Integrations

Submit a batch of emails for asynchronous validation, tagged to one of your integrations. The batch is processed in the background in chunks — poll for progress, or pass event_callback to get pinged as each chunk finishes.

POST

/v1/validate/integration

Submit an email batch for asynchronous processing under an integration.

Request body

integration_id

uuid

required

The integration to attribute this batch to. Must belong to you.

batch_id

string

Your own batch identifier, echoed back in the response and every progress event. Server-generated if omitted.

emails

string[]

required

Array of email addresses. Max 30,000.

event_callback

url

HTTPS URL to receive a progress event after each chunk completes. See Webhooks below for payload shape and signature verification — public URLs only, no private/internal addresses.

Example request

curl -X POST "https://api.thescrub.app/v1/validate/integration" \
  -H "X-API-Key: sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "integration_id": "6f1a1c1e-2b7a-4b6a-9c3e-8f2a1d4e5f60",
    "emails": ["[email protected]", "[email protected]"],
    "event_callback": "https://your-service.com/hook"
  }'

Response — 202 Accepted

{
  "results": {
    "request_id": "d6ec6377-70cc-463f-bab4-053dc6e0854d",
    "integration_id": "6f1a1c1e-2b7a-4b6a-9c3e-8f2a1d4e5f60",
    "batch_id": "1645cf45-c19b-4ded-8a8a-640163aa46f5",
    "submitted_count": 1000
  }
}

Notes

X-API-Key auth only — session/JWT auth is not accepted on this endpoint, since event_callback deliveries are signed using your API key.

• If your remaining balance is less than the number of emails submitted, the batch is truncated to what's allowed and submitted_count reflects that.

• Track progress with GET /v1/jobs/progress/{request_id}, or rely on event_callback — it fires once per chunk with cumulative counts so far.

History

All history is scoped to your account. You can only access records created by your own API key or user session.

GET

/v1/history

Cursor-paginated validation history, newest first.

integration_id

uuid

Filter to history tied to a specific integration. Omit to return all.

invalid_only

boolean

Return only invalid results. Default false (returns both).

limit

int

Results per page. Default 50, max 500.

cursor

string

Opaque cursor from a previous page's next_cursor. Omit for the first page.

{
  "integration_id": null,
  "has_more": false,
  "next_cursor": null,
  "results": [
    {
      "request_id": "b1f2...",
      "batch_id": null,
      "email": "[email protected]",
      "status": "invalid",
      "error_code": "domain_not_found",
      "created_at": "2026-08-11T09:30:00Z"
    }
  ]
}
POST

/v1/history/progress

Bulk-poll submitted/validated/invalid counters and status for one or more integration-batch submissions (POST /v1/validate/integration), by the request_id each submission returned.

// Request
{
  "integration_id": "11111111-1111-1111-1111-111111111111",
  "request_ids": ["b1f2c3d4-...", "c2a3d4e5-..."]
}

// Response
{
  "results": [
    {
      "batch_id": "9f8e7d6c-...",
      "submitted_total_count": 100,
      "validated_total_count": 85,
      "invalid_total_count": 15,
      "status": "processing"
    }
  ]
}

request_ids that don't exist, or don't belong to this user/integration, are silently omitted from the results rather than erroring.

GET

/v1/history/bulk/{request_id}

All results for a specific bulk job by its request_id.

GET

/v1/history/{email}

All validation history for a specific email address.

DELETE

/v1/history/{email}

Delete all history for an email address (GDPR right-to-erasure). Returns the number of records deleted.

{ "email": "[email protected]", "deleted": 3 }

API Keys

Manage API keys programmatically. These endpoints require your account session (Bearer token) — not an API key.

POST

/v1/api-keys

Create a new API key. The raw key is returned once and cannot be retrieved again.

name

string

required

Human-readable label. 1–100 characters.

{
  "id": 1,
  "name": "Zapier integration",
  "key": "sk_abc123...",
  "created_at": "2026-04-15T10:00:00"
}
GET

/v1/api-keys

List all your API keys. The raw key is never returned — only metadata.

[
  {
    "id": 1,
    "name": "Zapier integration",
    "created_at": "2026-04-15T10:00:00",
    "last_used_at": "2026-04-15T12:30:00",
    "active": true
  }
]
DELETE

/v1/api-keys/{key_id}

Revoke a key immediately. The key stops working as soon as this returns.

Webhooks

Register a URL to receive real-time results after each validation, or pass event_callback on a single request to receive events for just that call — both deliver the same payload shape and are signed the same way, so you can share one verification function.

POST

/v1/webhooks/register

Register a URL to receive validation events.

url

string

required

HTTPS URL to receive POST payloads.

{
  "url": "https://your-service.com/hook",
  "secret": "hex-signing-secret",
  "message": "Webhook registered. Save the secret — it will not be shown again."
}
DELETE

/v1/webhooks/deregister

Remove a registered webhook by URL.

url

string

required

The registered URL to remove (query param).

GET

/v1/webhooks/list

List all registered webhooks.

[
  { "id": 1, "url": "https://...", "active": true, "failure_count": 0 }
]

Event payload

Every delivery — registered webhooks and event_callback alike — shares this envelope. data varies by event_type.

{
  "event_id": "b3a1c2d4-5e6f-4a1b-8c9d-0e1f2a3b4c5d",
  "event_type": "events.progress",
  "created_at": "2026-08-13T11:22:37Z",
  "integration_id": "6f1a1c1e-2b7a-4b6a-9c3e-8f2a1d4e5f60",
  "data": {
    "batch_id": "1645cf45-c19b-4ded-8a8a-640163aa46f5",
    "status": "completed",
    "submitted_total_count": 1000,
    "validated_total_count": 940,
    "invalid_total_count": 60,
    "latest_invalid_emails": ["[email protected]"]
  }
}

validation.completed

Fired once after /v1/validate/single or /v1/validate/bulk (registered webhooks only). integration_id is null; data holds { endpoint, summary, result? }.

events.progress

Fired once per completed chunk of a POST /v1/validate/integration batch — "processing" for every chunk but the last, "completed" or "failed" for the one that finishes the job.

Verifying payloads

Each request includes an X-Webhook-Signature header shaped t={timestamp},v1={hex_hmac}. For registered webhooks, sign with the secret returned by /v1/webhooks/register. For event_callback, sign with the SHA-256 hash of the API key that made the request — no separate secret to store.

import hmac, hashlib

def verify(payload_bytes: bytes, header: str, secret: str) -> bool:
    # header looks like "t=1786360887,v1=9f8a7b..."
    parts = dict(p.split("=", 1) for p in header.split(","))
    timestamp, signature = parts["t"], parts["v1"]
    signed_message = f"{timestamp}.{payload_bytes.decode()}".encode()
    expected = hmac.new(secret.encode(), signed_message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

# event_callback secret — no registration step, so it's derived from your API key:
callback_secret = hashlib.sha256(b"sk_your_key_here").hexdigest()

Delivery & retries

Respond with any 1xx or 2xx status to acknowledge. Anything else — or a timeout — triggers up to 5 retries with exponential backoff before the delivery is given up on. Registered webhooks additionally get auto-deactivated after 5 consecutive failed deliveries; re-register to reactivate.

scrub

Scruubb

Scrub is an email hygiene tool that flags invalid, disposable, and role-based addresses so you can protect your sender reputation.

Powered by n0.

Quick Links

Pricing

Docs

Blog

© 2026 Scrub - Nerd Zero Private Limited. All rights reserved.

Privacy PolicyTerms of ServiceCookie Settings