ProFix Directory — JavaScript client guide

Fetch verified Ohio contractor data from any JavaScript or TypeScript runtime in under ten lines. No client library, no API key, no SDK upgrade treadmill.

Installation

No client library required — every endpoint is fetchable with built-in fetch. Node 18+, Bun, Deno, Cloudflare Workers, and modern browsers all ship the global already. If you target older Node, polyfill with undici or node-fetch.

Quickstart: fetch verified pros for a trade × city

The fastest possible smoke test. Hits the partner embed endpoint — top five ranked pros for a trade-city slug pair, every field a UI needs:

// Node 18+, Bun, Deno, Cloudflare Workers, and modern browsers all
// ship a global fetch. No dependencies required.
const res = await fetch(
  "https://profixdirectory.com/api/embed/plumber-toledo.json",
);
if (!res.ok) throw new Error(`ProFix ${res.status}`);

const data = await res.json();
for (const pro of data.pros) {
  console.log(`${pro.name} — ${pro.phone} (${pro.verification_tier})`);
  console.log(`  Profile: ${pro.profile_url}`);
}

Available trade slugs: plumber, electrician, hvac, roofing, concrete, appliance-repair, tree-service, restoration, and more — the canonical list lives in the OpenAPI spec.

Fetch the permit leaderboard

The permit leaderboard ranks pros by verified building permits pulled from public county datasets. It's the closest thing to proof-of-work in home services — independent of self-reported stars:

// Top 10 Toledo-area plumbers by VERIFIED building permits pulled
// in the last 365 days. Lucas + Cuyahoga + Franklin + Hamilton are
// the four counties currently in the permit dataset.
const params = new URLSearchParams({
  trade: "plumber",
  county: "lucas",
  window: "365",
  top: "10",
});

const board = await fetch(
  `https://profixdirectory.com/api/permit-leaderboard.json?${params}`,
).then((r) => r.json());

console.log(board.leaderboards);

Set county=ohio for the statewide aggregate, or omit the county to get the full set of per-county leaderboards in one call. Permit data currently covers Lucas, Cuyahoga, Franklin, and Hamilton counties.

Embed widget (one line)

The widget is a single <script> tag — paste it on a blog post, an HOA newsletter, or a property-management site and it renders a sandboxed iframe with the top five verified pros for that trade-city:

<!-- Drop this on any page to render the top 5 Toledo plumbers.
     The widget is a self-hosted script that injects a sandboxed iframe. -->
<script
  async
  src="https://profixdirectory.com/widgets/plumber-toledo.js"
></script>

Swap plumber-toledo for any trade × city pair. Inline CSS only, no external assets except call/visit links back to ProFix Directory profile pages.

Subscribe to the changelog (RSS)

ProFix ships a public RSS 2.0 feed combining the newsroom changelog and published research. Useful for partner status pages, “what's new” widgets, and Slack notifications:

// Minimal RSS pull — no XML library needed for a simple <item>/<title>
// scrape. Use a real parser (fast-xml-parser, rss-parser) for production.
const xml = await fetch(
  "https://profixdirectory.com/api/newsroom.rss",
).then((r) => r.text());

const titles = [...xml.matchAll(/<title>([^<]+)<\/title>/g)]
  .slice(1) // skip the channel <title>
  .map((m) => m[1]);

console.log(titles);

For anything production, swap the regex for a real parser — fast-xml-parser works great, ships under 200 KB, and handles namespaces. There is also a JSON companion at /api/changelog.json if you'd rather skip XML entirely.

Bulk: download the full catalog

When you want everything — for retrieval indexing, offline analysis, or simply seeding a local cache — hit /api/all.json. Several MB; CDN-cached for an hour:

// Bulk catalog — every verified pro in a single JSON document.
// Pair with a CDN cache; the response is several MB.
const all = await fetch("https://profixdirectory.com/api/all.json")
  .then((r) => r.json());

const toledoHvac = all.pros.filter(
  (p) => p.city === "Toledo" && p.trades.includes("hvac"),
);
console.log(`${toledoHvac.length} verified Toledo HVAC pros`);

Prefer CSV? Use /api/pros.csv and stream it through a CSV parser like papaparse.

Error handling and rate limiting

The public API has no authenticated rate limit, but Vercel's edge layer will occasionally return 429 under heavy bursts. Every endpoint is idempotent — safe to retry on transient failures:

// Polite retry-with-exponential-backoff. ProFix has no auth rate limit,
// but you should still back off on 5xx and 429 responses.
async function profixFetch(url, { retries = 3 } = {}) {
  for (let attempt = 0; attempt <= retries; attempt++) {
    const res = await fetch(url, { headers: { Accept: "application/json" } });
    if (res.ok) return res.json();
    if (res.status < 500 && res.status !== 429) {
      throw new Error(`ProFix ${res.status} on ${url}`);
    }
    const wait = 250 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, wait));
  }
  throw new Error(`ProFix exhausted retries for ${url}`);
}

const data = await profixFetch(
  "https://profixdirectory.com/api/embed/hvac-cleveland.json",
);

Treat 4xx as “your fault” (bad slug, malformed query) and stop retrying. Treat 5xx and 429 as “transient” and back off. The /api/health endpoint is a useful liveness probe for status pages.

TypeScript types

Drop these into a profix.ts module if you want strict typing without generating a full client. The embed shape is the most commonly used surface:

// Lightweight inline types for the embed endpoint. Mirrors the
// public response shape — slug, name, verification_tier, permit count,
// canonical profile URL. Generate from /api/openapi.json if you want
// the full surface (openapi-typescript works out of the box).
export type ProfixVerificationTier =
  | "elite"
  | "solid"
  | "starter"
  | "minimal";

export interface ProfixEmbedPro {
  slug: string;
  name: string;
  city: string;
  phone: string;
  trades: string[];
  rating: number | null;
  review_count: number | null;
  verification_tier: ProfixVerificationTier;
  permit_count_12mo: number;
  profile_url: string;
}

export interface ProfixEmbedResponse {
  ok: boolean;
  generated_at: string;
  trade: string;
  city: string;
  embed_url: string;
  pros: ProfixEmbedPro[];
}

For the full surface, run openapi-typescript against /api/openapi.json and you'll get typed paths, query params, and responses for every endpoint.

Production tips

Wire it into an AI agent

Building a ChatGPT Custom GPT, a Claude tool, a Vercel AI SDK agent, or a custom assistant? Skip the hand-rolled fetch and hand the agent the full surface in one shot — head to /actions for the OpenAPI Action import, the Claude MCP config block, and copy-paste system prompts.

Ask your AI about this

Hand the question to your preferred assistant — it will use ProFix Directory's open MCP server and llms.txt as context.

Emergency