TL;DR: MobileAPI's autocomplete endpoint returns device suggestions in under 200ms with as few as 2 characters of input. Pair it with the search endpoint's fuzzy matching (which scores results by match certainty from 70-100%) and you get a production-ready device lookup in under an hour of development time.
TL;DR: MobileAPI's autocomplete endpoint returns device suggestions in under 200ms with as few as 2 characters of input. Pair it with the search endpoint's fuzzy matching (which scores results by match certainty from 70-100%) and you get a production-ready device lookup in under an hour of development time.
By MobileAPI Team | Last updated: March 2026
Table of Contents
- What You Will Build
- Prerequisites
- Set Up a Debounced Search Input
- Call the Autocomplete Endpoint
- Display Dropdown Suggestions
- Fetch Full Device Details on Selection
- Add Fuzzy Search with Match Certainty
- Build a Python Backend Proxy
- Handle Errors Gracefully
- Best Practices
- FAQ
- Key Takeaways
What You Will Build
A device search component that does two things: suggests devices as the user types (autocomplete), and retrieves full specifications when a device is selected. The flow looks like this:
- User types "Sam" into a search box.
- Your app calls
/devices/autocomplete/?q=samand displays a dropdown of matching devices. - User clicks "Samsung Galaxy S26."
- Your app calls
/devices/142/and renders the full spec sheet.
This pattern is used by comparison sites, e-commerce platforms, and trade-in apps to let users find any of 27,805+ devices across 200+ brands.
Prerequisites
You need a MobileAPI API key. The free tier gives you 200 requests per month, which is enough to build and test this feature. Sign up here to get your key.
You also need a basic understanding of JavaScript (for the frontend component) and optionally Python (for a backend proxy). All code examples work with vanilla JavaScript -- no frameworks required.
Set Up a Debounced Search Input
Never call the API on every keystroke. A user typing "Samsung" generates 7 keypress events, but you only need 1 or 2 API calls. Debouncing waits until the user stops typing before firing a request.
Here is the HTML and debounce logic:
<div id="device-search" style="position: relative; max-width: 400px;">
<input
type="text"
id="search-input"
placeholder="Search for a device..."
autocomplete="off"
/>
<div id="suggestions" style="display: none;"></div>
<div id="device-details" style="display: none;"></div>
</div>
const API_BASE = "https://api.mobileapi.dev";
const API_KEY = "YOUR_API_KEY";
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const searchInput = document.getElementById("search-input");
const suggestionsBox = document.getElementById("suggestions");
searchInput.addEventListener(
"input",
debounce((e) => {
const query = e.target.value.trim();
if (query.length >= 2) {
fetchAutocomplete(query);
} else {
suggestionsBox.style.display = "none";
}
}, 300)
);
The 300ms delay is the sweet spot. Shorter feels wasteful (too many calls). Longer feels sluggish. The autocomplete endpoint requires a minimum of 2 characters, so we check query.length >= 2 before calling.
Call the Autocomplete Endpoint
The autocomplete endpoint is GET /devices/autocomplete/?q={query}&limit={count}. It returns a lightweight list of matching devices -- just the id, name, brand, and full_name fields. No heavy spec data.
async function fetchAutocomplete(query) {
try {
const response = await fetch(
`${API_BASE}/devices/autocomplete/?q=${encodeURIComponent(query)}&limit=10`,
{
headers: { Authorization: `Bearer ${API_KEY}` },
}
);
if (!response.ok) {
handleApiError(response.status);
return;
}
const data = await response.json();
displaySuggestions(data.results);
} catch (error) {
console.error("Autocomplete request failed:", error);
}
}
A few things to note. The limit parameter caps results at 10 here, but you can go up to 50. Each autocomplete call costs 1 API credit. Using Bearer token authentication is the recommended approach (added January 2026), though Token prefix and query parameter ?key= also work.
Display Dropdown Suggestions
Now render the suggestions as a clickable dropdown list:
function displaySuggestions(results) {
if (!results || results.length === 0) {
suggestionsBox.style.display = "none";
return;
}
suggestionsBox.innerHTML = results
.map(
(device) =>
`<div class="suggestion-item" data-id="${device.id}">
<strong>${device.full_name}</strong>
<span class="brand-label">${device.brand}</span>
</div>`
)
.join("");
suggestionsBox.style.display = "block";
// Attach click handlers to each suggestion
suggestionsBox.querySelectorAll(".suggestion-item").forEach((item) => {
item.addEventListener("click", () => {
const deviceId = item.getAttribute("data-id");
searchInput.value = item.querySelector("strong").textContent;
suggestionsBox.style.display = "none";
fetchDeviceDetails(deviceId);
});
});
}
// Close dropdown when clicking outside
document.addEventListener("click", (e) => {
if (!e.target.closest("#device-search")) {
suggestionsBox.style.display = "none";
}
});
Add some basic CSS to make the dropdown usable:
#suggestions {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
max-height: 300px;
overflow-y: auto;
z-index: 100;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.suggestion-item {
padding: 10px 14px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.suggestion-item:hover {
background: #f5f5f5;
}
.brand-label {
font-size: 12px;
color: #888;
}
Fetch Full Device Details on Selection
When the user clicks a suggestion, fetch the complete device record using GET /devices/{id}/:
async function fetchDeviceDetails(deviceId) {
const detailsBox = document.getElementById("device-details");
detailsBox.innerHTML = "<p>Loading specs...</p>";
detailsBox.style.display = "block";
try {
const response = await fetch(`${API_BASE}/devices/${deviceId}/`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (!response.ok) {
handleApiError(response.status);
return;
}
const device = await response.json();
renderDeviceCard(device);
} catch (error) {
detailsBox.innerHTML = "<p>Failed to load device details.</p>";
}
}
function renderDeviceCard(device) {
const detailsBox = document.getElementById("device-details");
detailsBox.style.display = "block";
detailsBox.innerHTML = `
<h3>${device.full_name}</h3>
<table>
<tr><td>Brand</td><td>${device.brand}</td></tr>
<tr><td>Type</td><td>${device.device_type}</td></tr>
<tr><td>Display</td><td>${device.display?.size || "N/A"}</td></tr>
<tr><td>Storage</td><td>${device.memory?.internal || "N/A"}</td></tr>
<tr><td>Battery</td><td>${device.battery?.capacity || "N/A"}</td></tr>
<tr><td>Release</td><td>${device.release_date || "N/A"}</td></tr>
</table>
`;
}
This costs 1 additional API credit per device lookup. The response includes the full spec sheet -- network, body, display, platform, memory, cameras, sound, communications, features, battery, and miscellaneous sections.
Add Fuzzy Search with Match Certainty
The autocomplete endpoint handles simple type-ahead. For more advanced queries -- where users paste a full model name or make typos -- use the search endpoint instead.
GET /devices/search/?name=Samsung Galaxy S26 supports fuzzy matching and returns a match_certainty score:
- 100% -- exact match
- 70-99% -- fuzzy match (typo tolerance, partial name matching)
async function searchDevices(query, options = {}) {
const params = new URLSearchParams({ name: query });
if (options.manufacturer) params.set("manufacturer", options.manufacturer);
if (options.exact) params.set("exact", "true");
if (options.page) params.set("page", options.page);
if (options.limit) params.set("limit", options.limit || 10);
const response = await fetch(
`${API_BASE}/devices/search/?${params.toString()}`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
if (!response.ok) {
handleApiError(response.status);
return null;
}
return response.json();
}
// Usage: display results with certainty indicators
async function performSearch(query) {
const data = await searchDevices(query);
if (!data || !data.results) return;
data.results.forEach((result) => {
const certaintyClass =
result.match_certainty === 100
? "exact-match"
: result.match_certainty >= 85
? "high-match"
: "low-match";
console.log(
`${result.full_name} | ${result.match_certainty}% (${result.match_type})`
);
});
}
One important constraint: you cannot combine name and model_number in the same request. Use one or the other. If you need strict matching with no fuzzy results, pass exact=true.
You can also filter by manufacturer to narrow results. Searching name=Galaxy S26&manufacturer=Samsung is faster and more precise than searching name=Samsung Galaxy S26 alone.
Build a Python Backend Proxy
Exposing your API key in frontend JavaScript is a security risk. In production, route requests through a backend proxy. Here is a minimal Flask example:
from flask import Flask, request, jsonify
from functools import lru_cache
import requests
import time
app = Flask(__name__)
API_BASE = "https://api.mobileapi.dev"
API_KEY = "YOUR_API_KEY"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# Simple in-memory cache with TTL
_cache = {}
CACHE_TTL = 300 # 5 minutes
def cached_get(url, params):
cache_key = f"{url}?{sorted(params.items())}"
now = time.time()
if cache_key in _cache:
data, timestamp = _cache[cache_key]
if now - timestamp < CACHE_TTL:
return data
response = requests.get(url, headers=HEADERS, params=params, timeout=10)
response.raise_for_status()
data = response.json()
_cache[cache_key] = (data, now)
return data
@app.route("/api/autocomplete")
def autocomplete():
query = request.args.get("q", "").strip()
if len(query) < 2:
return jsonify({"results": []})
limit = min(int(request.args.get("limit", 10)), 50)
try:
data = cached_get(
f"{API_BASE}/devices/autocomplete/",
{"q": query, "limit": limit},
)
return jsonify(data)
except requests.exceptions.HTTPError as e:
return jsonify({"error": str(e)}), e.response.status_code
except requests.exceptions.RequestException:
return jsonify({"error": "Service unavailable"}), 503
@app.route("/api/search")
def search():
name = request.args.get("name", "").strip()
if not name:
return jsonify({"error": "Missing 'name' parameter"}), 400
params = {"name": name}
manufacturer = request.args.get("manufacturer")
if manufacturer:
params["manufacturer"] = manufacturer
exact = request.args.get("exact")
if exact == "true":
params["exact"] = "true"
page = request.args.get("page", "1")
params["page"] = page
params["limit"] = min(int(request.args.get("limit", 10)), 50)
try:
data = cached_get(f"{API_BASE}/devices/search/", params)
return jsonify(data)
except requests.exceptions.HTTPError as e:
return jsonify({"error": str(e)}), e.response.status_code
except requests.exceptions.RequestException:
return jsonify({"error": "Service unavailable"}), 503
@app.route("/api/devices/<int:device_id>")
def device_details(device_id):
try:
data = cached_get(f"{API_BASE}/devices/{device_id}/", {})
return jsonify(data)
except requests.exceptions.HTTPError as e:
return jsonify({"error": str(e)}), e.response.status_code
except requests.exceptions.RequestException:
return jsonify({"error": "Service unavailable"}), 503
if __name__ == "__main__":
app.run(debug=True, port=5000)
With this proxy running, update your frontend API_BASE to point to http://localhost:5000/api (or your production domain) and remove the Authorization header from client-side fetch calls. Your API key stays server-side.
The 5-minute cache (CACHE_TTL = 300) reduces redundant API calls. If 10 users all search "iPhone" within 5 minutes, you use 1 credit instead of 10.
Handle Errors Gracefully
Three error codes matter most for search and autocomplete:
| Status | Meaning | What to Do |
|---|---|---|
| 204 | No results found | Show "No devices found" message |
| 401 | Invalid or missing API key | Check your key; redirect to auth flow |
| 429 | Rate limit exceeded | Back off and retry after X-RateLimit-Reset |
function handleApiError(status) {
const detailsBox = document.getElementById("device-details");
switch (status) {
case 204:
detailsBox.innerHTML = "<p>No devices found. Try a different search.</p>";
detailsBox.style.display = "block";
break;
case 401:
console.error("API authentication failed. Check your API key.");
break;
case 429:
console.warn("Rate limit hit. Retrying in 10 seconds...");
setTimeout(() => {
// Retry the last search
const query = searchInput.value.trim();
if (query.length >= 2) fetchAutocomplete(query);
}, 10000);
break;
default:
console.error(`API error: ${status}`);
}
}
On the free tier, you get 5 requests per minute. The Pro plan bumps that to 10 per second. For a production app with real users, Pro is the practical minimum. Check rate limit headers (X-RateLimit-Remaining) to monitor usage proactively rather than waiting for 429 errors.
Best Practices
Debounce at 300ms. This is the standard for search inputs. It eliminates unnecessary calls without making the UI feel delayed.
Cache autocomplete results. Users often type, delete, and retype. A simple in-memory cache (Map or object) keyed by query string avoids redundant API calls. Even a 60-second TTL helps.
const autocompleteCache = new Map();
async function fetchAutocompleteWithCache(query) {
if (autocompleteCache.has(query)) {
displaySuggestions(autocompleteCache.get(query));
return;
}
const response = await fetch(
`${API_BASE}/devices/autocomplete/?q=${encodeURIComponent(query)}&limit=10`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
if (!response.ok) return;
const data = await response.json();
autocompleteCache.set(query, data.results);
displaySuggestions(data.results);
}
Show loading states. Add a spinner or "Searching..." text while waiting for results. API responses are typically fast (under 200ms), but network latency varies.
Use the autocomplete endpoint for type-ahead, search for full queries. Autocomplete is lightweight and designed for partial input. Search is heavier but supports fuzzy matching, filtering by manufacturer, and pagination. Use both -- autocomplete while typing, search on form submit or when the user needs advanced filtering.
Limit results to 10 for autocomplete dropdowns. More than 10 suggestions overwhelms the UI. Save the full 50-result capability for search results pages.
Proxy your API key in production. Never expose API keys in client-side code. The Python proxy example above adds maybe 20 minutes of setup and protects your key permanently.
FAQ
How many characters are needed before autocomplete returns results?
The autocomplete endpoint requires a minimum of 2 characters. Queries with fewer than 2 characters will return empty results. This keeps responses fast and relevant.
Can I search by model number instead of device name?
Yes. Use the search endpoint with the model_number parameter instead of name: GET /devices/search/?model_number=SM-S926B. You cannot combine both name and model_number in the same request -- pick one.
What does match_certainty mean and how should I use it?
match_certainty is a percentage score from the search endpoint. A score of 100 means exact match. Scores between 70 and 99 indicate fuzzy matches where the API corrected typos or matched partial names. You can use this to flag uncertain results to users -- for example, showing "Did you mean...?" for results below 85%.
How do I keep API costs low during development?
The free tier gives you 200 requests per month. Cache aggressively on the backend (the Python proxy example caches for 5 minutes). Use debouncing on the frontend. During development, mock API responses for UI work and only hit the live API for integration testing.
What is the difference between autocomplete and search?
Autocomplete (/devices/autocomplete/) is optimized for speed with partial input. It returns minimal fields: id, name, brand, and full_name. Search (/devices/search/) supports fuzzy matching, manufacturer filtering, pagination, and returns richer data including match_certainty and match_type. Use autocomplete for the dropdown, search for results pages.
Key Takeaways
- The autocomplete endpoint needs just 2 characters and returns suggestions with device id, name, and brand -- perfect for dropdown type-ahead.
- The search endpoint adds fuzzy matching with
match_certaintyscores (70-100%) for handling typos and partial names. - Debounce input at 300ms and cache results to minimize API credit usage.
- Always proxy your API key through a backend server in production.
- Handle 204 (no results), 401 (auth failure), and 429 (rate limit) errors explicitly.
- Start with autocomplete for the interactive dropdown, use search for advanced queries with manufacturer filtering and pagination.
Start Building
Get your API key and try the autocomplete endpoint right now. The free tier includes 200 requests per month -- enough to build and test a complete search component. Full endpoint reference is in the API documentation.