URL & Phishing Scanner API
The URL & phishing scanner scores domains/URLs, captures redirect flows, pulls WHOIS/DNS/SSL data, and emits persona-ready EvidencePacks (Dralvia's exportable evidence reports). Use the REST endpoints below to automate scans from CI/CD, ticketing, or cron jobs.
Authentication & headers
- Use a workspace API key (generate it via https://dralvia.tech/#/api-keys). Prefer
Authorization: Api-Key <key>(legacy:X-API-KEY). - Signed-in browser sessions are supported for portal workflows. Workspace API keys are preferred for automation.
X-Actor: optional string that tags jobs (shown in audit logs and EvidencePacks).
All endpoints accept and return JSON.
Base URL
- Workspace persona:
https://dralvia.tech/api/tenant
Examples below show the path only. Use the versioned path for the core scan request, for example POST /v1/scan, and prefix it with the base URL.
POST /v1/scan
Runs a synchronous scan and waits for the final verdict.
{
"domain": "example.com",
"force_scan": false
}
Response (abbreviated):
{
"domain": "example.com",
"score": 21,
"risk_level": "medium",
"flags": ["ssl:missing_hsts", "content:brand_impersonation"],
"campaign_tagging_degraded": false,
"campaign_tagging": {
"feature_flag_enabled": false,
"shadow_mode": true,
"degraded": false,
"suppressed_reason": "feature_flag_disabled",
"autonomous_tags": []
},
"detailed_results": { "...": "..." },
"tabs": {
"detection": { "evidencepack": { "...": "..." } },
"redirect_timeline": { "...": "..." }
}
}
detailed_results holds every check's full result. tabs carries only summary
blocks (detection, community) and does not repeat detailed_results; stored and
cached results no longer include a tabs.details copy (changed 2026-09-29).
Use force_scan: true when you need to bypass caching (counts against the force-scan quota exposed by licensing).
OAuth/OpenID consent detection notes:
- URL scans can now persist
detailed_results.oauth_scopewhen the target looks like an OAuth or OpenID authorize flow. - The scanner promotes both classic high-risk scope abuse and fake-host authorize flows that reference a known provider brand on a different domain.
- New flags include
oauth:provider_mismatchandoauth:encoded_auth_payloadwhen the URL hides a nested authorize target inside an encoded payload. Packed nested targets can now be decoded from hex, standard base64, and URL-safe base64 forms. - New flags also include
oauth:malformed_authorize_flowwhen the URL uses a broken callback or inconsistent response-mode pattern, andoauth:generic_auth_handoffwhen a fake consent/login handoff avoids explicit scopes but still behaves like an authorize flow on a non-provider host. oauth:silent_authorize_bounce(2026-10-12): a Microsoft authorize link withprompt=noneand noredirect_uri, which sends the visitor without any sign-in screen to the address the link's app registered. Severityhigh, recommended action Block, served as Avoid.detailed_results.oauth_scope.dataaddssilent_authorize_bounce(boolean) andrecipient_in_state(the address the link carries instate, ornull).content:silent_sign_in_bounce_linkis the same finding for a page that sends its visitor to such an address (weight 70, Avoid by itself);detailed_results.content.data.silent_sign_in_bounce_linkholds the link shortened to its host, path,client_idandprompt=none.detailed_results.rendered_content(2026-10-13): findings that came from the page as the sandbox browser rendered it, for pages built by script.data.urlis the rendered page,data.flagsthe findings it added (only fromcontent:airdrop_claim_lure,content:seed_phrase_prompt,content:credential_exfil_endpoint,content:credential_capture_active,content:microsoft_sign_in_relay,content:device_code_lure, from 2026-10-13content:investment_lead_funneland, from 2026-10-14,content:hyip_plan_table), with their usual evidence fields. The same findings appear in the top-levelflagsand score as usual. The section is absent when the rendered page added nothing. The rendered HTML itself is never returned or stored.content:investment_lead_funnel(2026-10-13): an investing page whose form asks for name, email and phone, plus a promised daily income, "no experience needed", a win rate, a "marketing platform only" disclaimer or affiliate lead fields. Weight 70, Avoid by itself; capped like other page findings on established domains.detailed_results.content.data.investment_lead_funnelholdsasks_for(["name", "email", "phone"]) andmarkers, an object with any ofdaily_earning,no_experience,win_rate,marketing_only(the matched text) andaffiliate_fields(the hidden field names).content:hyip_plan_table(2026-10-14): the page shows at least two investment plans, each a return per period (or a bare ROI/profit) next to a minimum and maximum deposit or an amount range. Weight 70, Avoid by itself, and strong evidence (not capped on established domains).detailed_results.content.data.hyip_plan_tableholdsplans(up to three objects withreturnandlimits, the text as written on the page) andcount(how many plans were found).content:microsoft_sign_in_relay(2026-10-13): the page serves Microsoft's own sign-in page configuration from a non-Microsoft address and its password form posts back to that address on Microsoft's sign-in path (an attacker-in-the-middle relay). Weight 70, Avoid by itself, not capped on established domains.detailed_results.content.data.microsoft_sign_in_relay.post_pathholds the path the password is posted to; when it is found in a frame of the rendered page,detailed_results.sandbox_behavior.data.microsoft_sign_in_relayholds the same path.- Content-page auth-flow detection now also recognizes broader provider-family wording such as Apple, GitHub, Slack, Zoom, and Dropbox-style sign-in prompts in addition to the earlier Google/Microsoft/Okta set.
- The persisted detail uses the shape
detailed_results.oauth_scope.{data,flags,score}and includes fields such asprovider_hints,provider_mismatch,redirect_host,decoded_targets,authorize_flow_anomalies,generic_auth_terms, andrecommended_action.
Certificate Transparency notes:
- URL and domain scans read the public Certificate Transparency history for the
domain and persist it under
detailed_results.ssl.data.ctwith fieldsentry_count,newest_age_days,oldest_age_days,issuers,sibling_domains,has_wildcard, andnew_footprint. Human-readable lines are added todetailed_results.ssl.data.ct_evidence. - New flag
ct:new_certificate_footprintis raised when the domain's entire certificate history only just appeared, which indicates newly stood-up infrastructure rather than a routine renewal. It carries a modest weight. - New flag
ct:freshly_issued_certificaterecords that the newest certificate was issued in the last few days. It is evidence only and carries no weight on its own, because legitimate sites renew certificates frequently. - The lookup is best-effort. If the Certificate Transparency source is slow or
unreachable the scan still completes and the
ctfields are omitted.
Public web history notes:
- URL and domain scans read the public Internet Archive history for the domain
and persist it under
detailed_results.whois.data.domain_historywith fieldsfirst_seen_days_ago,capture_days,no_public_history, andthin_public_footprint. Human-readable lines are added todetailed_results.whois.data.domain_history_evidence. - New flag
history:thin_public_footprintis raised when the domain has little or no established public-web presence, a corroborating signal of newly stood-up or recently repurposed infrastructure. It carries a modest weight and stacks with the WHOIS registration-age signals. - New flag
history:recent_first_seenrecords that the earliest archived capture is recent. It is evidence only and carries no weight on its own. - The lookup is best-effort. If the archive is slow or unreachable the scan
still completes and the
domain_historyfields are omitted.
Hosting provider and ASN notes:
- URL and domain scans classify the resolved host's hosting provider / ASN and
persist it under
detailed_results.geolocation.data.hosting_classificationwith fieldshosting_provider,asn,abuse_prone, andmatched_marker. Human-readable lines are added todetailed_results.geolocation.data.hosting_evidence. - New flag
infrastructure:hosting_provider_identifiedrecords the provider as evidence and carries no weight on its own. - New flag
infrastructure:abuse_prone_hostingis raised when the provider or ASN matches an abuse-associated marker (such as bulletproof or offshore hosting). It carries a modest weight and names the matched marker in the evidence.
Host and infrastructure context notes:
- URL and domain scans record a host fingerprint under
detailed_results.ssl.data.host_contextwith fieldscert_fingerprint_sha256,favicon_hash,ip,vendor,queried_vendor,asn_org,open_ports, andservices. Human-readable lines are added todetailed_results.ssl.data.host_context_evidence. - The certificate fingerprint and favicon hash are recorded with no setup. Open
ports, services, and the ASN organization are populated only when the
workspace has configured a Censys or Shodan key; without a key
queried_vendorisfalseand no vendor lookup is made. - The vendor lookup is best-effort. If the vendor is slow or unreachable the scan still completes and the vendor fields stay empty.
Brand-impersonation (Certificate Transparency siblings) notes:
- The names found on a host's TLS certificates (via Certificate Transparency) are
compared against the trusted-brand catalogue. A name that closely imitates a
brand without being that brand raises
brand:ct_sibling_impersonation_<brand>. Matches are persisted underdetailed_results.brand.data.ct_sibling_impersonation(a list of{sibling, brand, distance}). - This recovers brand-impersonation detections where the scanned apex domain itself does not resemble the brand but a sibling name on the same certificate does.
Evidence and scoring transparency notes:
-
The plain-language findings behind the enrichment flags (Certificate Transparency, public web history, host context, hosting provider) are consolidated into
detailed_results.infrastructure.data.enrichment_evidence, a list shown in the scan view as "What we found". -
Every flag contributes to the score through its published weight, and the
score_breakdownfield reconciles the total by category (the scan view shows this as "Why this score?"). The new enrichment signals appear there like any other signal, so the score and its explanation always stay in sync. -
score_breakdownreconciles to the score actually served, which is not always the sum of the categories:Field Meaning categoriesPoints per category raw_totalSum of the categories, before any reduction applied_scoreThe score actually served — equals scoreandphishing_scorecappedtruefor any reduction, not only the 100 ceilingadjustmentsOrdered reductions, each with reason,detail,from,toreconcilesfalseif points were removed without a recorded reasonCurrent
reasonvalues aretrusted_brand,established_domain, andhygiene_only; see when points are removed again. Integrations that want the unreduced picture should readraw_totalandadjustmentsand apply their own policy, rather than treatingapplied_scoreas a measurement of the page.reconciles: falsemeans the served score cannot be explained by the parts we published — treat it as a defect on our side and report it.
Reused-infrastructure notes:
- Dralvia records the TLS certificate and favicon fingerprints of scanned hosts
and, on a new scan, checks whether any previously-flagged host shared the same
fingerprint. A match raises
infrastructure:reused_known_bad_fingerprintand the matching detail is persisted underdetailed_results.infrastructure.data.reused_fingerprint_match(fingerprint_type,fingerprint_value,distinct_sources). - This is a strong signal because shared infrastructure with known-bad hosts
often outlives a single throwaway domain. The match is learned only from your
own scan history, and the host under test is excluded so it cannot reinforce
its own past verdict. The exclusion uses the stored host name, so
www.example.com,example.com,EXAMPLE.COM.and the punycode form of an international name are the same host.campaign_context.matchesleaves out the host's own earlier scans the same way.
Content lure notes:
- Credential-style document-share phishing can now raise
content:document_share_lure. - The signal requires both document-share wording such as
shared document,secure share,view document, ormailbox documentand credential-oriented behavior such as password forms, fake-auth flow indicators, external credential post targets, or redirect-driven auth prompts. - Credential-oriented behavior no longer has to be on the same page. A page published on a shared multi-tenant platform whose call to action links to an unrelated registrable domain also satisfies the requirement, because that is the same lure with the credential form moved one hop away.
- Matched phrases are exposed as
detailed_results.content.data.document_share_lure_phrases. - A page on a shared multi-tenant platform (for example
*.freshdesk.com,*.zendesk.com,*.github.io,*.notion.site) whose action link — "View document", "Sign in", "Verify" — points at an unrelated registrable domain can raisecontent:offsite_credential_cta. The matched links are exposed asdetailed_results.content.data.offsite_credential_ctas, each withtext,url,hostandregistrable_domain, and the platform is reported asdetailed_results.content.data.offsite_cta_page_surface. The signal does not apply to a site on its own domain, and the platform's own hosts are not treated as tenants of themselves. - A site that blocks the scan and returns a short custom holding page while
writing browser storage, driving a redirect, or prompting for a permission can
raise
content:analysis_evasion_gate. It requires that no recognized vendor challenge marker (Cloudflare, Akamai, Imperva and similar) was present and that the response carried no real page content;detailed_results.content.datareportsanalysis_evasion_gatewith thestatus, thevisible_textthat was served, and thebehaviorsobserved, plusscanner_block_vendor_challengeandvisible_text_length. A recognized vendor challenge continues to raise onlycontent:scanner_blocked. - When
content:clickfix_paste_lurefires, the copied command is exposed asdetailed_results.content.data.clickfix_command(lowercased, at most 200 characters). The rule also reads pages embedded in an attribute (a Google Sitesdata-codeblock, an iframesrcdoc) and scripts packed as base64 XOR'd with a one-byte key; those are decoded as text and never run. The flag defeats the trusted-brand and established-domain discounts (added 2026-09-29). - Full reviews add three fields to
detailed_results.browser_capture.datawhen they have something to report:frame_findings(one entry per frame whose rendered content tripped a page-behaviour rule:url,depth,behaviors, andclickfix_commandwhen one was found),clipboard_writes(what the page copied:how,textup to 500 characters,frame), andverification_click(the one "I'm not a robot" control the sandbox pressed:frame,element, andhow:pointer, orscriptwhen a pointer click did not complete in time and the control was pressed from script instead). Only ClickFix is scored from these: a framed ClickFix finding, or a shell command on the clipboard, raisescontent:clickfix_paste_lure, anddetailed_results.sandbox_behavior.datacarriesclickfix_command,clipboard_commandandframe_behaviors(every frame's behaviours, scored or not). - When the desktop view of a full review is thin,
detailed_results.browser_capture.data.phone_viewholds the phone's second look:reason(little_textorsent_elsewhere),final_url,text_excerpt,frame_findings,clipboard_writes,redirect_chainandblocked_downloads(no screenshot). If there was no time left for it, it holdsreasonandskipped: "no_time_left". The phone's findings raise the same flags as the desktop's;detailed_results.sandbox_behavior.data.phone_viewrepeatsreason,final_urland the first 300 characters of the text. - A script that looks up the visitor's IP address next to a password field
raises
content:visitor_ip_lookup; the service is reported indetailed_results.content.data.visitor_ip_lookup(for exampleipapi.co). A lookup inside a phone widget'sgeoIpLookupoption is ignored. The flag also corroboratescorrelation:credential_harvest_untrusted. - A page that offers a code to copy (a "Copy code" control, or a clipboard write
next to a code shaped like
XXXX-XXXX) and points at a provider's device sign-in (microsoft.com/devicelogin,aka.ms/devicelogin, the Entradeviceauth/devicecodeendpoints,google.com/device,github.com/login/device) raisescontent:device_code_lure(weight 40) unless the scanned host belongs to Microsoft, Google or GitHub. The address is indetailed_results.content.data.device_code_target; a match found by the browser sandbox inside a frame is indetailed_results.sandbox_behavior.data.device_code_target. - A password page whose script builds a logo address from a variable (logo
services such as
logo.clearbit.com,img.logo.devor Google's favicon service, followed by${...}or+) raisescontent:victim_branded_login(weight 20); the service is indetailed_results.content.data.victim_branded_logo_service. - A link typed as
https://<domain>@<host>/raisesurl:userinfo_disguise(weight 35) when the part before the@is shaped like a domain on another registration. Likecontent:device_code_lure, it is not reduced by the established-domain cap: a disguised link to an old, hacked site is still a disguised link. The scan checks<host>;detailed_results.url_userinfo_disguise.datacarriesshown_hostandreal_host. - A host that reads as
<label>.govin front of a hyphen (txdmv.gov-lska.win) and is registered under a new generic ending raisesdomain:fake_government_address(weight 25) beside the unscoreddomain:fake_tld_boundary. Under a country or legacy ending (portal.gov-madeira.pt) onlydomain:fake_tld_boundaryis reported. - A full review whose frame or clipboard checks hit their time limit reports
detailed_results.browser_capture.data.page_findings_timeoutnaming the step that stopped (click,frames, or both); the rest of the capture is kept. - A form field labelled as a national ID or tax number sets
detailed_results.content.data.national_id_fieldtotrue. It carries no score; it lets lure phrases count ascontent:suspicious_phrases. - A threat-feed match on the host rather than on the scanned URL reports
detailed_results.threat_feeds.data.<source>_host_match_under_target_pathwhen every listing for the host could be read:truewhen a listed URL is the scanned page or on its path branch,falsewhen none is. On a platform where anyone publishes,falseturnsthreatfeed:<source>intothreatfeed:hosted_malicious_url. The field is absent when the scan names no path or the host has 200 or more listings, and the listing then convicts as before. - Credential-style parcel or shipping phishing can now raise
content:parcel_shipping_lure. - The signal requires both parcel / package / delivery wording such as
track package,parcel tracking,delivery update,missed delivery, ordpdand stronger credential-oriented behavior such as an external credential post target, fake-auth flow, active credential-capture posture, or a redirect-driven auth prompt. - Matched phrases are exposed as
detailed_results.content.data.parcel_shipping_lure_phrases. - Credential-style streaming or subscription phishing can now raise
content:streaming_clone_lure. - The signal requires streaming/subscription wording such as
watch now,continue watching,subscription expired,account on hold, orupdate paymentplus credential-funnel behavior such as off-host credential posts, active credential-capture posture, or a mismatched throwaway host branding itself as a streaming service. - Matched phrases and brand hits are exposed as
detailed_results.content.data.streaming_clone_lure_phrasesanddetailed_results.content.data.streaming_clone_brand_hits. - Credential-style marketplace-brand phishing can now raise
content:marketplace_brand_lure. - The signal requires marketplace/order/account/payment wording plus credential-funnel behavior, and supports conservative host-mismatch and path-stuffing handling for throwaway Amazon-style shopping subjects.
- Matched phrases, brand hits, and path hits are exposed as
detailed_results.content.data.marketplace_brand_lure_phrases,detailed_results.content.data.marketplace_brand_hits, anddetailed_results.content.data.marketplace_brand_path_hits.
Domain and TLS age notes:
- WHOIS results include
age_dayswhen registration date is available. - Domains registered within 60 days raise
whois:very_recent_registration; domains registered within 7 days also raisewhois:ultra_recent_registration. - TLS results include normalized issuer fields (
issuer_common_name,issuer_organization),subject_common_name, andcert_age_dayswhen the certificate exposesnotBefore. - Certificates issued within 7 days raise
ssl:very_recent_certificate. This is intentionally a low-weight corroborating signal, not a phishing verdict by itself.
Infrastructure notes:
- Scans can include
detailed_results.infrastructurewith normalizedasn,hosting_provider,hosting_tenancy,shared_hosting_provider,shared_hosting_suffix,free_hosting_provider, host, IP, domain-age, and TLS-age context. - The admin scan result UI shows the same data in Infrastructure
Signals, and the links and websites EvidencePack explanation includes a compact
summary.infrastructureblock plus an infrastructure decision driver when those signals contribute to the verdict. - Multi-workspace free-hosting surfaces can raise
infrastructure:free_hosting_surface. When paired with lure signals such as login forms, brand abuse, OAuth/OpenID evidence, or redirect abuse, the scanner also raisesinfrastructure:free_hosting_lure. - When curated free-hosting also coincides with trusted-brand lure
signals, the scanner raises
infrastructure:brand_free_hosting_lureto make that combined explanation explicit. hosting_tenancy = "shared_multi_tenant"means the target sits on a shared workspace platform and that hosting should be treated as corroborative context.hosting_tenancy = "dedicated_or_unknown"means the scanner did not detect a known shared workspace surface.- Bare IP targets raise
infrastructure:ip_literal_surface. - Fresh domain plus fresh TLS certificate plus lure evidence raises
infrastructure:fresh_domain_certificate_lure.
Campaign-tagging notes:
- Fresh scans no longer receive new legacy heuristic campaign flags (
campaign:active_cluster,campaign:related_cluster). - Historical rows can still carry those legacy tags; they remain queryable and are marked as legacy in the campaign-tagging decoration metadata.
- When autonomous campaign tagging is enabled for the request, autonomous tags (
campaign:autonomous.related,campaign:autonomous.active) are merged intoflagsand contribute to score/explanations. When it is off, thecampaign_taggingblock still records the decision path in shadow mode.
POST /scan_async
Queues a job and returns immediately:
{
"domain": "example.com",
"force_scan": true
}
Response:
{ "scan_id": 1234, "status": "queued" }
Poll GET /scan/jobs/1234 (or GET /scan_status/1234) until the job transitions to done, failed, or dead_letter.
Async worker notes:
- Transient worker faults are retried automatically with bounded backoff before a job is marked terminal.
dead_lettermeans the worker exhausted its retry budget and no further automatic attempts will be made.- Repeated stale-job recovery now also counts against a bounded stale
recovery budget, so a stranded job will eventually move to
dead_letterinstead of being re-dispatched forever. - Queue workers now hard-reset their database session between attempts so one aborted transaction cannot poison the next retry for the same scan.
POST /scan/bulk
Submit up to BULK_SCAN_LIMIT domains per call (defaults to 50). Duplicates inside the same payload are ignored and reported in the summary.
Request:
{
"domains": ["foo.example", "bar.example", "foo.example"],
"force_scan": false
}
Response:
{
"jobs": [
{ "domain": "foo.example", "scan_id": 101, "status": "queued", "queued": true },
{ "domain": "bar.example", "scan_id": 102, "status": "queued", "queued": true }
],
"errors": [
{ "input": "foo.example", "error": "duplicate_in_request" }
],
"summary": {
"accepted": 2,
"rejected": 1,
"duplicates": 1,
"limit": 50
}
}
Every job returned by /scan/bulk can be tracked via /scan/jobs/<id>.
GET /scan/jobs/<id>
Returns a single job:
{
"scan_id": 1234,
"status": "done",
"result": {
"domain": "example.com",
"score": 21,
"risk_level": "medium",
"flags": ["ssl:missing_hsts"]
}
}
If the job failed you receive { "error": "...reason..." } instead of result.
GET /scan/jobs
Lists recent jobs for the async queue and /scan/bulk. Query parameters:
limit(default 25, max 200)status(comma-separated:queued,running,done,failed,dead_letter)domain(filters by ASCII domain)force_scan(true/false)
Response:
{
"jobs": [
{
"scan_id": 1234,
"domain": "example.com",
"status": "done",
"force_scan": false,
"queued_at": "2025-11-21T08:15:27Z",
"run_duration_ms": 8320,
"result": { "score": 21, "risk_level": "medium", "flags": ["ssl:missing_hsts"] }
}
],
"meta": {
"limit": 25,
"counts": { "queued": 3, "running": 1, "done": 42, "failed": 2, "dead_letter": 0 }
}
}
Use this endpoint to power dashboards (the admin HUD consumes it to show queue health).
Retention and history notes
- Full nested links and websites scan payloads are retained only for a short evidence window.
- Older duplicate scan rows may be compacted to a lean history payload when a newer scan for the same scoped site already exists.
- History endpoints and scan listings still preserve score, risk, flags, host/target, and compact check summaries after compaction.
- Large inline screenshots, replay-heavy nested blocks, and duplicate debug payloads are not guaranteed to remain available indefinitely.
Visual preview degradation behavior
- Dralvia prefers isolated browser capture for visual impersonation evidence.
- If that isolated capture degrades before it can produce a safe screenshot, the API now still returns a safe remote preview mirror in
detailed_results.visual.data.screenshot_urlwhen policy allows it. - The scan payload continues to preserve the degraded browser state through
mode,degraded,degraded_reason, replay steps, and policy/runtime metadata. For example, a browser failure can still reportfinal_urlasabout:blankwhile the preview shown to analysts is the safe fallback mirror of the requested target.
EvidencePacks & downloads
- Every scan embeds the EvidencePack inside
tabs.detection.evidencepack. GET /evidencepacks/<id>returns the canonical JSON if you need to re-download it later.GET /reports/<id>(from the pentest module) pulls full PDF exports.
CLI helper
Run scans with the helper script (available from the Dralvia SDK examples):
python scan_cli.py example.com --api-key "$DRALVIA_API_KEY" --api-url "https://dralvia.tech/api/tenant"
python scan_cli.py --file domains.txt --async
python scan_cli.py --status 1234
It wraps /v1/scan, /scan_async, /scan/bulk, and /scan/jobs/<id> with basic error handling. Pass --force to bypass caching when you need a fresh scan (counts against your daily force-scan quota).
Error handling
400 BAD REQUEST: Invalid domain, empty payload, or unsupported parameter.402 PAYMENT REQUIRED: Force-scan is disabled for the active workspace plan.429 TOO MANY REQUESTS: Per-route rate limit reached.500 INTERNAL SERVER ERROR: Unexpected downstream failure (DNS/SSL/renderer). Failures are logged viarecord_scan_failurefor observability.
Combine /scan results with /ticketbridge/tickets to escalate suspicious verdicts automatically.