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

# Search the Investor Map

> Find potential buyers near your property and explore their past purchases.

export const WorkflowDemo = ({workflow = 'map-search', clip = 'map-address', caption = ''}) => {
  const labels = {
    'map-address': 'Select a property address',
    'map-filters': 'Adjust the search radius',
    'map-result': 'Open a property result',
    'deal-details': 'Enter the property address',
    'deal-publish': 'Save and publish a prepared deal',
    'deal-buyer-copy': 'Review the listing content',
    'import-upload': 'Choose a CSV file',
    'import-map': 'Map the phone column',
    'import-start': 'Start the contact import',
    'import-report': 'Review the import report'
  };
  const title = labels[clip] || 'DispoIQ demonstration';
  const host = useRef(null);
  const viewport = useRef(null);
  const frame = useRef(null);
  const inView = useRef(false);
  const active = useRef(false);
  const wanted = useRef(false);
  const [visible, setVisible] = useState(false);
  const [motion, setMotion] = useState(null);
  const [paused, setPaused] = useState(false);
  const [ready, setReady] = useState(false);
  const [html, setHtml] = useState('');
  const [error, setError] = useState('');
  const [attempt, setAttempt] = useState(0);
  const [dimensions, setDimensions] = useState({
    width: 1320,
    height: 748
  });
  const [width, setWidth] = useState(720);
  const scale = Math.min(1, Math.max(1, width) / dimensions.width);
  const send = useCallback((command, extra = {}) => {
    frame.current?.contentWindow?.postMessage({
      source: 'dispoiq-docs-player',
      version: 1,
      demo: workflow,
      clip,
      command,
      ...extra
    }, window.location.origin);
  }, [workflow, clip]);
  useEffect(() => {
    const preference = window.matchMedia('(prefers-reduced-motion: reduce)');
    const change = () => {
      setMotion(preference.matches);
      setPaused(preference.matches);
    };
    change();
    preference.addEventListener('change', change);
    const observer = new IntersectionObserver(([entry]) => {
      inView.current = Boolean(entry?.isIntersecting);
      active.current = inView.current && !document.hidden;
      setVisible(inView.current);
      send('activity', {
        active: active.current
      });
      send(active.current && wanted.current ? 'play' : 'pause');
    }, {
      threshold: 0.15
    });
    if (host.current) observer.observe(host.current);
    const resize = new ResizeObserver(([entry]) => {
      if (entry) setWidth(entry.contentRect.width);
    });
    if (viewport.current) resize.observe(viewport.current);
    const visibility = () => {
      active.current = inView.current && !document.hidden;
      send('activity', {
        active: active.current
      });
      send(active.current && wanted.current ? 'play' : 'pause');
    };
    document.addEventListener('visibilitychange', visibility);
    return () => {
      observer.disconnect();
      resize.disconnect();
      preference.removeEventListener('change', change);
      document.removeEventListener('visibilitychange', visibility);
    };
  }, [send]);
  useEffect(() => {
    wanted.current = motion !== null && !paused;
    send('activity', {
      active: active.current
    });
    send(wanted.current && active.current ? 'play' : 'pause');
  }, [paused, motion, ready, send]);
  useEffect(() => {
    const receive = event => {
      const data = event.data;
      if (event.source !== frame.current?.contentWindow || event.origin !== window.location.origin) return;
      if (!data || data.source !== 'dispoiq-docs-demo' || data.version !== 1 || data.demo !== workflow || data.clip !== clip) return;
      if (data.type === 'error') {
        console.error('DispoIQ animation could not start:', String(data.message || 'Native scene error').replace(/(?:pk|sk)\.[A-Za-z0-9._-]+/g, '[token]').slice(0, 500));
        setError('This animation is unavailable. Follow the written instructions.');
        send('pause');
        return;
      }
      if (data.type !== 'ready' && data.type !== 'progress') return;
      if (data.type === 'ready') {
        setReady(true);
        setError('');
        send('activity', {
          active: active.current
        });
        if (wanted.current && active.current) send('play');
      }
    };
    window.addEventListener('message', receive);
    return () => window.removeEventListener('message', receive);
  }, [send, workflow, clip]);
  useEffect(() => {
    if (!visible || html || error || motion === null) return;
    let cancelled = false;
    const load = async () => {
      try {
        if (!window.__dispoiqInlineDemoPayload) {
          const promise = (async () => {
            const abort = new AbortController();
            const timeout = window.setTimeout(() => abort.abort(), 20000);
            try {
              const manifestResponse = await fetch('/demos/native/manifest.json', {
                signal: abort.signal,
                cache: 'no-cache'
              });
              if (!manifestResponse.ok) throw new Error('Manifest unavailable');
              const manifest = await manifestResponse.json();
              if (manifest.version !== 1 || manifest.payload !== 'runtime.json') throw new Error('Invalid manifest');
              const response = await fetch('/demos/native/runtime.json', {
                signal: abort.signal,
                cache: 'no-cache'
              });
              if (!response.ok) throw new Error('Runtime unavailable');
              const payload = await response.json();
              if (payload.version !== 1 || typeof payload.script !== 'string' || typeof payload.style !== 'string' || payload.script.length > 15000000 || payload.style.length > 2000000) throw new Error('Invalid runtime');
              return {
                manifest,
                payload
              };
            } finally {
              window.clearTimeout(timeout);
            }
          })();
          window.__dispoiqInlineDemoPayload = promise;
          promise.catch(() => {
            if (window.__dispoiqInlineDemoPayload === promise) delete window.__dispoiqInlineDemoPayload;
          });
        }
        const {manifest, payload} = await window.__dispoiqInlineDemoPayload;
        const definition = manifest.clips?.[clip];
        if (!definition || definition.workflow !== workflow || !labels[clip]) throw new Error('Invalid clip');
        const sceneWidth = Number(definition.width);
        const sceneHeight = Number(definition.height);
        if (!Number.isFinite(sceneWidth) || !Number.isFinite(sceneHeight) || sceneWidth < 320 || sceneWidth > 1600 || sceneHeight < 200 || sceneHeight > 1200) throw new Error('Invalid dimensions');
        if (cancelled) return;
        setDimensions({
          width: sceneWidth,
          height: sceneHeight
        });
        const origin = window.location.origin.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
        const script = payload.script.replace(/<\/script/gi, '<\\/script');
        const stylesheet = payload.style.replace(/<\/style/gi, '<\\/style');
        setHtml(`<!doctype html><html lang="en" data-demo="${workflow}" data-clip="${clip}" data-parent-origin="${origin}"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><base href="${origin}/demos/native/"/><style>${stylesheet}</style><style>html,body{margin:0;width:${sceneWidth}px;height:${sceneHeight}px;overflow:hidden}body{background:#fff}</style></head><body><div id="demo-root"></div><script type="module">${script}</script></body></html>`);
      } catch (failure) {
        if (!cancelled) console.error('DispoIQ animation could not load:', failure instanceof Error ? failure.message : 'Animation loading failed');
        if (!cancelled) setError('This animation is unavailable. Follow the written instructions.');
      }
    };
    load();
    return () => {
      cancelled = true;
    };
  }, [visible, motion, workflow, clip, html, error, attempt]);
  useEffect(() => {
    if (!html || ready || error) return;
    const timeout = window.setTimeout(() => {
      setError('This animation could not start. Follow the written instructions.');
      send('pause');
    }, 30000);
    return () => window.clearTimeout(timeout);
  }, [html, ready, error, send]);
  const retry = () => {
    setHtml('');
    setReady(false);
    setError('');
    setAttempt(value => value + 1);
  };
  return <figure ref={host} className="not-prose" aria-label={`${title} animation`} style={{
    margin: '20px 0 26px',
    maxWidth: '100%',
    minWidth: 0
  }}>
      <div ref={viewport} style={{
    width: '100%',
    aspectRatio: `${dimensions.width}/${dimensions.height}`,
    position: 'relative',
    overflow: 'hidden',
    borderRadius: 12,
    border: '1px solid #dbe4ef',
    background: '#f4f7fc',
    boxSizing: 'border-box'
  }}>
        {html && !error ? <iframe key={`${clip}-${attempt}`} ref={frame} title={`${title} using sample data`} srcDoc={html} sandbox="allow-scripts allow-same-origin" tabIndex={-1} aria-hidden="true" style={{
    display: 'block',
    border: 0,
    width: dimensions.width,
    height: dimensions.height,
    transform: `scale(${scale})`,
    transformOrigin: 'top left',
    pointerEvents: 'none',
    opacity: ready ? 1 : 0
  }} /> : <div role={error ? 'status' : undefined} style={{
    position: 'absolute',
    inset: 0,
    display: 'grid',
    placeContent: 'center',
    padding: 24,
    color: '#52657e',
    textAlign: 'center',
    fontSize: 14
  }}><span>{error || `Loading ${title.toLowerCase()}…`}</span>{error && <button type="button" onClick={retry} style={{
    marginTop: 12,
    padding: '8px 14px',
    background: '#fff',
    border: '1px solid #c9d7ea',
    borderRadius: 8,
    cursor: 'pointer'
  }}>Retry animation</button>}</div>}
        {html && !ready && !error && <div aria-live="polite" style={{
    position: 'absolute',
    inset: 0,
    display: 'grid',
    placeContent: 'center',
    padding: 24,
    color: '#52657e',
    fontSize: 14
  }}>Loading animation…</div>}
        {ready && !error && <button type="button" aria-label={`${paused ? 'Play' : 'Pause'} ${title.toLowerCase()} animation`} title={paused ? 'Play animation' : 'Pause animation'} aria-pressed={paused} onClick={() => setPaused(value => !value)} style={{
    position: 'absolute',
    left: 10,
    top: 10,
    width: 36,
    height: 36,
    display: 'grid',
    placeItems: 'center',
    border: '1px solid #d5deeb',
    borderRadius: 8,
    background: 'rgba(255,255,255,0.95)',
    color: '#18314f',
    cursor: 'pointer',
    zIndex: 2
  }}><span aria-hidden="true">{paused ? '▶' : 'Ⅱ'}</span></button>}
      </div>
      <figcaption style={{
    marginTop: 8,
    fontSize: 12,
    lineHeight: 1.6,
    color: '#64748b'
  }}>{caption || title}.{motion && paused && <span> Motion is paused; choose Play to watch.</span>}</figcaption>
    </figure>;
};

Use **Investor Map** to find potential buyers by exploring purchases near your property. Enter the property address, then select a buyer to see their purchase details.

You don't need a property under contract to use **Investor Map**. During acquisitions, you can search a property you're considering and review what investors have paid for similar properties nearby before deciding whether to put it under contract.

The animations below use sample data to show how the search works.

## Search for buyers

<Steps>
  <Step title="Search your property address">
    1. Open **Investor Map**. The search panel is titled **Investor Search**.
    2. Type the property address into **Property address**. Wait for the address suggestions.
    3. Select the correct suggestion. Selecting it starts the investor search. You can also use the arrow keys to highlight a suggestion and Enter to select it.
    4. Wait for **Searching for investors…** to finish, then review the buyer list and map.

    Choose an address suggestion so DispoIQ can locate the property. Typing the address alone doesn't start a search.

    <WorkflowDemo workflow="map-search" clip="map-address" />
  </Step>

  <Step title="Review the buyers">
    Look at nearby buyers and their past purchases. One buyer may have purchased multiple properties.

    Past purchases can help you identify potential buyers. Ask about their current buying criteria and whether they're buying now.

    Read [Understand Investor Map results](/investor-map/read-results) for the meaning of buyer labels, prices, counts, and missing data.
  </Step>

  <Step title="Adjust your search">
    Open **Advanced filters**, change the radius or other filters, and select **Apply filters**. Wait for the updated results.

    A fresh search starts with a **1 Mile** radius, a **1 year** sold period, and **All buyers**. You can narrow the results by property type, price, funding, or purchase count. See [Adjust search filters](/investor-map/filters).

    <WorkflowDemo workflow="map-search" clip="map-filters" />
  </Step>

  <Step title="Explore a result">
    Choose a buyer row or a map pin to open that property's details. The card shows the owner, available property facts, purchase information, and the relevant flip or holding information.

    <WorkflowDemo workflow="map-search" clip="map-result" />
  </Step>
</Steps>

## Use the map controls

On a narrow screen, switch between **Map** and **Buyers** to move between the two views. Use **Recenter on search** to return the map to the searched property, and **Zoom in** or **Zoom out** to change the scale. **Street** and **Satellite** change the basemap.

The map shortcut beside a buyer opens that property's location in a new tab. The property-search shortcut opens an address search when an address is available. These shortcuts open external sites; selecting the buyer row opens details inside DispoIQ.

## Load more or start over

When **Load more buyers** appears, choose it to add the next set of results. Filters still apply to the expanded set. If loading more fails, the earlier results remain available.

Use **Clear address and results** to remove the current address and results before starting another search.

## Resolve a problem

| What you see | What to do |
| - | - |
| **Search an address** | Enter a complete property address and select a suggestion to start. |
| **Address lookup unavailable** | Try again shortly. Select a suggestion once lookup returns. |
| **No matching buyers found** | Open **Advanced filters** and widen the radius, extend the sold period, or relax property and other filters. |
| **Filters pending** | The current address or search choices have not produced new results yet. Select the intended address suggestion or apply your filter choices. |
| **Showing prior results** | Treat the visible list as results from the earlier successful search. Complete a new search before using it for the changed address or filters. |
| **Search could not be completed** | Read the message and retry when appropriate. If it keeps happening, contact support with the address, radius, and error shown. |
| **Map basemap unavailable** | Try the other map style or reload the page. If the map remains unavailable, contact support. |

Use the results as a starting point for finding buyers. The map shows the records found for your search and filters; it doesn't show every buyer in the market.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.