Autocomplete API

Provide real-time address suggestions as users type, improving data entry accuracy and user experience.

The Autocomplete API provides real-time address suggestions based on partial user input. This helps users enter addresses quickly and accurately, reducing errors and improving the overall user experience in forms and search interfaces.

Endpoint

GET /api/v1/autocomplete

Authentication

Requires API key authentication. Include your API key in the Authorization header:

Authorization: Bearer YOUR_API_KEY

Request Parameters

ParameterTypeRequiredDescription
querystringYesPartial address input (minimum 3 characters)
limitintegerNoMaximum number of suggestions to return (1-10, default: 5)
countrystringNoISO 3166-1 alpha-2 country code to filter results
boundsstringNoBounding box for geographic filtering (format: “minLat,minLng,maxLat,maxLng”)

Response Format

Success Response

{
  "predictions": [
    "1600 Amphitheatre Parkway, Mountain View, CA 94043",
    "1600 Pennsylvania Avenue NW, Washington, DC 20500",
    "1600 Holloway Avenue, San Francisco, CA 94132",
    "1600 Broadway, New York, NY 10019",
    "1600 Vine Street, Los Angeles, CA 90028"
  ]
}

Response Fields

FieldTypeDescription
predictionsarrayList of address suggestion strings

Error Responses

Missing Query Parameter

{
  "error": {
    "code": 400,
    "message": "Query parameter is required"
  }
}

Status Code: 400 Bad Request

Query Too Short

{
  "error": {
    "code": 400,
    "message": "Query must be at least 3 characters long"
  }
}

Status Code: 400 Bad Request

Invalid Limit

{
  "error": {
    "code": 400,
    "message": "Limit must be between 1 and 10"
  }
}

Status Code: 400 Bad Request

Insufficient Balance

{
  "error": {
    "code": 402,
    "message": "Insufficient balance. Please add funds to continue."
  }
}

Status Code: 402 Payment Required

Rate Limit Exceeded

{
  "error": {
    "code": 429,
    "message": "Rate limit exceeded"
  }
}

Status Code: 429 Too Many Requests

Service Error

{
  "error": {
    "code": 500,
    "message": "Internal Server Error"
  }
}

Status Code: 500 Internal Server Error

Code Examples

Basic Implementation

class AddressAutocomplete {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://core.streetverify.com/api/v1';
  }

  async getSuggestions(query, options = {}) {
    const params = new URLSearchParams({
      query,
      limit: options.limit || 5
    });

    if (options.country) params.append('country', options.country);
    if (options.bounds) params.append('bounds', options.bounds);

    const response = await fetch(`${this.baseUrl}/autocomplete?${params}`, {
      headers: {
        'Authorization': `Bearer ${this.apiKey}`
      }
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error?.message || `API Error: ${response.statusText}`);
    }

    return response.json();
  }
}

// Usage
const autocomplete = new AddressAutocomplete('YOUR_API_KEY');

const results = await autocomplete.getSuggestions('1600 Amph', {
  limit: 5,
  country: 'US'
});

results.predictions.forEach(address => {
  console.log(address);
});

React Autocomplete Component

import React, { useState, useEffect, useCallback } from 'react';
import debounce from 'lodash/debounce';

const AddressAutocomplete = ({ apiKey, onSelect }) => {
  const [query, setQuery] = useState('');
  const [suggestions, setSuggestions] = useState([]);
  const [loading, setLoading] = useState(false);
  const [selectedIndex, setSelectedIndex] = useState(-1);

  // Debounced search function
  const searchAddresses = useCallback(
    debounce(async (searchQuery) => {
      if (searchQuery.length < 3) {
        setSuggestions([]);
        return;
      }

      setLoading(true);
      try {
        const params = new URLSearchParams({
          query: searchQuery,
          limit: 5
        });

        const response = await fetch(
          `https://core.streetverify.com/api/v1/autocomplete?${params}`,
          {
            headers: {
              'Authorization': `Bearer ${apiKey}`
            }
          }
        );

        const data = await response.json();
        if (response.ok) {
          setSuggestions(data.predictions || []);
        } else {
          console.error('API error:', data.error);
          setSuggestions([]);
        }
      } catch (error) {
        console.error('Autocomplete error:', error);
        setSuggestions([]);
      } finally {
        setLoading(false);
      }
    }, 300),
    [apiKey]
  );

  useEffect(() => {
    searchAddresses(query);
  }, [query, searchAddresses]);

  const handleSelect = (suggestion) => {
    setQuery(suggestion);
    setSuggestions([]);
    onSelect(suggestion);
  };

  const handleKeyDown = (e) => {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      setSelectedIndex(prev => 
        prev < suggestions.length - 1 ? prev + 1 : prev
      );
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      setSelectedIndex(prev => prev > 0 ? prev - 1 : -1);
    } else if (e.key === 'Enter' && selectedIndex >= 0) {
      e.preventDefault();
      handleSelect(suggestions[selectedIndex]);
    } else if (e.key === 'Escape') {
      setSuggestions([]);
      setSelectedIndex(-1);
    }
  };

  return (
    <div className="autocomplete-container">
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        onKeyDown={handleKeyDown}
        placeholder="Start typing an address..."
        className="autocomplete-input"
        autoComplete="off"
      />
      
      {loading && (
        <div className="loading-indicator">Searching...</div>
      )}
      
      {suggestions.length > 0 && (
        <ul className="suggestions-list">
          {suggestions.map((suggestion, index) => (
            <li
              key={index}
              onClick={() => handleSelect(suggestion)}
              className={`suggestion-item ${
                index === selectedIndex ? 'selected' : ''
              }`}
              onMouseEnter={() => setSelectedIndex(index)}
            >
              {suggestion}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

// CSS
const styles = `
.autocomplete-container {
  position: relative;
  width: 100%;
}

.autocomplete-input {
  width: 100%;
  padding: 12px;
  font-size: 16px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.suggestions-list {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  max-height: 300px;
  overflow-y: auto;
  background: white;
  border: 1px solid #ddd;
  border-top: none;
  border-radius: 0 0 4px 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  z-index: 1000;
}

.suggestion-item {
  padding: 12px;
  cursor: pointer;
  border-bottom: 1px solid #f0f0f0;
}

.suggestion-item:hover,
.suggestion-item.selected {
  background-color: #f5f5f5;
}

.suggestion-main {
  font-weight: 500;
  color: #333;
}

.suggestion-secondary {
  font-size: 14px;
  color: #666;
  margin-top: 2px;
}
`;

Vue.js Implementation

<template>
  <div class="address-autocomplete">
    <input
      v-model="query"
      @input="onInput"
      @keydown="onKeyDown"
      placeholder="Enter address"
      class="address-input"
    />
    <ul v-if="showSuggestions" class="suggestions">
      <li
        v-for="(suggestion, index) in suggestions"
        :key="index"
        @click="selectSuggestion(suggestion)"
        :class="{ active: index === selectedIndex }"
        class="suggestion"
      >
        {{ suggestion }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    apiKey: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      query: '',
      suggestions: [],
      selectedIndex: -1,
      debounceTimer: null
    };
  },
  computed: {
    showSuggestions() {
      return this.suggestions.length > 0 && this.query.length >= 3;
    }
  },
  methods: {
    async fetchSuggestions() {
      if (this.query.length < 3) {
        this.suggestions = [];
        return;
      }

      try {
        const params = new URLSearchParams({
          query: this.query,
          limit: 5
        });

        const response = await fetch(
          `https://core.streetverify.com/api/v1/autocomplete?${params}`,
          {
            headers: {
              'Authorization': `Bearer ${this.apiKey}`
            }
          }
        );

        const data = await response.json();
        if (response.ok) {
          this.suggestions = data.predictions || [];
        } else {
          console.error('API error:', data.error);
          this.suggestions = [];
        }
      } catch (error) {
        console.error('Autocomplete error:', error);
        this.suggestions = [];
      }
    },
    
    onInput() {
      clearTimeout(this.debounceTimer);
      this.debounceTimer = setTimeout(() => {
        this.fetchSuggestions();
      }, 300);
    },
    
    selectSuggestion(suggestion) {
      this.query = suggestion;
      this.suggestions = [];
      this.$emit('select', suggestion);
    },
    
    onKeyDown(event) {
      switch (event.key) {
        case 'ArrowDown':
          event.preventDefault();
          this.selectedIndex = Math.min(
            this.selectedIndex + 1,
            this.suggestions.length - 1
          );
          break;
        case 'ArrowUp':
          event.preventDefault();
          this.selectedIndex = Math.max(this.selectedIndex - 1, -1);
          break;
        case 'Enter':
          if (this.selectedIndex >= 0) {
            event.preventDefault();
            this.selectSuggestion(this.suggestions[this.selectedIndex]);
          }
          break;
        case 'Escape':
          this.suggestions = [];
          break;
      }
    }
  }
};
</script>

Advanced Features

1. Geographic Filtering

Limit suggestions to a specific area:

// Search within San Francisco Bay Area
const bounds = "37.0,-123.0,38.0,-122.0";

const results = await autocomplete.getSuggestions('market st', {
  bounds: bounds,
  country: 'US'
});

2. Highlighting Matched Text

Display matched portions in bold:

const highlightMatches = (text, matches) => {
  if (!matches || matches.length === 0) return text;
  
  let result = '';
  let lastOffset = 0;
  
  matches.forEach(match => {
    // Add non-matched portion
    result += text.substring(lastOffset, match.offset);
    // Add matched portion in bold
    result += `<strong>${text.substring(match.offset, match.offset + match.length)}</strong>`;
    lastOffset = match.offset + match.length;
  });
  
  // Add remaining text
  result += text.substring(lastOffset);
  
  return result;
};

3. Mobile Optimization

Optimize for mobile keyboards and touch interactions:

const MobileAutocomplete = () => {
  const [isVirtualKeyboard, setIsVirtualKeyboard] = useState(false);
  
  useEffect(() => {
    const checkVirtualKeyboard = () => {
      const threshold = 100;
      const heightDiff = window.screen.height - window.innerHeight;
      setIsVirtualKeyboard(heightDiff > threshold);
    };
    
    window.addEventListener('resize', checkVirtualKeyboard);
    return () => window.removeEventListener('resize', checkVirtualKeyboard);
  }, []);
  
  return (
    <div className={`autocomplete ${isVirtualKeyboard ? 'keyboard-open' : ''}`}>
      <input
        type="search"
        inputMode="search"
        autoComplete="street-address"
        enterKeyHint="search"
        // ... other props
      />
      {/* Adjust suggestion list position when keyboard is open */}
    </div>
  );
};

Performance Optimization

1. Debouncing

Always debounce API calls to reduce unnecessary requests:

const useDebounce = (value, delay) => {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => clearTimeout(handler);
  }, [value, delay]);
  
  return debouncedValue;
};

// Usage
const debouncedQuery = useDebounce(query, 300);

useEffect(() => {
  if (debouncedQuery.length >= 3) {
    fetchSuggestions(debouncedQuery);
  }
}, [debouncedQuery]);

2. Caching

Implement client-side caching for repeated queries:

class CachedAutocomplete {
  constructor(apiKey, cacheTime = 300000) { // 5 minutes
    this.apiKey = apiKey;
    this.cache = new Map();
    this.cacheTime = cacheTime;
  }
  
  getCacheKey(query, options) {
    return `${query}|${options.country || ''}|${options.bounds || ''}`;
  }
  
  async getSuggestions(query, options = {}) {
    const cacheKey = this.getCacheKey(query, options);
    const cached = this.cache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < this.cacheTime) {
      return cached.data;
    }
    
    const result = await this.fetchFromAPI(query, options);
    this.cache.set(cacheKey, {
      data: result,
      timestamp: Date.now()
    });
    
    return result;
  }
}

3. Request Cancellation

Cancel outdated requests when user continues typing:

class AutocompleteWithCancellation {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.abortController = null;
  }
  
  async getSuggestions(query, options = {}) {
    // Cancel previous request
    if (this.abortController) {
      this.abortController.abort();
    }
    
    // Create new abort controller
    this.abortController = new AbortController();
    
    try {
      const response = await fetch(url, {
        headers: {
          'Authorization': `Bearer ${this.apiKey}`
        },
        signal: this.abortController.signal
      });
      
      return await response.json();
    } catch (error) {
      if (error.name === 'AbortError') {
        // Request was cancelled, ignore
        return null;
      }
      throw error;
    }
  }
}

Best Practices

1. Accessibility

Ensure your autocomplete is accessible:

<div role="combobox" aria-expanded="true" aria-haspopup="listbox">
  <input
    type="text"
    role="textbox"
    aria-autocomplete="list"
    aria-controls="suggestions-list"
    aria-activedescendant="suggestion-2"
  />
  <ul id="suggestions-list" role="listbox">
    <li id="suggestion-1" role="option">...</li>
    <li id="suggestion-2" role="option" aria-selected="true">...</li>
  </ul>
</div>

2. Error Handling

Provide fallback behavior for API failures:

const robustAutocomplete = async (query) => {
  try {
    return await autocomplete.getSuggestions(query);
  } catch (error) {
    console.error('Autocomplete failed:', error);
    
    // Fallback to basic filtering if you have local data
    if (localAddressData) {
      return filterLocalAddresses(query);
    }
    
    // Show user-friendly error
    return {
      success: false,
      fallback: true,
      suggestions: []
    };
  }
};

3. Analytics Integration

Track autocomplete usage for insights:

const trackAutocomplete = (event, data) => {
  // Send to analytics
  analytics.track('Autocomplete', {
    event: event,
    query: data.query,
    suggestionsCount: data.suggestionsCount,
    selectedIndex: data.selectedIndex,
    responseTime: data.responseTime
  });
};

// Track search initiation
trackAutocomplete('search_started', { query });

// Track selection
trackAutocomplete('suggestion_selected', {
  query,
  selectedIndex,
  suggestion: suggestion.text
});

Rate Limits

The Autocomplete API has higher rate limits due to its real-time nature:

Endpoint TypeRate LimitWindow
Autocomplete200 requestsPer minute

All plans have the same rate limit for autocomplete to ensure a smooth user experience. Usage is tracked against your account balance at $0.01 per request.