Docs menu
API reference

POST /api/v1/verify

Screen a sign-up. Screen a sign-up from your backend. Generated from the OpenAPI file.

Last updated

Overview

POST https://api.inlineshield.io/api/v1/verify

Screens one sign-up and returns the verdict, the action to take and the reasons. Call it from your backend before you create the account.

  • action: allow (create the account), hold (create it but keep it on hold until the review is decided), block (refuse it).
  • recommendedAction: advice next to action, never instead of it. ALLOWED with riskScore 25 or more gives allow_no_bonus; QUARANTINED gives review; BLOCKED gives block.
  • A live hold has a reviewId; a test hold has none.

With the header X-Response-Mode: http-status (or ?mode=http-status) a BLOCKED screening is answered with HTTP 403 and the same body.

POST /api/v1/evaluate is a deprecated alias with identical behaviour; use /api/v1/verify.

Authentication

Send your secret API key in the X-Api-Key header.

Your secret API key (bs_live_… or bs_test_…), from the console under Settings › Keys. Never put it in a browser or an app. The header name X-ApiKey is also accepted (deprecated).

Parameters

NameInTypeRequiredDescription
X-Response-Modeheaderstring: http-statusnoAnswer a BLOCKED screening with HTTP 403 instead of 200.
modequerystring: http-statusnoSame as the X-Response-Mode header.

Request body

JSON (Content-Type: application/json), required.

Other fields are accepted and ignored.

FieldTypeRequiredDescription
emailstringyesThe address the player signed up with.at least 1 character
ipstringnoThe player's IP address (IPv4 or IPv6). Without it, the IP of the calling connection is used, which is your server's. Anything that is not an IP address gets 400 INVALID_IP.
userAgentstringnoThe player's browser User-Agent. Only this field is read; the request's own User-Agent header (your HTTP client) is ignored. Without it the User-Agent counts as empty.
usernamestringnoThe username the player chose, if any.
clientDwellMsnumbernoMilliseconds the sign-up form was open before it was submitted, if you measure it. Not measured means unknown, never a fast submission.
honeypotstringnoThe value of a hidden form field that people leave empty. A non-empty value is a bot signal.

Request examples

{
  "email": "allow@test.inlineshield.io",
  "ip": "203.0.113.24",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
  "clientDwellMs": 8200
}

The first example as a cURL command:

cURL
curl -X POST https://api.inlineshield.io/api/v1/verify \
  -H "X-Api-Key: $INLINESHIELD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "allow@test.inlineshield.io",
  "ip": "203.0.113.24",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
  "clientDwellMs": 8200
}'

Responses

200 OK

The screening result. A live hold also has reviewId. Live responses carry the quota headers; test responses do not.

Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Quota-Limit (optional), X-Quota-Used (optional), X-Quota-Reset (optional), X-Quota-State (optional).

FieldTypeRequiredDescription
requestIdstringyesThe screening's id. Use it for feedback and support.matches ^AUD-
decisionstringyesALLOWED: let the player in. QUARANTINED: hold for review. BLOCKED: refuse.Values: ALLOWED, QUARANTINED, BLOCKED
actionstringyesWhat to do with the sign-up: ALLOWED → allow, QUARANTINED → hold, BLOCKED → block.Values: allow, hold, block
recommendedActionstringyesAdvice next to action: allow, allow without a sign-up bonus, review, block, or ask for KYC before paying out.Values: allow, allow_no_bonus, review, block, require_kyc
reviewIdstringnoLive holds only (action: "hold"): the review to poll or wait for.matches ^rev_
verdictstringyesSame as decision (kept for older integrations).Values: ALLOWED, QUARANTINED, BLOCKED
riskScorenumberyes0 (lowest risk) to 100.0 to 100
reasonsarray of stringyesPlain-language reasons for the result.
triggersarray of stringyesMachine-readable signal codes (e.g. DISPOSABLE_DOMAIN, TEST_MAGIC_ADDRESS). New codes may be added.
policyVersion"1"yes
processingTimeMsnumberyesat least 0
forwardedToDatabasebooleanyesTrue when the decision is ALLOWED.
emailAnalysisobjectyes
emailAnalysis.normalizedEmailstringyes
emailAnalysis.localPartstringyes
emailAnalysis.domainstringyes
emailAnalysis.isDisposablebooleanyes
emailAnalysis.disposableCategorystringno
emailAnalysis.disposableProviderstringno
emailAnalysis.isPlusAliasedbooleanyes
emailAnalysis.isRoleAccountbooleanyes
emailAnalysis.hasTypoRiskbooleanyes
emailAnalysis.suggestedCorrectionstringno
emailAnalysis.mxValidbooleanyesTrue only when mx.status is found.
emailAnalysis.mxobjectyes
emailAnalysis.mx.statusstringyesabsent only on a definite DNS answer; unknown on any lookup failure.Values: found, absent, unknown
emailAnalysis.mx.recordsarray of objectyes
emailAnalysis.mxRecordsarray of objectyesSame as mx.records.
emailAnalysis.mxRecords[].exchangestringyes
emailAnalysis.mxRecords[].prioritynumberyes
emailAnalysis.mailServersstringyesValues: present, none, unknown
emailAnalysis.spfobjectyes
emailAnalysis.spf.existsbooleanyes
emailAnalysis.spf.statusstringyesabsent only on a definite DNS answer; unknown on any lookup failure.Values: found, absent, unknown
emailAnalysis.spf.rawstringno
emailAnalysis.spf.qualifierstringyesValues: PASS, NEUTRAL, FAIL, NONE
emailAnalysis.spf.isPermissivebooleanyes
emailAnalysis.spf.mechanismsarray of stringyes
emailAnalysis.dmarcobjectyes
emailAnalysis.dmarc.existsbooleanyes
emailAnalysis.dmarc.statusstringyesabsent only on a definite DNS answer; unknown on any lookup failure.Values: found, absent, unknown
emailAnalysis.dmarc.rawstringno
emailAnalysis.dmarc.policystringyesValues: reject, quarantine, none, missing
emailAnalysis.dmarc.compliantbooleanyes
emailAnalysis.dmarc.percentagenumberno
emailAnalysis.dkimobjectyes
emailAnalysis.dkim.selectorCheckedstringyes
emailAnalysis.dkim.existsbooleanyes
emailAnalysis.dkim.rawstringno
emailAnalysis.dkim.statusstringyesValues: VERIFIED, FOUND, NOT_FOUND, UNVERIFIABLE
emailAnalysis.providerTypestringnoValues: FREE_WEBMAIL, DISPOSABLE, CORPORATE_PRIVATE
emailAnalysis.isCatchAllbooleanno
emailAnalysis.localPartRandomnessobject or nullno
emailAnalysis.localPartRandomness.randombooleanyes
emailAnalysis.localPartRandomness.scorenumberyes0 to 1
botAnalysisobjectyes
botAnalysis.dwellTimeMsnumber or nullyesnull when not measured (never a fast submission).
botAnalysis.isFastSubmissionbooleanyes
botAnalysis.honeypotTriggeredbooleanyes
botAnalysis.trapFieldTriggeredbooleanno
botAnalysis.trapFieldValuestringno
botAnalysis.isHeadlessBrowserbooleanyes
botAnalysis.headlessUserAgentbooleanno
botAnalysis.automationSignalsarray of stringno
botAnalysis.isAutomationAgentbooleanyes
botAnalysis.scriptedUserAgentbooleanyesThe User-Agent is a known scripted HTTP client (curl, python-requests, …).
botAnalysis.scriptedClientstring or nullyes
botAnalysis.entropyScorenumberyes
ipRiskobjectyes
ipRisk.ipstringyes
ipRisk.isDatacenterboolean or nullyesnull = unknown.
ipRisk.isTorExitboolean or nullyesnull = unknown.
ipRisk.isVpnOrProxybooleanyesisDatacenter or isTorExit is true.
ipRisk.isVpnboolean or nullyesOn the VPN provider list; null = unknown.
ipRisk.countrystringyesCountry name, or "Unknown".
ipRisk.countryCodestring or nullyes
ipRisk.countryNamestring or nullno
ipRisk.citystring or nullno
ipRisk.regionstring or nullno
ipRisk.flagEmojistringno
ipRisk.orgstringyesThe network owner, or "Unknown".
ipRisk.asOrgstring or nullno
ipRisk.asnstring or nullyes"AS<number>", or null.
ipRisk.ispstring or nullno
{
  "requestId": "AUD-MHH3K2QZ-4F1C9B2E7D10",
  "decision": "ALLOWED",
  "action": "allow",
  "recommendedAction": "allow",
  "verdict": "ALLOWED",
  "riskScore": 0,
  "reasons": [
    "Test mode: allow@test.inlineshield.io always gives ALLOWED / allow."
  ],
  "triggers": [
    "TEST_MAGIC_ADDRESS"
  ],
  "policyVersion": "1",
  "processingTimeMs": 0,
  "forwardedToDatabase": true,
  "emailAnalysis": {
    "normalizedEmail": "allow@test.inlineshield.io",
    "localPart": "allow",
    "domain": "test.inlineshield.io",
    "isDisposable": false,
    "isPlusAliased": false,
    "isRoleAccount": false,
    "hasTypoRisk": false,
    "mxValid": false,
    "mx": {
      "status": "unknown",
      "records": []
    },
    "mxRecords": [],
    "mailServers": "unknown",
    "spf": {
      "exists": false,
      "status": "unknown",
      "qualifier": "NONE",
      "isPermissive": false,
      "mechanisms": []
    },
    "dmarc": {
      "exists": false,
      "status": "unknown",
      "policy": "missing",
      "compliant": false
    },
    "dkim": {
      "selectorChecked": "",
      "exists": false,
      "status": "UNVERIFIABLE"
    },
    "localPartRandomness": null
  },
  "botAnalysis": {
    "dwellTimeMs": 8200,
    "isFastSubmission": false,
    "honeypotTriggered": false,
    "isHeadlessBrowser": false,
    "isAutomationAgent": false,
    "scriptedUserAgent": false,
    "scriptedClient": null,
    "entropyScore": 0
  },
  "ipRisk": {
    "ip": "203.0.113.24",
    "isDatacenter": null,
    "isTorExit": null,
    "isVpnOrProxy": false,
    "isVpn": null,
    "country": "Unknown",
    "countryCode": null,
    "org": "Unknown",
    "asn": null
  }
}

400 Bad Request

email missing, empty or not a string; an ip that is not an IP address; or a body that is not valid JSON.

FieldTypeRequiredDescription
errorstringyesValues: BAD_REQUEST, INVALID_IP
messagestringyes
{
  "error": "BAD_REQUEST",
  "message": "Field \"email\" is required for registration verification."
}

401 Unauthorized

No X-Api-Key header, or an unknown, revoked or expired key.

FieldTypeRequiredDescription
errorstringyesValues: UNAUTHORIZED, INVALID_API_KEY, EXPIRED_API_KEY, ORGANIZATION_NOT_FOUND
messagestringyes
{
  "error": "UNAUTHORIZED",
  "message": "Access denied: Missing required X-Api-Key header."
}

402 Payment Required

The monthly quota is used up. Test requests of an account over its quota get this too (they are still not counted).

Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.

FieldTypeRequiredDescription
error"QUOTA_EXCEEDED"yes
messagestringyes
usedintegeryesat least 0
limitintegeryesat least 0
resetsAtstring (date-time)yesThe start of the next UTC month.
quota
{
  "error": "QUOTA_EXCEEDED",
  "message": "Monthly check limit reached for this account. Checks resume at 2026-12-01T00:00:00.000Z.",
  "used": 27500,
  "limit": 25000,
  "resetsAt": "2026-12-01T00:00:00.000Z"
}

403 Forbidden

Either a BLOCKED screening with X-Response-Mode: http-status (the body is the screening result), or TENANT_SUSPENDED.

Headers: X-RateLimit-Limit (optional), X-RateLimit-Remaining (optional), X-RateLimit-Reset (optional), X-Quota-Limit (optional), X-Quota-Used (optional), X-Quota-Reset (optional), X-Quota-State (optional).

One of 2 shapes:

VerifyResponse

FieldTypeRequiredDescription
requestIdstringyesThe screening's id. Use it for feedback and support.matches ^AUD-
decisionstringyesALLOWED: let the player in. QUARANTINED: hold for review. BLOCKED: refuse.Values: ALLOWED, QUARANTINED, BLOCKED
actionstringyesWhat to do with the sign-up: ALLOWED → allow, QUARANTINED → hold, BLOCKED → block.Values: allow, hold, block
recommendedActionstringyesAdvice next to action: allow, allow without a sign-up bonus, review, block, or ask for KYC before paying out.Values: allow, allow_no_bonus, review, block, require_kyc
reviewIdstringnoLive holds only (action: "hold"): the review to poll or wait for.matches ^rev_
verdictstringyesSame as decision (kept for older integrations).Values: ALLOWED, QUARANTINED, BLOCKED
riskScorenumberyes0 (lowest risk) to 100.0 to 100
reasonsarray of stringyesPlain-language reasons for the result.
triggersarray of stringyesMachine-readable signal codes (e.g. DISPOSABLE_DOMAIN, TEST_MAGIC_ADDRESS). New codes may be added.
policyVersion"1"yes
processingTimeMsnumberyesat least 0
forwardedToDatabasebooleanyesTrue when the decision is ALLOWED.
emailAnalysisobjectyes
emailAnalysis.normalizedEmailstringyes
emailAnalysis.localPartstringyes
emailAnalysis.domainstringyes
emailAnalysis.isDisposablebooleanyes
emailAnalysis.disposableCategorystringno
emailAnalysis.disposableProviderstringno
emailAnalysis.isPlusAliasedbooleanyes
emailAnalysis.isRoleAccountbooleanyes
emailAnalysis.hasTypoRiskbooleanyes
emailAnalysis.suggestedCorrectionstringno
emailAnalysis.mxValidbooleanyesTrue only when mx.status is found.
emailAnalysis.mxobjectyes
emailAnalysis.mx.statusstringyesabsent only on a definite DNS answer; unknown on any lookup failure.Values: found, absent, unknown
emailAnalysis.mx.recordsarray of objectyes
emailAnalysis.mxRecordsarray of objectyesSame as mx.records.
emailAnalysis.mxRecords[].exchangestringyes
emailAnalysis.mxRecords[].prioritynumberyes
emailAnalysis.mailServersstringyesValues: present, none, unknown
emailAnalysis.spfobjectyes
emailAnalysis.spf.existsbooleanyes
emailAnalysis.spf.statusstringyesabsent only on a definite DNS answer; unknown on any lookup failure.Values: found, absent, unknown
emailAnalysis.spf.rawstringno
emailAnalysis.spf.qualifierstringyesValues: PASS, NEUTRAL, FAIL, NONE
emailAnalysis.spf.isPermissivebooleanyes
emailAnalysis.spf.mechanismsarray of stringyes
emailAnalysis.dmarcobjectyes
emailAnalysis.dmarc.existsbooleanyes
emailAnalysis.dmarc.statusstringyesabsent only on a definite DNS answer; unknown on any lookup failure.Values: found, absent, unknown
emailAnalysis.dmarc.rawstringno
emailAnalysis.dmarc.policystringyesValues: reject, quarantine, none, missing
emailAnalysis.dmarc.compliantbooleanyes
emailAnalysis.dmarc.percentagenumberno
emailAnalysis.dkimobjectyes
emailAnalysis.dkim.selectorCheckedstringyes
emailAnalysis.dkim.existsbooleanyes
emailAnalysis.dkim.rawstringno
emailAnalysis.dkim.statusstringyesValues: VERIFIED, FOUND, NOT_FOUND, UNVERIFIABLE
emailAnalysis.providerTypestringnoValues: FREE_WEBMAIL, DISPOSABLE, CORPORATE_PRIVATE
emailAnalysis.isCatchAllbooleanno
emailAnalysis.localPartRandomnessobject or nullno
emailAnalysis.localPartRandomness.randombooleanyes
emailAnalysis.localPartRandomness.scorenumberyes0 to 1
botAnalysisobjectyes
botAnalysis.dwellTimeMsnumber or nullyesnull when not measured (never a fast submission).
botAnalysis.isFastSubmissionbooleanyes
botAnalysis.honeypotTriggeredbooleanyes
botAnalysis.trapFieldTriggeredbooleanno
botAnalysis.trapFieldValuestringno
botAnalysis.isHeadlessBrowserbooleanyes
botAnalysis.headlessUserAgentbooleanno
botAnalysis.automationSignalsarray of stringno
botAnalysis.isAutomationAgentbooleanyes
botAnalysis.scriptedUserAgentbooleanyesThe User-Agent is a known scripted HTTP client (curl, python-requests, …).
botAnalysis.scriptedClientstring or nullyes
botAnalysis.entropyScorenumberyes
ipRiskobjectyes
ipRisk.ipstringyes
ipRisk.isDatacenterboolean or nullyesnull = unknown.
ipRisk.isTorExitboolean or nullyesnull = unknown.
ipRisk.isVpnOrProxybooleanyesisDatacenter or isTorExit is true.
ipRisk.isVpnboolean or nullyesOn the VPN provider list; null = unknown.
ipRisk.countrystringyesCountry name, or "Unknown".
ipRisk.countryCodestring or nullyes
ipRisk.countryNamestring or nullno
ipRisk.citystring or nullno
ipRisk.regionstring or nullno
ipRisk.flagEmojistringno
ipRisk.orgstringyesThe network owner, or "Unknown".
ipRisk.asOrgstring or nullno
ipRisk.asnstring or nullyes"AS<number>", or null.
ipRisk.ispstring or nullno

SuspendedError

FieldTypeRequiredDescription
errorstringyesValues: TENANT_SUSPENDED
messagestringyes

429 Too Many Requests

RATE_LIMIT_EXCEEDED: the per-minute limit (your plan's; 300 for test keys). TEST_LIMIT: 10,000 test requests per account per UTC day; Retry-After is the time to the next UTC midnight.

Headers: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.

FieldTypeRequiredDescription
errorstringyesValues: RATE_LIMIT_EXCEEDED, TEST_LIMIT
messagestringyes
{
  "error": "RATE_LIMIT_EXCEEDED",
  "message": "Tenant verification rate limit of 600 req/min exceeded. Try again in 12 seconds."
}

500 Internal Server Error

The screening failed or could not be stored. Your integration decides whether to fail open or closed.

FieldTypeRequiredDescription
errorstringyesValues: VERIFICATION_ERROR, PERSISTENCE_FAILURE, AUTH_ERROR
messagestringyes

503 Service Unavailable

The key could not be checked right now. Try again.

FieldTypeRequiredDescription
errorstringyesValues: DATABASE_ERROR
messagestringyes

Errors

Errors are JSON, { "error": "CODE", "message": "…" }. Branch on error, never on message.

HTTP statuserrorWhen
400BAD_REQUEST, INVALID_IPemail missing, empty or not a string; an ip that is not an IP address; or a body that is not valid JSON.
401UNAUTHORIZED, INVALID_API_KEY, EXPIRED_API_KEY, ORGANIZATION_NOT_FOUNDNo X-Api-Key header, or an unknown, revoked or expired key.
402QUOTA_EXCEEDEDThe monthly quota is used up. Test requests of an account over its quota get this too (they are still not counted).
403TENANT_SUSPENDEDEither a BLOCKED screening with X-Response-Mode: http-status (the body is the screening result), or TENANT_SUSPENDED.
429RATE_LIMIT_EXCEEDED, TEST_LIMITRATE_LIMIT_EXCEEDED: the per-minute limit (your plan's; 300 for test keys). TEST_LIMIT: 10,000 test requests per account per UTC day; Retry-After is the time to the next UTC midnight.
500VERIFICATION_ERROR, PERSISTENCE_FAILURE, AUTH_ERRORThe screening failed or could not be stored. Your integration decides whether to fail open or closed.
503DATABASE_ERRORThe key could not be checked right now. Try again.

See all errors for every endpoint.

Headers and limits

Response headerMeaning
X-RateLimit-LimitRequests allowed in the current one-minute window.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetSeconds until the current window ends.
X-Quota-LimitLive requests only: the monthly quota of the plan.
X-Quota-UsedLive requests only: screenings counted this month, this one included.
X-Quota-ResetLive requests only: when the count resets (the start of the next UTC month).
X-Quota-StateLive requests only: ok, warn (80% used), limit (100%), grace (over 100%, still served) or blocked.
Retry-AfterSeconds to wait before trying again.

The limits per endpoint and plan are on Rate limits and quotas.