> ## Documentation Index
> Fetch the complete documentation index at: https://docs.context.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Enrich companies and contacts

> Fill missing company fields, add person context and sourced account research, and preserve your team's CRM edits.

export const AgentSetupPrompt = ({children, variant = "setup"}) => {
  const isRecipe = variant === "recipe";
  const promptLabel = isRecipe ? "recipe prompt" : "setup prompt";
  const [copyState, setCopyState] = useState("idle");
  const contentRef = useRef(null);
  const detailsRef = useRef(null);
  const timerRef = useRef(null);
  const mountedRef = useRef(true);
  const pendingRef = useRef(false);
  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
      if (timerRef.current !== null) clearTimeout(timerRef.current);
    };
  }, []);
  const copyPrompt = useCallback(async () => {
    if (pendingRef.current) return;
    pendingRef.current = true;
    if (timerRef.current !== null) {
      clearTimeout(timerRef.current);
      timerRef.current = null;
    }
    setCopyState("copying");
    try {
      const code = contentRef.current?.querySelector("pre code") ?? contentRef.current?.querySelector("pre");
      const prompt = code?.textContent;
      if (typeof prompt !== "string" || !prompt.trim()) {
        throw new Error("Prompt is not available.");
      }
      if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
        throw new Error("Clipboard access is not available.");
      }
      await navigator.clipboard.writeText(prompt);
      if (!mountedRef.current) return;
      setCopyState("copied");
      timerRef.current = setTimeout(() => {
        if (mountedRef.current) setCopyState("idle");
        timerRef.current = null;
      }, 4000);
    } catch {
      if (!mountedRef.current) return;
      if (detailsRef.current) detailsRef.current.open = true;
      setCopyState("error");
    } finally {
      pendingRef.current = false;
    }
  }, []);
  const feedback = copyState === "copied" ? `${isRecipe ? "Recipe" : "Setup"} prompt copied. Paste it into your coding agent.` : copyState === "error" ? "Couldn't copy automatically. The prompt is open below. Select and copy the text manually." : "";
  return <section className="agent-setup-prompt not-prose" aria-label={`Agent ${promptLabel}`}>
      <style>{`
        .agent-setup-prompt {
          --asp-surface: oklch(0.985 0.005 255);
          --asp-border: oklch(0.9 0.012 255);
          --asp-title: oklch(0.25 0.025 255);
          --asp-text: oklch(0.45 0.021 255);
          --asp-accent: oklch(0.49 0.2 264);
          --asp-accent-hover: oklch(0.44 0.19 264);
          --asp-on-accent: oklch(0.985 0.005 255);
          --asp-focus: oklch(0.6 0.19 255);
          margin: 1.5rem 0;
          border: 1px solid var(--asp-border);
          border-radius: 0.875rem;
          background: var(--asp-surface);
          color: var(--asp-title);
          font-family: inherit;
          min-width: 0;
        }
        .dark .agent-setup-prompt {
          --asp-surface: oklch(0.2 0.014 255);
          --asp-border: oklch(0.33 0.016 255);
          --asp-title: oklch(0.96 0.006 255);
          --asp-text: oklch(0.78 0.013 255);
          --asp-accent: oklch(0.73 0.15 255);
          --asp-accent-hover: oklch(0.8 0.11 255);
          --asp-on-accent: oklch(0.19 0.025 255);
          --asp-focus: oklch(0.78 0.14 255);
        }
        .agent-setup-prompt__header { padding: 1.125rem 1.25rem; }
        .agent-setup-prompt__row {
          display: flex;
          align-items: flex-start;
          justify-content: space-between;
          gap: 1.25rem;
        }
        .agent-setup-prompt__intro { min-width: 0; }
        .agent-setup-prompt__title {
          margin: 0;
          font-size: 1.125rem;
          font-weight: 650;
          line-height: 1.4;
          color: var(--asp-title);
        }
        .agent-setup-prompt__subtitle {
          max-width: 65ch;
          margin: 0.375rem 0 0;
          font-size: 0.875rem;
          line-height: 1.6;
          color: var(--asp-text);
        }
        .agent-setup-prompt__copy {
          display: inline-flex;
          flex-shrink: 0;
          align-items: center;
          justify-content: center;
          gap: 0.5rem;
          min-height: 2.75rem;
          padding: 0.625rem 0.875rem;
          border: 1px solid transparent;
          border-radius: 0.5rem;
          background: var(--asp-accent);
          color: var(--asp-on-accent);
          font: inherit;
          font-size: 0.8125rem;
          font-weight: 600;
          line-height: 1.4;
          cursor: pointer;
        }
        .agent-setup-prompt__copy:hover:not(:disabled) {
          background: var(--asp-accent-hover);
        }
        .agent-setup-prompt__copy:disabled { cursor: wait; }
        .agent-setup-prompt__copy:focus-visible,
        .agent-setup-prompt__summary:focus-visible {
          outline: 2px solid var(--asp-focus);
          outline-offset: 3px;
        }
        .agent-setup-prompt__feedback {
          margin: 0.75rem 0 0;
          font-size: 0.8125rem;
          line-height: 1.5;
          color: var(--asp-text);
        }
        .agent-setup-prompt__feedback:empty { margin: 0; }
        .agent-setup-prompt__details { border-top: 1px solid var(--asp-border); }
        .agent-setup-prompt__summary {
          display: flex;
          align-items: center;
          gap: 0.5rem;
          padding: 0.75rem 1.25rem;
          border-radius: 0 0 0.875rem 0.875rem;
          font-size: 0.8125rem;
          font-weight: 500;
          line-height: 1.5;
          color: var(--asp-text);
          list-style: none;
          cursor: pointer;
        }
        .agent-setup-prompt__summary::-webkit-details-marker { display: none; }
        .agent-setup-prompt__summary:hover { color: var(--asp-title); }
        .agent-setup-prompt__details[open] .agent-setup-prompt__chevron {
          transform: rotate(90deg);
        }
        .agent-setup-prompt__content { min-width: 0; padding: 0 1.25rem 1rem; }
        .agent-setup-prompt__content > :first-child { margin-top: 0; }
        .agent-setup-prompt__content > :last-child { margin-bottom: 0; }
        .agent-setup-prompt__content pre {
          max-height: 32rem;
          overflow: auto;
          white-space: pre-wrap;
          overflow-wrap: anywhere;
        }
        @media (max-width: 640px) {
          .agent-setup-prompt__header { padding: 1rem; }
          .agent-setup-prompt__row { flex-direction: column; gap: 0.875rem; }
          .agent-setup-prompt__copy { width: 100%; }
          .agent-setup-prompt__summary { padding: 0.75rem 1rem; }
          .agent-setup-prompt__content { padding: 0 1rem 1rem; }
        }
      `}</style>

      <div className="agent-setup-prompt__header">
        <div className="agent-setup-prompt__row">
          <div className="agent-setup-prompt__intro">
            <h2 className="agent-setup-prompt__title">
              {isRecipe ? "Have your agent implement this recipe" : "Set up Context.dev"}
            </h2>
            <p className="agent-setup-prompt__subtitle">
              {isRecipe ? "Copy this prompt into your coding agent to build the recipe in your project." : "Paste into your coding agent. It will ask about your goal and project before making changes."}
            </p>
          </div>
          <button className="agent-setup-prompt__copy" type="button" onClick={copyPrompt} disabled={copyState === "copying"} aria-busy={copyState === "copying"}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" aria-hidden="true" focusable="false">
              {copyState === "copied" ? <path d="m5 12 4 4L19 6" strokeLinecap="round" strokeLinejoin="round" /> : <>
                  <rect x="8" y="8" width="12" height="12" rx="2" />
                  <path d="M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3" />
                </>}
            </svg>
            {copyState === "copied" ? "Copied" : copyState === "copying" ? "Copying…" : `Copy ${promptLabel}`}
          </button>
        </div>
        <p className="agent-setup-prompt__feedback" role="status" aria-live="polite" aria-atomic="true">
          {feedback}
        </p>
      </div>

      <details className="agent-setup-prompt__details" ref={detailsRef}>
        <summary className="agent-setup-prompt__summary">
          <svg className="agent-setup-prompt__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" aria-hidden="true" focusable="false">
            <path d="m9 5 7 7-7 7" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
          {`View ${promptLabel}`}
        </summary>
        <div className="agent-setup-prompt__content" ref={contentRef}>
          {children}
        </div>
      </details>
    </section>;
};

export const LeadEnrichmentDemo = () => {
  const TRY = ["nick@mintlify.com", "aaron@docsbot.ai", "ceo@vercel.com", "maria@hubspot.com"];
  const DEFAULT_EMAIL = "nick@mintlify.com";
  const BRANDS = {
    "nick@mintlify.com": {
      domain: "mintlify.com",
      title: "Mintlify",
      description: "Mintlify is an intelligent documentation platform that empowers millions of developers by providing AI-native, visually appealing documentation. Their platform is used by over 10,000 companies and reaches more than 100 million developers annually, making it a leading tool in the industry.",
      colors: [{
        hex: "#1be39b"
      }, {
        hex: "#0c8c5c"
      }, {
        hex: "#040404"
      }],
      logos: [{
        url: "https://media.brand.dev/c2ac8bb2-e752-4dff-ae87-539ff23415e2.svg",
        mode: "light",
        type: "logo"
      }],
      address: {
        city: "San Francisco",
        country: "United States",
        state_province: "California"
      },
      socials: [{
        type: "x",
        url: "https://x.com/mintlify"
      }, {
        type: "linkedin",
        url: "https://linkedin.com/company/mintlify"
      }, {
        type: "github",
        url: "https://github.com/mintlify"
      }],
      phone: "(415) 555-0132",
      industries: {
        eic: [{
          industry: "Technology",
          subindustry: "Developer Tools & APIs"
        }]
      }
    },
    "aaron@docsbot.ai": {
      domain: "docsbot.ai",
      title: "DocsBot",
      description: "DocsBot delivers AI‑powered chatbots that are trained on a company’s own content and documentation. Their solution enhances customer support and boosts team productivity by providing instant, accurate answers, reducing support costs, and streamlining internal workflows, all while delivering a personalized, intelligent experience tailored to each business.",
      colors: [{
        hex: "#109fab"
      }, {
        hex: "#176277"
      }, {
        hex: "#121525"
      }],
      logos: [{
        url: "https://media.brand.dev/7f603b53-908e-424b-9f27-edba38a6d20f.jpg",
        mode: "has_opaque_background",
        colors: [{
          hex: "#109fab",
          name: "Turkish Boy"
        }, {
          hex: "#121525",
          name: "Corbeau"
        }],
        resolution: {
          width: 200,
          height: 200,
          aspect_ratio: 1
        },
        type: "icon"
      }, {
        url: "https://media.brand.dev/f52d9218-1c23-4b24-a63a-ad3665079035.svg",
        mode: "light",
        colors: [{
          hex: "#0ca3ac",
          name: "Aare River Brienz"
        }],
        resolution: {
          width: 2000,
          height: 500,
          aspect_ratio: 4
        },
        type: "logo"
      }, {
        url: "https://media.brand.dev/13aca6c6-a321-4c4d-b3fe-4c2931d188ce.png",
        mode: "light",
        colors: [{
          hex: "#0da1ae",
          name: "Aare River Brienz"
        }],
        resolution: {
          width: 180,
          height: 180,
          aspect_ratio: 1
        },
        type: "icon"
      }, {
        url: "https://media.brand.dev/105f27e6-adac-4d24-b569-b4c8cb7f6de2.svg",
        mode: "light",
        colors: [{
          hex: "#040404",
          name: "Armor Wash"
        }],
        resolution: {
          width: 400,
          height: 400,
          aspect_ratio: 1
        },
        type: "icon"
      }],
      industries: {
        eic: [{
          industry: "Technology",
          subindustry: "Software (B2B)"
        }]
      },
      address: {
        city: "Middletown",
        country: "United States",
        country_code: "US",
        state_province: "Delaware",
        state_code: "DE",
        postal_code: "19709"
      },
      socials: [{
        type: "x",
        url: "https://x.com/docsbotai"
      }, {
        type: "linkedin",
        url: "https://linkedin.com/company/docsbot"
      }]
    },
    "ceo@vercel.com": {
      domain: "vercel.com",
      title: "Vercel",
      description: "Vercel is a developer‑focused platform that provides cloud infrastructure and tools to build, deploy, and scale modern web applications. Its core services include instant CI/CD pipelines, a global edge CDN, and serverless “Fluid Compute” that automatically provisions the right resources from a single Git push. Vercel’s AI Cloud adds AI‑specific capabilities such as the AI Gateway for model access, an AI SDK for TypeScript, sandboxed code execution, and Vercel Agent that integrates with any stack. The platform also offers security features like DDoS protection, WAF, and BotID, plus workflow orchestration for long‑running tasks. By unifying development, deployment, and AI services, Vercel lets teams ship faster, deliver personalized experiences, and focus on product value rather than infrastructure.",
      colors: [{
        hex: "#040404"
      }, {
        hex: "#7c7c7c"
      }, {
        hex: "#cdcdcd"
      }],
      logos: [{
        url: "https://media.brand.dev/6ef82987-0f0e-4228-ba55-0d2d83ffe464.png",
        mode: "has_opaque_background",
        colors: [{
          hex: "#7c7c7c",
          name: "Namara Grey"
        }, {
          hex: "#040404",
          name: "Armor Wash"
        }, {
          hex: "#dadada",
          name: "Porpoise"
        }],
        resolution: {
          width: 512,
          height: 512,
          aspect_ratio: 1
        },
        type: "icon"
      }, {
        url: "https://media.brand.dev/88ccb380-0c6f-4bb6-9162-dfbd6792de8f.png",
        mode: "light",
        colors: [{
          hex: "#7c7c7c",
          name: "Namara Grey"
        }, {
          hex: "#050505",
          name: "Black Metal"
        }, {
          hex: "#cdcdcd",
          name: "Compact Disc Grey"
        }],
        resolution: {
          width: 144,
          height: 144,
          aspect_ratio: 1
        },
        type: "icon"
      }, {
        url: "https://media.brand.dev/adec906a-ad71-4409-bc68-edbe7dd3bfac.svg",
        mode: "light",
        colors: [{
          hex: "#040404",
          name: "Armor Wash"
        }],
        resolution: {
          width: 91,
          height: 18,
          aspect_ratio: 5.06
        },
        type: "logo"
      }, {
        url: "https://media.brand.dev/02330a89-e36f-4fe9-ae55-b1db52c8e7b6.svg",
        mode: "light",
        colors: [],
        resolution: {
          width: 230,
          height: 40,
          aspect_ratio: 5.75
        },
        type: "logo"
      }],
      industries: {
        eic: [{
          industry: "Technology",
          subindustry: "Cloud Infrastructure & DevOps"
        }]
      },
      address: {
        street: "Covina Avenue",
        city: "San Francisco",
        country: "United States",
        country_code: "US",
        state_province: "California",
        state_code: "CA",
        postal_code: "94133"
      },
      socials: [{
        type: "x",
        url: "https://x.com/vercel"
      }, {
        type: "linkedin",
        url: "https://linkedin.com/company/vercel"
      }, {
        type: "github",
        url: "https://github.com/vercel"
      }]
    },
    "maria@hubspot.com": {
      domain: "hubspot.com",
      title: "HubSpot",
      description: "HubSpot is a leading CRM platform that provides AI-powered software and support to help businesses grow. Their platform includes marketing, sales, service, and website management products that scale to meet customer needs. With a strong focus on culture and employee growth, HubSpot is a hybrid company with a global presence, headquartered in Cambridge, MA, and offices worldwide.",
      colors: [{
        hex: "#fb5c35"
      }, {
        hex: "#f5ac94"
      }],
      logos: [{
        url: "https://media.brand.dev/2b50f498-0470-4e86-a0e4-eaf4cb61e215.jpg",
        mode: "has_opaque_background",
        colors: [{
          hex: "#fb5c35",
          name: "Portland Orange"
        }, {
          hex: "#f5ac94",
          name: "Pretty Primrose"
        }],
        resolution: {
          width: 320,
          height: 320,
          aspect_ratio: 1
        },
        type: "icon"
      }],
      industries: {
        eic: [{
          industry: "Technology",
          subindustry: "Software (B2B)"
        }]
      },
      address: {
        street: "25 First Street",
        city: "Cambridge",
        country: "United States",
        country_code: "US",
        state_province: "Massachusetts",
        state_code: "MA",
        postal_code: "02141"
      },
      socials: [{
        type: "x",
        url: "https://x.com/hubspot"
      }, {
        type: "linkedin",
        url: "https://linkedin.com/company/hubspot"
      }]
    }
  };
  const DEFAULT_BRAND = BRANDS[DEFAULT_EMAIL];
  const [email, setEmail] = useState(DEFAULT_EMAIL);
  const [brand, setBrand] = useState(DEFAULT_BRAND);
  const [loading, setLoading] = useState(false);
  const isDefault = brand === DEFAULT_BRAND && email === DEFAULT_EMAIL;
  function pickLogo(logos) {
    if (!logos || !logos.length) return null;
    return logos.find(l => l.type === "logo" && l.mode === "light") || logos.find(l => l.mode === "light") || logos.find(l => l.type === "logo") || logos[0];
  }
  async function selectEmail(d) {
    if (d === email || loading) return;
    setEmail(d);
    setLoading(true);
    await new Promise(r => setTimeout(r, 700));
    setBrand(BRANDS[d]);
    setLoading(false);
  }
  function reset() {
    setEmail(DEFAULT_EMAIL);
    setBrand(DEFAULT_BRAND);
  }
  const logo = brand ? pickLogo(brand.logos) : null;
  const initial = brand && brand.title && brand.title[0].toUpperCase() || "?";
  const industry = brand && brand.industries && brand.industries.eic && brand.industries.eic[0];
  const industryText = industry ? [industry.industry, industry.subindustry].filter(Boolean).join(" · ") : null;
  const linkedin = brand && brand.socials && brand.socials.find(s => s.type === "linkedin");
  const xUrl = brand && brand.socials && brand.socials.find(s => s.type === "x");
  const address = brand && brand.address;
  return <div className="le-demo not-prose">
      <style>{`
        .le-demo {
          --le-blue: #1373e8;
          --le-blue-press: #0e5cc0;
          --le-pill-bd: #cfd9e8;
          --le-line: #ececec;
          --le-line-soft: #f4f4f5;
          --le-ink: #0a0a0a;
          --le-ink-2: #3f3f46;
          --le-ink-3: #919191;
          --le-bg: #ffffff;
          --le-bg-soft: #fbfcfe;
          --le-sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
          --le-mono: "Geist Mono", "IBM Plex Mono", ui-monospace, Menlo, monospace;
          position: relative;
          margin: 28px 0; padding: 22px 22px 20px;
          background: var(--le-bg);
          border: 1px solid var(--le-line);
          border-radius: 6px;
          font-family: var(--le-sans);
          color: var(--le-ink);
          overflow: hidden;
        }
        .le-demo::before {
          content: ""; position: absolute; inset: 0; pointer-events: none;
          background: radial-gradient(800px 280px at 50% 100%, rgba(96, 165, 250, 0.10), transparent 70%);
        }
        .le-demo > * { position: relative; }
        .le-demo *, .le-demo *::before, .le-demo *::after { box-sizing: border-box; }

        .dark .le-demo {
          --le-blue: #3b82f6;
          --le-blue-press: #2563eb;
          --le-pill-bd: #2f3a4d;
          --le-line: #26262d;
          --le-line-soft: #1a1a1f;
          --le-ink: #f5f5f7;
          --le-ink-2: #c2c2cb;
          --le-ink-3: #8b8b93;
          --le-bg: #0a0a0c;
          --le-bg-soft: #141418;
        }
        .dark .le-pill { background: rgba(96, 165, 250, 0.12); }
        .dark .le-card-logo { background: #1a1a1f; }
        .dark .le-overlay { background: rgba(10, 10, 12, 0.85); }

        .le-chrome {
          display: flex; align-items: center; justify-content: space-between;
          margin-bottom: 18px; min-height: 24px;
        }
        .le-kicker {
          display: inline-flex; align-items: center; gap: 9px;
          font-family: var(--le-mono);
          font-size: 11px; font-weight: 500;
          letter-spacing: 0.04em; text-transform: uppercase;
          color: var(--le-ink-3);
        }
        .le-kicker::before { content: ""; width: 5px; height: 5px; background: var(--le-blue); border-radius: 1px; }
        .le-logo-light, .le-logo-dark { height: 22px; display: block; }
        .le-logo-dark { display: none; }
        .dark .le-logo-light { display: none; }
        .dark .le-logo-dark { display: block; }
        .le-reset {
          font-family: var(--le-mono);
          font-size: 10.5px; letter-spacing: 0.10em;
          text-transform: uppercase; color: var(--le-ink-2);
          background: var(--le-bg);
          border: 1px solid var(--le-line);
          border-radius: 0;
          padding: 5px 10px;
          cursor: pointer;
        }
        .le-reset:hover { color: var(--le-blue); border-color: var(--le-blue); }

        .le-form { display: flex; flex-direction: column; gap: 14px; margin-bottom: 14px; }
        .le-field { display: flex; flex-direction: column; }
        .le-label {
          font-family: var(--le-mono);
          font-size: 10.5px; letter-spacing: 0.10em;
          text-transform: uppercase; color: var(--le-ink-3);
          margin-bottom: 8px;
        }
        .le-row { display: flex; gap: 8px; }
        .le-input {
          flex: 1;
          padding: 11px 13px;
          background: var(--le-bg);
          border: 1px solid var(--le-line);
          border-radius: 0;
          font-family: var(--le-mono);
          font-size: 14px; color: var(--le-ink);
          outline: none; min-width: 0;
        }
        .le-input:focus { border-color: var(--le-blue); }
        .le-btn {
          padding: 11px 18px;
          background: var(--le-blue);
          color: #fff;
          border: 1px solid var(--le-blue);
          border-radius: 0;
          font-family: var(--le-mono);
          font-size: 13px; font-weight: 500;
          cursor: pointer; white-space: nowrap;
        }
        .le-btn:hover { background: var(--le-blue-press); }
        .le-btn:disabled { opacity: 0.55; cursor: progress; }
        .le-trys {
          display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap;
          font-family: var(--le-mono);
          font-size: 11px; color: var(--le-ink-3);
        }
        .le-try {
          font-family: var(--le-mono);
          font-size: 11px;
          padding: 2px 7px;
          background: var(--le-bg);
          border: 1px solid var(--le-line);
          color: var(--le-ink-2);
          cursor: pointer;
        }
        .le-try:hover { color: var(--le-blue); border-color: var(--le-blue); }
        .le-divider { height: 1px; margin: 22px 0; background: var(--le-line); border: 0; }
        .le-card {
          position: relative;
          background: var(--le-bg-soft);
          border: 1px solid var(--le-line);
          padding: 22px 22px 18px;
        }
        .le-card-head {
          display: flex; align-items: center; gap: 14px;
          padding-bottom: 16px;
          border-bottom: 1px solid var(--le-line);
          margin-bottom: 16px;
        }
        .le-card-logo {
          width: 56px; height: 56px;
          background: var(--le-bg-soft);
          border: 1px solid var(--le-line);
          overflow: hidden; flex: none;
          display: flex; align-items: center; justify-content: center;
          color: var(--le-ink-3);
          font-family: var(--le-sans);
          font-weight: 600; font-size: 22px;
        }
        .le-card-logo img { width: 100%; height: 100%; object-fit: contain; }
        .le-head-meta { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }
        .le-name {
          font-family: var(--le-sans);
          font-size: 18px; font-weight: 600;
          color: var(--le-ink); letter-spacing: -0.01em;
        }
        .le-domain {
          font-family: var(--le-mono);
          font-size: 11.5px; color: var(--le-ink-3);
        }
        .le-domain a { color: inherit; text-decoration: none; }
        .le-pill {
          display: inline-flex; align-items: center; gap: 6px;
          background: rgba(19, 115, 232, 0.08);
          border: 1px dashed var(--le-pill-bd);
          padding: 3px 9px;
          font-family: var(--le-mono);
          font-size: 11px; color: var(--le-blue);
          letter-spacing: 0.02em; text-transform: uppercase;
        }
        .le-pill::before { content: ""; width: 5px; height: 5px; background: var(--le-blue); }
        .le-rows { display: flex; flex-direction: column; gap: 12px; }
        .le-prow {
          display: grid;
          grid-template-columns: 96px 1fr;
          gap: 14px; align-items: start;
        }
        .le-prow-k {
          font-family: var(--le-mono);
          font-size: 10px; letter-spacing: 0.14em;
          text-transform: uppercase; color: var(--le-ink-3);
          padding-top: 2px;
        }
        .le-prow-v {
          font-family: var(--le-sans);
          font-size: 13.5px; color: var(--le-ink);
          line-height: 1.5;
        }
        .le-prow-v.mono { font-family: var(--le-mono); font-size: 12.5px; }
        .le-socials { display: flex; gap: 6px; flex-wrap: wrap; }
        .le-social {
          display: inline-flex; align-items: center; gap: 6px;
          padding: 5px 10px 5px 8px;
          background: var(--le-bg);
          border: 1px solid var(--le-line);
          font-family: var(--le-mono);
          font-size: 11px; color: var(--le-ink-2);
          text-decoration: none;
        }
        .le-social:hover { border-color: var(--le-blue); color: var(--le-blue); }
        .le-social svg { width: 12px; height: 12px; }
        .le-actions {
          margin-top: 18px; padding-top: 16px;
          border-top: 1px solid var(--le-line);
          display: flex; gap: 8px; justify-content: flex-end;
        }
        .le-action {
          padding: 7px 14px;
          background: var(--le-ink); color: var(--le-bg);
          border: 1px solid var(--le-ink);
          font-family: var(--le-mono);
          font-size: 12px; font-weight: 500;
          cursor: pointer;
        }
        .le-overlay {
          position: absolute; inset: 0;
          background: rgba(255, 255, 255, 0.85);
          backdrop-filter: blur(2px);
          display: flex; flex-direction: column;
          align-items: center; justify-content: center;
          gap: 12px; z-index: 5;
        }
        .le-spin {
          width: 22px; height: 22px; border-radius: 50%;
          border: 1.5px solid var(--le-line);
          border-top-color: var(--le-blue);
          animation: le-spin 0.7s linear infinite;
        }
        @keyframes le-spin { to { transform: rotate(360deg); } }
        .le-line {
          font-family: var(--le-mono);
          font-size: 10.5px; letter-spacing: 0.14em;
          text-transform: uppercase; color: var(--le-ink-3);
        }
      `}</style>

      <div className="le-chrome">
        <span className="le-kicker">Live demo</span>
        {isDefault ? <span className="le-logo">
            <img noZoom className="le-logo-light" src="/logo/light.svg" alt="context.dev" />
            <img noZoom className="le-logo-dark" src="/logo/dark.svg" alt="context.dev" />
          </span> : <button className="le-reset" type="button" onClick={reset}>
            Reset
          </button>}
      </div>

      <form className="le-form" onSubmit={e => e.preventDefault()}>
        <div className="le-field">
          <label className="le-label" htmlFor="le-email-input">
            New lead email
          </label>
          <div className="le-row">
            <input id="le-email-input" className="le-input" type="email" value={email} readOnly placeholder="contact@company.com" spellCheck={false} required />
          </div>
          <div className="le-trys" style={{
    marginTop: 10
  }}>
            <span>Change demo</span>
            {TRY.map(d => <button key={d} type="button" className="le-try" onClick={() => selectEmail(d)}>
                {d}
              </button>)}
          </div>
        </div>
      </form>

      <div className="le-divider" />

      <article className="le-card">
        <header className="le-card-head">
          <div className="le-card-logo">
            {logo && logo.url ? <img noZoom src={logo.url} alt="" /> : <span>{initial}</span>}
          </div>
          <div className="le-head-meta">
            <span className="le-name">{brand.title || "—"}</span>
            <span className="le-domain">
              {brand.domain ? <a href={`https://${brand.domain}`} target="_blank" rel="noreferrer">
                  {brand.domain} ↗
                </a> : "—"}
            </span>
          </div>
          {industryText && <span className="le-pill">{industry.industry}</span>}
        </header>

        <div className="le-rows">
          {brand.description && <div className="le-prow">
              <div className="le-prow-k">About</div>
              <div className="le-prow-v">
                {brand.description.length > 220 ? brand.description.slice(0, 220) + "…" : brand.description}
              </div>
            </div>}
          {industryText && <div className="le-prow">
              <div className="le-prow-k">Industry</div>
              <div className="le-prow-v mono">{industryText}</div>
            </div>}
          {address && (address.street || address.city) && <div className="le-prow">
              <div className="le-prow-k">HQ</div>
              <div className="le-prow-v">
                {[address.street, address.city, address.state_province || address.state_code, address.country].filter(Boolean).join(", ")}
              </div>
            </div>}
          {brand.phone && <div className="le-prow">
              <div className="le-prow-k">Phone</div>
              <div className="le-prow-v mono">{brand.phone}</div>
            </div>}
          {(linkedin || xUrl) && <div className="le-prow">
              <div className="le-prow-k">Socials</div>
              <div className="le-prow-v">
                <div className="le-socials">
                  {linkedin && <a className="le-social" href={linkedin.url} target="_blank" rel="noreferrer">
                      <svg viewBox="0 0 24 24" fill="currentColor">
                        <path d="M20.45 20.45h-3.55v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.14 1.45-2.14 2.94v5.67H9.36V9h3.41v1.56h.05c.47-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.45zM5.34 7.43a2.06 2.06 0 1 1 0-4.13 2.06 2.06 0 0 1 0 4.13zM7.12 20.45H3.56V9h3.56zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.55C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.72V1.72C24 .77 23.21 0 22.22 0z" />
                      </svg>
                      LinkedIn
                    </a>}
                  {xUrl && <a className="le-social" href={xUrl.url} target="_blank" rel="noreferrer">
                      <svg viewBox="0 0 24 24" fill="currentColor">
                        <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
                      </svg>
                      X
                    </a>}
                </div>
              </div>
            </div>}
        </div>

        <div className="le-actions">
          <button className="le-action" type="button" onClick={() => alert("Lead saved.")}>
            Save to CRM →
          </button>
        </div>

        {loading && <div className="le-overlay">
            <div className="le-spin" />
            <div className="le-line">Resolving company</div>
          </div>}
      </article>
    </div>;
};

<AgentSetupPrompt variant="recipe">
  ```text Recipe prompt theme={null}
  Implement this recipe in my project:
  https://docs.context.dev/use-cases/lead-enrichment

  Read the recipe and linked API guides, inspect this project's stack, and build the complete flow using its existing conventions.

  Connect the CRM lead event to a durable enrichment worker. Use Brand by email for company fields, People enrichment when person data is needed, and sourced web research for account context. Fill missing fields, preserve CRM edits, deduplicate repeated events, and make failed or partial enrichment retryable without losing good data.

  Reuse existing Context.dev configuration and keep secret API keys on the server. Add focused tests, run the relevant checks, and document setup and how to try the result.
  ```
</AgentSetupPrompt>

When a CRM creates a lead, use the work email to retrieve the associated Brand profile and fill missing company fields before the first sales touch.

<LeadEnrichmentDemo />

You'll need a server-side API key from the [Quickstart](/quickstart) and a CRM webhook or background job. Each successful Brand lookup costs 10 credits.

<Note>
  This workflow enriches the company associated with an email. It does not verify the person's identity, role, seniority, or employment. Use the [People API](/api-reference/people/enrich) when you need person-level data.
</Note>

## How it works

```mermaid theme={null}
flowchart LR
  A[CRM lead-created webhook] --> B[Enrichment worker]
  B --> C{Already enriched?}
  C -->|yes| D[Stop]
  C -->|no| E[Brand lookup by email]
  E --> F[Validate and map fields]
  F --> G[Patch blank CRM fields]
  G --> H[Record outcome]
```

Run enrichment in a queue or background worker. CRM webhooks are commonly retried, and a synchronous handler can time out while the Brand lookup is still running.

## Choose CRM fields

| CRM field        | Brand source                 | Important caveat                                           |
| ---------------- | ---------------------------- | ---------------------------------------------------------- |
| Company name     | `brand.title`                | Editable, not a legal-entity name.                         |
| Website          | `brand.domain`               | Useful as your company-level deduplication key.            |
| Description      | `brand.description`          | Marketing description, not due-diligence evidence.         |
| Logo             | `brand.logos[]`              | Choose by `type`, `mode`, and resolution; keep a fallback. |
| Industry         | `brand.industries.eic[]`     | Optional Context.dev classification.                       |
| Social URLs      | `brand.socials[]`            | Map by each item's `type`.                                 |
| Contact details  | `brand.email`, `brand.phone` | Company-level contact data when discovered.                |
| Physical address | `brand.address`              | Not guaranteed to be a legal or headquarters address.      |

Do not create required CRM fields from optional API fields. Decide which data is informational and which data your team must confirm.

## Look up the company

Choose your client for the API request. The PHP tab uses the SDK's low-level Brand method.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.context.dev/v1/brand/retrieve \
    --request POST \
    --header "Authorization: Bearer $CONTEXT_DEV_API_KEY" \
    --header "Content-Type: application/json" \
    --data '{
    "type": "by_email",
    "email": "founder@stripe.com",
    "timeoutMS": 15000
  }'
  ```

  ```typescript TypeScript theme={null}
  import ContextDev from "context.dev";

  const client = new ContextDev({ apiKey: process.env.CONTEXT_DEV_API_KEY });

  const response = await client.brand.retrieve({
    type: "by_email",
    email: "founder@stripe.com",
    timeoutMS: 15000,
  });

  console.log(response.brand?.title);
  ```

  ```python Python theme={null}
  import os
  from context.dev import ContextDev

  client = ContextDev(api_key=os.environ["CONTEXT_DEV_API_KEY"])

  response = client.brand.retrieve(
      type="by_email",
      email="founder@stripe.com",
      timeout_ms=15000,
  )

  print(response.brand.title if response.brand else None)
  ```

  ```ruby Ruby theme={null}
  require "cgi/core"
  require "context_dev"

  client = ContextDev::Client.new(api_key: ENV.fetch("CONTEXT_DEV_API_KEY"))

  response = client.brand.retrieve(
    body: {
      "type" => "by_email",
      "email" => "founder@stripe.com",
      "timeout_ms" => 15000,
    }
  )

  puts response.brand&.title
  ```

  ```go Go theme={null}
  package main

  import (
      "context"
      "fmt"
      "os"

      contextdev "github.com/context-dot-dev/context-go-sdk/v2"
      "github.com/context-dot-dev/context-go-sdk/v2/option"
      "github.com/context-dot-dev/context-go-sdk/v2/packages/param"
  )

  func main() {
      client := contextdev.NewClient(option.WithAPIKey(os.Getenv("CONTEXT_DEV_API_KEY")))
      response, err := client.Brand.Get(context.Background(), contextdev.BrandGetParams{
          OfByEmail: &contextdev.BrandGetParamsBodyByEmail{
              Email:     "founder@stripe.com",
              TimeoutMs: param.NewOpt(int64(15000)),
          },
      })
      if err != nil {
          panic(err)
      }
      fmt.Println(response.Brand.Title)
  }
  ```

  ```php PHP theme={null}
  <?php

  require __DIR__.'/vendor/autoload.php';

  $client = new ContextDev\Client(apiKey: getenv('CONTEXT_DEV_API_KEY'));

  // Use the SDK's low-level request: its generated Brand helper cannot express this lookup.
  $response = $client->request(
      method: 'post',
      path: 'brand/retrieve',
      body: [
        "type" => "by_email",
        "email" => "founder@stripe.com",
        "timeoutMS" => 15000,
      ],
  );
  $data = json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR);
  echo $data['brand']['title'] ?? 'No match', PHP_EOL;
  ```
</CodeGroup>

## Normalize the lookup result

This TypeScript adapter keeps the rest of your CRM integration independent of upstream error shapes:

```typescript theme={null}
type CompanyLookup =
  | { status: "matched"; brand: Record<string, unknown> }
  | { status: "unmatched" }
  | { status: "retryable"; retryAfterSeconds?: number }
  | { status: "error"; httpStatus: number; code?: string };

export async function lookupCompany(email: string): Promise<CompanyLookup> {
  const response = await fetch("https://api.context.dev/v1/brand/retrieve", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CONTEXT_DEV_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      type: "by_email",
      email,
      timeoutMS: 15_000,
    }),
  });

  const payload = await response.json();

  if (response.ok && payload.brand) {
    return { status: "matched", brand: payload.brand };
  }

  if (
    response.ok ||
    response.status === 422 ||
    (response.status === 400 && payload.error_code === "NOT_FOUND")
  ) {
    return { status: "unmatched" };
  }

  if (![408, 429].includes(response.status) && response.status < 500) {
    return {
      status: "error",
      httpStatus: response.status,
      code: payload.error_code,
    };
  }

  const retryAfterHeader = response.headers.get("Retry-After");
  const retryAfter = retryAfterHeader === null ? NaN : Number(retryAfterHeader);
  return {
    status: "retryable",
    retryAfterSeconds:
      Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter : undefined,
  };
}
```

Free and disposable email addresses return `422`; treat those as unmatched. Retry `408`, `429`, server errors, and network failures with a bounded policy. Authentication and input errors need correction before retrying. The worker should also catch fetch failures and record them as retryable.

## Patch blank fields only

Make the mapping explicit and preserve anything a rep or another trusted system already entered:

```typescript theme={null}
function fillBlank<T>(current: T | null | undefined, candidate: T | undefined) {
  return current == null || (typeof current === "string" && current.trim() === "")
    ? candidate
    : current;
}

function buildCrmPatch(existing: any, brand: any) {
  const logo = brand.logos?.find(
    (item: any) => item.type === "logo" && item.mode === "light",
  );
  const linkedin = brand.socials?.find((item: any) => item.type === "linkedin");

  return {
    companyName: fillBlank(existing.companyName, brand.title),
    companyDomain: fillBlank(existing.companyDomain, brand.domain),
    companyDescription: fillBlank(existing.companyDescription, brand.description),
    companyLogoUrl: fillBlank(existing.companyLogoUrl, logo?.url),
    companyLinkedIn: fillBlank(existing.companyLinkedIn, linkedin?.url),
    industry: fillBlank(existing.industry, brand.industries?.eic?.[0]?.industry),
  };
}
```

Filter out `undefined` fields before sending the patch if your CRM interprets them as clears.

## Handle duplicate events

Use the CRM event ID as an idempotency key. If the CRM does not provide one, derive a stable key from the object ID and event timestamp.

1. Acknowledge the webhook quickly after validating its signature.
2. Enqueue the CRM object ID, email, and event ID.
3. Skip event IDs already processed.
4. Cache matched profiles by normalized `brand.domain`, not by the full email address.
5. Retry transient failures with a bounded exponential backoff and jitter.
6. Send permanent misses to a completed state so the webhook is not retried forever.

## Add person context when the workflow needs it

Use the [People API](/api-reference/people/enrich) for a person's identity and profile, separately from the company Brand lookup. Send additive clues you actually have, such as a work email and a confirmed company domain:

```json People enrichment request body theme={null}
{
  "email": "person@example.com",
  "company": { "domain": "example.com" }
}
```

Send this body to `POST /people/enrich`. Handle `match.status` as `candidate` or `not_found`, and review the returned identity match before writing person fields. The People identity match score is distinct from the Brand response, which has no public numeric match score. Neither a company email domain nor a marketing description establishes someone's current role or employment.

For a candidate, inspect `match.person.current_role_status` before using `current_role`. `present` means a role is populated, `none` means the available work history shows ended roles, and `unknown` means the current role could not be confirmed. Do not convert an unknown role into an unemployment claim.

Keep your CRM contact ID and account ID as the durable identities. Domains can help deduplicate company candidates, but subsidiaries, rebrands, and shared domains may need separate account records.

## Attach sourced account research

Use selected [website pages](/guides/scrape-websites-to-markdown) or [structured extraction](/guides/extract-structured-data-from-websites) for public product descriptions, announced updates, or hiring signals. With Extract, use nullable fields and `factCheck: true`; preserve the analyzed URLs and verify the supporting page before treating a signal as established.

Keep the research layer separate from the fields your team maintains:

| Context                                  | Store with it                                    | Refresh behavior                                          |
| ---------------------------------------- | ------------------------------------------------ | --------------------------------------------------------- |
| Brand profile                            | Lookup domain, retrieval time, mapped fields     | Suggest updates without replacing confirmed edits.        |
| Person candidate                         | Supplied clues, match outcome, review decision   | Recheck identity before replacing role or profile fields. |
| Public website fact                      | Source URL, supporting excerpt, observation time | Create a new observation; retain prior evidence.          |
| CRM conversation or rep note             | Internal record ID, author, update time          | Keep under the CRM's access and edit rules.               |
| Account score or outreach recommendation | Inputs and application scoring version           | Recompute using your own logic.                           |

A careers page can support a statement that a role was listed at an observation time. It does not establish a prospect's budget, buying intent, or current headcount. Label inferences separately and keep failed checks distinct from a signal disappearing.

The resulting account brief can power a [personalized sales demo](/use-cases/personalized-sales-demos). Context.dev supplies source data; your application joins CRM history, computes an ICP score, and decides when to send outreach.

## Keep records useful

* Keep the API key in the worker's server environment.
* Verify the CRM webhook signature before accepting an event.
* Minimize the email and company data written to logs.
* Record the lookup time and source for fields that may become stale.
* Give reps a way to correct enriched fields.
* Re-enrich on an intentional schedule, not every time a lead record is read.

<CardGroup cols={2}>
  <Card title="Retrieve a brand by email" icon="envelope" href="/guides/retrieve-brand-by-email">
    Map work email addresses to company profiles.
  </Card>

  <Card title="Rate limits" icon="gauge-high" href="/optimization/rate-limits">
    Plan throughput and handle rate-limited requests.
  </Card>

  <Card title="API stability" icon="shield-check" href="/optimization/api-stability">
    Handle response changes and schema upgrades.
  </Card>

  <Card title="Personalized sales demos" icon="presentation-screen" href="/use-cases/personalized-sales-demos">
    Use reviewed account facts and branding in a prospect-specific preview.
  </Card>
</CardGroup>
