DEV Community

Faraz Ahmad
Faraz Ahmad

Posted on Originally published at stadiasoft.com AI-assisted

Build an Explainable Vendor-Risk Gate in Node.js

A vendor signs up with a company domain. Your application wants one fast answer: approve or reject?

The network cannot give you that answer. It can give you evidence.

In this tutorial, we will call a domain-intelligence API, normalize its response and return one of three application decisions:

type Decision = "allow" | "review" | "block";
Enter fullscreen mode Exit fullscreen mode

The implementation uses registration, DNS, TLS, HTTPS and browser-header signals. It deliberately does not call a technically healthy site “safe.”

1. Define the boundary

The API can observe:

  • RDAP registration age, expiry and status;
  • public DNS and nameserver configuration;
  • TLS trust, hostname coverage and expiry;
  • HTTPS reachability and redirects;
  • selected HTTP security headers.

It cannot prove:

  • who controls the domain;
  • whether the business is legitimate;
  • whether the site contains malware or phishing content;
  • how the domain will behave tomorrow.

That boundary belongs in code review, product copy and user-facing explanations.

2. Call the endpoint with a deadline

Store the RapidAPI key in the environment. Copy the exact host from the marketplace snippet.

const API_HOST =
  "domain-intelligence-website-trust-score.p.rapidapi.com";

async function fetchDomainAudit(domain, { forceRefresh = false } = {}) {
  const query = new URLSearchParams({
    domain,
    timeout_ms: "6000",
    force_refresh: String(forceRefresh),
  });

  const response = await fetch("https://" + API_HOST + "/audit?" + query, {
    headers: {
      "x-rapidapi-host": API_HOST,
      "x-rapidapi-key": process.env.RAPIDAPI_KEY,
    },
    signal: AbortSignal.timeout(12_000),
  });

  const body = await response.json();

  if (!response.ok) {
    const error = new Error(
      body?.error?.message ?? "Audit failed with " + response.status,
    );
    error.status = response.status;
    error.requestId = body?.request_id;
    throw error;
  }

  return body;
}
Enter fullscreen mode Exit fullscreen mode

The external timeout is longer than the requested audit timeout so the API can return a structured failure instead of being cut off by the caller first.

3. Normalize only the fields your policy understands

Do not spread the provider response through your whole application. Build an adapter.

function normalizeAudit(raw = {}) {
  const findings = Array.isArray(raw.findings) ? raw.findings : [];

  return {
    domain: raw.domain ?? null,
    score: Number.isFinite(raw.score?.value) ? raw.score.value : null,
    grade: raw.score?.grade ?? null,
    provisional: raw.score?.provisional === true,
    maximumPossible: raw.score?.maximum_possible ?? null,
    topRecommendation: raw.summary?.top_recommendation ?? null,
    severeFindings: findings.filter((finding) =>
      ["critical", "high"].includes(finding.severity),
    ),
    findings: findings.map(({ code, severity, title, recommendation }) => ({
      code,
      severity,
      title,
      recommendation,
    })),
    checks: raw.checks ?? {},
  };
}
Enter fullscreen mode Exit fullscreen mode

Stable finding codes are better policy inputs than human-readable messages, which can change for clarity.

4. Separate evidence from the business decision

function decideVendor(evidence, policy = {}) {
  const allowAt = policy.allowAt ?? 75;
  const blockBelow = policy.blockBelow ?? 40;

  if (evidence.provisional || evidence.score === null) {
    return { decision: "review", reason: "incomplete technical evidence" };
  }

  if (evidence.severeFindings.length > 0) {
    return { decision: "block", reason: evidence.severeFindings[0].code };
  }

  if (evidence.score < blockBelow) {
    return { decision: "block", reason: "score below " + blockBelow };
  }

  if (evidence.score >= allowAt) {
    return {
      decision: "allow",
      reason: "technical posture meets vendor policy",
    };
  }

  return { decision: "review", reason: "mixed technical evidence" };
}
Enter fullscreen mode Exit fullscreen mode

These thresholds are illustrative. A marketplace seller, a payroll vendor and a public-link preview need different policies. Keep the numbers configurable and record the version of the policy used for each decision.

5. Fail into review during outages

A timeout does not make a vendor fraudulent.

export async function assessVendorDomain(domain, policy) {
  const checkedAt = new Date().toISOString();

  try {
    const raw = await fetchDomainAudit(domain);
    const evidence = normalizeAudit(raw);
    const outcome = decideVendor(evidence, policy);

    return { checked: true, checkedAt, ...outcome, evidence };
  } catch (error) {
    return {
      checked: false,
      checkedAt,
      decision: "review",
      reason: "domain evidence temporarily unavailable",
      errorCode: error.status === 429 ? "RATE_LIMITED" : "AUDIT_UNAVAILABLE",
      requestId: error.requestId,
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

For transient failures, retry in a worker with exponential backoff. Do not lock a person out because an RDAP server or certificate endpoint was temporarily slow.

6. Test the policy, not a live score

External evidence changes. Unit tests should use fixtures.

import test from "node:test";
import assert from "node:assert/strict";

test("provisional evidence goes to review", () => {
  const result = decideVendor({
    score: 80,
    provisional: true,
    severeFindings: [],
  });
  assert.equal(result.decision, "review");
});

test("a critical finding blocks even with a high score", () => {
  const result = decideVendor({
    score: 82,
    provisional: false,
    severeFindings: [{ code: "DOMAIN_EXPIRED", severity: "critical" }],
  });
  assert.deepEqual(result, {
    decision: "block",
    reason: "DOMAIN_EXPIRED",
  });
});

test("middle scores require review", () => {
  const result = decideVendor({
    score: 62,
    provisional: false,
    severeFindings: [],
  });
  assert.equal(result.decision, "review");
});
Enter fullscreen mode Exit fullscreen mode

Add integration tests with domains you control, but do not assert that a public domain always returns the same score.

7. Add the evidence your workflow still needs

For real vendor onboarding, combine the technical audit with:

  • legal-entity and business-registration checks;
  • proof of domain control;
  • sanctions or compliance screening where appropriate;
  • bank-account and payout controls;
  • malware/phishing intelligence when security risk requires it;
  • human review and an appeal path.

The domain score helps route work. It should not become a black-box denial system.

Try the workflow

The Domain Intelligence & Website Trust Score API returns the five evidence groups, an explainable score, stable finding codes and remediation. The Basic plan includes 50 requests per month.

For the complete score model, bulk example and implementation boundaries, see the StadiaSoft domain reputation checker guide.

Disclosure: I publish the RapidAPI product referenced here. The tutorial intentionally documents what it cannot prove.

References

Top comments (0)