Enrich a lead from a domain

Turn a work email or a domain into a company record with headcount, headquarters and two views of its tech stack.

A lead signs up with jane@northwind.example. Three calls fill in the CRM record: who the company is, what it hires for, and what its website runs. A full function that combines them is at the end.

CallWhat it addsCost
GET /v1/companies/{key}Name, domain, logo, headcount, headquarters, description, founding year1 credit
GET /v1/companies/{key}/technologies (beta)Technologies the company's job postings name, graded by evidence1 credit
POST /v1/stack/scanTechnologies detected on the company's live homepage1 credit

The company tech stack endpoint is in beta, enabled per account. Without access it answers 404 with {"error": "company_technographics_not_enabled"} and charges nothing. Request access. The other two calls work on every plan.

1. Look up the company

The key can be a domain, a careers URL, an email address or a company name. An email or URL must be percent-encoded. A domain resolves more often than a name.

curl "https://api.jobspipe.dev/v1/companies/jane%40northwind.example" \
  -H "Authorization: Bearer jp_live_your_key_here"
200 OK
{
  "name": "Northwind Analytics",
  "domain": "northwind.example",
  "url": "https://northwind.example",
  "logo": null,
  "employee_count": 1800,
  "description": "Northwind builds analytics software for retailers.",
  "location": { "street": null, "city": "Berlin", "region": "Berlin", "postal_code": null, "country": "DE" },
  "founded": "2009"
}

Every field except name can be null. A 404 is a normal answer: no confident record matched, so treat it as "unknown" and move on. It still costs 1 credit.

2. What the company hires for

Job postings show the tools a company's teams use day to day: data warehouse, languages, CRM, ERP.

curl "https://api.jobspipe.dev/v1/companies/northwind.example/technologies?tier_min=likely&kind=product" \
  -H "Authorization: Bearer jp_live_your_key_here"
200 OK
{
  "as_of": "2026-09-25 06:00:00",
  "company": { "id": "2512632719191904832", "name": "Northwind Analytics", "domain": "northwind.example" },
  "num_jobs": 214,
  "num_technologies": 2,
  "data": [
    {
      "technology": { "slug": "snowflake", "name": "Snowflake", "category": "Data Warehouse", "kind": "product" },
      "tier": "likely",
      "confidence": "medium",
      "jobs": 27,
      "required_jobs": 19,
      "last_date_found": "2026-09-22",
      "recency": "active",
      "share": 0.1262
    },
    {
      "technology": { "slug": "salesforce", "name": "Salesforce", "category": "CRM", "kind": "product" },
      "tier": "likely",
      "confidence": "medium",
      "jobs": 6,
      "required_jobs": 4,
      "last_date_found": "2026-09-10",
      "recency": "active",
      "share": 0.028
    }
  ]
}

share is the fraction of the company's postings that name the technology. A 404 here means either no technologies were found for the company or the beta is not enabled; the error body tells you which.

3. What the website runs

The stack scan detects what the company's homepage serves: frameworks, analytics, CDN, payment and chat widgets.

curl https://api.jobspipe.dev/v1/stack/scan \
  -H "Authorization: Bearer jp_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "northwind.example" }'
200 OK
{
  "domain": "northwind.example",
  "scanned_at": "2026-09-20T08:14:00Z",
  "http_status": 200,
  "detected": [
    { "slug": "nextjs", "name": "Next.js", "categories": ["Web frameworks"], "confidence": 100, "version": null, "saas": false, "oss": true },
    { "slug": "hubspot", "name": "HubSpot", "categories": ["Marketing automation"], "confidence": 100, "version": null, "saas": true, "oss": false }
  ]
}

http_status: 0 means the scanner could not reach the domain. scanned_at is when this stack was first seen unchanged, so read it as "the site has looked like this since".

Why use both stack sources

The two sources see different parts of a company:

Company tech stack (job postings)Stack scan (website)
SeesInternal tools: data platforms, languages, cloud, ERP, CRM, certificationsPublic-facing tools: frameworks, analytics, CDN, marketing and payment widgets
EvidenceTier, posting counts, required or mentioned, first and last dateFingerprint matches with a 0-100 confidence
Strong for"Do they run Snowflake?" "Are they hiring Kubernetes engineers?""Do they use HubSpot on their site?" "Which analytics do they load?"
MissesTools nobody hires for by nameEverything behind the login or inside the company

Put it together

One CRM record from a work email. A missing piece becomes an empty value, so one unknown company does not stop a batch.

from urllib.parse import quote
import requests

API = "https://api.jobspipe.dev"
HEADERS = {"Authorization": "Bearer jp_live_your_key_here"}

def enrich(email: str) -> dict:
    record = {"email": email}

    r = requests.get(f"{API}/v1/companies/{quote(email, safe='')}", headers=HEADERS, timeout=30)
    if r.status_code != 200:
        return record
    company = r.json()
    record.update(
        company=company["name"],
        domain=company["domain"],
        employees=company["employee_count"],
        country=(company["location"] or {}).get("country"),
    )
    domain = company["domain"] or email.split("@", 1)[1]

    r = requests.get(
        f"{API}/v1/companies/{domain}/technologies",
        headers=HEADERS,
        params={"tier_min": "likely"},
        timeout=30,
    )
    record["hires_for"] = [t["technology"]["slug"] for t in r.json()["data"]] if r.status_code == 200 else []

    r = requests.post(f"{API}/v1/stack/scan", headers=HEADERS, json={"domain": domain}, timeout=60)
    record["website_stack"] = [t["slug"] for t in r.json()["detected"]] if r.status_code == 200 else []

    return record

print(enrich("jane@northwind.example"))
const API = "https://api.jobspipe.dev";
const headers = { Authorization: "Bearer jp_live_your_key_here" };

async function enrich(email) {
  const record = { email };

  let r = await fetch(`${API}/v1/companies/${encodeURIComponent(email)}`, { headers });
  if (r.status !== 200) return record;
  const company = await r.json();
  Object.assign(record, {
    company: company.name,
    domain: company.domain,
    employees: company.employee_count,
    country: company.location?.country ?? null,
  });
  const domain = company.domain ?? email.split("@")[1];

  r = await fetch(`${API}/v1/companies/${domain}/technologies?tier_min=likely`, { headers });
  record.hires_for = r.status === 200 ? (await r.json()).data.map((t) => t.technology.slug) : [];

  r = await fetch(`${API}/v1/stack/scan`, {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({ domain }),
  });
  record.website_stack = r.status === 200 ? (await r.json()).detected.map((t) => t.slug) : [];

  return record;
}

console.log(await enrich("jane@northwind.example"));

Notes

  • A company appears only once it has posted a job we collected; one that never has is absent, not empty.
  • A domain is returned only when two independent sources agree on it, so you never get another employer's details attached to your lead.
  • To find new leads by technology instead, see Find companies that use a technology.

On this page