Documentation · VerifIP · API 1.4

VerifIP IP reputation API reference 

The reference for VerifIP, the IP reputation and fraud-scoring API you call from your server at https://verifip.hextner.com: every endpoint, response field and signal weight, batch rules, response headers, retries, rate limits and error codes. New to the API? Start with the quickstart.

Jump toOpenAPI spec

Every endpoint takes Authorization: Bearer YOUR_API_KEY (your VerifIP API key (under API Keys → VerifIP)). Batch endpoints take 1–100 items and are for paid plans only. Every endpoint and field is in the OpenAPI spec; GET /health needs no key.

VerifIP endpoints and methods

MethodPathInputNotes
GET/v1/checkip (query)IPv4 or IPv6. Score, verdict, flags, geo, ASN, signal_breakdown.
POST/v1/check/batch{"ips": [...]} (1–100)Paid plans only. IPv4 or IPv6. Results in input order. Local data only (no reverse DNS, no external reputation lookup), so a batch score can be lower than /v1/check.
GET/v1/emailemail (query)Syntax, MX, disposable, free and role-based, domain age. Bad syntax is a 200: valid_syntax false, risk_score 100 (invalid_syntax). No verdict field: decide on risk_score.
POST/v1/email/batch{"emails": [...]} (1–100)Paid plans only. ~15 uncached unique domains get full lookups; see Batch rules.
GET/v1/phonephone (query, international format with country code, e.g. +14155552671; send + as %2B)Validity, country, line type, VoIP. The number must be international (E.164 style, leading +country code); a national number such as 9876543210 is not valid. Invalid is a 200: valid false, phone "", risk_score 100.
POST/v1/phone/batch{"phones": [...]} (1–100)Paid plans only.
GET/v1/urlurl (query, URL-encoded)PhishTank, domain age, TLS (ssl_valid + ssl_status); Google Safe Browsing and the URLhaus API when configured. A scheme is optional (http:// is assumed).
POST/v1/url/batch{"urls": [...]} (1–100)Paid plans only. ~15 uncached unique domains get full lookups; see Batch rules.
GET/v1/whoisip (query)RDAP network registration. No risk score.
GET/v1/assessany of ip, email, phone, url (query)One call (1 unit), up to four entities. overall_risk = max(ip × 1.0, url × 0.9, email × 0.8, phone × 0.7), rounded down. IP and URL are validated as on their own endpoints (400 invalid_ip / invalid_url); a malformed email or phone is scored 100, not refused. 503 when any entity could not be assessed.
POST/v1/report{"ip", "is_fraud", "category", "comment"}Stored for review; not used in scoring. Body: ip (required), is_fraud (boolean, default false), category (spam, phishing, brute_force, scraping, bot, other, or null), comment (≤ 1,000 characters). Any field error is a 400, not billed; an accepted report is 1 unit.

Example request for each endpoint

GET /v1/check?ip=185.220.101.1   (illustrative; live values change)

{
  "ip": "185.220.101.1", "ip_version": 4,
  "fraud_score": 43, "verdict": "challenge", "threat_confidence": "low",
  "threat_categories": ["tor", "proxy", "attacker"],
  "is_tor": true, "is_proxy": true, "is_datacenter": true,
  "blocklist_count": 3, "country_code": "DE", "asn": 60729,
  "signal_breakdown": { "tor_exit": 25, "proxy_detected": 15, "datacenter_ip": 10,
    "blocklist_consensus": 12, "known_attacker": 15, "firehol_l2": 15 }
}

What fields does /v1/check return?

FieldTypeDescription
request_idstringUnique request identifier (UUID).
ipstringThe IP address that was checked.
fraud_scoreinteger0 (no evidence) to 100. Use for ordering; enforce on verdict.
is_proxybooleanIP is a known open proxy.
is_vpnbooleanIP belongs to a VPN provider.
is_torbooleanIP is a Tor exit node.
is_datacenterbooleanIP belongs to a hosting/cloud provider.
is_bogonbooleanUnroutable/reserved IP (bogon).
is_known_attackerbooleanOne listing is enough: on blocklist.de, CINS Army or GreenSnow, or in a Spamhaus DROP range.
is_botnet_c2booleanKnown botnet command-and-control server.
is_malware_hostbooleanHosts malware or malicious content.
is_compromisedbooleanKnown compromised system.
cloud_providerstring?Cloud provider that owns the range (AWS, Google, Cloudflare, Fastly), or null.
is_good_botbooleanVerified search engine bot (Googlebot, Bingbot).
good_bot_namestring?Bot name: googlebot, bingbot, google_special_crawler.
is_icloud_relaybooleanApple iCloud Private Relay egress IP.
blocklist_countintegerHow many of the five consensus blocklists list the address (0–5).
threat_categoriesstring[]Labels for the flags that fired: tor, vpn, proxy (classification) and botnet_c2, malware, attacker, compromised, bogon, blocklisted (accusation). Do not treat a non-empty list as hostile: a Tor exit alone is ["tor"] with verdict allow.
country_codestringISO 3166-1 alpha-2 country code.
country_namestringCountry name in English (e.g. Germany); empty when geo is unknown.
region, city, timezonestringRegion, city, and IANA timezone.
isp / asnstring / intISP name and Autonomous System Number.
connection_typestringData Center · Residential · Mobile · Education · Corporate · Unknown
hostnamestringReverse DNS hostname. Empty on batch entries (no reverse DNS) and when the lookup did not finish (then rdns is in enrichment_incomplete).
signal_breakdownmapIndividual signal scores contributing to fraud_score.

Verdict, freshness and completeness

FieldTypeDescription
verdictstringallow, challenge or block. Enforce on this. Absent on a degraded 503.
threat_confidencestringnone, low, medium or high. none exactly when verdict is allow.
ip_versioninteger4 or 6. An IPv4-mapped address is 4.
signals_unavailablestring[]IPv6 only: IPv4 sources with no IPv6 data, so the score is built from fewer lists.
degradedbooleantrue only on a 503: the threat database was unreachable, so there is no verdict.
enrichment_incompletestring[]Optional lookups this answer lacks (e.g. geo, asn, rdns). Empty fields are unknown, not negative.
stale_sourcesstring[]Feeds past their refresh budget, then freshness_unknown when freshness could not be read.
is_scannerbooleanKnown research scanner. Reported, not scored.
threat_categoriesstring[]Labels for the flags that fired. tor, vpn and proxy classify the address (what it is); botnet_c2, malware, attacker, compromised, bogon and blocklisted accuse it (what it has done). Every challenge or block has at least one accusing label; blocklisted means accusing evidence with no category of its own. is_scanner is never listed. Empty means no labelled flag fired (hosting alone has no label).
connection_typestringData Center, Residential, Mobile, Education, Corporate (no other keyword matched) or Unknown (no ASN organisation to classify).
invalid (batch entry)booleanBatch entry: your input was not accepted. Not billed.
degraded (batch entry)booleanBatch entry: we failed to check it. Not billed.

Email answer · /v1/email

FieldTypeDescription
request_idstringUnique request identifier (UUID); same as the X-Request-ID header.
emailstringThe address, normalised (lower case; IDN domains in punycode). As sent when the syntax is invalid.
risk_scoreinteger0–100, the sum of the signals below, capped at 100. There is no verdict field.
valid_syntaxbooleanfalse: the address does not parse; risk_score is 100 and nothing else was checked.
mx_foundbooleanThe domain accepts mail (MX, or an implicit A/AAAA). Also false when the lookup failed; then mx is in enrichment_incomplete and no_mx is not scored.
is_disposablebooleanDomain is a known disposable-mail provider.
is_free_providerbooleanDomain is a free webmail provider (gmail.com, outlook.com …).
is_role_basedbooleanLocal part is a role address (info@, admin@, hello@ …).
domain_age_daysintegerDays since the domain was registered (RDAP); -1 when unknown.
domain_age_unknownstringWhy domain_age_days is -1, when that is permanent: hosted_platform, no_rdap_service, no_registration_date, not_registered. Absent otherwise.
domainstringThe domain part; empty when the syntax is invalid.
signal_breakdownmapThe signals that fired and their weights.
degradedbooleanPresent (true) only when the disposable/free/role tables could not be read: a single call answers 503; in a batch the entry is marked and not billed. Fail closed.
enrichment_incompletestring[]mx, domain_age, external_lookups (batch budget spent), batch_deadline (batch time limit). Present only when non-empty.
SignalWeightDescription
invalid_syntax100Address does not parse. Returned alone; nothing else is checked.
no_mx40Domain accepts no mail. Not scored when the DNS lookup failed.
disposable_domain35Known disposable-mail domain.
new_domain20Domain registered under 30 days ago.
young_domain10Domain registered 30–179 days ago.
role_based15Role address (info@, admin@ …).
free_provider5Free webmail provider.

Phone answer · /v1/phone

FieldTypeDescription
request_idstringUnique request identifier (UUID).
phonestringThe number in E.164 form (+14155552671). Empty when the number is not valid.
risk_scoreinteger0–100, the sum of the signals below, capped at 100. There is no verdict field.
validbooleanThe number is valid for its country. Needs international format with the country code; a national number (9876543210) is not valid. false scores 100.
country_codestringISO 3166-1 alpha-2 region of the number (US, IN, GG …). Empty for non-geographic numbers (+800, +882 …).
carrierstringAlways empty today: no carrier data is available.
line_typestringmobile, landline, voip, toll_free, premium_rate, shared_cost, personal, pager or unknown. Regions that do not separate fixed and mobile (US, Canada) report mobile.
is_voipbooleanline_type is voip.
signal_breakdownmapThe signals that fired and their weights.
SignalWeightDescription
invalid_phone100Not a valid number. Returned alone.
voip_number30VoIP number.
toll_free10Toll-free number.

URL answer · /v1/url

FieldTypeDescription
request_idstringUnique request identifier (UUID).
urlstringThe URL as sent.
risk_scoreinteger0–100, the sum of the signals below, capped at 100. There is no verdict field.
is_phishingbooleanOn PhishTank, or matched by Google Safe Browsing (when configured).
is_malwarebooleanListed by the URLhaus API (when configured).
safe_browsing_threatstringComma-separated Safe Browsing threat types; present only on a match.
in_phishtankbooleanURL is in the PhishTank feed.
spamhaus_dblbooleanAlways false; kept for compatibility.
domain_age_daysintegerDays since the registered domain was registered; -1 when unknown.
domain_age_unknownstringWhy domain_age_days is -1, when that is permanent: hosted_platform, no_rdap_service, no_registration_date, not_registered.
ssl_validbooleantrue exactly when ssl_status is valid.
ssl_statusstringvalid: an HTTPS request succeeded under Cloudflare’s strict certificate validation (trusted chain, hostname match, not expired); revocation is not checked. For a host itself behind Cloudflare, its edge certificate is what is tested. invalid (scored no_ssl), unknown (probe did not finish; not scored), not_probed (not scored).
signal_breakdownmapThe signals that fired and their weights.
degradedbooleanPresent (true) only when the PhishTank data could not be read: a single call answers 503; in a batch the entry is marked and not billed. Fail closed.
stale_sourcesstring[]phishtank when that feed is past its refresh budget, or freshness_unknown.
enrichment_incompletestring[]safe_browsing, urlhaus (lookup failed — not the same as not listed), domain_age, ssl, external_lookups, batch_deadline.
SignalWeightDescription
safe_browsing40Google Safe Browsing match. Only when Safe Browsing is configured.
phishtank35Listed on PhishTank.
urlhaus35URLhaus API match. Only when the URLhaus API is configured.
new_domain15Domain registered under 30 days ago.
no_ssl15HTTPS failed (ssl_status invalid). Not scored when the probe did not finish or was not made.

WHOIS answer · /v1/whois

FieldTypeDescription
ipstringThe address, canonical.
ip_versioninteger4 or 6.
network_cidrstringCIDR notation (8.8.8.0/24). A block that is not one prefix is several, joined with ", " — split before parsing.
network_handlestringThe registry’s identifier for the network (ARIN NET-34-64-0-0-1; RIPE and APNIC give an address range).
network_namestringRegistry network name.
org_namestringRegistrant organisation.
abuse_contactstringAbuse e-mail address, when the registry gives one.
rirstringARIN, RIPE, APNIC, LACNIC or AFRINIC.
allocation_datestringYYYY-MM-DD; empty if none.
country_codestringThe registry’s country for the network (else the registrant’s). Not geolocation.
asnintegerOrigin AS number.
asn_orgstringAS organisation.
enrichment_incompletestring[]rdap (lookup failed: the RDAP fields are empty because we could not ask) or asn.

Assess answer · /v1/assess

FieldTypeDescription
overall_riskintegermax(ip × 1.0, url × 0.9, email × 0.8, phone × 0.7), rounded down.
ipobjectThe /v1/check answer, when ip was given.
emailobjectThe /v1/email answer, when email was given.
phoneobjectThe /v1/phone answer, when phone was given.
urlobjectThe /v1/url answer, when url was given.
degradedbooleanPresent (true) on a 503: an entity could not be assessed, so overall_risk is an under-estimate. Fail closed.
stale_sourcesstring[]Union of the parts’ stale_sources.
enrichment_incompletestring[]The parts’ markers prefixed with the entity (ip.geo, url.ssl), sorted.

Report body and answer · /v1/report

FieldTypeDescription
ip (request)stringRequired. IPv4 or IPv6, same rules as /v1/check.
is_fraud (request)booleanOptional; default false.
category (request)string?Optional: spam, phishing, brute_force, scraping, bot, other, or null.
comment (request)string?Optional; at most 1,000 characters.
status (answer)stringaccepted.
message (answer)stringFraud report submitted successfully.

Batch response format

FieldTypeDescription
resultsarrayOne entry per input, in input order: the single-endpoint answer, or an entry marked invalid or degraded (see Batch rules). 200; 503 when no entry could be answered (invalid ones aside); 400 when every entry is invalid.

Health · GET /health

FieldTypeDescription
statusstringok (200) or degraded (503, with X-VerifIP-Degraded).
versionstringBuild identifier (edge-1.0.0).
d1stringGeo/ASN database: ok, error, timeout or not configured.
d1_threatsstringThreat database: ok only when a real shard was read and decoded; otherwise undecodable, missing, error, timeout or not configured.
supabase_authstringconfigured or not configured.
uptime_secondsintegerSeconds since this server instance first answered /health; absent on that first call.

Signal breakdown · weights

Only signals that fired appear in signal_breakdown. Correlated signals are combined, so the score is not their sum. Sources are listed on Data sources.

SignalWeightDescription
bogon100Unroutable/reserved IP — instant block.
botnet_c240Botnet C2 server (abuse.ch ThreatFox).
tor_exit25Tor exit node (Tor Project exit list, refreshed every 6h).
malware_host25Malware hosting (abuse.ch URLhaus, ThreatFox).
blocklist_consensusup to 204 per list, across blocklist.de, CINS Army, GreenSnow, Emerging Threats and IPsum.
blocklist_corroboration11blocklist_count of 4 or more: at least three independent operators list it.
vpn_detected20X4BNet VPN ranges, or a VPN-pattern reverse-DNS hostname.
firehol_l120FireHOL level 1 (highest severity).
spamhaus_listed20In a Spamhaus DROP range (imported daily).
proxy_detected15FireHOL proxy/anonymous lists, iplocate open proxies.
known_attacker15On blocklist.de, CINS Army or GreenSnow, or in a Spamhaus DROP range.
firehol_l215FireHOL level 2.
compromised10Known compromised host (Emerging Threats).
datacenter_ip10X4BNet datacenter ranges.
firehol_l310FireHOL level 3.
icloud_relay-10Apple iCloud Private Relay (reduces score).
good_bot-50Google or Bing crawler, matched against their published ranges (reduces score).

How should I read the fraud score?

0–19
allow
No evidence worth acting on. An address nobody has accused stays allow up to 70 too: Tor, VPN, proxy and hosting are classification, not accusation (they top out at 40).
20–70
challenge
Only when accused: accusatory evidence (attack-feed or blocklist listings, FireHOL, Spamhaus DROP, a C2 or malware listing) adding up to at least 10. One consensus blocklist alone (4) is not enough.
71–100
block
A bogon (always 100), or an accused address whose combined score passes 70. Today that takes several accusations together with an anonymity classification such as Tor or VPN; evidence alone tops out at 66.

How do batch requests work?

1–100 items, paid plans only (403 plan_required on Free, not billed). Each item must be a string of at most 64 characters (IPs, phones), 320 (emails) or 2,048 (URLs); otherwise the whole request is a 400. A batch is one unit of your burst rate, and it reserves its full length from your quota up front (refused whole with 429 if it does not fit); only answered entries are billed. Results come back in input order. Entries without an answer are marked — never read their zeros as clean. If every entry is invalid the batch is a 400; if no entry could be answered (invalid ones aside) it is a 503; neither is billed. IP batches use local data only (no reverse DNS, no external reputation lookup). Email and URL batches do full lookups for about 15 uncached unique domains, marking the rest external_lookups, and stop waiting on lookups 2 seconds after the batch starts, marking what was still running batch_deadline. Neither is billed; resubmit those entries.

Entry markerBilledMeaning
invalid: true + errorNoYour input was not accepted.
degraded: true + errorNoWe failed to check it (sets X-VerifIP-Degraded).
enrichment_incomplete: ["external_lookups"]NoEmail/URL: answered from local data only. Resubmit it.
enrichment_incomplete: [..., "batch_deadline"]NoEmail/URL: a lookup was still running at the batch time limit. Resubmit or check singly.
(full answer)Yes, 1 eachScored normally, including entries with other enrichment_incomplete markers (mx, domain_age, ssl …).

Does VerifIP support IPv6?

/v1/check, /v1/check/batch, /v1/whois, /v1/assess (its ip) and /v1/report accept IPv6. IPv6 answers set ip_version: 6 and list in signals_unavailable the sources that have no IPv6 data, so the score is built from fewer lists. blocklist_count is always 0 for IPv6. Link-local, unique-local (fc00::/7), multicast, loopback, unspecified and documentation (2001:db8::/32, 3fff::/20) addresses are refused with 400 invalid_ip; an IPv4-mapped address (::ffff:a.b.c.d) is answered as the IPv4 it carries.

Which response headers does VerifIP send?

HeaderMeaning
X-VerifIP-Units-BilledQuota units this request was charged. A single call: 1, also when it returns a 5xx (including a degraded 503); 0 when it is refused as invalid (400), refused by your quota (429 rate_limit_exceeded) or when the quota counter was unreachable (503). A batch: the entries billed (not invalid or degraded entries, and not entries marked external_lookups or batch_deadline); 0 for a 5xx or a refused 400. 0 for a burst 429, capacity_unavailable or a free-plan batch’s 403 plan_required. Absent on 401, an authentication 503, the 403 key errors (key_disabled, public_key_not_allowed, key_type_not_allowed), 404 and 405, which are never billed.
X-RateLimit-Limit / -Remaining / -ResetMonthly quota, what is left after this request’s settled charge, and when it resets (Unix time: 00:00:00 UTC on the 1st). Remaining is approximate between exact counts (it can differ by a few requests across server instances); it is exact near your limit, and a quota refusal is only ever made from an exact count.
X-RateLimit-Daily-Limit / -Daily-RemainingFree plan only: the 1,000/day cap and what is left (approximate, like Remaining). Resets 00:00 UTC.
X-RateLimit-Burst-Limit / -Burst-PeriodYour plan’s burst rate and its window in seconds (10). Approximate, a soft brake: counted per Cloudflare location and per machine with delayed sync, so several connections together can briefly exceed it. Pace client-side; only the monthly quota is exact. Lower while paid keys are held to a fair share. On every metered response, success or refusal.
X-VerifIP-Degradedtrue when the answer (or, on a batch, at least one entry) was produced without a database it depends on, and on every other 503 (capacity_unavailable, service_unavailable). Branch on the status and error code; a 503 whose body has degraded: true has no verdict, so fail closed.
X-VerifIP-IncompleteBatch responses only: true when at least one entry carries enrichment_incomplete. Single answers: read the enrichment_incomplete field.
Retry-AfterSeconds to wait, rounded up. On every 429 (burst: 10, or 60 for the per-key ceiling; quota: until the window resets), on capacity_unavailable (free-plan shed: until 00:00 UTC) and on some quota-counter 503s (1).
X-Request-IDOn every response: a UUID, the same as request_id in a successful answer’s body. Quote it when contacting support.
AllowOn a 405: the method the endpoint takes (GET or POST).

When should I retry, and what are the rate limits?

When to retry

Wait the Retry-After header (every 429 and capacity_unavailable); since API 1.4.2 the body repeats it as retry_after. A retry is a new request: it is billed like one.

ResponseWhat to do
429 burst_limit_exceededWait the Retry-After header (10 s; 60 s for the per-key ceiling of about 3,000 requests/60 s), then retry. Pace with X-RateLimit-Burst-Limit / X-RateLimit-Burst-Period.
429 rate_limit_exceededMonthly quota (or the Free daily cap) is spent. Not billed. Do not retry before the Retry-After header’s seconds have passed: that is when the window resets.
503 capacity_unavailableLoad shedding; nothing ran, not billed. Wait the Retry-After header (free plan: until 00:00 UTC), then retry.
503 service_unavailableAuthentication, quota or storage briefly unreachable. Retry with back-off, honouring Retry-After when present.
503 with degraded: trueNo error field and no verdict, and a single call is billed 1 — so is each retry. Retry once or twice with back-off, then fail closed.
500 / 502 / 504Retry with exponential back-off and jitter. Do not blindly retry an accepted POST /v1/report: a retry stores a duplicate and is billed again.

What are the rate limits on each plan?

PlanMonthly limitDaily limitBurst rate (req / 10s, approximate)Price
Free10,0001,00010$0
Starter50,000—30$49/mo
Growth250,000—100$199/mo
Scale1,000,000—300$699/mo
EnterpriseCustom volume—Custom (per agreement)Custom

The Free plan is also capped at 1,000 requests/day (reset 00:00 UTC) so the monthly quota can't be used up in a single day. Paid plans have no daily cap — only the monthly quota applies (reset 00:00 UTC on the 1st). Requests beyond the quota are refused with HTTP 429 rate_limit_exceeded, which is not billed, until the window resets or you upgrade. A batch reserves its full length up front, so a batch that does not fit in what is left is refused whole (429, not billed). One /v1/assess call is 1 unit, however many entities it checks.

Every metered response carries X-RateLimit-Burst-Limit and X-RateLimit-Burst-Period. Burst rates are a soft brake, not an exact limit: they are enforced per Cloudflare location and per machine, with counts shared after a short delay, so traffic spread over several connections can briefly exceed them, while a single connection is held close to the figure. The monthly quota is exact and is the commercial limit. Pace with your own client-side limiter and honour Retry-After rather than relying on 429s to slow you down. Independently, the edge blocks a single source IP above about 350 requests per 10 seconds. A batch is one burst unit whatever its length. Each key is also held to about 3,000 requests per 60 seconds (429 burst_limit_exceeded, Retry-After 60) against runaway loops and leaked keys. At very high load, free-plan requests may be refused 503 capacity_unavailable until 00:00 UTC, and paid keys may drop to a fair-share burst limit; neither is billed. A request body that is not valid JSON is a 400 invalid_request, not billed.

What do VerifIP error codes mean?

Errors are JSON: { "error": "<code>", "message": "<text>" }. Wait the Retry-After header (every 429 and capacity_unavailable); since API 1.4.2 the body repeats it as retry_after. A degraded 503 has no error field: it carries the partial answer with degraded: true. Every 503 sets X-VerifIP-Degraded: true, so branch on the HTTP status and the error code first, and treat the header as a monitoring signal. Every response carries X-Request-ID; quote it to support.

errorHTTPWhen
invalid_ip400ip missing or not an IP address; a private (10/8, 172.16/12, 192.168/16, fc00::/7), loopback, link-local, multicast or unspecified (0.0.0.0, ::) address; IPv6 documentation space (2001:db8::/32, 3fff::/20); an IPv6 zone identifier (%eth0); a deprecated IPv4-compatible IPv6 address. Reserved IPv4 ranges such as 203.0.113.0/24 are not refused: they are answered is_bogon, score 100, block. Not billed.
invalid_email400email missing or empty. A malformed address is not an error: it is a 200 with valid_syntax false and risk_score 100 (signal invalid_syntax). Not billed.
invalid_phone400phone missing or empty. A number that does not parse is not an error: it is a 200 with valid false, phone "" and risk_score 100 (signal invalid_phone). Not billed.
invalid_url400url missing, unparseable or without a host; a bare IPv4 host; a host with no dot; an internal or reserved name (localhost, metadata, *.local, *.internal, *.localhost, *.test, *.example, *.invalid). Not billed.
invalid_request400Body is not valid JSON ("Request body is not valid JSON.") or (since API 1.4.2; earlier versions answered a null batch body with 500) not a JSON object; a batch array missing, empty or over 100; a batch entry that is not a string or is longer than the cap (ip 64, email 320, phone 64, url 2048 characters); every batch entry invalid; /v1/assess with none of ip, email, phone, url; a /v1/report field error (an unacceptable ip is invalid_ip). Not billed.
invalid_api_key401Missing or invalid API key.
unauthorized401Console endpoints: no Authorization header.
invalid_token401Console endpoints: the session token is invalid or expired. Sign in again.
wrong_auth_type401Console endpoints: an API key (vip_…) was sent where a session token is required.
user_not_found401Console endpoints: the session is valid but the account no longer exists.
key_disabled403Key deactivated. Not billed.
public_key_not_allowed403A DetectBT publishable key (vip_pub_…) on the data API. Use your VerifIP API key (under API Keys → VerifIP). Not billed.
key_type_not_allowed403Any other key that is not a VerifIP API key, such as a DetectBT secret key (sk_live_…). Use your VerifIP API key (under API Keys → VerifIP). Not billed.
plan_required403Batch endpoint on the Free plan. Not billed.
not_found404No such endpoint. Answered before the key is checked. Not billed.
method_not_allowed405Wrong method for the endpoint; the Allow header names the right one. Not billed.
burst_limit_exceeded429Your plan’s burst rate (per 10 s), or the per-key ceiling of about 3,000 requests per 60 s. Both are approximate soft limits, not a guaranteed cut-off, so pace client-side. Not billed. Wait the Retry-After header (10 s, or 60 for the ceiling); the body repeats it as retry_after.
rate_limit_exceeded429Monthly quota (or the Free plan’s 1,000/day cap) spent. Not billed. Wait the Retry-After header: the seconds until the window resets (00:00 UTC on the 1st, or 00:00 UTC for the daily cap); since API 1.4.2 the body repeats it as retry_after. Do not retry before then.
internal_error500Server error. Retry with back-off.
capacity_unavailable503Load shedding; nothing ran. Not billed. Wait the Retry-After header (the body repeats it as retry_after): for free-plan requests shed at high load it is the seconds to 00:00 UTC; for paid keys held to a fair share, 10. Also sets X-VerifIP-Degraded.
service_unavailable503Authentication, the quota counter or report storage could not be reached. Not billed when authentication or the quota counter was unreachable; a report-storage 503 is billed 1. Retry with back-off (Retry-After: 1 when sent). Also sets X-VerifIP-Degraded.
(none; body has degraded: true)503Degraded answer: a database behind the answer could not be read, so the flags are defaults and there is no verdict. Fail closed. Billed 1 for a single call, 0 for a batch.

This product includes GeoLite Data created by MaxMind, available from https://www.maxmind.com. IP blocklist data: The Spamhaus Project (DROP). Phishing data: PhishTank, CC BY-SA 2.5. Malware data: abuse.ch. Hextner uses the IP2Proxy LITE database for IP geolocation. Full notices: Data sources.

Ready to see who isbehind your traffic?