Company Search & Autocomplete

Build a professional company search experience for your application. The BoldData search API is optimized for autocomplete use cases, returning results quickly as users type.

Common Use Cases

  • Registration forms - Let users find and select their company
  • B2B onboarding - Verify company details during signup
  • CRM data entry - Reduce manual typing errors
  • Invoice/order forms - Quick company selection with autofill

How It Works

  1. User starts typing a company name (minimum 2 characters)
  2. Debounce the input to avoid excessive API calls
  3. Call the search API with the query
  4. Display matching companies in a dropdown
  5. On selection, fetch full company details

Try Company Search Autocomplete

Don't have an API key? Get your API key →

Type at least 2 characters. Use arrow keys to navigate, Enter to select.

TypeScript Implementation

GET
/api/company/search
// Company Search Autocomplete - works in Node.js 18+ and browsers
const API_KEY = 'YOUR_API_KEY';
const BASE_URL = 'https://app.companydata.com/api/company';

interface SearchResult {
  ID: string;
  'Company Name': string;
  'Country Name'?: string;
  'City'?: string;
}

interface CompanyDetails {
  ID: string;
  'Company Name': string;
  'Trade Name'?: string;
  'Address 1'?: string;
  'City'?: string;
  'Postal Code'?: string;
  'Country Name'?: string;
  'Phone Number'?: string;
  'Email'?: string;
  'Website'?: string;
  [key: string]: any;
}

async function fetchApi<T>(endpoint: string, params: Record<string, string | number>): Promise<T> {
  const url = new URL(`${BASE_URL}/${endpoint}`);
  Object.entries(params).forEach(([key, value]) => {
    if (value) url.searchParams.append(key, String(value));
  });

  const response = await fetch(url.toString(), {
    headers: { 'x-api-key': API_KEY },
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  }

  return response.json();
}

// Search for companies (autocomplete)
async function searchCompanies(
  query: string,
  options: { countryCode?: string; limit?: number } = {}
): Promise<SearchResult[]> {
  if (query.length < 2) return [];

  const params: Record<string, string | number> = {
    search: query,
    page: 1,
    pageSize: options.limit || 10,
  };

  if (options.countryCode) {
    params.countryCode = options.countryCode;
  }

  const response = await fetchApi<{ data: { records: SearchResult[] } }>('search', params);
  return response.data?.records || [];
}

// Get full company details after selection
async function getCompanyDetails(companyId: string): Promise<CompanyDetails | null> {
  const response = await fetchApi<{ data: { records: CompanyDetails[] } }>('export', {
    ID: companyId,
    page: 1,
    pageSize: 1,
  });

  return response.data?.records?.[0] || null;
}

// Debounce helper for autocomplete
function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: ReturnType<typeof setTimeout>;

  return (...args: Parameters<T>) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => func(...args), wait);
  };
}

// Example: React-style autocomplete handler
class CompanyAutocomplete {
  private cache = new Map<string, SearchResult[]>();

  async search(query: string, countryCode?: string): Promise<SearchResult[]> {
    const cacheKey = `${query}-${countryCode || ''}`;

    // Check cache first
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey)!;
    }

    const results = await searchCompanies(query, { countryCode });

    // Cache results
    this.cache.set(cacheKey, results);

    return results;
  }

  async select(company: SearchResult): Promise<CompanyDetails | null> {
    return getCompanyDetails(company.ID);
  }
}

// Example usage
async function main() {
  const autocomplete = new CompanyAutocomplete();

  // Simulate user typing "phil"
  const results = await autocomplete.search('phil', 'NL');

  console.log('Search results:');
  results.forEach((company, i) => {
    console.log(`  ${i + 1}. ${company['Company Name']}${company['Country Name'] ? ` (${company['Country Name']})` : ''}`);
  });

  // User selects first result
  if (results.length > 0) {
    const details = await autocomplete.select(results[0]);
    if (details) {
      console.log('\nSelected company details:');
      console.log(`  Name: ${details['Company Name']}`);
      console.log(`  Address: ${details['Address 1']}, ${details['City']}`);
      console.log(`  Phone: ${details['Phone Number']}`);
    }
  }
}

main();

React Component Example

Here's a complete React autocomplete component:

import { useState, useCallback, useRef, useEffect } from 'react';

interface Company {
  ID: string;
  'Company Name': string;
  'Country Name'?: string;
  'City'?: string;
}

interface Props {
  apiKey: string;
  countryCode?: string;
  onSelect: (company: Company) => void;
  placeholder?: string;
}

export function CompanyAutocomplete({ apiKey, countryCode, onSelect, placeholder }: Props) {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<Company[]>([]);
  const [isOpen, setIsOpen] = useState(false);
  const [loading, setLoading] = useState(false);
  const [highlighted, setHighlighted] = useState(0);
  const debounceRef = useRef<ReturnType<typeof setTimeout>>();

  const search = useCallback(async (searchQuery: string) => {
    if (searchQuery.length < 2) {
      setResults([]);
      return;
    }

    setLoading(true);
    try {
      const url = new URL('https://app.companydata.com/api/company/search');
      url.searchParams.set('search', searchQuery);
      url.searchParams.set('page', '1');
      url.searchParams.set('pageSize', '8');
      if (countryCode) url.searchParams.set('countryCode', countryCode);

      const response = await fetch(url.toString(), {
        headers: { 'x-api-key': apiKey },
      });

      const data = await response.json();
      setResults(data.data?.records || []);
      setIsOpen(true);
      setHighlighted(0);
    } catch (error) {
      console.error('Search error:', error);
      setResults([]);
    }
    setLoading(false);
  }, [apiKey, countryCode]);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    setQuery(value);

    // Debounce search
    if (debounceRef.current) clearTimeout(debounceRef.current);
    debounceRef.current = setTimeout(() => search(value), 300);
  };

  const handleSelect = (company: Company) => {
    setQuery(company['Company Name']);
    setIsOpen(false);
    onSelect(company);
  };

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (!isOpen) return;

    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        setHighlighted(i => Math.min(i + 1, results.length - 1));
        break;
      case 'ArrowUp':
        e.preventDefault();
        setHighlighted(i => Math.max(i - 1, 0));
        break;
      case 'Enter':
        e.preventDefault();
        if (results[highlighted]) handleSelect(results[highlighted]);
        break;
      case 'Escape':
        setIsOpen(false);
        break;
    }
  };

  return (
    <div className="relative">
      <input
        type="text"
        value={query}
        onChange={handleChange}
        onKeyDown={handleKeyDown}
        onFocus={() => results.length > 0 && setIsOpen(true)}
        placeholder={placeholder || 'Search for a company...'}
        className="w-full rounded-lg border px-4 py-2"
      />

      {loading && (
        <div className="absolute right-3 top-1/2 -translate-y-1/2">
          <span className="animate-spin">⟳</span>
        </div>
      )}

      {isOpen && results.length > 0 && (
        <ul className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-lg border bg-white shadow-lg">
          {results.map((company, index) => (
            <li
              key={company.ID}
              onClick={() => handleSelect(company)}
              onMouseEnter={() => setHighlighted(index)}
              className={`cursor-pointer px-4 py-2 ${
                index === highlighted ? 'bg-blue-50' : 'hover:bg-gray-50'
              }`}
            >
              <div className="font-medium">{company['Company Name']}</div>
              <div className="text-sm text-gray-500">
                {[company['City'], company['Country Name']].filter(Boolean).join(', ')}
              </div>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

Best Practices

Performance

  • Debounce input - Wait 200-300ms after user stops typing before searching
  • Cache results - Store recent searches to avoid duplicate API calls
  • Limit results - Request only 8-10 results for autocomplete dropdown

User Experience

  • Show loading state - Display a spinner while searching
  • Keyboard navigation - Support arrow keys and Enter for selection
  • Highlight matches - Bold the matching portion of company names
  • Show location - Display city/country to help disambiguate similar names

Accessibility

  • Use proper ARIA attributes (role="listbox", aria-activedescendant)
  • Support keyboard navigation
  • Announce results to screen readers

Was this page helpful?