Company Search & Autocomplete

Build a company search autocomplete for registration forms, CRM data entry, and B2B onboarding. The CompanyData search endpoint returns lightweight records quickly as users type, typically ID, Company Name, and location fields.

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.

Example Response

Request: GET /api/company/search?search=Heineken&countryCode=NL&page=1&pageSize=10

{
  "data": {
    "records": [
      {
        "ID": "330114331",
        "Company Name": "Heineken N.V.",
        "Country": "Netherlands",
        "City": "AMSTERDAM"
      }
    ],
    "totalCount": 3
  },
  "page": 1,
  "pageSize": 10,
  "totalPages": 1
}

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?