API reference/email

Verify an address from the browser with a publishable key

POST/api/v1/email/public

authentication
Publishable key in the body, checked against the browser Origin header
billing
Bills the key owner like a single lookup; capped by the key's daily credit cap when one is set.

The endpoint behind the form widget (https://spaw.co/spaw-form.js). It is authenticated by a publishable pk_ key in the body plus the browser's Origin header, which must match one of the domains the key is locked to. A missing Origin is rejected on purpose: servers use a secret key and POST /api/v1/email instead.

The lookup bills the key's owner under the normal rules and answers { "success", "data" } with no meta block, so page visitors never see the owner's balance. Because the key sits in page source, give it a daily credit cap in the dashboard: once the cap is spent the endpoint answers 429 KEY_SPEND_CAP_REACHED until the next day. Free answers (undeliverable, cache hits, test addresses) never count toward the cap.

For forms open to the public, store a Cloudflare Turnstile site key and secret on the publishable key. The endpoint then requires a confirmed turnstile_token with every lookup and answers 403 TURNSTILE_FAILED without one, before any credit is spent. Tokens are single-use. The form widget obtains a fresh token per lookup when the script tag carries data-turnstile-site-key.

Throttled at 20 requests per minute per IP.

Parameters

name in type description
OriginrequiredheaderstringSent by browsers automatically. Its host must be on the key's allowed-domain list.

Request body

field type description
keyrequiredstringA publishable key, which starts with `pk_`. At most 64 characters.
emailrequiredstringThe address to verify. At most 254 characters.
turnstile_tokenstring | nullRequired when the key carries a Turnstile pair. A single-use token from the Turnstile widget on the page. At most 2,048 characters.

Example request

curl -X POST https://spaw.co/api/v1/email/public \
  -H "Origin: https://www.example.com" \
  -H "Content-Type: application/json" \
  -d '{
  "key": "pk_live_…",
  "email": "[email protected]"
}'
const response = await fetch('https://spaw.co/api/v1/email/public', {
  method: 'POST',
  headers: {
    'Origin': 'https://www.example.com',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "key": "pk_live_…",
    "email": "[email protected]"
  }),
});
const result = await response.json();
import requests

response = requests.post(
    'https://spaw.co/api/v1/email/public',
    headers={'Origin': 'https://www.example.com'},
    json={
        'key': 'pk_live_…',
        'email': '[email protected]'
    },
)
result = response.json()
$ch = curl_init('https://spaw.co/api/v1/email/public');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => ['Origin: https://www.example.com', 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'key' => 'pk_live_…',
        'email' => '[email protected]'
    ]),
]);
$result = json_decode(curl_exec($ch), true);

Responses

200The same fields as `POST /api/v1/email`, without a meta block.

{
    "success": true,
    "data": {
        "email": "[email protected]",
        "deliverable": "deliverable",
        "reason": null,
        "risk_score": 0,
        "risk_level": "low",
        "did_you_mean": null
    }
}

401The publishable key does not exist or was revoked.

{
    "success": false,
    "error": {
        "code": "INVALID_PUBLISHABLE_KEY",
        "message": "That publishable key does not exist or was revoked.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

402The balance is empty. The lookup did not run.

{
    "success": false,
    "error": {
        "code": "INSUFFICIENT_CREDITS",
        "message": "Your credit balance is empty.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

403The page's origin is not on the key's allowed-domain list (`ORIGIN_NOT_ALLOWED`), or the key requires a Turnstile token that was missing or not confirmed (`TURNSTILE_FAILED`).

{
    "success": false,
    "error": {
        "code": "ORIGIN_NOT_ALLOWED",
        "message": "This publishable key cannot be used from this origin.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

422The request body could not be validated; `error.errors` lists the fields.

{
    "success": false,
    "error": {
        "code": "VALIDATION_FAILED",
        "message": "The email field is required.",
        "errors": {
            "email": [
                "The email field is required."
            ]
        },
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

429The key has spent its owner's daily credit cap (`KEY_SPEND_CAP_REACHED`), or the IP exceeded 20 requests per minute (`RATE_LIMITED`).

{
    "success": false,
    "error": {
        "code": "KEY_SPEND_CAP_REACHED",
        "message": "This publishable key has reached its daily credit cap. The counter resets each day.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

Error codes

Failures answer { success: false, error: { code, message, request_id } }. Each code has its own page.

markdown version·openapi.json