Skip to main content

URL & Phishing Scanner Guide

If a term is unfamiliar, open the Glossary.

Use this page to master the URL and smart contract scanner UI, interpret the EvidencePack (Dralvia's exportable evidence report), and automate verdicts via API.

Running scans in the UI

  1. Go to URL & Phishing Scanner (#/phishing).
  2. Enter a URL or domain and click Scan.
  3. Watch the progress widget (WHOIS, DNS, SSL, Content, Threat feeds). It shows which subsystem is running and a percentage so you know it's alive.
  4. When the scan finishes you'll see:
    • A verdict-first hero with the risk score, scanned target, and the strongest plain-English outcome.
    • Top reasons cards so the main verdict drivers are visible before the raw detail.
    • A Verified via row showing which capture/provenance paths contributed to the result.
    • A Recommended next step band that points you to the most relevant existing action.
    • Deeper sections for redirects, headers, infrastructure, visual impersonation, replay, and EvidencePack context.
  5. Use Visual Impersonation to inspect the captured page safely:
    • Enlarge preview opens a larger Dralvia-hosted screenshot or replay surface.
    • Preview clicks do not navigate directly to the live hostile target.
    • If the live page is unreachable or times out, the scanner can still show a safe synthetic timeout/error preview instead of a blank panel.
    • The panel reads as one coherent statement, not a pile of separate fields: Potential [brand] impersonation detected when a brand is imitated, Matches the real [brand] when the page legitimately is that brand (with a confidence figure), or No brand impersonation detected when the visual check found nothing. It never claims a trusted-brand match at 0% confidence.

Reading the result page

  • Start with the verdict hero and score ring. That is the fastest summary of whether the target looks safe, suspicious, or actively risky.
  • Read Top reasons next. This is the client-readable explanation layer before the analyst sections.
  • Use Verified via when you need to explain where confidence came from. It surfaces the current capture/provenance paths without changing the underlying score.
  • Use Processing snapshot for a fast operational read of final URL, cache/freshness, header grade, and replay/preview availability.
  • If you scan the same full URL again during the freshness window, Dralvia can return a cached verdict instead of starting a duplicate scan. The reuse window is limited by how long the underlying scan evidence is kept, so the report states the real date a given verdict stops being reused rather than a nominal maximum.
  • When the early evidence already requires deeper browser review, Dralvia goes straight to that review instead of visiting the page twice. The visible result is the same type of browser evidence, only produced with less repeated navigation.
  • The links and websites flow uses the same shared platform loading/error grammar as the other scanners, but keeps a specialized unreachable-state banner when Dralvia cannot safely score the target.
  • Keep scrolling for the full analyst depth. The page still includes Edge AI, security headers, infrastructure signals, EvidencePack summary, replay, grouped flags, history, details, and community context.
  • Report to security team appears only when it is actionable: on a risky result (a Caution or Avoid verdict) and only when you are signed in to a workspace. It opens the incident form so you can escalate the finding. It is deliberately hidden on Safe verdicts and for guest scans, where escalation is not the next step.

Hiding the navigation

The left navigation can be collapsed to give the report the full window, using the button at the left of the top bar or Ctrl + B (Cmd + B on macOS). Press it again to bring the navigation back.

The button stays in the top bar rather than in the navigation itself, so it is always available once the navigation is hidden. The choice is remembered for the browser you are using, including across reloads and sign-ins, and the shortcut is ignored while you are typing so it never interferes with the scan box.

Verdict wording and colour

The whole product uses three verdict words, matching the API exactly: Safe (score 20 or below), Caution (21 to 69) and Avoid (70 or above). The score gauge previously read "Likely Safe" while the verdict chip beside it read "Safe", and the colour band did not line up with the label — a score of 30 was labelled caution but painted green. Label and colour now come from the same thresholds, so a Caution verdict is always amber and an Avoid verdict always red, at every score.

Each tool also appears once in the left navigation. The scanners were listed twice under two names ("Check a URL or website" and "URL & Phishing Scanner"), which pointed at the same page.

The fourth outcome: we could not check it

A scan can also come back unknown, with no score at all and a recommended action of Review. This is not a fourth risk level — it is Dralvia declining to give one.

It happens when the scan could not see the site: the domain did not resolve, or it resolved but no page could be retrieved, or the checks that carry the most weight did not complete. Phishing pages are usually taken down within hours or days of being reported, so a reported address you look up later very often lands here.

The distinction matters because a scan that learned nothing produces no findings, and no findings would otherwise total zero — which looks exactly like a clean result. A dead domain and a genuinely safe site are not the same answer, and Dralvia will not use the same word for both. The reason is always stated with the result.

One change to be aware of if you read scan history or exports. Until 5 August 2026 the stored risk_level on a saved scan recorded the raw score band rather than the verdict that was actually shown, so a scan reported as unknown could appear in history as Safe. The live result was always correct; only the stored copy disagreed. Saved scans from 5 August 2026 onward record the verdict you were shown. Historical rows were deliberately not rewritten, because reconstructing a verdict after the fact would be a guess — treat risk_level on earlier rows as the score band, not as the verdict served.

The "How this result was produced" panel

Every scan report ends with a panel titled How this result was produced. It lists the kinds of analysis that actually ran for that target — heuristic web scan, sandboxed browser capture, static header analysis, threat-intelligence lookup and so on — with one sentence each. It is the plain-language answer to "where did this verdict come from", and it appears on the website, contract, repository and email scanners alike.

Each label is colour-coded by the kind of analysis, not by severity, so the colours there never imply a verdict. The panel previously used a different, older colour set than the rest of the report; it now uses the same palette as every other panel and follows the light and dark themes correctly.

Two related interface corrections ship with it:

  • Hover feedback on the report's buttons and links (the highlight you get when the pointer is over them) was not being applied. It now works everywhere on the report.
  • The dashboard top bar showed two hamburger icons side by side — one decorative leftover and one working control. Only the working one remains: the button that hides and shows the left navigation.

Every screen in the product now uses the same palette. The scanners, the dashboards, the module centre and the administrative consoles were previously a mix of two colour systems; they are now a single one, and all of them follow the light and dark themes consistently. Status colours stay meaningful: one green for safe, one amber for caution and one red for risk, in every console. All of the status and label colour combinations are checked against the WCAG AA contrast minimum in both themes.

Typefaces and wording

The product's three typefaces (DM Sans for the interface, DM Serif Display for headings, JetBrains Mono for hashes, headers and domain names) are now loaded and served by Dralvia itself rather than fetched from a third-party font service. Before this, none of them were being loaded at all, so the interface rendered in whichever fonts your operating system substituted, and monospaced values such as certificate hashes were not in the monospaced face they were meant to use. No font request leaves Dralvia at all, including the icon font used for the interface symbols.

Explanatory sentences no longer join two clauses with a dash. For example, the domain reputation note now reads "...built from our scan history and age. Higher is more trustworthy." A dash on its own (—) still means "no value" in a table column, and ranges such as A–Z are unchanged.

When the browser did not open the page

Dralvia renders most pages in its own isolated browser and records what the page does. That is not always possible, and the report now says which of three things happened rather than always claiming a browser capture:

  • Sandboxed browser capture means our browser opened the page and the replay, redirect and visual evidence is ours.
  • Page could not be reached means nothing was listening on the host when we tried, so the page was never opened. This is not a verdict on the page. An unreachable host may be offline, already taken down, or blocking us.
  • External preview, not our browser means our browser was tried and did not finish in time, so the preview image came from a third-party service. In that case the address was sent outside Dralvia and no in-page behaviour was observed by us.

Previously every scan showed the first of these, including scans where the page was never opened at all.

More than one photograph of the page

A phishing page is not one moment. A credential-harvest kit commonly renders a harmless-looking shell first and swaps the fake sign-in form in once its own scripts run, and the overlay that asks for the password often appears only after someone selects a field. A single screenshot of the settled page can miss both.

When our browser opens a page for a full capture, it now photographs it at up to three moments, and the report shows each one under How the page behaved while we watched:

FrameWhat it shows
First paintThe page before its own scripts have finished, which is what a visitor sees for the first fraction of a second.
SettledThe page once it stopped changing. This is the same image as the main preview.
After focusing the password fieldWhat the page reveals once a credential field is selected.

The third frame is only taken when the page actually asks for a credential. Nothing is ever typed into any field and no form is ever submitted. The field is scrolled into view and focused, and that is all.

Every scan that reaches a live page gets the first two frames. The third is reserved for the deeper capture Dralvia runs when a page shows signals worth detonating in full, because focusing a field costs time on every scan and buys nothing on a page with no credential form.

Frames are stored images served from Dralvia. Opening one shows the picture we took, never the live page.

When you see fewer than three frames

The report lists any moment it did not store, and why, under Moments not stored. The reasons are:

  • The page looked exactly the same at this point. Identical frames are collapsed into one. Showing the same picture twice would imply a change that did not happen, so "one frame" here is itself a finding: the page did not move between first paint and settle.
  • The capture had already reached the number of frames it is allowed to keep, or the frames already kept had used the storage this capture is allowed. Both are budget limits, in place because a full-page bitmap of a very tall page is what makes a browser run out of memory.
  • The browser returned no image for this moment.

Scans where the host was never reachable show no frame panel at all, because no browser session happened. See When the browser did not open the page above.

API

Frames appear in the scan payload under visual_artifacts.capture_frames.metadata:

{
"available": true,
"frame_count": 3,
"frames": [
{
"name": "landing",
"label": "First paint, before the page's own scripts finish",
"url": "/scan/artifacts/frame/<public_id>/landing",
"captured_ms": 240,
"note": "viewport at DOM load"
},
{
"name": "settled",
"label": "The page once it stopped changing",
"url": "/scan/artifacts/preview/<public_id>",
"primary": true,
"captured_ms": 1400
}
],
"frames_dropped": [{ "frame": "interaction", "reason": "identical_to_earlier_frame" }]
}

GET /scan/artifacts/frame/<public_id>/<frame_name> returns that frame as an image. The settled frame carries no image of its own. It is the main preview, and its request redirects to /scan/artifacts/preview/<public_id> rather than storing the same picture twice.

Frame names are limited to [a-z0-9_]{1,32}, and only images already inside the stored scan payload are ever served; the route decodes nothing supplied by the caller. An unknown frame name returns 404 rather than a blank image.

Both /scan/artifacts/preview/… and /scan/artifacts/frame/… are served from the host root rather than under /api, and they honour platform maintenance the same way the rest of the product does.

Sharing a scan

Copy shareable link on a result copies a link to that stored scan. Opening it shows the scan that was run, with the same verdict and the same evidence. It does not run a new scan, so the person you send it to sees exactly what you saw.

Who can open a link:

the scan was runwho can open the link
without an accountanyone
by a workspacemembers of that workspace

If you open a workspace link without being signed in, you are asked to sign in. If you are signed in to a different workspace, the link is declined.

Scans run by a workspace cannot be shared outside it. This is deliberate and there is no setting to change it. The address you scan is stored exactly as you typed it, including anything after the ?, and the links people ask about are usually the ones carrying a password reset, an invite, or a signed download token. Publishing the report would publish that address. To give a scan to someone outside your workspace, export the report or the EvidencePack and send that instead, so you choose what leaves.

Every report says so itself, in a retention block:

"retention": {
"payload_retention_days": 1,
"scope": "guest",
"generated_at": "2026-08-25T13:33:35Z",
"full_evidence_until": "2026-08-26T13:33:35Z"
}

After that point the verdict, score and findings remain, and the heavy evidence such as screenshots is cleared. The figure is measured from what was actually cleared rather than from configuration, so it reflects what happened to your data.

Changed on 2026-08-25

These two links now carry a short-lived signed token, ?t=…, and a request without a valid one is refused with 403.

Previously the scan id was the entire check, so anyone who came into possession of an id could fetch that scan's screenshot even when the report itself was private, and turning a report back to private did not take the image back. Both are now tied to the same permission as the report.

What this means in practice:

  • Use the URL exactly as the scan response gives it to you. Do not rebuild it from the scan id.
  • The link is valid for a bounded window (one hour by default, operator configurable through SCAN_ARTIFACT_TOKEN_TTL_SECONDS). Re-read the scan to get a fresh one; the picture itself does not change.
  • Saved or bookmarked artifact links stop working once they expire. If you need to keep a screenshot, export it, or export the EvidencePack, rather than storing the link.

Limits are operator-configurable: DRALVIA_CAPTURE_FRAMES (default on), DRALVIA_CAPTURE_FRAMES_ON_RENDER (default on — the landing/settled pair on the quick pass that runs on every scan, switchable separately because it is on the hot path), DRALVIA_CAPTURE_FRAME_MAX_COUNT (default 3), DRALVIA_CAPTURE_FRAME_TOTAL_BYTES (default 1.5 MB per capture) and DRALVIA_CAPTURE_FRAME_INTERACTION_WAIT_MS (default 400 ms).

Telling a reused verdict from a fresh one

If the same address was scanned recently, Dralvia reuses that verdict instead of running a duplicate scan. The report now says so in plain words, for example:

This verdict was produced 12 hours ago and reused, not re-run. Sign in to run a fresh scan of this address. Reusable until Aug 27, 2026.

Signed-in users are pointed at the Force fresh scan option instead, which re-runs the scan immediately. That option is not available without an account, so the report says which of the two applies to you rather than showing only a timestamp. The note becomes more prominent once a reused verdict is more than a few days old.

Scanning itself is never limited by this. Only re-running an address you have already scanned requires an account.

How long a verdict is actually reusable

The Reusable until date is not a preference, it is a consequence. A verdict can only be reused while the evidence behind it still exists, and raw scan payloads are trimmed on a retention schedule. Dralvia therefore advertises the smaller of the two: the cache window it would like to offer, and the window the payload actually survives.

That figure is now read from what the retention job recorded on its last run, not from configuration. The two used to be able to disagree — the job trimming payloads and the API answering questions about them run as separate services, and if only one of them was configured the API would state a window nothing could honour. The scan payload reports both numbers so the derivation is checkable:

"cache": {
"hit": true,
"ttl_days": 1,
"payload_retention_days": 1,
"expires_at": "2026-08-04T07:35:58Z"
}

ttl_days is never larger than payload_retention_days. If your deployment trims payloads after one day, verdicts are advertised as reusable for one day.

Security hygiene vs phishing evidence

The scanner separates findings into two groups so a hardening gap is not mistaken for an attack:

  • Phishing evidence drives the verdict: domain age, typosquatting, brand impersonation, suspicious redirects, credential harvesting, threat-intel hits, certificate mismatch, fake login indicators.

  • Security hygiene findings are browser-hardening gaps: missing or weak security headers (CSP, HSTS, Referrer-Policy, Permissions-Policy, COOP, CORP, COEP) and cookie flags (Secure, HttpOnly, SameSite). They appear in their own Security Hygiene section with one of three statuses:

    StatusMeaning
    GoodThe security-header check completed and found no gaps
    Needs ReviewThe check completed and found hardening gaps, listed in the section
    Not checkedThe check did not complete for this host, so its hardening was not assessed

    Not checked is deliberately not folded into Good. If the header request failed, timed out, or was refused, we have no opinion on the site's hardening and say so — an empty findings list is not a clean result.

Missing headers alone do not mean a site is phishing. They carry a much smaller phishing weight, and when they are the only findings the phishing score is capped so the verdict stays Allow with Phishing Risk: Low and Security Hygiene: Needs Review. A well-known site like netflix.com with only header gaps reads as Allow, not Caution. Security headers still matter for hardening, so Dralvia keeps scanning, reporting, and explaining them.

The API response carries both tracks: phishing_score, phishing_risk_level, phishing_findings, security_hygiene_score, security_hygiene_status, security_hygiene_findings, and final_verdict (the legacy score and risk_level remain for backwards compatibility and equal the phishing values).

security_hygiene_status is one of Good, Needs Review, or Not checked. Integrations that previously treated any non-Needs Review value as a pass should treat Not checked as "no data" instead: before 2026-08-04 that state was reported as Good, so historical scans stored with Good and an empty security_hygiene_findings list are not evidence of good hardening.

How the phishing score is built

Each piece of evidence adds risk points, and the total is capped at 100. Some scans then have points taken away again (see When points are removed again below), so the final score is not always the total. The result page shows a Why this score panel that lists every category, its points, and every reduction, so the number is never a black box:

  • Domain age, Domain intelligence (suspicious top-level domains, brand-in-subdomain impersonation), TLS certificate, Redirects, Page content (credential, OTP, and card capture; lure language; evasive page behavior), Brand impersonation, Threat intelligence, and Security hygiene (the reduced header weight).
  • Correlated risk is the most important line. Single signals are scored moderately so one tripped check never over-escalates a benign site, but real phishing trips several independent signals at once. When that happens Dralvia adds a high-confidence correlated risk signal. For example a freshly registered domain that also redirects across domains to a suspicious top-level domain, or a credential form on a domain that is not the brand it imitates.

The panel shows each category's points, the total risk points, and the final score. When the evidence totals more than 100 the panel says so, so a high-confidence verdict is fully explainable.

When points are removed again

Three rules can lower a score below the sum of its evidence. Each one is listed in the Why this score panel as its own line, with the reason, the points before, and the points after — a reduction is never applied silently.

ReasonWhen it appliesEffect
Trusted brandThe host is a trusted-brand domain, or a subdomain of one, and the scan found nothing critical (no blocklist or threat-intelligence hit, no invalid certificate, no very recent registration)Score forced to 0
Established domainThe domain is long-standing with valid TLS, or has a clean scan history with us, and nothing on the strong-evidence list was trippedScore capped at 15
Security hygiene onlyThe only positively-weighted findings are missing headers or cookie flagsPhishing score capped so the verdict stays Allow

Two consequences are worth stating plainly:

  • A clamped score is a ceiling, not a measurement. An established domain whose evidence totals 21, 45, or 90 all display 15. The number tells you the clamp ran; the raw total above it tells you what was actually found. Read both.
  • Trusted-brand and established-domain reductions are assumptions about the owner, not about the page. A long-standing site that has been compromised can be clamped. If the raw total is high and the reason line says a reduction ran, treat the raw findings as the thing to investigate.

The same values are on the API response under score_breakdown (see the Web2 scanner API), so an integration can apply its own policy to the raw total instead of the served score.

How ages are written

Ages are reported in days, with a human scale added once the raw number stops being readable on its own:

ValueShown as
1 day1 day
3 days3 days
90 days90 days (about 3 months)
11,280 days11,280 days (about 31 years)

The exact day count is always kept, because that is the number you would quote in a ticket. The scale in brackets is deliberately approximate ("about 31 years", never "30.87 years"), and it is omitted under two weeks, where the day count is already the clearest thing to say. This applies to Domain age and TLS cert age on a URL result, and to the contract and verified-source ages on a contract result.

What the scanner looks for

Beyond domain age and security headers, the scanner detects:

  • Credential harvesting: login and password forms, one-time-passcode (2FA) fields, payment-card fields, and forms that post to a different registrable domain than the page (your secret going to someone else).
  • Redirect evasion: URL shorteners and link wrappers, open-redirect parameters, redirect targets hidden behind base64 or percent-encoding, scripted redirects that decode a hidden off-site target, Base64 Blob wrappers that auto-open a hidden meta-refresh document, and a wrapper that lands on a suspicious or different domain.
  • Fingerprint redirect gates: pages that fingerprint the visitor before a timed redirect can raise stronger redirect-gate evidence, even when the first hop stays on the same domain.
  • Dynamic script injection: WordPress pages that fetch JavaScript from a custom REST/admin-ajax endpoint and inject it into the page are treated as suspicious endpoint-loader behavior.
  • Hosted payload surfaces: public bucket pages and full IPFS gateway payload paths are recognized as hosted-payload infrastructure, even when the gateway route itself is still live.
  • Shared site-builder surfaces: site-builder and edge-page hosts are low-weight infrastructure signals on their own, then become stronger when paired with credential, brand, or redirect-lure evidence.
  • Brand impersonation: lookalike and typosquatting domains, a brand name in a subdomain while the real domain belongs to someone else, brand logos and favicons, and visual impersonation.
  • Evasive page behavior: obfuscated or packed JavaScript, hidden or zero-size iframes, clipboard hijacking (swapping pasted crypto addresses), and redirects that only fire after a delay or for specific visitors.
  • Threat intelligence: OpenPhish, URLhaus, and PhishTank, plus Google Safe Browsing and VirusTotal when configured. A confirmed listing is treated as high confidence on its own.

Understanding the EvidencePack

  • Click Download EvidencePack to retrieve the JSON artifact (persona explanations, flags, Merkle proof).
  • Use the Transparency Log tab to see the entry that anchors the pack (for auditors).
  • For manual reviews, use the incident/escalation flow to push the pack into TicketBridge.

When a site cannot be scanned

If the scanner cannot reach the target at all. DNS fails, the host is unresponsive, the request times out, or the resolved IP is blocked by SSRF protection. The platform deliberately refuses to invent a risk score for it. Instead of a misleading green "0", the result shows:

  • a gray gauge with -- in place of a numeric score
  • a banner above the gauge: "We could not reach this site (<reason>). Score unavailable. Try rescanning later."
  • the verdict tab reports unknown instead of Safe / Caution / Avoid

The reason is one of: DNS failed, Blocked IP, Timeout, or Fetch failed.

In the JSON response and EvidencePack, this is conveyed by:

  • score: null
  • risk_score: null
  • risk_level: "unknown"
  • score_unavailable: true
  • score_unavailable_reason: "dns_failed" | "blocked_ip" | "timeout" | "fetch_failed"

Cached unscannable results carry a shorter TTL and are flagged rescan_recommended, so a later rescan will re-attempt the fetch rather than serving the stale unavailable verdict.

A page that loads normally is not marked unreachable just because one secondary check was blocked. Large sites sometimes anti-bot a single probe (commonly the redirect trace) while the page itself is perfectly reachable. The score is only withheld when the page content itself could not be analyzed, or when several core checks did not complete, so a reachable site keeps its verdict.

Learned family and infrastructure evidence

The scanner now adds controlled learning signals from previously observed malicious families. These appear as evidence labels and explanation text in the result and EvidencePack.

  • matched_known_bad_family: the scanned URL/page resembles a previously observed malicious family based on repeated page, redirect, or infrastructure patterns.
  • reused_malicious_infrastructure: the scan reused known-bad hosting, ASN, redirect, favicon, or HTML-family indicators.
  • These are confidence and context signals, not blind proof by themselves. Review them alongside the primary scanner flags, redirect timeline, and page evidence.
  • Learned-family evidence is promoted only after internal backtesting and shadow validation; it is not raw auto-learning from one scan.
  • A host is only labelled a known scam-campaign target (the campaign:known_redirect_target evidence) when multiple flagged (Avoid) wrappers were sighted pointing at it. Ordinary, clean redirects do not count: a brand's own domains legitimately redirecting to its canonical site (for example ericsson.se and ericsson.com both landing on www.ericsson.com) are not treated as a campaign and will not pull the destination into a malicious verdict.
  • Repeated-pattern detector rows in the EvidencePack now also carry a stable source_family label so future reviews can trace the detector back to the lure family it was promoted from.

Why a low-risk result no longer asks for human review

A clean, low-scoring result no longer shows a "human review is required" next step just because the page has a login form or the site shows its own brand logo. Those are normal on a legitimate site: every real sign-in page has a login form, and a brand's own logo on its own domain is evidence that the site is what it claims.

This is a floor, not a mute. A result is still escalated when something genuinely risky is present, at any score:

  • credential-capture, seed-phrase or fake-auth behaviour;
  • a hit from a threat feed or reputation provider;
  • a visual-impersonation match;
  • lure patterns (document-share, parcel/shipping, streaming-clone, marketplace-brand);
  • two or more of the weaker signals at once, or a score that is already elevated.

If you previously saw a score of 0 alongside "Suspicious web flow detected", that contradiction is fixed.

When a page capture is degraded

Dralvia renders the page in its own browser so the scan can see what the page actually does: redirects, scripts, login flows and visual impersonation. If that browser capture cannot complete, the report now says so explicitly instead of quietly showing a plain thumbnail as if it were a full capture.

A capture marked degraded means the page was not executed in our browser, so behavioural and visual-impersonation evidence for that scan is limited. Rescanning usually resolves it. Very heavy pages take longer to capture; the capture budget is operator-tunable via DRALVIA_BROWSER_CAPTURE_WAIT_CEILING_SECONDS.

Every degraded capture carries a degraded_reason that names a cause, such as screenshot_failed:... when the browser could not photograph the page, capture_failed:capture_timeout when it ran out of time, runner_exit:<reason> when the browser stopped early, or no_screenshot_returned when the capture completed but produced no image. The field never restates the capture mode: a value like headless describes how we captured, not why it degraded, and is not a reason you can act on.

Host and TLS infrastructure evidence

The Infrastructure Signals panel on the result page shows what we observed about the server behind the address, and every row is explained in plain language underneath the value, so you never have to interpret a raw hash on your own.

Alongside the hosting fields (IP, ASN, hosting type and provider, domain age, TLS certificate age and issuer), the panel surfaces the host's TLS identity when the scan produced it:

  • TLS certificate fingerprint — a unique ID for the exact certificate the site presented. If two different sites share one certificate, they are run by the same operator.
  • JARM TLS fingerprint — a fingerprint of how the server's encryption is configured. Phishing kits deployed from the same toolkit answer TLS handshakes identically, so a shared JARM links a host to other sites we have already seen, even when the domains look unrelated. This is a context and clustering signal: a shared JARM alone is not proof of harm (large hosting platforms share one JARM across many legitimate customers), so read it alongside the primary flags.
  • Network owner, open ports and services — who owns the network block and what else is reachable on the server, when that context is available.

Rows only appear when the scan actually produced them; a field we could not observe is omitted rather than shown blank. The same values are available through the API in detailed_results.ssl.data.host_context, so the UI and API report identical evidence.

OAuth/OpenID authorize-flow signals

The URL & phishing scanner now also treats suspicious OAuth/OpenID authorize URLs as first-class phishing evidence when you scan a full URL instead of just the bare host.

  • Fake-host consent pages can now raise OAuth risk even when the visible scopes are sparse, if the URL references a known provider brand such as Microsoft or Amazon on a different domain.
  • OpenID redirect targets like openid.return_to now surface redirect evidence in the scan result, not just classic redirect_uri values.
  • Encoded nested authorize targets can now produce their own evidence and score lift when the URL hides another provider authorize URL inside a long payload. This now covers hex, standard base64, and URL-safe base64 packed targets.
  • Malformed authorize flows can now raise oauth:malformed_authorize_flow when the URL uses a broken redirect_uri, suspicious slash formatting, inconsistent response_mode, or fake-host callback syntax.
  • Generic fake consent/login handoffs can now raise oauth:generic_auth_handoff when a lure avoids explicit risky scopes but still exposes a provider-style authorize path, client id, callback, and related auth parameters on a non-provider host.
  • The content auth detector now also recognizes broader provider-family wording on phishing pages, including Apple, GitHub, Slack, Zoom, and Dropbox-style sign-in prompts, not only Google/Microsoft/Okta.
  • In the API/EvidencePack, look for detailed_results.oauth_scope, oauth:provider_mismatch, oauth:encoded_auth_payload, oauth:malformed_authorize_flow, and oauth:generic_auth_handoff.

Document-share lure signals

The scanner now promotes recurring document-share phishing patterns into their own content evidence instead of leaving them buried inside generic keyword hits.

  • Pages that pair phrases like shared document, secure share, view document, or mailbox document with credential-capture behavior can now raise content:document_share_lure.
  • Credential-style behavior includes password forms, external credential post targets, fake-auth flows, active credential-capture posture, or redirect-driven auth prompts.
  • Normal shared-document language by itself is not enough to trigger the signal. The scanner requires both the lure wording and auth-style behavior so routine collaboration pages do not become noisy verdicts.
  • The auth-style behavior does not have to be on the same page. A page published on a shared platform whose call to action links to an unrelated domain also counts, because that is the same lure with the credential form moved one click away.

Pages published on a shared platform

Anyone can open a free tenant on a helpdesk, notes or site-builder platform and publish under that platform's domain, inheriting its age, certificate and reputation.

  • Such a page does not receive the "long established domain" benefit from the platform's registration date. It can still earn good standing from its own scan history on that exact address.
  • When its action link ("View document", "Sign in", "Verify") points at an unrelated domain, the scanner raises content:offsite_credential_cta and lists the links in the technical evidence.
  • A site on its own domain is out of scope for this signal, and a platform's own hosts are not treated as tenants of themselves.

Sites that refuse analysis

A challenge from a recognized security provider stays the mild, common content:scanner_blocked signal. A site that blocks the scan and returns a short custom holding message such as "Preparing secure session", with no real page content, while writing browser storage or driving a redirect, raises content:analysis_evasion_gate — enough on its own to reach Caution. A site that returns a real page, even behind a block, does not raise it.

Parcel and shipping lure signals

The scanner also promotes recurring fake-delivery and parcel-tracking patterns into their own content evidence.

  • Pages that pair phrases like track package, parcel tracking, delivery update, missed delivery, or dpd with credential-style behavior can now raise content:parcel_shipping_lure.
  • This signal is intentionally stricter than a generic login-page heuristic. It requires credential-oriented behavior such as an external credential post target, fake-auth flow, active credential-capture posture, or a redirect-driven auth prompt with login/password corroboration.
  • Routine carrier status pages and shipment updates without those credential-style behaviors are not enough to trigger the signal.

Streaming-clone lure signals

The scanner now also promotes recurring streaming-login and subscription-renewal phishing pages into their own content evidence.

  • Pages that pair streaming/subscription wording like watch now, continue watching, subscription expired, account on hold, or update payment with credential-funnel behavior can now raise content:streaming_clone_lure.
  • Throwaway Netflix-style hosts are handled conservatively. Brand text on its own is not enough; the signal is meant for lookalike subscription or login funnels, not for a same-brand streaming sign-in page.
  • The scanner promotes this family when the page behaves like a credential funnel, such as posting credentials off-host, showing an active credential-capture posture, or presenting a password form on a mismatched throwaway host that is branding itself as a streaming service.

Marketplace-brand and free-hosting correlation

The scanner now also promotes recurring shopping and order-themed lookalike pages into their own content evidence.

  • Amazon-style fake-host or path-stuffed shopping pages can now raise content:marketplace_brand_lure when order/account/payment wording is paired with credential-funnel behavior.
  • The rule is conservative. Same-brand marketplace sign-in pages do not trigger this new signal on wording alone; the scanner is looking for throwaway hosts that combine marketplace branding, stuffed shopping paths, and credential capture.
  • When a trusted-brand lure also sits on a curated free-hosting surface, the infrastructure layer now adds infrastructure:brand_free_hosting_lure so analysts can see the combined hosting-plus-brand pressure directly instead of inferring it from separate chips.

Domain and TLS age signals

The scanner checks domain registration age when WHOIS data is available. Domains under 60 days old raise whois:very_recent_registration; domains under 7 days old also raise whois:ultra_recent_registration.

The TLS check now also exposes normalized issuer details and certificate age. A certificate issued within the last 7 days raises ssl:very_recent_certificate. Treat this as corroborating evidence; a new certificate alone is not enough to call a site phishing.

Hosting infrastructure signals

The scanner now records hosting infrastructure context in detailed_results.infrastructure when available. This includes ASN, hosting provider, hosting tenancy, shared-hosting surface, optional free-hosting provider, IP, final host, domain age, and TLS certificate age.

The scan result page shows this context in Infrastructure Signals, and EvidencePack Summary includes compact infrastructure evidence when ASN, hosting, free-hosting, domain-age, or TLS-age context contributes to the verdict.

Free hosting is treated carefully. A target on a common multi-workspace surface such as Vercel, GitHub Pages, Cloudflare Workers, Netlify, Firebase Hosting, or Dropboxusercontent can raise infrastructure:free_hosting_surface, but the stronger infrastructure:free_hosting_lure signal only appears when the hosting surface is paired with phishing lure evidence. Bare IP targets raise infrastructure:ip_literal_surface.

The infrastructure block now also tells you whether the target sits on a shared multi-workspace surface or on dedicated / unknown infrastructure. Shared hosting is intentionally treated as corroborative context, not a standalone phishing verdict, so analysts can separate "common workspace platform" from stronger infrastructure signals quickly.

Brand country domains and ownership

Many brands run country or regional domains: samsung.ro, samsung.de, samsung.it. These are a different registration from the main brand domain, and that is exactly the shape of a lookalike squat such as roblox.com.pt — same brand word, different registration, different owner. The scanner used to convict both, so a brand's own country domain could be reported as an imitation of the brand it belongs to.

Ownership is decided from the brand registry, not guessed from the network. Each registry entry records a company_name, which groups a brand's domains into one family (samsung.com and samsung.ro are both recorded under Samsung, the same way gmail.com and youtube.com are both recorded under Google). When the scanned domain and the matched brand belong to the same company, the domain is reported as the brand's own (brand:exact_match_) instead of as an imitation.

This is a recorded fact rather than a live probe, and deliberately so. No network signal distinguishes a brand's own country domain from a squat: measured against samsung.ro, the certificate carries no organisation (it is a plain domain-validated certificate), the brand's own certificate does not list the country domain, the nameservers are the same commercial DNS provider a squat uses, the brand's site never links to the country domain because it localises by path (samsung.com/ro/), and the registry publishes no registrant organisation. Anything inferred from the country domain's own behaviour can also be staged by whoever controls it.

Because that shape is genuinely ambiguous, the name on its own no longer produces a verdict for any brand. A domain whose name is exactly a tracked brand's name under a different suffix raises brand:registrable_impersonation_ as evidence, but the finding is only treated as an imitation when the scan also observes something a real localized brand site does not do:

  • credential capture — a login, one-time-code or card field, a form posting somewhere else, or a seed-phrase prompt;
  • lure language or a cloned-kit tell, including hiding the page from the scanner;
  • a third-party verdict — a threat-feed or blocklist listing, a known malicious reputation, or a visual-impersonation match;
  • disposable or suspect infrastructure — a parked domain, a freshly registered domain, an invalid or self-signed certificate, a bare IP, or a homograph name.

That correlation is reported as correlation:registrable_brand_impersonation and carries the weight the raw name match used to. Ordinary redirects and missing security headers are explicitly not accepted as corroboration: a country domain forwarding to its own brand raises redirect:cross_etld, and treating "it sent us to the real brand" as evasive is what previously turned two innocent facts into a conviction. For the same reason, when a domain's name is the brand name, serving the brand's own favicon and logo is no longer counted as imitation — a genuine local site serves them too.

A misspelling or visual trick is treated differently and still counts on its own: nobody registers paypa1 or a Cyrillic lookalike innocently.

Redirects are judged the same way. A redirect that leaves one registrable domain for another normally raises redirect:cross_etld, and that remains a genuine signal — measured against our known-good control corpus it appears on only about 4% of legitimate sites. But every one of those legitimate cases is a hop that stays inside the same company: gmail.com to google.com, zoom.us to zoom.com, or a country domain to its brand. A hop is therefore not treated as leaving the company when either the brand registry records both domains under the same company, or the two domains share a name and are verified to share DNS operators. A redirect to a company we cannot tie to the starting domain still raises the flag, and a domain that is not in the registry never inherits an owner.

Two limits are worth knowing:

  • Ownership records do not skip checks. A recorded country domain is still scanned in full and can still be reported as risky on its own behaviour. Ownership only stops it being reported as an imitation of its own brand. Recording ownership deliberately does not grant the trusted-brand bypass, so if a brand ever lets a country domain lapse, whoever buys it does not inherit a clean verdict.
  • Coverage is explicit. A country domain that is not yet recorded falls back to a narrower rule: if it forwards to the brand it would otherwise be flagged against, and the scan observed no hostile behaviour on it (no credential capture, login or one-time-code fields, threat-feed or blocklist hit, visual impersonation, or scanner blocking), the imitation finding is withheld. A domain that forwards somewhere else, or that behaves hostilely, stays flagged.

The scan result page names the owning company under the trusted-brand badge, together with the sibling domain the ownership record is based on, so the reason a country domain was accepted is visible rather than implied. To have a country domain recorded, report it through the "something looks wrong" flow on the scan result and an analyst reviews it.

When a domain is the brand's own site

Once the registry establishes that a domain belongs to a brand — because it is that brand's registration, or because the registry records it under the same company — signals that merely say "this page looks like that brand" stop counting as suspicion. That covers the brand's logo, its favicon, and a visual-impersonation match to that same brand. A page cannot be impersonating a brand it belongs to.

This applies to the recommended action as well as the score. A visual match to the brand a domain belongs to no longer produces a block recommendation, which previously meant a brand's own country site could be reported as Safe with a score of 0 and still carry "block this URL" guidance.

Three limits keep this from becoming a free pass:

  • ownership comes from the registry, never from the page, so a lookalike cannot claim it;
  • a visual match to a different brand still escalates — owning google.ro says nothing about a page dressed as PayPal;
  • evidence of actual harm still escalates at any score and regardless of ownership: credential capture, a threat-feed or blocklist listing, or an elevated score. If a brand's own site is compromised and harvesting credentials, that is still a block.

The evidence line names the domain's own registration. Where a domain is recognised only through its company family, the report names the sibling domain that proved ownership, so you can always see what the decision rested on. A name collision with several registry rows of one brand is reported once rather than once per row.

Community feedback (comments and votes)

Inside a scan result, the Community tab lets signed-in users leave a short comment and cast a Safe or Phishing vote for that domain.

  • Comments and votes are independent actions against POST /community. You can do either, both, or neither.
  • It is safe to leave a comment and cast a vote in rapid succession. The UI serializes the refresh of community state so the most recent action always wins; an in-flight comment refresh cannot revert a vote you just cast (or vice versa).
  • Post has a 10-second throttle per session; voting is unthrottled.
  • Clicking the same vote again clears your vote; clicking the opposite vote switches it.
  • Comments and vote counts inside a scan result's Community tab are always read live from the database, even when the rest of the scan payload is served from cache. New feedback shows up in the same tab immediately, not only in the global Community modal.

Bulk / API scans

  • Single request: POST /scan with { "domain": "example.com" }. Include "force_scan": true for a fresh scan (subject to your plan's daily force-scan quota).
  • Async/batch: POST /scan/bulk with up to 50 domains. Each entry returns a scan_id; poll GET /scan/jobs/<id> (alias of GET /scan_status/<id>) until it reaches done to retrieve the results.
  • /scan/jobs lists the most recent async jobs plus queue depth so you can monitor batches with one call.
  • REST headers: Authorization: Api-Key <key> or legacy X-API-KEY, plus optional X-Actor for audit attribution.
  • Parse detailed_results to see per-subsystem scores/flags; checks provides a summarized list for dashboards.
  • EvidencePacks are included inline (tabs.detection) and you can still call /evidencepacks/<id> to download the canonical JSON.

Async job queue in the HUD

The Async Scan Jobs card on #/phishing mirrors the /scan/jobs endpoint. It refreshes automatically and shows:

  • Queue counts (queued, running, completed, failed).
  • Per-job metadata: force-scan flag, risk score, runtime, and the top three flags or latest error.
  • Pagination at 3 jobs per page so longer queues stay readable.
  • A manual refresh button when you need to pause the auto-poll.

Use it to reassure pilots that async batches are progressing and to spot repeated failures early.

The dashboard leaderboards for Top Trusted Domains, Top Risky Domains, and Most Scanned Domains now use the same panel and table treatment as the async queue and recent-scan cards. The data and controls are unchanged; the update is visual consistency across the dashboard.

The same panel treatment now also applies to:

  • Unified Multi-Surface Scanner
  • Redirect Chain Timeline
  • Predicted Campaigns

Unified Multi-Surface Scanner behavior

The dashboard's Unified Multi-Surface Scanner accepts URLs, domains, IPs, wallets and contracts, transaction or file hashes, and pasted email text. What happens after you click Scan depends on the input:

  • URLs and domains go through the scan queue. You get a first verdict quickly (usually in about 30 seconds), and the deeper browser and visual checks keep running in the background. While they run, the panel shows "Deeper browser and visual checks are still running" and the verdict can update. The completed scan also lands in your scan history.
  • Other input types (IP, wallet, transaction, file hash, email text) return in one step. Email text is triaged by extracting up to three links and scoring each one; the verdict is the highest link score.
  • The result card leads with the verdict, risk score, and plain-language reasons. Open Technical details under the verdict when you need the full JSON payload.
  • If you hit your scan limit, the panel tells you how long to wait. Signed-in workspaces have higher limits than anonymous visitors.
  • Copy Shareable Link (available in both the result action row at the top of the verdict and in the Summary & Exports panel) copies a link to the scanned target. Opening that link lands on the scanner with the target prefilled and the scan re-running automatically, so the recipient sees a fresh result for the same target rather than a stale snapshot. The link carries only the target (?prefill=<target>&autoscan=1), not your session, so the recipient runs the scan under their own access.

Command-line scanner

A helper script (available from the Dralvia SDK examples) lets you run scans from cron/CI without writing glue code:

python scan_cli.py example.com --api-key changeme
python scan_cli.py --file domains.txt --async --api-key changeme
python scan_cli.py --status 123
python scan_cli.py --contract 0x1234...abcd --chain Ethereum --api-key changeme
  • --force requests a fresh scan (counts against your force-scan quota).
  • --async queues a job and returns immediately; poll with --status.
  • Repeated stale async recoveries no longer loop forever. Once the queue exhausts the stale-recovery budget, the job moves to dead_letter and requires a fresh submission.
  • --file accepts a newline-delimited list and uses the /scan/bulk API under the hood.
  • --contract scans a smart contract on the specified --chain (Ethereum, BSC, Polygon, Arbitrum, Optimism, Avalanche, Base, Fantom, or any chain you add to config.yaml).

Smart contract scanner

  • Open Contract Scanner (#/contract) to analyze EVM bytecode/ABI, proxy setups, AccessControl roles, tokenomics, liquidity locks, and holder concentration.
  • Each scan now returns a signed EvidencePack (exposed in the response JSON and available for download) along with a transparency log index so you can attest to tamper-proof results.
  • The EvidencePack highlights:
    • Upgradeability (proxy type + implementation).
    • Ownership/roles (owner address, renounced status, AccessControl roles).
    • Tokenomics capabilities (mint/burn/blacklist/pause).
    • Trading limits/taxes and liquidity lock signals.
    • Holder concentration snapshots.
  • Use the CLI command above for automation or set python scan_cli.py --contract ... inside your GitHub/GitLab/Azure pipelines to block deploys when risk_level is Avoid.

Redirect timeline

  • Click Redirect Timeline in the UI to see every hop, destination, and total duration.
  • The full redirect timeline view now paginates captured chains at 3 entries per page so long investigations stay readable.
  • Use the "Download CSV" button if you need to attach it to an incident ticket.

Threat Actor Graph

The dashboard Threat Actor Graph turns recent scanner and smart contract relationship data into a directed attack-hop map.

  1. Enter a domain, IP address, EVM address, or email.
  2. Click Explore.
  3. Review the graph:
    • The scanned entity appears as the red center node with a pulsing critical ring.
    • Related domains, IPs, and addresses appear as severity-colored nodes around it.
    • Directional lines show relationships returned by the graph API, such as DNS resolution, redirects, ownership, proxy implementation, liquidity pair, and attestation links.
    • Inline edge labels and the footer legend help distinguish critical, high, medium, and lateral relationships.
    • Monospaced node labels identify each connected entity.
  4. Click any graph node to pivot and query the next hop.

Limitations and assumptions:

  • Before you run a query, the card shows a reference-style preview graph so the dashboard keeps the same visual shape.
  • The preview graph now mirrors the shipped design reference with LIVE badge, subtitle, pulse ring, and legend so the card does not collapse back to a bare placeholder.
  • If the live graph query times out or a transient upstream error occurs, the card keeps the current graph or reference preview visible and shows a product error banner instead of raw upstream timeout text.
  • The graph shows recent observed relationships from workspace-scoped scan data; it is not a standalone attribution verdict.
  • The current UI walks one returned neighborhood at a time. Click connected nodes to continue investigation across hops.
  • Guest graph queries may use tighter backend limits than authenticated workspace queries.

Best practices

  • Trigger scans from CI/CD for every domain you publish (fail the build on medium or higher risk).
  • Use EvidencePacks as attachments in your SOC tickets so analysts see the reasoning immediately.
  • Share the risk score and flags with downstream automations (Slack alerts, SIEM ingestion) to reduce manual triage.

Who this is for

This guide is for workspace owners, workspace admins, and security operators who need clear, repeatable steps without support intervention for day-to-day execution.

Role-based start here

  • Workspace Owner: Start with Before you start, then complete Step-by-step and Known limits and rate limits.
  • Workspace Admin: Focus on Step-by-step, What each button does, and API and automation.
  • Security Analyst: Start at Day-2 operations and Troubleshooting, then use API error quick reference.
  • Integrator/Engineer: Start at API and automation, then validate with Step-by-step and FAQ.

Before you start

Use this short checklist before making changes:

  1. Confirm you are signed into the correct workspace account.
  2. Confirm your role includes the permissions needed for this page.
  3. Confirm your browser session is fresh (if pages behave unexpectedly, sign out/in once).
  4. Confirm required prerequisites (API keys, agent enrollment, license, upstream integrations) are already in place.

Step-by-step

Follow this sequence for predictable results:

  1. Open the workspace from the workspace menu.
  2. Review current status/health/last update indicators before making changes.
  3. Apply one change at a time and save.
  4. Run the available validate/probe/refresh action.
  5. Confirm the expected output appears (status change, new event, successful result).
  6. If behavior is not as expected, use Troubleshooting below before repeating actions.

Day-2 operations

After initial setup, keep this surface healthy with a simple routine:

  1. Daily: verify data freshness and error banners.
  2. Weekly: review trends, limits, and failed actions.
  3. Monthly: review permissions, keys/tokens, and stale entities.
  4. After any incident: capture evidence and update your internal operating notes.

What each button does

Button labels can vary by module, but behavior is consistent:

  • Refresh: reloads the latest data from backend APIs without changing configuration.
  • Save: persists workspace-scoped configuration changes.
  • Run/Probe/Validate: executes a non-destructive health or verification action.
  • Download: fetches workspace-scoped artifact(s) (for example bundle, checksum, signature, or report).
  • Verify: checks integrity/consistency and returns pass/fail details.
  • Enable/Disable: toggles module behavior for your workspace; audit evidence should be recorded.

If a button appears disabled, check role permissions, required fields, and workspace license/feature entitlement first.

Self-check playbook

Use this 5-step isolation flow before escalating:

  1. Configuration: confirm required inputs are present and formatted correctly.
  2. Permission: confirm your role can perform the action (401/403 usually indicates authz/authn mismatch).
  3. License/feature: confirm the feature is enabled for your workspace plan and module toggles.
  4. Quota/rate limit: check for 429 responses and cooldown windows.
  5. Service health: if you see 5xx, retry once after 30-60 seconds and capture exact error text.

If still failing, escalate with workspace ID, UTC timestamp, route, action, payload shape (no secrets), and screenshot/error response.

Troubleshooting

Use this quick triage order to reduce time-to-fix:

  1. Auth/session: refresh token by signing out/in.
  2. Workspace context: confirm you are in the correct workspace.
  3. Inputs/config: verify required fields and formats.
  4. Quota/license: confirm limits and feature entitlement.
  5. Service health: retry after short delay if backend is transiently degraded.

For escalation, include workspace ID, timestamp (UTC), route name, action attempted, and full error message.

API and automation

Everything in this page should remain workspace-scoped. If your team prefers automation, use the corresponding API endpoints with the same guardrails as the UI:

  • Use authenticated requests bound to your workspace context.
  • Use idempotency/retry controls where available.
  • Validate outcomes in the UI after automated runs.

If your endpoint mapping is not obvious, start from Help Center and follow the linked API docs.

Next best actions

After finishing this page, continue with related workflows so your workspace setup stays end-to-end complete:

FAQ

Q: I clicked save but nothing changed. A: Refresh once, confirm permissions, and verify required fields.

Q: Why do I see missing API key/unauthorized errors? A: Confirm your workspace API key/session is valid and mapped to the correct workspace scope.

Q: Can non-admin users use this page? A: Usually read-only access is possible; write actions require workspace-admin or equivalent roles.

Next steps

After finishing this guide:

  1. Validate the result in the related dashboard/workspace.
  2. Export or capture evidence if this affects compliance/incident operations.
  3. Share the same runbook internally so other operators follow identical steps.
  4. Return to Help Center for adjacent workflows.

API error quick reference

Use this matrix when a UI action fails with an HTTP/API error.

ErrorMeaningWhat to do now
401 UnauthorizedSession token is missing/expired or request is not authenticated.Sign out/in, refresh once, then retry. Confirm your session is active in the correct workspace.
403 ForbiddenYou are authenticated but your role is not allowed to perform this action.Confirm your role includes the required permission for this button/action. Ask workspace admin to grant access.
404 Not FoundThe route/resource does not exist in current workspace context (or feature not enabled).Confirm URL/route, workspace context, and feature availability. Refresh and retry; if persistent, capture timestamp and route and contact support.
429 Too Many RequestsRate limit/quota window was exceeded.Wait for cooldown/reset window, retry once, then reduce burst traffic/backoff if automated.
500 Internal Server ErrorBackend failed unexpectedly while processing the request.Retry after 30-60 seconds. If still failing, escalate with workspace ID, UTC time, route, action, and full error text.

Known limits and rate limits

These limits can vary by plan and feature, but behavior is consistent:

  • Burst traffic can trigger 429 Too Many Requests.
  • Workspace quotas apply per feature/module and reset on configured windows.
  • Repeated retries without backoff can extend recovery time during saturation.

Recommended operator behavior:

  1. Retry once after cooldown for 429 responses.
  2. Use exponential backoff in automation.
  3. Monitor usage/quota dashboards for sustained high utilization.
  4. Request quota review when normal workload regularly approaches limits.