Security Accelerated

Documentation

Domain Intelligence

Concurrent WHOIS, DNS, SSL, HTTP header, screenshot, reputation, email security, subdomain, certificate transparency, BGP/ASN, technology fingerprinting, threat intelligence, and DNS security lookups for any domain — with an aggregate risk score in every response.

https://api.securityaccelerated.com v0.74.1
support@securityaccelerated.com

Authentication

All requests require an API key passed via the X-API-Key header:

X-API-Key: your-api-key-here
ScenarioResponse
Missing API key401 Unauthorized
Invalid API key401 Unauthorized
Feature not included in plan403 Forbidden

Plans

Feature access and limits are determined by your API key's plan.

FreePro
Rate limit5 req/min120 req/min
Max domains per request120
WHOIS, DNS, SSL, HeadersIncludedIncluded
Email SecurityIncludedIncluded
SubdomainsIncludedIncluded
CT LogsIncludedIncluded
BGP / ASNIncludedIncluded
FingerprintIncludedIncluded
DNS Security (DNSSEC / CAA)IncludedIncluded
Aggregate Risk ScoreIncludedIncluded
Screenshot--Pro
Reputation--Pro
Threat Intelligence--Pro

Pricing

Get started with a free plan or upgrade for higher limits and premium features.

PlanFeatures
Free 5 req/min, 1 domain/request, all lookups except screenshot, reputation, and threat intel Included with any API key
Pro 120 req/min, 20 domains/request, all lookups including screenshot, reputation, and threat intel Subscribe
After subscribing, your API key will be shown on a one-time confirmation page. Save it immediately -- it cannot be retrieved again.

Already a subscriber? Manage your subscription

Rate Limiting

Requests are rate-limited per API key using a sliding window. Responses report your current window state:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed per minute
X-RateLimit-RemainingRequests remaining in the current window
Retry-AfterSeconds until the window resets (only present on 429 responses)

When the limit is exceeded, the API returns 429 Too Many Requests. Wait until the Retry-After period expires before retrying.

Domain Intelligence

POST /v1/domain/lookup

Look up one or more domains concurrently. You choose exactly which lookups to run via the lookups array — there is no default set. All requested lookups run concurrently with a 30-second timeout.

Request Body

FieldTypeRequiredDescription
domains string[] Required Domain names to look up. Max count depends on plan (1 / 5 / 20), counted on the entries you submit. Requesting subdomain enumeration lowers that cap — see Subdomain batch limits.
lookups string[] Required Which lookups to run. Must be non-empty — there is no default set and no "all" value. Names are trimmed, lowercased and deduplicated, so [" SSL ", "ssl"] is just ["ssl"].

Valid Lookups

NamePlanDescription
whoisAllRegistration, registrar, expiry, nameservers, contacts.
dnsAllA, AAAA, MX, NS, TXT, and CNAME records.
sslAllTLS certificate subject, issuer, validity window, SANs.
headersAllHTTP response headers and security header analysis.
email_securityAllCheck SPF, DMARC, and DKIM records.
subdomainsAllEnumerate subdomains via Certificate Transparency logs.
ct_logsAllQuery Certificate Transparency logs for issued certificates.
bgpAllLook up BGP/ASN information for the domain's IP addresses.
fingerprintAllDetect web technologies from HTTP response headers. Costs no extra network call — see Caching.
dns_securityAllDNSSEC deployment (DS + DNSKEY + algorithms) and CAA records.
riskAllWeighted 0–100 aggregate risk score over the other lookups you requested. No network call, never cached — see Caching.
screenshotProCapture a page screenshot.
reputationProCheck domain against threat blocklists.
threat_intelProQuery threat intelligence feeds (VirusTotal, URLhaus, AlienVault OTX).

An unrecognized name returns 400 naming the offending entry. A lookup outside your plan returns 403.

Subdomain batch limits

Subdomain enumeration queries Certificate Transparency logs, which are slower and more rate-limited than the other sources, so a request that includes subdomains is held to a tighter domain cap than the same plan's usual one. The cap applies to the whole request: exceeding it returns 400 and nothing is looked up.

PlanDomains per requestWith subdomains requested
Free11
Pro205

To enumerate subdomains for a larger batch, split it into several requests, or run subdomains in its own request and the remaining lookups in another.

Domain Normalization

Domain names are normalized automatically. You can pass bare domains or full URLs with any scheme — the API extracts the hostname by stripping scheme prefixes, userinfo, ports, paths, query strings, and fragments, then trims whitespace and lowercases the result. Entries that normalize to the same domain are deduplicated into a single result. The domain field in the response holds the normalized form and inputs lists the original entries that mapped to it. Raw IP addresses are rejected — use the IP Intelligence endpoint instead.

You sendWe look up
example.comexample.com
https://example.com/path?q=1example.com
ftp://example.comexample.com
example.com:8080example.com
user:pass@example.comexample.com
Example.COM/example.com
  example.com  example.com

Example

curl -X POST https://api.securityaccelerated.com/v1/domain/lookup \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: your-api-key' \
  -H 'X-Client-Request-ID: req-001' \
  -d '{
    "domains": ["example.com"],
    "lookups": ["dns", "ssl", "email_security", "subdomains"]
  }'
const res = await fetch("https://api.securityaccelerated.com/v1/domain/lookup", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "your-api-key",
    "X-Client-Request-ID": "req-001",
  },
  body: JSON.stringify({
    domains: ["example.com"],
    lookups: ["dns", "ssl", "email_security", "subdomains"],
  }),
});

if (!res.ok) {
  const { error } = await res.json();
  throw new Error(error);
}

// Results are wrapped in a "results" envelope. Match by `domain` rather than
// by position: duplicates collapse and ordering is not guaranteed.
const { results } = await res.json();

for (const r of results) {
  // Every lookup carries its own cache_hit.
  console.log(r.domain, r.dns?.a, r.dns?.cache_hit);

  // Individual lookups can fail while the rest succeed.
  for (const e of r.errors ?? []) {
    console.warn(`${r.domain}: ${e.lookup} failed — ${e.message}`);
  }
}
import requests

resp = requests.post(
    "https://api.securityaccelerated.com/v1/domain/lookup",
    headers={
        "X-API-Key": "your-api-key",
        "X-Client-Request-ID": "req-001",
    },
    json={
        "domains": ["example.com"],
        "lookups": ["dns", "ssl", "email_security", "subdomains"],
    },
)

if resp.status_code != 200:
    print("Error:", resp.json()["error"])
else:
    # Results are wrapped in a "results" envelope.
    for result in resp.json()["results"]:
        print(result["domain"], result["dns"]["a"], result["dns"]["cache_hit"])
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

func main() {
    body, _ := json.Marshal(map[string]any{
        "domains": []string{"example.com"},
        "lookups": []string{"dns", "ssl", "email_security", "subdomains"},
    })

    req, _ := http.NewRequest("POST",
        "https://api.securityaccelerated.com/v1/domain/lookup",
        bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-API-Key", "your-api-key")
    req.Header.Set("X-Client-Request-ID", "req-001")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        var apiErr map[string]string
        json.NewDecoder(resp.Body).Decode(&apiErr)
        fmt.Println("Error:", apiErr["error"])
        return
    }

    // Results are wrapped in a "results" envelope.
    var response struct {
        Results []map[string]any `json:"results"`
    }
    json.NewDecoder(resp.Body).Decode(&response)
    fmt.Println(response.Results[0]["domain"])
}

Response Structure

Results are wrapped in a results envelope, so every response — success or error — is a JSON object. The envelope leaves room for response-level fields to be added later without breaking clients.

{
  "results": [
    {
      "domain": "example.com",
      "inputs": ["HTTPS://Example.COM/", "example.com"],
      "dns": { "cache_hit": true, "a": ["93.184.216.34"] },
      "ssl": { "cache_hit": false, ... }
    }
  ]
}

Identifying Results

results contains one element per unique normalized domain:

Array position is not part of the API contract. Do not assume results[i] corresponds to domains[i] — duplicates collapse and ordering is not guaranteed.

Each result contains only the lookups you requested — never any you did not — plus domain and inputs, and errors when a lookup failed. A lookup that failed is omitted rather than returned empty, so you can get fewer fields than you asked for.

Per-Lookup cache_hit

Every lookup object carries its own cache_hit flag: true when that lookup was served from cache, false when it was fetched live. There is no top-level cache_hit — different fields in the same result routinely differ, because each lookup is cached separately with its own TTL. See Caching.

Invalid Domains

Invalid domains in a multi-domain request do not fail the entire request. They are included in the response with a validation error while valid domains return normally:

{
  "results": [
    {
      "domain": "example.com",
      "inputs": ["example.com"],
      "whois": { "cache_hit": false, "domain_name": "example.com", ... },
      "dns": { "cache_hit": true, ... }
    },
    {
      "domain": "notadomain",
      "inputs": ["notadomain"],
      "errors": [{ "lookup": "validation", "message": "invalid domain name" }]
    }
  ]
}

A request whose only domain is invalid returns 400.

Response Fields

whois -- Domain Registration

FieldTypeDescription
cache_hitboolWhether this lookup was served from cache (true) or fetched live (false)
domain_namestringThe registered domain name
registrarstringRegistrar (e.g., GoDaddy, Namecheap)
created_datestringRegistration date -- new domains are higher risk
updated_datestringLast modification date
expiry_datestringExpiration date -- expiring domains are red flags
statusstring[]EPP status codes (e.g., clientTransferProhibited)
name_serversstring[]Authoritative DNS servers
registrantobjectRegistrant contact (name, organization, country, email)
adminobjectAdministrative contact
techobjectTechnical contact
{
  "domain_name": "example.com",
  "registrar": "RESERVED-Internet Assigned Numbers Authority",
  "created_date": "1995-08-14T04:00:00Z",
  "updated_date": "2024-08-14T07:01:38Z",
  "expiry_date": "2025-08-13T04:00:00Z",
  "status": ["clientDeleteProhibited", "clientTransferProhibited"],
  "name_servers": ["a.iana-servers.net", "b.iana-servers.net"],
  "registrant": { "organization": "REDACTED FOR PRIVACY", "country": "US" }
}
Contact fields are often redacted by WHOIS privacy services.

dns -- DNS Records

FieldTypeDescription
cache_hitboolWhether this lookup was served from cache (true) or fetched live (false)
astring[]IPv4 addresses
aaaastring[]IPv6 addresses
mxobject[]Mail servers (host, priority)
nsstring[]Name servers
txtstring[]TXT records (SPF, verification tokens, etc.)
cnamestringCanonical name alias
{
  "a": ["93.184.216.34"],
  "aaaa": ["2606:2800:220:1:248:1893:25c8:1946"],
  "mx": [{ "host": "mail.example.com", "priority": 10 }],
  "ns": ["a.iana-servers.net", "b.iana-servers.net"],
  "txt": ["v=spf1 -all"]
}

ssl -- TLS Certificate

FieldTypeDescription
cache_hitboolWhether this lookup was served from cache (true) or fetched live (false)
subjectstringEntity the certificate was issued to
issuerstringCertificate authority
not_beforestringStart of validity period
not_afterstringEnd of validity period
expiredboolWhether the certificate has expired
days_until_expiryintDays remaining until expiration
sansstring[]Subject Alternative Names -- all hostnames the cert covers
serial_numberstringCertificate serial number
signature_algorithmstringCryptographic algorithm (e.g., SHA-256)
{
  "subject": "www.example.org",
  "issuer": "DigiCert Global G2 TLS RSA SHA256 2020 CA1",
  "not_before": "2024-01-30T00:00:00Z",
  "not_after": "2025-03-01T23:59:59Z",
  "expired": false,
  "days_until_expiry": 142,
  "sans": ["www.example.org", "example.net", "example.org"],
  "serial_number": "0F:BE:08:B0:85:4D:05:73:8A:B0...",
  "signature_algorithm": "SHA256-RSA"
}

headers -- HTTP Response

FieldTypeDescription
cache_hitboolWhether this lookup was served from cache (true) or fetched live (false)
status_codeintHTTP status code (200, 301, 404, etc.)
headersobjectRaw response headers as key-value pairs
security_headersobjectSecurity header audit (see below)

Security Headers Checked

FieldHTTP Header
strict_transport_securityStrict-Transport-Security (HSTS)
content_security_policyContent-Security-Policy (CSP)
x_frame_optionsX-Frame-Options
x_content_type_optionsX-Content-Type-Options
x_xss_protectionX-XSS-Protection
referrer_policyReferrer-Policy
permissions_policyPermissions-Policy
{
  "status_code": 200,
  "headers": {
    "Server": "ECAcc (dcd/7D5A)",
    "Content-Type": "text/html; charset=UTF-8",
    "Cache-Control": "max-age=604800"
  },
  "security_headers": {
    "strict_transport_security": "max-age=31536000",
    "x_content_type_options": "nosniff",
    "x_frame_options": "DENY"
  }
}

screenshot -- Page Screenshot Pro

FieldTypeDescription
cache_hitboolWhether this lookup was served from cache (true) or fetched live (false)
status_codeintHTTP status code from probing the domain
pngstringBase64-encoded PNG image (empty for non-200 responses)
{
  "status_code": 200,
  "png": "iVBORw0KGgoAAAANSUhEUgAA..."
}
Tries HTTPS first, then falls back to HTTP. Captured at 1280x720 with headless Chrome.

Screenshots are captured a few at a time

The other lookups are a network call at most — fingerprint and risk are pure computation over results you already asked for. A screenshot is a browser, and a browser wants roughly half a gigabyte of memory, so captures run under a small service-wide concurrency cap — typically one at a time — rather than in parallel. A request asking for screenshot waits its turn behind any capture already running — including captures from other customers.

If a capture has not started by the time the request deadline arrives, that lookup reports a queue timeout instead of the request piling up another browser:

{ "lookup": "screenshot", "message": "screenshot queue timed out, please retry" }

The rest of the response is unaffected — every other lookup you asked for still returns, and the status is still 200.

This one is worth retrying. Most lookup errors mean the answer is not available; this one only means the service was busy, and the same request a moment later usually succeeds. Two practical habits: ask for screenshot over a handful of domains rather than a full batch, and put it in its own request so a queue wait never delays the lookups that would have returned immediately.

reputation -- Domain Reputation Pro

FieldTypeDescription
cache_hitboolWhether this lookup was served from cache (true) or fetched live (false)
scoreintRisk score from 0 (clean) to 100 (maximum risk)
riskstringclean, low, medium, or high
providersarrayPer-provider results

Provider Result

FieldTypeDescription
listedboolWhether the domain appears on this blocklist
categorystringClassification if listed (e.g., phishing, malware)
providerstringBlocklist name
return_codestringRaw DNS return code (only when listed)
{
  "score": 35,
  "risk": "medium",
  "providers": [
    { "listed": true, "category": "phishing", "provider": "spamhaus_dbl", "return_code": "127.0.1.4" },
    { "listed": false, "provider": "surbl" },
    { "listed": false, "provider": "uribl" },
    { "listed": false, "provider": "spamhaus_zen" }
  ]
}

Providers

The following blocklists are checked: Spamhaus DBL, SURBL, URIBL, and Spamhaus ZEN.

Risk Levels

RiskScore Range
clean0
low1 -- 29
medium30 -- 59
high60 -- 100

email_security -- Email Authentication

spf -- Sender Policy Framework

FieldTypeDescription
cache_hitboolWhether this lookup was served from cache (true) or fetched live (false)
rawstringFull SPF TXT record
versionstringSPF version (typically spf1)
mechanismsstring[]Parsed mechanisms (include:, ip4:, etc.)
allstringTerminal qualifier: -all, ~all, ?all, or +all

dmarc -- DMARC Policy

FieldTypeDescription
rawstringFull DMARC TXT record
versionstringDMARC version (typically DMARC1)
policystringnone, quarantine, or reject
pctintPercentage of messages the policy applies to
ruastringAggregate report destination
rufstringForensic report destination
spstringSubdomain policy

dkim -- DKIM Records

FieldTypeDescription
selectorstringSelector name probed
foundboolWhether a DKIM record exists for this selector
rawstringFull DKIM TXT record (only when found)

mta_sts -- MTA Strict Transport Security

Only present when a record exists at _mta-sts.<domain>. When the TXT record is found, the policy file at https://mta-sts.<domain>/.well-known/mta-sts.txt is fetched for the mode and MX patterns.

FieldTypeDescription
rawstringFull MTA-STS TXT record
idstringPolicy version identifier (id= tag)
policy_fetchedboolWhether the well-known policy file was retrieved
modestringenforce, testing, or none (from the policy file)
max_ageintPolicy cache lifetime in seconds
mx_patternsstring[]MX host patterns the policy allows

tls_rpt -- SMTP TLS Reporting

Only present when a record exists at _smtp._tls.<domain>.

FieldTypeDescription
rawstringFull TLS-RPT TXT record
ruastringReport destination (e.g., mailto:tlsrpt@example.com)

bimi -- Brand Indicators for Message Identification

Only present when a record exists at default._bimi.<domain>.

FieldTypeDescription
rawstringFull BIMI TXT record
locationstringBrand logo SVG URL (l= tag)
authoritystringVerified Mark Certificate URL (a= tag)
{
  "spf": {
    "raw": "v=spf1 include:_spf.google.com -all",
    "version": "spf1",
    "mechanisms": ["include:_spf.google.com"],
    "all": "-all"
  },
  "dmarc": {
    "raw": "v=DMARC1; p=reject; rua=mailto:dmarc@example.com",
    "version": "DMARC1",
    "policy": "reject",
    "rua": "mailto:dmarc@example.com"
  },
  "dkim": [
    { "selector": "google", "found": true, "raw": "v=DKIM1; k=rsa; p=MIGf..." },
    { "selector": "default", "found": false }
  ],
  "mta_sts": {
    "raw": "v=STSv1; id=20240101T000000",
    "id": "20240101T000000",
    "policy_fetched": true,
    "mode": "enforce",
    "max_age": 86400,
    "mx_patterns": ["*.example.com"]
  },
  "tls_rpt": { "raw": "v=TLSRPTv1; rua=mailto:tlsrpt@example.com", "rua": "mailto:tlsrpt@example.com" },
  "bimi": { "raw": "v=BIMI1; l=https://example.com/logo.svg", "location": "https://example.com/logo.svg" }
}
DKIM probes 8 common selectors: google, default, selector1, selector2, k1, s1, mail, dkim. The mta_sts, tls_rpt, and bimi objects are omitted when the corresponding DNS record does not exist.

subdomains -- Subdomain Enumeration

FieldTypeDescription
cache_hitboolWhether this lookup was served from cache (true) or fetched live (false)
countintNumber of subdomains returned
cappedbooltrue if results were truncated at the cap (500)
subdomainsarrayDiscovered subdomains

Subdomain Entry

FieldTypeDescription
domainstringThe subdomain
sourcestringData source (crt.sh)
{
  "count": 3,
  "capped": false,
  "subdomains": [
    { "domain": "www.example.com", "source": "crt.sh" },
    { "domain": "mail.example.com", "source": "crt.sh" },
    { "domain": "api.example.com", "source": "crt.sh" }
  ]
}

ct_logs -- Certificate Transparency Logs

FieldTypeDescription
cache_hitboolWhether this lookup was served from cache (true) or fetched live (false)
countintNumber of certificate entries returned
cappedbooltrue if results were truncated at the cap (100)
entriesarrayCertificate log entries, sorted newest-first

CT Log Entry

FieldTypeDescription
logged_atstringWhen the certificate was logged to CT
not_beforestringCertificate validity start
not_afterstringCertificate validity end
issuerstringCertificate Authority
common_namestringCertificate CN field
name_valuestringAll names covered by the certificate
serialstringCertificate serial number
{
  "count": 2,
  "capped": false,
  "entries": [
    {
      "logged_at": "2024-01-15T00:00:00",
      "not_before": "2024-01-15T00:00:00",
      "not_after": "2025-04-15T23:59:59",
      "issuer": "Let's Encrypt",
      "common_name": "example.com",
      "serial": "abc123def456"
    }
  ]
}
Reveals infrastructure, related domains, and issuance patterns. Useful for detecting phishing campaigns and tracking certificate history.

bgp -- BGP / ASN Enrichment

FieldTypeDescription
cache_hitboolWhether this lookup was served from cache (true) or fetched live (false)
recordsarrayASN records, deduplicated by ASN

ASN Record

FieldTypeDescription
ipstringResolved IP address
asnintAutonomous System Number
prefixstringIP prefix (CIDR notation)
countrystringCountry code
registrystringRegional Internet Registry (arin, ripe, apnic, etc.)
descriptionstringOrganization name
{
  "records": [
    {
      "ip": "93.184.216.34",
      "asn": 15133,
      "prefix": "93.184.216.0/24",
      "country": "US",
      "registry": "arin",
      "description": "EDGECAST, US"
    }
  ]
}
Identifies who owns the IP behind the domain. Useful for pivot analysis and detecting bulletproof hosting.

fingerprint -- Technology Fingerprinting

Detects web servers, frameworks, CDNs, CMS platforms, and programming languages from HTTP response headers. No extra HTTP requests are made -- detection uses the headers already fetched, so requesting headers alongside it costs nothing extra. Approximately 90 technologies are covered.

FieldTypeDescription
cache_hitboolWhether the underlying headers data was served from cache. Fingerprint has no cache entry of its own -- see Caching
countintNumber of detected technologies
technologiesarrayDetected technologies

Technology Entry

FieldTypeDescription
namestringTechnology name (e.g., Nginx, PHP, Cloudflare)
categorystringCategory (see below)
versionstringVersion extracted from header value (empty if not detectable)
confidencestringWhich signal matched: header or cookie
{
  "count": 2,
  "technologies": [
    { "name": "Nginx", "category": "web-server", "version": "1.25.3", "confidence": "header" },
    { "name": "Cloudflare", "category": "cdn", "version": "", "confidence": "header" }
  ]
}

Categories

web-server, language, framework, cdn, cms, analytics, security, hosting, cache, javascript, other

threat_intel -- Threat Intelligence Pro

Queries external threat intelligence feeds concurrently and returns an aggregated risk assessment. Three providers are queried: VirusTotal (70+ antivirus engines), URLhaus (Abuse.ch malware URL database), and AlienVault OTX (community threat pulses).

FieldTypeDescription
cache_hitboolWhether this lookup was served from cache (true) or fetched live (false)
risk_levelstringAggregate risk: none, low, medium, high, or critical
scoreintWeighted aggregate score (0--100)
categoriesstring[]Merged threat categories across all providers
providersarrayPer-provider results (see below)

Provider Result

FieldTypeDescription
providerstringProvider name: virustotal, urlhaus, or alienvault_otx
detectedboolWhether the provider flagged this domain
scoreintProvider-specific score (0--100). Omitted when not detected.
categoriesstring[]Threat categories from this provider. Omitted when not detected.
detailsstringHuman-readable summary (e.g., "3/70 engines detected this domain"). Omitted when not detected.
{
  "risk_level": "medium",
  "score": 45,
  "categories": ["malware"],
  "providers": [
    {
      "provider": "virustotal",
      "detected": true,
      "score": 3,
      "categories": ["malware"],
      "details": "3/70 engines detected this domain"
    },
    {
      "provider": "urlhaus",
      "detected": false
    },
    {
      "provider": "alienvault_otx",
      "detected": true,
      "score": 20,
      "categories": ["malware"],
      "details": "5 threat pulses reference this domain"
    }
  ]
}

Scoring

The aggregate score is a weighted average across providers: VirusTotal (50%), URLhaus (30%), AlienVault OTX (20%). Risk levels map from the aggregate score: 0 = none, 1--20 = low, 21--50 = medium, 51--80 = high, 81--100 = critical.

dns_security -- DNSSEC & CAA

Checks DNSSEC deployment (DS records at the parent zone, DNSKEY records at the zone, signing algorithms) and CAA records via raw DNS queries. Available to all plans. Missing DNSSEC means DNS responses can be spoofed; missing CAA means any certificate authority can issue certificates for the domain.

dnssec

FieldTypeDescription
enabledbooltrue when both DS (parent) and DNSKEY (zone) records exist
ds_recordsintNumber of DS records at the parent zone
dnskey_recordsintNumber of DNSKEY records at the zone
algorithmsstring[]Signing algorithms from DS records (e.g., ECDSAP256SHA256)

caa

FieldTypeDescription
presentboolWhether any CAA records exist
recordsarrayCAA records: flag (int), tag (issue, issuewild, or iodef), value

Example

curl -X POST https://api.securityaccelerated.com/v1/domain/lookup \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: your-api-key' \
  -d '{
    "domains": ["example.com"],
    "lookups": ["dns_security"]
  }'

The dns_security object in the response:

{
  "dnssec": {
    "enabled": true,
    "ds_records": 1,
    "dnskey_records": 2,
    "algorithms": ["ECDSAP256SHA256"]
  },
  "caa": {
    "present": true,
    "records": [
      { "flag": 0, "tag": "issue", "value": "letsencrypt.org" },
      { "flag": 0, "tag": "iodef", "value": "mailto:security@example.com" }
    ]
  }
}

risk -- Aggregate Risk Score

A computed 0--100 weighted risk score. Only present when risk is included in lookups. It makes no extra network calls and is never cached — the score is derived from whichever other requested lookups landed on the response, renormalized over the weights of the components that have data. Its cache_hit is true only when every scoring input came from cache.

FieldTypeDescription
scoreintWeighted aggregate risk score (0--100)
risk_levelstringnone, low, medium, high, or critical
componentsarrayPer-component breakdown (see below)

Risk Component

FieldTypeDescription
componentstringWhich signal contributed (see weights below)
scoreintThis component's 0--100 score
weightfloatThe component's weight in the aggregate
detailstringHuman-readable explanation (e.g., certificate expires in 12 days)

Component Weights

ComponentWeightSignal
threat_intel0.35Aggregate threat intelligence score (when requested)
reputation0.25DNS blocklist reputation score (when requested)
ssl0.10Certificate expired or expiring soon
security_headers0.10Missing common security headers
email_security0.10Missing or weak SPF / DMARC (when requested)
domain_age0.10Recently registered domains score higher

Components only contribute when their underlying lookup ran and returned data. A base lookup scores from SSL, security headers, and domain age alone; requesting threat_intel or reputation makes those signals dominate.

{
  "score": 28,
  "risk_level": "medium",
  "components": [
    { "component": "ssl", "score": 40, "weight": 0.1, "detail": "certificate expires in 12 days" },
    { "component": "security_headers", "score": 20, "weight": 0.1, "detail": "missing: content-security-policy, referrer-policy" },
    { "component": "domain_age", "score": 25, "weight": 0.1, "detail": "domain registered 210 days ago" }
  ]
}

Risk Levels

Risk LevelScore Range
none0
low1 -- 20
medium21 -- 50
high51 -- 80
critical81 -- 100

errors -- Partial Failures

Individual lookups can fail without blocking the rest of the response. Each error contains:

FieldTypeDescription
lookupstringWhich lookup failed (e.g., whois, dns, ssl)
messagestringHuman-readable error description

What the lookup name tells you

The name is either a plain lookup or <lookup>:<provider>, and the two mean different things:

ShapeExampleWhat it means for the result
Plain name whois The lookup failed outright. Its field is absent from the result
Qualified name threat_intel:virustotal One provider inside a fan-out lookup failed while others answered. That lookup's field is present, scored over the providers that responded

Only reputation and threat_intel fan out to providers, so only those produce qualified names. Treating every entry in errors as a missing field would throw away perfectly good partial results from those two.

A lookup that failed outright omits its field entirely

This is the case worth writing your client against. The domain is valid, the request succeeded, and one upstream did not answer — here a WHOIS server that timed out while DNS and SSL returned normally:

{
  "results": [
    {
      "domain": "example.com",
      "inputs": ["example.com"],
      "dns": { "cache_hit": false, "a": ["93.184.216.34"], ... },
      "ssl": { "cache_hit": true, "issuer": "DigiCert Inc", ... },
      "errors": [
        { "lookup": "whois", "message": "i/o timeout" }
      ]
    }
  ]
}

There is no whois key at all — not null, not an empty object. Requesting n lookups does not guarantee n fields in the response, so code that reaches straight for result.whois.registrar works in testing and throws the first time an upstream has a bad minute. Contrast a reputation:spamhaus_dbl entry, where reputation is still there with the other providers' verdicts.

Failures are never cached, so the next request retries the lookup rather than remembering the failure. If a lookup matters to your workflow, checking errors and retrying that domain is usually better than treating an absent field as an absent answer — the two look identical in the response, and only errors tells them apart.

Caching

Results are cached per lookup, per domain — not per response. Each lookup object in the response carries its own cache_hit flag, so a single result routinely mixes cached and freshly fetched fields.

Because each lookup is cached independently, the combination you request has no effect on reuse: a dns-only request warms the same entry a later dns + ssl request reads from.

TTLs

Every lookup has its own TTL, reflecting how fast that data actually changes. Expiry is absolute — an expired entry is gone and the lookup is refetched. There is no stale serving.

LookupTTL
whois, ct_logs, subdomains, bgp24 hours
ssl, email_security, dns_security4 hours
screenshot60 minutes
dns, headers, reputation, threat_intel30 minutes

fingerprint and risk are never cached separately

Both are pure computation over data already fetched for other lookups, so they cost no network call and get no cache entry of their own.

risk scores whichever of threat_intel, reputation, ssl, headers, email_security and whois you also requested. Its cache_hit is true only when every scoring input came from cache.

Technology fingerprinting is computation over the headers payload, so caching headers already caches it. That means:

Cache failures never fail a request

Caching is fail-open. If the cache is unreachable, every lookup is treated as a miss and fetched live. If a cached entry is unreadable, it is treated as a miss and overwritten by the fresh result. If a cache write fails, the lookup still succeeds and is returned normally. Cache problems never appear in the errors array — that is reserved for actual lookup failures.

Error Handling

StatusMeaning
400 Bad RequestInvalid request body, missing domains field, or invalid domain name
400 Bad Requestlookups missing or empty after normalization — lookups is required, e.g. ["dns", "ssl"]
400 Bad RequestUnrecognized lookup name — the message names the offending entry
400 Bad RequestMore domains than your plan allows
400 Bad RequestMore domains than the tighter cap that applies when subdomains is requested — see Subdomain batch limits
401 UnauthorizedMissing or invalid API key
403 ForbiddenYour plan does not include this endpoint, or a requested lookup — e.g. screenshot requires a pro plan
413 Content Too LargeRequest body exceeds the 1 MB limit
429 Too Many RequestsRate limit exceeded -- check the Retry-After header
500 Internal Server ErrorServer error -- contact support@securityaccelerated.com

Error Response Format

All error responses use this format:

{
  "error": "description of the problem"
}
Partial failures within a domain lookup are different from HTTP error responses. When individual lookups fail (e.g., a WHOIS server is unreachable), the request still returns 200 OK with the successful lookups populated and failures listed in the errors array.

Request Correlation

Every response carries an X-Request-ID the API generates for you. You can additionally send your own X-Client-Request-ID to tie a request to an identifier your systems already use — it is echoed back and recorded in server-side logs.

HeaderDirectionDescription
X-Client-Request-IDRequestYour correlation ID. Up to 64 characters, limited to letters, digits, hyphens, underscores, dots, and colons — enough for UUIDs and common trace IDs.
X-Client-Request-IDResponseEchoed back byte for byte when the value you sent was accepted.
X-Request-IDResponseA UUID the API generates for every request, whether or not you sent a correlation ID. Quote this when contacting support.
A correlation ID that is too long or contains other characters is ignored rather than rejected — the request succeeds normally, but nothing is echoed back. If you are relying on the header for tracing, treat a missing X-Client-Request-ID on the response as a signal that the value did not pass validation. X-Request-ID is always present — including on error responses, where correlation matters most — so it works as a fallback.

Full Response Example

Response Headers

Returned alongside the body on a successful lookup:

Content-Type: application/json; charset=utf-8
X-Request-ID: 9f8b2c14-6d3a-4e57-b8f1-2a7c05e91d34
X-Client-Request-ID: req-001
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118

X-Request-ID is generated by the API for every request — quote it when contacting support. X-Client-Request-ID appears only when you sent a valid one; see Request Correlation. The X-RateLimit-* pair reflects your plan's window, and a 429 adds Retry-After — see Rate Limiting.

Response Body

A complete response for "lookups": ["whois", "dns", "ssl", "headers", "screenshot", "reputation", "email_security", "subdomains", "ct_logs", "bgp", "fingerprint", "threat_intel"]:

{
  "results": [
    {
      "domain": "example.com",
      "inputs": ["example.com"],
      "whois": {
        "cache_hit": false,
        "domain_name": "example.com",
        "registrar": "RESERVED-Internet Assigned Numbers Authority",
        "created_date": "1995-08-14T04:00:00Z",
        "expiry_date": "2025-08-13T04:00:00Z",
        "name_servers": ["a.iana-servers.net", "b.iana-servers.net"]
      },
      "dns": {
        "cache_hit": true,
        "a": ["93.184.216.34"],
        "aaaa": ["2606:2800:220:1:248:1893:25c8:1946"],
        "ns": ["a.iana-servers.net", "b.iana-servers.net"],
        "txt": ["v=spf1 -all"]
      },
      "ssl": {
        "cache_hit": false,
        "subject": "www.example.org",
        "issuer": "DigiCert Global G2 TLS RSA SHA256 2020 CA1",
        "not_after": "2025-03-01T23:59:59Z",
        "expired": false,
        "days_until_expiry": 142,
        "sans": ["www.example.org", "example.net", "example.org"]
      },
      "headers": {
        "cache_hit": false,
        "status_code": 200,
        "headers": { "Server": "ECAcc (dcd/7D5A)" },
        "security_headers": {
          "strict_transport_security": "max-age=31536000",
          "x_content_type_options": "nosniff"
        }
      },
      "screenshot": {
        "cache_hit": false,
        "status_code": 200,
        "png": "iVBORw0KGgoAAAANSUhEUgAA..."
      },
      "reputation": {
        "cache_hit": false,
        "score": 0,
        "risk": "clean",
        "providers": [
          { "listed": false, "provider": "spamhaus_dbl" },
          { "listed": false, "provider": "surbl" },
          { "listed": false, "provider": "uribl" },
          { "listed": false, "provider": "spamhaus_zen" }
        ]
      },
      "email_security": {
        "cache_hit": true,
        "spf": { "raw": "v=spf1 -all", "version": "spf1", "all": "-all" },
        "dmarc": { "raw": "v=DMARC1; p=reject", "version": "DMARC1", "policy": "reject" },
        "dkim": [{ "selector": "default", "found": false }]
      },
      "subdomains": {
        "cache_hit": false,
        "count": 2,
        "capped": false,
        "subdomains": [
          { "domain": "www.example.com", "source": "crt.sh" },
          { "domain": "mail.example.com", "source": "crt.sh" }
        ]
      },
      "ct_logs": {
        "cache_hit": false,
        "count": 1,
        "capped": false,
        "entries": [
          { "logged_at": "2024-01-15T00:00:00", "issuer": "Let's Encrypt", "common_name": "example.com", "serial": "abc123" }
        ]
      },
      "bgp": {
        "cache_hit": false,
        "records": [
          { "ip": "93.184.216.34", "asn": 15133, "prefix": "93.184.216.0/24", "country": "US", "description": "EDGECAST, US" }
        ]
      },
      "fingerprint": {
        "cache_hit": false,
        "count": 1,
        "technologies": [
          { "name": "ECAcc", "category": "cdn", "version": "", "confidence": "header" }
        ]
      },
      "threat_intel": {
        "cache_hit": false,
        "risk_level": "none",
        "score": 0,
        "providers": [
          { "provider": "virustotal", "detected": false },
          { "provider": "urlhaus", "detected": false },
          { "provider": "alienvault_otx", "detected": false }
        ]
      }
    }
  ]
}

Note the per-lookup cache_hit flags: dns and email_security came from cache here while the rest were fetched live. fingerprint reports the cache state of the headers data it was computed from.