Browser autocomplete
Framework-independent JavaScript autocomplete with domain-restricted public tokens.
The browser entry point has no runtime dependencies on React, Vue, Angular, Svelte, or other UI libraries. It exposes autocomplete and resolveAutocomplete through the same primary contract as the server SDKs. Use it with a bundler or as a native ES module. Primary autocomplete supports Canada, the United States, and Mexico. Configure optional Google/HERE public keys for direct browser fallbacks, including other countries.
Create a public token
In the account portal, open API keys (/app/keys). Under Create a key, select Browser token, enter a Key name, and list 1–20 Allowed domains or origins, one per line. Click Create browser token, then Copy key when the confirmation panel appears. The pub_ value is shown only after creation. Owners and admins can revoke it from the active-key list. To change its origins, create a replacement and revoke the old token. See key management for both credential types.
A bare domain such as app.example.com means https://app.example.com. Origins match exactly, including scheme and port. Add subdomains separately; wildcards, paths, credentials, query strings and fragments are rejected. For local development, add http://localhost:5173 with your frontend's actual port. HTTP is allowed only for localhost, 127.0.0.1 and ::1.
Requests made with these tokens must send an allowed Origin, and the tokens work only on GET /v1/autocomplete and GET /v1/place. Missing or unlisted origins and other operations return HTTP 403. Credentials are accepted only in the apikey header. CORS preflight (OPTIONS) requests do not require a token; the allow-list is enforced on the request that follows.
Public tokens are visible to users of your frontend. Origin checks restrict browser use; non-browser callers can forge an Origin header. They do not make a public token secret or prove the caller owns the domain. Account rate limits and usage billing still apply. Keep server API keys on your backend. Use separate public, website-restricted keys for browser provider fallbacks.
Install locally
Packages are not yet published. From the repository root:
cd sdk/typescript
npm install
npm run build
npm pack
Install the resulting archive in your frontend project, using its actual path:
npm install /path/to/affordable-geo-apis-sdk-0.1.0.tgz
import { AutocompleteClient } from '@affordable-geo-apis/sdk/browser';
const client = new AutocompleteClient({
baseUrl: 'https://YOUR_API_HOST',
token: 'pub_YOUR_BROWSER_TOKEN',
});
const response = await client.autocomplete({ q: '11 Wall', country: 'US' });
const selected = response.suggestions[0];
if (selected) {
const address = await client.resolveAutocomplete(selected);
console.log(address.label, address.lat, address.lon);
}
For native ES modules, serve the built dist/esm/ directory with your frontend assets and import AutocompleteClient from ./sdk/browser.js, keeping index.js and countries.js beside it. The included sdk/typescript/examples/browser.html demonstrates autocomplete and resolution without a UI framework. Serve it over HTTP with the built SDK, set its base URL and token, and whitelist the page's origin.
Public provider fallbacks
const client = new AutocompleteClient({
baseUrl: 'https://YOUR_API_HOST',
token: 'pub_YOUR_BROWSER_TOKEN',
fallbacks: [
{ provider: 'google', apiKey: 'YOUR_PUBLIC_GOOGLE_KEY' },
{ provider: 'here', apiKey: 'YOUR_PUBLIC_HERE_KEY' },
],
fallbackBudget: { maxRequests: 100 },
});
const sessionToken = crypto.randomUUID();
const response = await client.autocomplete({ q: '11 Wall', country: 'US', sessionToken });
Providers run in configured order after eligible primary failures or empty results. The first nonempty response wins. These are SDK options, never HTTP API fields. Primary HTTP 403 remains terminal. Selection resolves with the suggestion's provider and configured key, without another fallback chain or consuming the fallback budget.
Enable Google Places API (New), configure Website restrictions for your frontend, and restrict the key to the required API. See Google key restrictions. For HERE, enable Geocoding and Search and configure trusted domains. Provider restrictions are separate from your browser token's allowed origins. Keys are visible in browser traffic; provider quotas and billing apply directly to your provider accounts. Display the provider attribution required by their policies.
Reuse a UUID sessionToken across keystrokes and pass the returned Google suggestion unchanged to resolution; start a new token after selection or abandonment. If omitted, a token is generated per autocomplete call. Provider requests use CORS and omit cookies; allow the provider hosts in your frontend's CSP. Deploy with a referrer policy compatible with provider website restrictions.
Input integration in any frontend
Keep one client instance. Debounce keystrokes by approximately 250 ms and abort the previous call when the input changes or your UI is destroyed. Keep suggestion objects unchanged for resolution. Use a generation counter to prevent older responses or selections from replacing newer input:
let timer;
let controller;
let generation = 0;
function cancelSearch() {
generation++;
clearTimeout(timer);
controller?.abort();
}
function search(text, renderSuggestions, showError) {
cancelSearch();
const current = generation;
renderSuggestions([]);
if (text.trim().length < 3) return;
controller = new AbortController();
const signal = controller.signal;
timer = setTimeout(async () => {
try {
const response = await client.autocomplete({ q: text, country: 'US' }, { signal });
if (current === generation) renderSuggestions(response.suggestions);
} catch (error) {
if (!signal.aborted && current === generation) showError(error.message);
}
}, 250);
}
Call search from your library's input handler, and call cancelSearch during teardown. Resolve a selected suggestion with client.resolveAutocomplete(suggestion, { signal }); cancel pending searches and protect selection results with the same generation rule.
React example
React is used only in this example, not by the SDK. Put the client outside the component or create it once. The effect cleanup aborts requests and ignores stale results, following React's effect guidance.
import { useEffect, useState } from 'react';
import { AutocompleteClient } from '@affordable-geo-apis/sdk/browser';
const client = new AutocompleteClient({
baseUrl: 'https://YOUR_API_HOST',
token: 'pub_YOUR_BROWSER_TOKEN',
});
export function AddressSearch() {
const [text, setText] = useState('');
const [suggestions, setSuggestions] = useState([]);
const [error, setError] = useState('');
const [selected, setSelected] = useState(null);
const [address, setAddress] = useState(null);
useEffect(() => {
const controller = new AbortController();
let active = true;
setSuggestions([]);
setError('');
setAddress(null);
const timer = setTimeout(async () => {
try {
if (selected) {
const result = await client.resolveAutocomplete(selected, { signal: controller.signal });
if (active) setAddress(result);
} else if (text.trim().length >= 3) {
const result = await client.autocomplete({ q: text, country: 'US' }, { signal: controller.signal });
if (active) setSuggestions(result.suggestions);
}
} catch (error) {
if (active && !controller.signal.aborted) setError(error.message);
}
}, selected ? 0 : 250);
return () => { active = false; clearTimeout(timer); controller.abort(); };
}, [text, selected]);
return <div>
<label>Address <input value={text} onChange={e => { setSelected(null); setText(e.target.value); }} /></label>
<ul>{suggestions.map(item => <li key={item.id}>
<button type="button" onClick={() => { setText(item.label); setSelected(item); }}>{item.label}</button>
</li>)}</ul>
{error && <p role="alert">{error}</p>}
{address && <output>{address.label}: {address.lat}, {address.lon}</output>}
</div>;
}
Reference
| Constructor option | Behavior |
|---|---|
baseUrl | Required HTTPS base URL (origin or deployment prefix); HTTP allowed only for localhost/loopback. |
token | Required public pub_ token. Server keys are rejected by this facade. |
fallbacks | Ordered { provider: 'google' or 'here', apiKey } public provider keys. Default empty. |
fallbackOn | Shared SDK triggers; defaults to empty results, low confidence, timeout, network and server errors. Country routing is independent. |
fallbackBudget | { maxRequests } lifetime backup request limit. Default unlimited. |
fallbackCircuitBreaker | { failureThreshold, cooldownMs } per-provider circuit settings. |
timeoutMs | Per-request timeout, including reading the body; default 10,000 ms. |
fetch | Optional injected Fetch implementation. Requests use CORS and omit cookies. |
autocomplete({ q, country, near?, limit?, exclude_sources?, sessionToken? }, { signal? }) returns the standard SDK suggestion response with provider, fallbackReason and attempts. country accepts normalized names/codes; CA, US and MX use primary; other countries route directly to configured fallbacks. limit is 1–10, default 5. near is { lat, lon }. Empty matches return an empty suggestion list; there are no automatic retries. Configured fallbacks follow the shared SDK policy.
resolveAutocomplete(suggestion, { signal? }) returns one normalized address without an envelope or attempt history. Primary and configured provider suggestions are accepted; an unresolved ID raises GeocodingError with code provider_error. Errors retain code, status and provider as in the server SDK. Aborted calls propagate the abort reason. An HTTP 403 is terminal in every SDK and cannot trigger a provider fallback. Autocomplete searches and successful resolution are separate metered requests; preflights are not metered. See usage and billing.
Python (blocking and async) and PHP retain their backend clients, naming, timeouts and serialization. The new browser entry point shares TypeScript's existing autocomplete normalization and validation. No additional HTTP JSON fields are introduced.