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
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 type | Credits consumed |
|---|---|
| Email address | 1 credit |
| Email address (bulk) | 1 credit per unique email |
| URL | 5 credits |
| Document / file | 20 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:
{
"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:
{
"message": "The email field is required.",
"errors": {
"email": ["The email field is required."]
}
} | HTTP status | Meaning |
|---|---|
400 | Invalid input (e.g. neither file nor url provided to /scan, or both) |
401 | Missing or invalid API key |
403 | Not enough credits to complete the request |
422 | Request body failed validation, or the scan could not be completed |
429 | Rate limit exceeded — see the Retry-After header |
500 | Server 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.
| Field | Type | Notes |
|---|---|---|
file | binary | Document to scan. Required if url is omitted. |
url | string (uri) | Link to a document to download and scan. Required if file is omitted. |
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).
{
"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
{
"url": "https://phish-site.xyz/login"
} 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.
{
"ok": false
} {
"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.
{
"email": "test@mailinator.com",
"ip": "192.0.2.10"
} 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
{
"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
| Field | Meaning |
|---|---|
disposable | Temporary or throwaway address |
email_blocked | Matched one of your email/domain block rules |
role_account | Generic role address (admin@, info@) |
public_domain | Consumer / shared provider (Gmail, Yahoo, etc.) |
did_you_mean | Suggested correction for a likely typo, or null |
ip_blocked | Supplied IP matched a block rule (null if no ip sent) |
country_blocked | Country 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
{
"emails": [
"user@example.com",
"admin@example.org",
"not-an-email"
]
} 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
{
"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.
{
"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 -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
{
"request_id": "3f1c...uuid",
"verdict": "clean",
"risk_score": 5,
"categories": ["public_email"],
"reasons": ["The email uses a public email provider."],
"language": "en"
} Verdict values
| Verdict | Risk score | Meaning |
|---|---|---|
clean | 0–24 | No meaningful risk signals |
review | 25–79 | Some risk signals — worth a human look |
spam | 80–100 | Strong 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
{
"url": "http://malicious.example/phish"
} 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
{
"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:
<!-- 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).
{
"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 -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.
{
"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.
{
"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 range | Recommendation |
|---|---|
0 – 30 | Allow — high confidence human |
30 – 50 | Review — low-risk signals present |
50 – 100 | Block — 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.
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
}
} 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 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.