> ## Documentation Index
> Fetch the complete documentation index at: https://docs.loqate.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Address Capture Implementation Guide

> Add real-time address autocomplete to your website with copy-paste code examples

export const GistEmbed = ({url, height = "500px"}) => {
  const [htmlContent, setHtmlContent] = useState('');
  const [copied, setCopied] = useState(false);
  const [rawCode, setRawCode] = useState('');
  const [isDark, setIsDark] = useState(true);
  useEffect(() => {
    const checkTheme = () => {
      const htmlElement = document.documentElement;
      const computedStyle = window.getComputedStyle(htmlElement);
      const colorScheme = computedStyle.getPropertyValue('color-scheme');
      const isDarkMode = htmlElement.classList.contains('dark') || htmlElement.getAttribute('data-theme') === 'dark' || colorScheme.includes('dark') || window.matchMedia('(prefers-color-scheme: dark)').matches;
      setIsDark(isDarkMode);
    };
    checkTheme();
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
    mediaQuery.addEventListener('change', checkTheme);
    const observer = new MutationObserver(checkTheme);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class', 'data-theme', 'style']
    });
    return () => {
      mediaQuery.removeEventListener('change', checkTheme);
      observer.disconnect();
    };
  }, []);
  useEffect(() => {
    fetch(url).then(r => r.text()).then(code => {
      setRawCode(code);
      const bgColor = isDark ? '#0a0c0e' : '#ffffff';
      const textColor = isDark ? '#c9d1d9' : '#24292e';
      const scrollTrack = isDark ? '#161b22' : '#f6f8fa';
      const scrollThumb = isDark ? '#30363d' : '#d1d5da';
      const scrollThumbHover = isDark ? '#484f58' : '#959da5';
      const iframe = `
          <!DOCTYPE html>
          <html>
            <head>
              <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
              <style>
                body {
                  margin: 0;
                  padding: 0;
                  background: ${bgColor};
                  overflow: auto;
                }
                pre {
                  margin: 0;
                  background: transparent !important;
                }
                code {
                  font-family: 'JetBrains Mono', JetBrains Mono, monospace !important;
                  font-size: 14px !important;
                  line-height: 1.7 !important;
                  background: transparent !important;
                  padding: 0 !important;
                }
                .hljs {
                  background: transparent !important;
                  color: ${textColor} !important;
                }
                ::-webkit-scrollbar {
                  width: 12px;
                  height: 12px;
                }
                ::-webkit-scrollbar-track {
                  background: ${scrollTrack};
                  border-radius: 10px;
                }
                ::-webkit-scrollbar-thumb {
                  background: ${scrollThumb};
                  border-radius: 10px;
                  border: 2px solid ${scrollTrack};
                }
                ::-webkit-scrollbar-thumb:hover {
                  background: ${scrollThumbHover};
                }
                * {
                  scrollbar-width: thin;
                  scrollbar-color: ${scrollThumb} ${scrollTrack};
                }
              </style>
            </head>
            <body>
              <pre><code class="language-html">${escapeHtml(code)}</code></pre>
              <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
              <script>hljs.highlightAll();</script>
            </body>
          </html>
        `;
      setHtmlContent(iframe);
    }).catch(e => {
      setRawCode('Error loading code');
      setHtmlContent('<pre>Error loading code</pre>');
    });
  }, [url, isDark]);
  const escapeHtml = text => {
    return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
  };
  const copyToClipboard = () => {
    navigator.clipboard.writeText(rawCode);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };
  const containerBg = isDark ? '#0a0c0e' : '#ffffff';
  const containerBorder = isDark ? '#30363d' : '#e1e4e8';
  return <div style={{
    position: 'relative',
    marginBottom: '1.5rem',
    marginTop: '1.5rem',
    background: containerBg,
    borderRadius: '12px',
    border: `1px solid ${containerBorder}`,
    padding: '16px 20px'
  }}>
      <button onClick={copyToClipboard} style={{
    position: 'absolute',
    top: '12px',
    right: '32px',
    padding: '8px',
    background: 'rgba(255, 255, 255, 0.08)',
    backdropFilter: 'blur(4px)',
    border: '1px solid rgba(255, 255, 255, 0.1)',
    borderRadius: '6px',
    cursor: 'pointer',
    fontSize: '13px',
    fontWeight: '500',
    color: 'rgba(255, 255, 255, 0.8)',
    zIndex: 10,
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    transition: 'all 0.15s',
    fontFamily: 'system-ui, -apple-system, sans-serif',
    width: '32px',
    height: '32px'
  }} onMouseEnter={e => {
    e.currentTarget.style.background = 'rgba(255, 255, 255, 0.12)';
  }} onMouseLeave={e => {
    e.currentTarget.style.background = 'rgba(255, 255, 255, 0.08)';
  }}>
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          {copied ? <polyline points="20 6 9 17 4 12" /> : <>
              <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
              <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
            </>}
        </svg>
      </button>
      <iframe srcDoc={htmlContent} style={{
    width: '100%',
    height: height,
    border: 'none',
    overflow: 'auto',
    background: 'transparent'
  }} title="Code Example" />
    </div>;
};

**Reduce address entry errors and improve data quality with real-time address autocomplete as users type.**

## Who Should Use This Guide

**For:** Business owners and developers integrating address autocomplete without complex setup.

**You'll need:** A Loqate API key and basic HTML knowledge.

**Not covered:** Tag-based integrations ([see Tag Setup](/our-services/tag-setup-guide)) or pre-built platform integrations ([see Integrations](/integrations/all-integrations)).

## What You'll Build

An address autocomplete form that:

* Shows address suggestions as users type
* Fills in complete address details when selected
* Works with addresses in over 250 countries
* Provides formatted address lines for easy form population

## Quick Start

To set up Address Capture, you'll need an API key. Find out how to get yours [here](/loqate-basics/create-an-api-key/).

### Choose Your Approach

<Tabs>
  <Tab title="Standalone Form">
    **Use case:** Testing the API or adding to a static page

    A self-contained HTML file you can copy and use immediately.

    Jump to [standalone HTML example](#standalone-html-example)
  </Tab>

  <Tab title="Integrate with Existing Form">
    **Use case:** Add autocomplete to your current checkout or registration forms

    Step-by-step guide to add autocomplete code to forms you already have.

    Jump to [integration example](#integration-example)
  </Tab>
</Tabs>

**Using Shopify or WordPress?** Check [pre-built integrations](/integrations/all-integrations).

### Add Your Key

Replace `YOUR_API_KEY` in the code with your actual Loqate API key.

### Test

1. Open your page
2. Start typing an address like "10 Downing"
3. Select from the suggestions
4. Complete address populates automatically

<Note>
  Address Capture supports over 250 countries and territories. Use the Countries parameter to restrict results to specific countries.
</Note>

***

## Sample Project: Checkout Form Integration

This walkthrough shows how to add address autocomplete to a checkout form. Follow along to see a basic form transform into an intelligent address capture form.

<Note>
  This is a detailed walkthrough. If you just want working code, skip to [Integration Example](#integration-example) or [Standalone HTML Example](#standalone-html-example).
</Note>

### Before: Manual Address Entry

A checkout form with manual address entry:

<GistEmbed url="https://raw.githubusercontent.com/loqate/javascript-docs-sample/refs/heads/main/address-capture/start/basic-checkout-form.html" height="450px" />

**This form works, but users must type complete addresses manually.** Let's add autocomplete.

***

## How Address Capture Works

Address Capture uses a two-step process:

### 1. Find Step

**What it does:** Searches for addresses as users type.

**When to use:** On every keystroke or with debouncing (recommended).

**What you get:** A list of matching addresses and containers (groups of addresses).

**API endpoint:** `https://api.addressy.com/Capture/Interactive/Find/v1.10/json6.ws`

**Key parameters:**

* `Text` - The search term (what the user typed)
* `Container` - Optional ID for drilling down into results
* `Countries` - Optional filter (e.g., "GBR" for UK only)

### 2. Retrieve Step

**What it does:** Gets the complete address details.

**When to use:** When a user selects an address from the suggestions.

**What you get:** Full address in multiple formats (lines, elements, label).

**API endpoint:** `https://api.addressy.com/Capture/Interactive/Retrieve/v1.00/json6.ws`

**Key parameter:**

* `Id` - The unique address identifier from Find results

### How They Work Together

```
User types "10 Dow"
    ↓
Find API returns suggestions
    ↓
User selects "10 Downing Street"
    ↓
Retrieve API returns complete address
    ↓
Form fields populate automatically
```

***

## Standalone HTML Example

A minimal address autocomplete form for quick testing:

<GistEmbed url="https://raw.githubusercontent.com/loqate/javascript-docs-sample/refs/heads/main/address-capture/finish/simple-address-capture.html" height="450px" />

**Perfect for:**

* Quick API testing
* Learning how address autocomplete works
* Simple integrations without checkout flows

**To use:**

1. Copy the code
2. Replace `YOUR_API_KEY`
3. Save and test with different addresses

<Note>
  This example searches all countries by default. Add a Countries parameter to restrict results.
</Note>

## Code on GitHub

If you'd like to browse the code snippets in this implementation guide or clone them, visit the repository on [GitHub](https://github.com/loqate/javascript-docs-sample/tree/main/address-capture).

## Integration Example

Here's the checkout form with full address autocomplete integrated:

<GistEmbed url="https://raw.githubusercontent.com/loqate/javascript-docs-sample/refs/heads/main/address-capture/finish/complete-checkout-with-address-capture.html" height="700px" />

**To use this code:**

1. Copy the code
2. Replace `YOUR_API_KEY` with your actual Loqate API key
3. Save as an HTML file
4. Open in a browser to test
5. Customize styling to match your brand

**Key features in this example:**

* Real-time address suggestions as users type
* Debounced API calls to reduce requests
* Automatic form population on selection
* Support for multiple countries
* Clean, production-ready code structure

<Note>
  This example uses debouncing (300ms delay) to reduce API calls while users type. Adjust the delay based on your needs.
</Note>

***

## Integration Tips

### Adding to Your Existing Forms

To add address autocomplete to your current forms:

1. **Add the autocomplete functions** from the integration example to your page's `<script>` section
   * `findAddress()` for searching
   * `retrieveAddress()` for getting full details

2. **Add the dropdown styles** from the integration example to your page's `<style>` section
   * Positions suggestions below the input
   * Provides hover and selection states

3. **Add the suggestion dropdown element** next to your address input field
   * `<div id="suggestions" class="suggestions"></div>`

4. **Add the event listeners** to trigger search and handle selection
   * Input event with debouncing for Find
   * Click event on suggestions for Retrieve

Reference the [integration example](#integration-example) above for all the code you need.

### Debouncing API Calls

Debouncing delays API calls until the user stops typing, reducing unnecessary requests:

```javascript theme={null}
let debounceTimer;

addressInput.addEventListener('input', function(e) {
    const searchTerm = e.target.value;
    
    clearTimeout(debounceTimer);
    
    if (searchTerm.length < 3) {
        hideSuggestions();
        return;
    }
    
    debounceTimer = setTimeout(async () => {
        const results = await findAddress(searchTerm);
        showSuggestions(results);
    }, 300); // Wait 300ms after last keystroke
});
```

**Recommended delay:** 300-500ms balances responsiveness with API efficiency.

### Restricting to Specific Countries

Limit results to one or more countries using ISO codes:

```javascript theme={null}
async function findAddress(text, container = '') {
    const params = new URLSearchParams({
        Key: apiKey,
        Text: text,
        Container: container,
        Countries: 'GBR,USA' // UK and US only
    });
    
    const response = await fetch(
        `https://api.addressy.com/Capture/Interactive/Find/v1.10/json6.ws?${params}`
    );
    
    return response.json();
}
```

**Common country codes:**

* `GBR` - United Kingdom
* `USA` - United States
* `CAN` - Canada
* `AUS` - Australia
* `FRA` - France

### Handling Container Results

Some Find results are "containers" (groups of addresses) that need another Find call:

```javascript theme={null}
if (result.Type === 'Address') {
    // Final address - retrieve full details
    const address = await retrieveAddress(result.Id);
    populateForm(address);
} else {
    // Container - search again with this as Container parameter
    const moreResults = await findAddress(result.Text, result.Id);
    showSuggestions(moreResults);
}
```

### Populating Form Fields

Map the Retrieve response to your form fields using the Lines format (recommended):

```javascript theme={null}
function populateForm(address) {
    document.getElementById('company').value = address.Company || '';
    document.getElementById('addressLine1').value = address.Line1 || '';
    document.getElementById('addressLine2').value = address.Line2 || '';
    document.getElementById('city').value = address.City || '';
    document.getElementById('postalCode').value = address.PostalCode || '';
    document.getElementById('country').value = address.CountryName || '';
}
```

**Available formats:**

* **Lines** (recommended) - `Line1`, `Line2`, `City`, `PostalCode`
* **Elements** - `BuildingNumber`, `Street`, `District`
* **Label** - Single formatted string with `\n` line breaks

### Adding Custom Data

Get additional fields like latitude/longitude using Field parameters:

```javascript theme={null}
const params = new URLSearchParams({
    Key: apiKey,
    Id: addressId,
    Field1Format: '{Latitude}',
    Field2Format: '{Longitude}'
});

const response = await fetch(
    `https://api.addressy.com/Capture/Interactive/Retrieve/v1.00/json6.ws?${params}`
);

const data = await response.json();
// Access via data.Items[0].Field1 and Field2
```

### Styling the Dropdown

Customize the suggestions dropdown to match your design:

```css theme={null}
.suggestions {
    position: absolute;
    background: white;
    border: 1px solid #ddd;
    border-top: none;
    max-height: 300px;
    overflow-y: auto;
    z-index: 1000;
    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}

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

.suggestion:hover {
    background: #f5f5f5;
}

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

.suggestion-description {
    font-size: 12px;
    color: #666;
}
```

***

## Troubleshooting

### Invalid API Key

Verify you've replaced `YOUR_API_KEY` with your actual key from your Loqate account.

### No Suggestions Appearing

**Check:**

* Search term is at least 3 characters (recommended minimum)
* You have available credits in your account
* API key hasn't been restricted by domain, IP, or rate limits (check account settings)
* Browser console for errors
* Network tab shows successful API calls

### Suggestions Not Closing

Ensure you handle clicks outside the dropdown:

```javascript theme={null}
document.addEventListener('click', function(e) {
    if (!e.target.closest('.address-field') && 
        !e.target.closest('.suggestions')) {
        hideSuggestions();
    }
});
```

### Address Not Populating Form

**Check:**

* You're calling `retrieveAddress()` with the correct `Id` from Find results
* Result type is "Address" (not "Container")
* Form field IDs match the JavaScript selectors
* Response contains expected fields (check browser console)

### Dropdown Positioning Issues

Ensure the parent container has `position: relative`:

```css theme={null}
.address-field-container {
    position: relative;
}

.suggestions {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
}
```

### CORS Errors

The examples must be hosted on a web server, not opened as local `file://` URLs. Upload to your website or use a development server like Python's `http.server` or Node's `http-server`.

***

## Data Privacy

* Addresses searched through Loqate's API
* Stored in infrastructure logs for 30 days for operational purposes
* Results returned immediately
* See [Privacy Policy](https://www.loqate.com/en-gb/legal/privacy-policy/) for complete details

***

## Support

* [Find Endpoint API Reference](/api-reference/address-capture/find)
* [Retrieve Endpoint API Reference](/api-reference/address-capture/retrieve)
* [Contact](https://www.loqate.com/en-gb/contact/customer-support/)

***

## Related

* [API Security](/loqate-basics/api-security)
* [Pricing](https://www.loqate.com/en-gb/pricing/)
