Creator Autocomplete Best Practices
The Creator Autocomplete API provides real-time search suggestions as users type. Follow these best practices to build fast, cost-effective autocomplete experiences.
Endpoint Overview
GET /v1/creators/autocomplete?q={query}&limit={limit}&platform={platform}
Pricing : 0.001 credits per request (extremely cost-effective)
Use Case : Real-time search suggestions in UI components
Critical: Implement Debouncing
Always implement client-side debouncing to prevent excessive API calls. Without debouncing, autocomplete can generate hundreds of unnecessary requests.
Recommended Settings
Setting Value Reason Debounce delay 300ms Balances responsiveness with API efficiency Minimum query length 2 characters Reduces noise from single-letter searches Maximum requests/sec 10 Stays well within rate limits
Implementation Examples
Vanilla JavaScript
function createAutocomplete ( inputElement , onResults ) {
let debounceTimer ;
let abortController = null ;
inputElement . addEventListener ( 'input' , async ( e ) => {
const query = e . target . value . trim ();
// Clear previous timer and cancel in-flight request
clearTimeout ( debounceTimer );
if ( abortController ) {
abortController . abort ();
}
// Don't search for queries shorter than 2 characters
if ( query . length < 2 ) {
onResults ([]);
return ;
}
// Debounce the API call
debounceTimer = setTimeout ( async () => {
try {
abortController = new AbortController ();
const response = await fetch (
`https://api.influship.com/v1/creators/autocomplete?q= ${ encodeURIComponent ( query ) } &limit=5` ,
{
headers: {
'X-API-Key' : 'your-api-key-here' ,
},
signal: abortController . signal
}
);
if ( ! response . ok ) {
throw new Error ( `API error: ${ response . status } ` );
}
const data = await response . json ();
onResults ( data . results || []);
} catch ( error ) {
if ( error . name === 'AbortError' ) {
// Request was cancelled, ignore
return ;
}
console . error ( 'Autocomplete error:' , error );
onResults ([]);
}
}, 300 ); // 300ms delay
});
}
// Usage
const searchInput = document . querySelector ( '#creator-search' );
const resultsContainer = document . querySelector ( '#results' );
createAutocomplete ( searchInput , ( results ) => {
resultsContainer . innerHTML = results
. map ( creator => `
<div class="result">
<img src=" ${ creator . avatar_url } " alt=" ${ creator . name } ">
<div>
<strong> ${ creator . name } </strong>
${ creator . platforms . map ( p =>
`<span> ${ p . platform } : @ ${ p . username } </span>`
). join ( '' ) }
</div>
</div>
` )
. join ( '' );
});
React with Hooks
import { useState , useEffect , useCallback , useRef } from 'react' ;
function CreatorAutocomplete () {
const [ query , setQuery ] = useState ( '' );
const [ results , setResults ] = useState ([]);
const [ loading , setLoading ] = useState ( false );
const abortControllerRef = useRef ( null );
const searchCreators = useCallback ( async ( searchQuery ) => {
if ( searchQuery . length < 2 ) {
setResults ([]);
return ;
}
// Cancel previous request
if ( abortControllerRef . current ) {
abortControllerRef . current . abort ();
}
abortControllerRef . current = new AbortController ();
setLoading ( true );
try {
const response = await fetch (
`https://api.influship.com/v1/creators/autocomplete?q= ${ encodeURIComponent ( searchQuery ) } &limit=5` ,
{
headers: {
'X-API-Key' : process . env . REACT_APP_INFLUSHIP_API_KEY ,
},
signal: abortControllerRef . current . signal
}
);
if ( ! response . ok ) {
throw new Error ( `API error: ${ response . status } ` );
}
const data = await response . json ();
setResults ( data . results || []);
} catch ( error ) {
if ( error . name === 'AbortError' ) {
// Request was cancelled, ignore
return ;
}
console . error ( 'Autocomplete error:' , error );
setResults ([]);
} finally {
setLoading ( false );
}
}, []);
useEffect (() => {
const debounceTimer = setTimeout (() => {
searchCreators ( query );
}, 300 );
return () => {
clearTimeout ( debounceTimer );
if ( abortControllerRef . current ) {
abortControllerRef . current . abort ();
}
};
}, [ query , searchCreators ]);
return (
< div className = "autocomplete" >
< input
type = "text"
value = { query }
onChange = { ( e ) => setQuery ( e . target . value ) }
placeholder = "Search creators..."
aria-label = "Search creators"
aria-autocomplete = "list"
aria-controls = "autocomplete-results"
/>
{ loading && < div className = "loading" > Searching... </ div > }
< ul id = "autocomplete-results" role = "listbox" >
{ results . map (( creator ) => (
< li key = { creator . id } role = "option" >
< img src = { creator . avatar_url } alt = "" />
< div >
< strong > { creator . name } </ strong >
{ creator . platforms . map (( platform ) => (
< div key = { platform . platform } >
{ platform . platform } : @ { platform . username }
{ platform . match_type === 'username' && ' (username match)' }
{ platform . match_type === 'display_name' && ' (name match)' }
</ div >
)) }
</ div >
</ li >
)) }
</ ul >
</ div >
);
}
Vue 3 Composition API
< template >
< div class = "autocomplete" >
< input
v-model = " query "
type = "text"
placeholder = "Search creators..."
@ input = " handleInput "
/>
< div v-if = " loading " > Searching... </ div >
< ul v-if = " results . length " >
< li v-for = " creator in results " : key = " creator . id " >
< img : src = " creator . avatar_url " : alt = " creator . name " />
< div >
< strong > {{ creator . name }} </ strong >
< div v-for = " platform in creator . platforms " : key = " platform . platform " >
{{ platform . platform }}: @{{ platform . username }}
</ div >
</ div >
</ li >
</ ul >
</ div >
</ template >
< script setup >
import { ref } from 'vue' ;
import { useDebounceFn } from '@vueuse/core' ; // or implement your own
const query = ref ( '' );
const results = ref ([]);
const loading = ref ( false );
let abortController = null ;
const searchCreators = async ( searchQuery ) => {
if ( searchQuery . length < 2 ) {
results . value = [];
return ;
}
// Cancel previous request
if ( abortController ) {
abortController . abort ();
}
abortController = new AbortController ();
loading . value = true ;
try {
const response = await fetch (
`https://api.influship.com/v1/creators/autocomplete?q= ${ encodeURIComponent ( searchQuery ) } &limit=5` ,
{
headers: {
'X-API-Key' : import . meta . env . VITE_INFLUSHIP_API_KEY ,
},
signal: abortController . signal
}
);
const data = await response . json ();
results . value = data . results || [];
} catch ( error ) {
if ( error . name !== 'AbortError' ) {
console . error ( 'Autocomplete error:' , error );
results . value = [];
}
} finally {
loading . value = false ;
}
};
const handleInput = useDebounceFn (( e ) => {
searchCreators ( e . target . value );
}, 300 );
</ script >
Advanced: Client-Side Caching
Reduce API calls by caching recent searches:
class AutocompleteCache {
constructor ( maxSize = 100 , ttl = 300000 ) { // 5 minutes TTL
this . cache = new Map ();
this . maxSize = maxSize ;
this . ttl = ttl ;
}
get ( key ) {
const item = this . cache . get ( key );
if ( ! item ) return null ;
// Check if expired
if ( Date . now () - item . timestamp > this . ttl ) {
this . cache . delete ( key );
return null ;
}
return item . data ;
}
set ( key , data ) {
// Implement LRU eviction
if ( this . cache . size >= this . maxSize ) {
const firstKey = this . cache . keys (). next (). value ;
this . cache . delete ( firstKey );
}
this . cache . set ( key , {
data ,
timestamp: Date . now (),
});
}
clear () {
this . cache . clear ();
}
}
const cache = new AutocompleteCache ();
async function searchWithCache ( query ) {
// Check cache first
const cached = cache . get ( query . toLowerCase ());
if ( cached ) {
console . log ( 'Cache hit for:' , query );
return cached ;
}
// Fetch from API
const results = await searchCreators ( query );
// Store in cache
cache . set ( query . toLowerCase (), results );
return results ;
}
Narrow results by platform when the target platform is known:
// Search only Instagram creators
const response = await fetch (
`https://api.influship.com/v1/creators/autocomplete?q= ${ query } &platform=instagram&limit=5`
);
// Search only TikTok creators
const response = await fetch (
`https://api.influship.com/v1/creators/autocomplete?q= ${ query } &platform=tiktok&limit=5`
);
Match Highlighting
The API returns match_type to help you highlight relevant matches:
function highlightMatch ( text , query ) {
const regex = new RegExp ( `( ${ escapeRegex ( query ) } )` , 'gi' );
return text . replace ( regex , '<mark>$1</mark>' );
}
function escapeRegex ( string ) {
return string . replace ( / [ .*+?^${}()|[ \]\\ ] / g , ' \\ $&' );
}
// Render with highlighted matches
results . forEach (( creator ) => {
creator . platforms . forEach (( platform ) => {
if ( platform . match_type === 'username' ) {
// Highlight in username
const highlighted = highlightMatch ( platform . username , query );
console . log ( `@ ${ highlighted } ` );
} else if ( platform . match_type === 'display_name' ) {
// Highlight in display name
const highlighted = highlightMatch ( platform . display_name || creator . name , query );
console . log ( highlighted );
}
});
});
Error Handling
Gracefully handle errors without breaking the UX:
async function safeAutocomplete ( query ) {
try {
const response = await fetch ( url , options );
if ( response . status === 429 ) {
// Rate limited - show user-friendly message
console . warn ( 'Rate limited. Please slow down your search.' );
return { results: [], error: 'rate_limited' };
}
if ( ! response . ok ) {
throw new Error ( `API error: ${ response . status } ` );
}
return await response . json ();
} catch ( error ) {
console . error ( 'Autocomplete error:' , error );
// Return empty results instead of breaking the UI
return { results: [], error: 'network_error' };
}
}
Debounce Aggressively Use 300ms+ delays to reduce unnecessary requests
Cancel In-Flight Requests Use AbortController to cancel outdated requests
Cache Aggressively Cache results for 5-10 minutes to reduce API calls
Limit Results Request only 5-10 results for optimal UX and cost
Platform Filter Use platform filtering when the target is known
Minimum Query Length Require 2-3 characters before searching
Cost Optimization
Autocomplete is extremely cost-effective:
Cost : 0.001 credits per request
Free tier : 1,000 credits/month = 1,000,000 autocomplete requests
Pro tier : 10,000 credits/month = 10,000,000 autocomplete requests
With proper debouncing (300ms) and minimum query length (2 chars), typical usage:
Heavy user typing “fitness” : ~3-4 requests = 0.003-0.004 credits
1,000 searches/day : ~3,000 requests/day = 3 credits/day = 90 credits/month
Even with thousands of daily searches, autocomplete costs remain negligible. Focus on UX optimization rather than cost concerns.
Monitoring Usage
Track autocomplete performance and costs:
class AutocompleteMetrics {
constructor () {
this . totalRequests = 0 ;
this . cacheHits = 0 ;
this . errors = 0 ;
this . totalLatency = 0 ;
}
recordRequest ( latency , cached = false , error = false ) {
this . totalRequests ++ ;
if ( cached ) {
this . cacheHits ++ ;
}
if ( error ) {
this . errors ++ ;
} else {
this . totalLatency += latency ;
}
}
getStats () {
const avgLatency = this . totalLatency / ( this . totalRequests - this . cacheHits );
const cacheHitRate = ( this . cacheHits / this . totalRequests * 100 ). toFixed ( 1 );
const errorRate = ( this . errors / this . totalRequests * 100 ). toFixed ( 1 );
return {
totalRequests: this . totalRequests ,
cacheHitRate: ` ${ cacheHitRate } %` ,
errorRate: ` ${ errorRate } %` ,
avgLatency: ` ${ avgLatency . toFixed ( 0 ) } ms` ,
estimatedCredits: ( this . totalRequests * 0.001 ). toFixed ( 3 )
};
}
}
const metrics = new AutocompleteMetrics ();
async function trackedAutocomplete ( query ) {
const start = Date . now ();
try {
const results = await searchWithCache ( query );
const latency = Date . now () - start ;
metrics . recordRequest ( latency , results . cached );
return results ;
} catch ( error ) {
metrics . recordRequest ( Date . now () - start , false , true );
throw error ;
}
}
// Log stats periodically
setInterval (() => {
console . log ( 'Autocomplete Stats:' , metrics . getStats ());
}, 60000 ); // Every minute
Accessibility
Make autocomplete accessible to all users:
< div role = "combobox" aria-expanded = { results . length > 0 } aria-haspopup = "listbox" >
< input
type = "text"
role = "searchbox"
aria-autocomplete = "list"
aria-controls = "autocomplete-results"
aria-activedescendant = { activeItemId }
/>
< ul id = "autocomplete-results" role = "listbox" >
{ results . map (( creator , index ) => (
< li
key = { creator . id }
role = "option"
id = { `result- ${ index } ` }
aria-selected = { index === activeIndex }
>
{ creator . name }
</ li >
)) }
</ ul >
</ div >
Next Steps