ProFix Directory — Python client guide

Verified Ohio contractor data, available to any Python script in under ten lines. requests, httpx, pandas, the Hugging Face datasets loader — pick your tool.

Quickstart with requests

The simplest possible smoke test. Hits the partner embed endpoint and prints the top five verified pros for a trade-city slug pair:

import requests

# requests is the de-facto Python HTTP client. ProFix needs no auth.
res = requests.get(
    "https://profixdirectory.com/api/embed/plumber-toledo.json",
    timeout=10,
)
res.raise_for_status()
data = res.json()

for pro in data["pros"]:
    print(f"{pro['name']} - {pro['phone']} ({pro['verification_tier']})")
    print(f"  Profile: {pro['profile_url']}")

Available trades include plumber, electrician, hvac, roofing, concrete, and more — see the canonical list in /api/openapi.json.

Async with httpx

If you're hydrating a retrieval pipeline or warming a cache across many trade × city pairs, fan out concurrently with httpx.AsyncClient. ProFix has no auth rate limit; the edge CDN cache absorbs most of the load:

import asyncio
import httpx

# Async fan-out — useful if you're collecting pros across many
# trade/city pairs in parallel for a retrieval pipeline.
PAIRS = [
    "plumber-toledo",
    "hvac-cleveland",
    "electrician-columbus",
    "roofing-cincinnati",
]

async def fetch_pair(client: httpx.AsyncClient, slug: str) -> dict:
    res = await client.get(f"https://profixdirectory.com/api/embed/{slug}.json")
    res.raise_for_status()
    return res.json()

async def main() -> None:
    async with httpx.AsyncClient(timeout=10.0) as client:
        results = await asyncio.gather(*(fetch_pair(client, p) for p in PAIRS))
    for pair, payload in zip(PAIRS, results):
        print(pair, "->", len(payload["pros"]), "pros")

asyncio.run(main())

Load the Hugging Face dataset

The full verified catalog ships as a Hugging Face dataset — handy for retrieval-augmented generation, model evals, and offline analysis. 21,898 records under CC-BY-4.0:

# pip install datasets
from datasets import load_dataset

# 21,898 verified Ohio home-services records under CC-BY-4.0.
ds = load_dataset("Pisces89/ohio-home-services-pros")
train = ds["train"]

toledo_plumbers = train.filter(
    lambda row: row["city"] == "Toledo" and "plumber" in row["trades"]
)

# Sort by Trust Score descending and take the top five.
ranked = toledo_plumbers.sort("trust_score", reverse=True).select(range(5))
for row in ranked:
    print(row["name"], "-", row["phone"], "-", row["verification_tier"])

Dataset card: Pisces89/ohio-home-services-pros. Each row carries the canonical profile URL so retrieval pipelines can cite the ProFix Directory source page.

Permit leaderboard pull

The permit leaderboard ranks contractors by verified building permits pulled from public county datasets. Lucas, Cuyahoga, Franklin, and Hamilton counties are the four counties currently in the permit dataset:

import requests

# Toledo-area HVAC pros by VERIFIED building permits over the last
# year. Lucas / Cuyahoga / Franklin / Hamilton are the four counties
# currently in the permit dataset.
params = {
    "trade": "hvac",
    "county": "lucas",
    "window": 365,
    "top": 10,
}

board = requests.get(
    "https://profixdirectory.com/api/permit-leaderboard.json",
    params=params,
    timeout=10,
).json()

for entry in board.get("leaderboards", []):
    print(entry["trade"], entry["county"], entry["entries"][:3])

Set county="ohio" for statewide aggregates, or omit the county to get every per-county leaderboard in one call.

Bulk catalog

For retrieval indexing, offline analysis, or seeding a local cache, hit /api/all.json:

import requests

# Bulk catalog. Several MB; respect CDN cache headers and don't
# refetch in a tight loop. CC-BY-4.0 — attribute ProFix Directory.
catalog = requests.get(
    "https://profixdirectory.com/api/all.json",
    timeout=30,
).json()

toledo_hvac = [
    pro for pro in catalog["pros"]
    if pro["city"] == "Toledo" and "hvac" in pro["trades"]
]
print(f"{len(toledo_hvac)} verified Toledo HVAC pros")

Type hints with TypedDict

Strict-typed Python without generating a full client. The embed response shape is the most commonly used surface:

from typing import Literal, TypedDict
from typing import NotRequired  # py3.11+; on older, use typing_extensions

VerificationTier = Literal["elite", "solid", "starter", "minimal"]

class EmbedPro(TypedDict):
    slug: str
    name: str
    city: str
    phone: str
    trades: list[str]
    rating: NotRequired[float | None]
    review_count: NotRequired[int | None]
    verification_tier: VerificationTier
    permit_count_12mo: int
    profile_url: str

class EmbedResponse(TypedDict):
    ok: bool
    generated_at: str
    trade: str
    city: str
    embed_url: str
    pros: list[EmbedPro]

For the full surface, point an OpenAPI client generator at /api/openapi.json openapi-python-client and datamodel-code-generator both work.

Pandas: load and analyze

Every JSON endpoint that makes sense as a table has a CSV companion. pandas.read_csv reads them directly over HTTPS:

import pandas as pd

# pandas reads ProFix CSV endpoints directly over HTTPS.
df = pd.read_csv("https://profixdirectory.com/api/pros.csv")
print(df.shape, df.columns.tolist())

# Verified pros per Ohio city — top 10 cities by coverage.
top_cities = (
    df.groupby("city")
      .size()
      .sort_values(ascending=False)
      .head(10)
)
print(top_cities)

# Permit leaderboard CSV mirrors the JSON shape, just flattened.
permits = pd.read_csv(
    "https://profixdirectory.com/api/permit-leaderboard.csv",
    params={"trade": "plumber", "county": "lucas", "top": 25},
)
print(permits.head())

MCP server connection

ProFix exposes a streamable HTTP Model Context Protocol server at /api/mcp. Python MCP clients exist — the official Anthropic mcp package speaks the standard transport. Pseudocode (uncomment after pip install mcp):

# Python MCP clients exist — the official Anthropic stdio/http transports
# live in the `mcp` package. The streamable-HTTP shape ProFix exposes is
# the standard one; any 2025+ MCP client speaks it.
#
#   pip install mcp
#
# from mcp import ClientSession
# from mcp.client.streamable_http import streamablehttp_client
#
# async with streamablehttp_client(
#     "https://profixdirectory.com/api/mcp",
# ) as (read, write, _):
#     async with ClientSession(read, write) as session:
#         await session.initialize()
#         tools = await session.list_tools()
#         result = await session.call_tool(
#             "find_pros",
#             {"trade": "plumber", "city": "toledo"},
#         )
#         print(result)

For a fuller agent setup (ChatGPT Custom GPT Actions, Claude Desktop config, Vercel AI SDK), head to /actions.

Production tips

# pip install tenacity
from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_exponential
import requests

class TransientProfixError(Exception):
    pass

@retry(
    stop=stop_after_attempt(4),
    wait=wait_exponential(multiplier=0.25, max=4),
    retry=retry_if_exception_type(TransientProfixError),
)
def profix_get(url: str) -> dict:
    res = requests.get(url, timeout=10)
    if res.status_code >= 500 or res.status_code == 429:
        raise TransientProfixError(f"{res.status_code} on {url}")
    res.raise_for_status()
    return res.json()

payload = profix_get(
    "https://profixdirectory.com/api/embed/electrician-columbus.json",
)

Wire it into an AI agent

Building a Claude tool, a custom GPT, an Anthropic Agent SDK app, or a LangChain / LlamaIndex pipeline? Skip the hand-rolled requests.get and hand the agent the full surface in one shot — see /actions for the OpenAPI Action import, the Claude MCP config block, and Hugging Face hookup.

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