Get API key

API Reference

The Verifence REST API lets you embed document scanning, URL screening, email validation, and bot protection directly in your product. All endpoints return JSON.

Authentication

All requests require an API key in the API-KEY header. Keys are generated from Settings → API Keys in your dashboard.

  • Each key is shown once on creation — store it securely
  • Multiple keys can be created and revoked independently
HTTP header
API-KEY: your_api_key

Base URL

https://app.verifence.io/api

All endpoint paths below are relative to this base — for example, POST /scan/email is https://app.verifence.io/api/scan/email.

Rate limits

Requests are limited per API key:

  • 60 requests per minute per API key
  • Batch operations (up to 500 items) count as a single request

When the limit is exceeded the API returns 429 Too Many Requests with a Retry-After header.

Credit costs

Each scan consumes credits from your shared account balance. Requests fail gracefully with an error if your balance is insufficient.

Scan typeCredits consumed
Email address1 credit
Email address (bulk)1 credit per unique email
URL5 credits
Document / file20 credits

Lead analysis, phishing-link submissions, and Shield CAPTCHA verifications do not consume scan credits.

Errors

Business errors (bad input, insufficient credits, a failed scan) return a flat JSON object with ok: false and a message:

JSON
{
  "ok": false,
  "message": "Insufficient credits to perform this scan."
}

Some endpoints use error instead of message for the description. Request-validation failures (422) return Laravel's standard validation shape:

JSON — 422 validation
{
  "message": "The email field is required.",
  "errors": {
    "email": ["The email field is required."]
  }
}
HTTP statusMeaning
400Invalid input (e.g. neither file nor url provided to /scan, or both)
401Missing or invalid API key
403Not enough credits to complete the request
422Request body failed validation, or the scan could not be completed
429Rate limit exceeded — see the Retry-After header
500Server error — retry with backoff

POST /scan

Scan a document (PDF or Office file) for threats. The scan runs synchronously. Provide either an uploaded file or a url to download and scan — not both. Each scan costs 20 credits.

Request

Send the request as multipart/form-data.

FieldTypeNotes
filebinaryDocument to scan. Required if url is omitted.
urlstring (uri)Link to a document to download and scan. Required if file is omitted.
cURL
curl -X POST https://app.verifence.io/api/scan \
  -H "API-KEY: your_api_key" \
  -F "file=@invoice.pdf"

Response

Returns ok and a result object containing the full scan report (verdict and any flagged elements such as embedded scripts or suspicious URLs).

JSON — 200
{
  "ok": true,
  "result": { }
}

Returns 400 if neither file nor url is provided (or both), 401 if the API key is invalid, and 422 if a supplied url could not be downloaded or processed.

POST /scan/url

Scan a single URL against Google Web Risk and the Verifence internal blocklist. Returns an immediate safe/bad verdict via the boolean ok field. Each URL costs 5 credits.

Request body

JSON
{
  "url": "https://phish-site.xyz/login"
}
cURL
curl -X POST https://app.verifence.io/api/scan/url \
  -H "API-KEY: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://phish-site.xyz/login"}'

Response

ok is true when the URL is safe and false when it matched a known threat source.

JSON — bad URL
{
  "ok": false
}
JSON — safe URL
{
  "ok": true
}

If the scan itself fails, the endpoint returns 422 with { "ok": false, "error": "..." }.

POST /scan/email

Validate a single email address. Returns a breakdown of boolean risk signals. Each email costs 1 credit.

Request body

Requires email. Optionally pass an ip (IPv4) to also evaluate your IP and country block rules.

JSON
{
  "email": "test@mailinator.com",
  "ip": "192.0.2.10"
}
cURL
curl -X POST https://app.verifence.io/api/scan/email \
  -H "API-KEY: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"email": "test@mailinator.com"}'

Response

JSON — 200
{
  "ok": true,
  "email": "test@mailinator.com",
  "domain": "mailinator.com",
  "disposable": true,
  "email_blocked": false,
  "role_account": false,
  "public_domain": false,
  "did_you_mean": null,
  "ip_blocked": false,
  "country_blocked": false
}

Response fields

FieldMeaning
disposableTemporary or throwaway address
email_blockedMatched one of your email/domain block rules
role_accountGeneric role address (admin@, info@)
public_domainConsumer / shared provider (Gmail, Yahoo, etc.)
did_you_meanSuggested correction for a likely typo, or null
ip_blockedSupplied IP matched a block rule (null if no ip sent)
country_blockedCountry of the supplied IP is blocked (null if no ip sent)

On failure the endpoint returns 422 with { "ok": false, "message": "..." }.

POST /scan/email/bulk

Validate up to 500 email addresses in a single request. Invalid values are returned per-item in errors without failing the whole batch. Costs 1 credit per unique email.

Request body

JSON
{
  "emails": [
    "user@example.com",
    "admin@example.org",
    "not-an-email"
  ]
}
cURL
curl -X POST https://app.verifence.io/api/scan/email/bulk \
  -H "API-KEY: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"emails": ["user@example.com", "not-an-email"]}'

Response

JSON — 200
{
  "ok": true,
  "results": [
    {
      "index": 0,
      "email": "user@example.com",
      "domain": "example.com",
      "disposable": false,
      "email_blocked": false,
      "role_account": false,
      "public_domain": true,
      "did_you_mean": null
    }
  ],
  "errors": [
    {
      "index": 1,
      "email": "not-an-email",
      "message": "Invalid email"
    }
  ]
}

Returns 422 with { "ok": false, "message": "..." } for invalid input or insufficient credits.

POST /lead/analyze

Score an inbound lead for spam and risk by combining email reputation, message classification, and URL threat intelligence into a single verdict. Built for contact forms, signup flows, and CRM intake. This endpoint does not consume scan credits.

Request body

Either email or message must be provided. name, company, website (http/https), and source are optional.

JSON
{
  "email": "jane@example.com",
  "message": "Interested in a demo for our team.",
  "name": "Jane Doe",
  "company": "Example Inc",
  "website": "https://example.com",
  "source": "contact-form"
}
cURL
curl -X POST https://app.verifence.io/api/lead/analyze \
  -H "API-KEY: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@example.com", "message": "Interested in a demo."}'

Response

JSON — 200
{
  "request_id": "3f1c...uuid",
  "verdict": "clean",
  "risk_score": 5,
  "categories": ["public_email"],
  "reasons": ["The email uses a public email provider."],
  "language": "en"
}

Verdict values

VerdictRisk scoreMeaning
clean0–24No meaningful risk signals
review25–79Some risk signals — worth a human look
spam80–100Strong risk signals

A known-malicious URL forces risk_score to 100. language is the detected message language, or null if no message was provided. Returns 422 if neither email nor message is supplied.

POST /submit-phishing-link

Submit a suspected phishing URL for review and potential inclusion in Verifence threat data. Does not consume scan credits.

Request body

JSON
{
  "url": "http://malicious.example/phish"
}
cURL
curl -X POST https://app.verifence.io/api/submit-phishing-link \
  -H "API-KEY: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "http://malicious.example/phish"}'

Response

JSON — 201
{
  "id": 1234,
  "url": "http://malicious.example/phish"
}

Returns 401 if the API key is missing or invalid.

POST /shield/siteverify

Verify a Shield CAPTCHA token on your server after a form submission. Call this endpoint before processing the form. Unlike the scan endpoints, it authenticates with your site's secret key in the request body rather than the API-KEY header, and it does not consume scan credits.

Frontend setup

Add the Shield widget to your page and include the hidden token field in your form:

HTML
<!-- In your <head> -->
<script src="https://app.verifence.io/shield/widget.js" async></script>

<!-- In your <form> -->
<input type="hidden" name="shield-token" id="shield-token" />
<div class="shield-widget" data-sitekey="your_site_key"></div>

Server-side verification

Required: secret and token. Optional: email (runs email rules), text (spam classifier), honeypot (value of your hidden field — must be empty).

JSON request body
{
  "secret": "your_secret_key",
  "token": "shield_token_from_form_submission",
  "email": "user@example.com",
  "text": "Hello, I'd like to get in touch...",
  "honeypot": ""
}
cURL
curl -X POST https://app.verifence.io/api/shield/siteverify \
  -H "Content-Type: application/json" \
  -d '{
    "secret": "your_secret_key",
    "token": "shield_token_from_form_submission"
  }'

Response — human

geo is included when available; spam_score and language are included only when spam detection is enabled for the site and text was supplied.

JSON
{
  "success": true,
  "score": 12,
  "geo": { "country": "US", "asn": "AS15169" },
  "spam_score": 0.04,
  "language": "en"
}

Response — rejected

Honeypot hits, an invalid secret or token, and email/IP/country rule blocks all return success: false with a short error.

JSON
{
  "success": false,
  "error": "Bot detected"
}

Degraded mode

If the edge issued a degraded token during an outage (or the token was empty), verification fails open and returns { "success": true, "degraded": true }. Treat degraded responses as a pass with reduced protection.

Score interpretation

The score is an integer from 0 (very likely human) to 100 (very likely bot). When success is false, reject the submission regardless of score.

Score rangeRecommendation
0 – 30Allow — high confidence human
30 – 50Review — low-risk signals present
50 – 100Block — likely bot

Service errors & timeouts

Shield uses a fail-open policy: if the /shield/siteverify call fails due to a network error, timeout, or a 5xx response from our servers, your server should allow the form submission through rather than blocking the user. Log the error so you have visibility into any outage.

This keeps your forms available even during a Verifence service disruption. The trade-off is a brief window where bot protection is reduced — we monitor uptime closely to minimise this risk.

Node.js — fail-open with logging
async function verifyShieldToken(token) {
  try {
    const res = await fetch('https://app.verifence.io/api/shield/siteverify', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ secret: process.env.SHIELD_SECRET, token }),
      signal: AbortSignal.timeout(3000), // 3-second timeout
    });

    if (!res.ok) {
      console.error('[shield] siteverify error', res.status);
      return { allow: true, reason: 'service_error' }; // fail-open
    }

    const data = await res.json();

    if (!data.success || data.score > 50) {
      return { allow: false, reason: 'bot_detected' };
    }

    return { allow: true, reason: 'ok' };

  } catch (err) {
    console.error('[shield] siteverify unreachable', err.message);
    return { allow: true, reason: 'service_unavailable' }; // fail-open
  }
}
Python — fail-open with logging
import httpx, logging, os

def verify_shield_token(token: str) -> dict:
    try:
        r = httpx.post(
            "https://app.verifence.io/api/shield/siteverify",
            json={"secret": os.environ["SHIELD_SECRET"], "token": token},
            timeout=3.0,
        )
        r.raise_for_status()
        data = r.json()

        if not data.get("success") or data.get("score", 0) > 50:
            return {"allow": False, "reason": "bot_detected"}

        return {"allow": True, "reason": "ok"}

    except httpx.HTTPStatusError as e:
        logging.error(f"[shield] siteverify error {e.response.status_code}")
        return {"allow": True, "reason": "service_error"}  # fail-open

    except Exception as e:
        logging.error(f"[shield] siteverify unreachable: {e}")
        return {"allow": True, "reason": "service_unavailable"}  # fail-open
What to log — include the reason field, the originating IP, and a timestamp. If you see a spike in service_error or service_unavailable events, check our status page for active incidents.