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
Parameter | Type | Required | Description |
---|---|---|---|
query | string | Yes | Partial address input (minimum 3 characters) |
limit | integer | No | Maximum number of suggestions to return (1-10, default: 5) |
country | string | No | ISO 3166-1 alpha-2 country code to filter results |
bounds | string | No | Bounding 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
Field | Type | Description |
---|---|---|
predictions | array | List 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 Type | Rate Limit | Window |
---|---|---|
Autocomplete | 200 requests | Per 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.
Related Endpoints
- Address Verification - Validate complete addresses
- Geocoding - Convert addresses to coordinates
- Reverse Geocoding - Convert coordinates to addresses