Skip to main content

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_scope when 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_mismatch and oauth:encoded_auth_payload when 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_flow when the URL uses a broken callback or inconsistent response-mode pattern, and oauth:generic_auth_handoff when 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 with prompt=none and no redirect_uri, which sends the visitor without any sign-in screen to the address the link's app registered. Severity high, recommended action Block, served as Avoid. detailed_results.oauth_scope.data adds silent_authorize_bounce (boolean) and recipient_in_state (the address the link carries in state, or null). content:silent_sign_in_bounce_link is 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_link holds the link shortened to its host, path, client_id and prompt=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.url is the rendered page, data.flags the findings it added (only from content: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-13 content:investment_lead_funnel and, from 2026-10-14, content:hyip_plan_table), with their usual evidence fields. The same findings appear in the top-level flags and 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_funnel holds asks_for (["name", "email", "phone"]) and markers, an object with any of daily_earning, no_experience, win_rate, marketing_only (the matched text) and affiliate_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_table holds plans (up to three objects with return and limits, the text as written on the page) and count (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_path holds 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_relay holds 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 as provider_hints, provider_mismatch, redirect_host, decoded_targets, authorize_flow_anomalies, generic_auth_terms, and recommended_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.ct with fields entry_count, newest_age_days, oldest_age_days, issuers, sibling_domains, has_wildcard, and new_footprint. Human-readable lines are added to detailed_results.ssl.data.ct_evidence.
  • New flag ct:new_certificate_footprint is 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_certificate records 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 ct fields 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_history with fields first_seen_days_ago, capture_days, no_public_history, and thin_public_footprint. Human-readable lines are added to detailed_results.whois.data.domain_history_evidence.
  • New flag history:thin_public_footprint is 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_seen records 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_history fields 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_classification with fields hosting_provider, asn, abuse_prone, and matched_marker. Human-readable lines are added to detailed_results.geolocation.data.hosting_evidence.
  • New flag infrastructure:hosting_provider_identified records the provider as evidence and carries no weight on its own.
  • New flag infrastructure:abuse_prone_hosting is 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_context with fields cert_fingerprint_sha256, favicon_hash, ip, vendor, queried_vendor, asn_org, open_ports, and services. Human-readable lines are added to detailed_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_vendor is false and 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 under detailed_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_breakdown field 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_breakdown reconciles to the score actually served, which is not always the sum of the categories:

    FieldMeaning
    categoriesPoints per category
    raw_totalSum of the categories, before any reduction
    applied_scoreThe score actually served — equals score and phishing_score
    cappedtrue for any reduction, not only the 100 ceiling
    adjustmentsOrdered reductions, each with reason, detail, from, to
    reconcilesfalse if points were removed without a recorded reason

    Current reason values are trusted_brand, established_domain, and hygiene_only; see when points are removed again. Integrations that want the unreduced picture should read raw_total and adjustments and apply their own policy, rather than treating applied_score as a measurement of the page. reconciles: false means 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_fingerprint and the matching detail is persisted under detailed_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.matches leaves 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, or mailbox document and 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 raise content:offsite_credential_cta. The matched links are exposed as detailed_results.content.data.offsite_credential_ctas, each with text, url, host and registrable_domain, and the platform is reported as detailed_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.data reports analysis_evasion_gate with the status, the visible_text that was served, and the behaviors observed, plus scanner_block_vendor_challenge and visible_text_length. A recognized vendor challenge continues to raise only content:scanner_blocked.
  • When content:clickfix_paste_lure fires, the copied command is exposed as detailed_results.content.data.clickfix_command (lowercased, at most 200 characters). The rule also reads pages embedded in an attribute (a Google Sites data-code block, an iframe srcdoc) 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.data when they have something to report: frame_findings (one entry per frame whose rendered content tripped a page-behaviour rule: url, depth, behaviors, and clickfix_command when one was found), clipboard_writes (what the page copied: how, text up to 500 characters, frame), and verification_click (the one "I'm not a robot" control the sandbox pressed: frame, element, and how: pointer, or script when 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, raises content:clickfix_paste_lure, and detailed_results.sandbox_behavior.data carries clickfix_command, clipboard_command and frame_behaviors (every frame's behaviours, scored or not).
  • When the desktop view of a full review is thin, detailed_results.browser_capture.data.phone_view holds the phone's second look: reason (little_text or sent_elsewhere), final_url, text_excerpt, frame_findings, clipboard_writes, redirect_chain and blocked_downloads (no screenshot). If there was no time left for it, it holds reason and skipped: "no_time_left". The phone's findings raise the same flags as the desktop's; detailed_results.sandbox_behavior.data.phone_view repeats reason, final_url and 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 in detailed_results.content.data.visitor_ip_lookup (for example ipapi.co). A lookup inside a phone widget's geoIpLookup option is ignored. The flag also corroborates correlation: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 Entra deviceauth / devicecode endpoints, google.com/device, github.com/login/device) raises content:device_code_lure (weight 40) unless the scanned host belongs to Microsoft, Google or GitHub. The address is in detailed_results.content.data.device_code_target; a match found by the browser sandbox inside a frame is in detailed_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.dev or Google's favicon service, followed by ${...} or +) raises content:victim_branded_login (weight 20); the service is in detailed_results.content.data.victim_branded_logo_service.
  • A link typed as https://<domain>@<host>/ raises url:userinfo_disguise (weight 35) when the part before the @ is shaped like a domain on another registration. Like content: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.data carries shown_host and real_host.
  • A host that reads as <label>.gov in front of a hyphen (txdmv.gov-lska.win) and is registered under a new generic ending raises domain:fake_government_address (weight 25) beside the unscored domain:fake_tld_boundary. Under a country or legacy ending (portal.gov-madeira.pt) only domain:fake_tld_boundary is reported.
  • A full review whose frame or clipboard checks hit their time limit reports detailed_results.browser_capture.data.page_findings_timeout naming 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_field to true. It carries no score; it lets lure phrases count as content: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_path when every listing for the host could be read: true when a listed URL is the scanned page or on its path branch, false when none is. On a platform where anyone publishes, false turns threatfeed:<source> into threatfeed: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, or dpd and 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, or update payment plus 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_phrases and detailed_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, and detailed_results.content.data.marketplace_brand_path_hits.

Domain and TLS age notes:

  • WHOIS results include age_days when registration date is available.
  • Domains registered within 60 days raise whois:very_recent_registration; domains registered within 7 days also raise whois:ultra_recent_registration.
  • TLS results include normalized issuer fields (issuer_common_name, issuer_organization), subject_common_name, and cert_age_days when the certificate exposes notBefore.
  • 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.infrastructure with normalized asn, 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.infrastructure block 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 raises infrastructure:free_hosting_lure.
  • When curated free-hosting also coincides with trusted-brand lure signals, the scanner raises infrastructure:brand_free_hosting_lure to 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 into flags and contribute to score/explanations. When it is off, the campaign_tagging block 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_letter means 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_letter instead 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_url when 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 report final_url as about:blank while 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 via record_scan_failure for observability.

Combine /scan results with /ticketbridge/tickets to escalate suspicious verdicts automatically.