Loading…

Documentation

AICA Address Autocomplete

Canadian address autocomplete, validation, and geocoding over HTTPS. Pick your stack, keep production keys safe, and ship.

How do you want to integrate?

Overview

The AICA Address Autocomplete API returns structured Canadian address suggestions as users type. Use it from your backend or via our hosted embed widget. All production traffic must use HTTPS.

Base URL:

https://aica-apis.quebecstore.ca/v1/address/autocomplete/canada

Misconfigured or unknown routes return JSON that includes service status (status, service, note) plus error_code — no separate health page.

Authentication

API requests use your aica_live_ key. For the widget, the key is used in the browser and must be domain-restricted. For server integrations, keep the key only on your backend.

X-AICA-API-Key: aica_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Authorization: Bearer aica_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

URL-style query key (server / backend)

You can also pass the key as ?api_key= on a standard GET URL. Prefer the X-AICA-API-Key header when possible. Use a server-purpose key only — never put it in browser pages. Keys in URLs can appear in proxy logs and browser history.

https://aica-apis.quebecstore.ca/v1/address/autocomplete?search=123%20main&api_key=YOUR_API_KEY
Missing key → HTTP 401, AICA-E002. Error codes.

Widget = browser key + domain allowlist (AddressComplete-style). Server = keep key secret. Prefer separate keys for each purpose.

Embed widget

Drop one CDN script and one empty div. Put your aica_live_ key on the div (data-api-key). Allowlist your domain in the dashboard — no customer proxy PHP required.

1. One-script install (recommended)

Add this single script in your page head (or before the closing body tag). aica-loader.js injects CSS once and loads the widget. No aica-proxy.php. Restrict the key to your domain(s) in API key management.

<script src="https://aica-apis.quebecstore.ca/v1/address/autocomplete/canada/interactive/aica-loader.js" async></script>

1.A Mount the search field

Place this empty div where the address search UI should appear (checkout, billing, contact form). Put your aica_live_ key on data-api-key. The loader from step 1 finds every data-aica-autocomplete div automatically.

<div id="address-field"
     data-aica-autocomplete
     data-api-key="aica_live_YOUR_KEY_HERE"></div>

Sample page: checkout.html

Full working checkout layout using the recommended install (loader + mount + autofill fields). View the colored source or download the file.

2. Classic install (optional — more URLs)

Explicit CSS + JS URLs + mount() — pass apiKey in options. Use this only if you prefer not to use aica-loader.js (e.g. CSP-strict sites).

<link rel="stylesheet" href="https://aica-apis.quebecstore.ca/v1/address/autocomplete/canada/interactive/aica-autocomplete.css" />
<script src="https://aica-apis.quebecstore.ca/v1/address/autocomplete/canada/interactive/aica-autocomplete.js"></script>
<script>AicaAutocomplete.mount("#address-field", { apiKey: "aica_live_YOUR_KEY_HERE" });</script>

Auto-fill form fields

After the visitor selects a final address, AICA can fill your existing inputs so it looks like they typed the address. No custom JSON mapping required. Use data-aica-field tokens, or matching name/id aliases.

Supported field tokens

result / address / full_address
street_line / street / address1
building_line / building
unit / apt / suite
city
postal_code / postal / zip
province / provice_abbr / state
addr_id
lat / lon
city_prov
<form id="checkout">
  <div id="address-field"
       data-aica-autocomplete
       data-api-key="aica_live_YOUR_KEY_HERE"
       data-aica-fields-root="#checkout"></div>
  <input name="street" data-aica-field="street_line">
  <input name="city" data-aica-field="city">
  <input name="postal_code">
  <input name="province" data-aica-field="province">
</form>
<script src="https://aica-apis.quebecstore.ca/v1/address/autocomplete/canada/interactive/aica-loader.js" async></script>

Autofill writes into form fields only — it does not search using those fields. Advanced sites can still use onSelect JSON for custom mapping.

Widget language

The search placeholder and unit-count hint follow the host page first: set or (regional tags like fr-CA and en-US count). If the page does not declare a language, the widget uses the visitor's browser language (navigator.languages). If neither is French or English, it defaults to English. Best practice: always set lang on the document root so every visitor of that site sees the same placeholder.

URL selection privacy

By default the widget also writes ?aica_selection= (base64 JSON) into the page URL. That value contains address data — avoid sharing those links, or clear the param after you save the address.

Troubleshooting

If the widget looks unstyled (no CSS), the box is empty, or typing never calls the API, fix it on your site first:

  • Use a <script src="…"> tag (or aica-loader.js). Do not iframe, <embed>, or <object> our pages. AICA HTML sends X-Frame-Options: DENY, so an iframe stays blank.
  • Your Content-Security-Policy, WAF, or “allowed domains” list must let the page talk to https://aica-apis.quebecstore.ca — scripts, CSS, images, and API requests (script-src, style-src, img-src, and connect-src). Blocking that host = no styles and no autocomplete calls.
  • In the AICA dashboard, add the exact domain of the page that hosts the widget on that widget API key (example.com and www.example.com if you use both).
Content-Security-Policy:
  script-src 'self' https://aica-apis.quebecstore.ca;
  style-src 'self' https://aica-apis.quebecstore.ca;
  img-src 'self' https://aica-apis.quebecstore.ca;
  connect-src 'self' https://aica-apis.quebecstore.ca;

Point the widget at your proxy — Backend by stack

Try live demo

Backend by stack

Never expose your API key in HTML or browser JavaScript. Pick your stack below — each path keeps the key on the server and points the widget at your proxy.

Direct server-side API calls (no browser-facing proxy) are the most secure: there is no public URL for abusers to hit. Use a widget proxy only when you need front-end autocomplete.

PHP setup

1. aica-config.php

Place next to aica-proxy.php (e.g. public_html/api/). Set api_key and site_origin to your live site (https://yourdomain.com). In the dashboard add only yourdomain.com — www is added automatically.

<?php

declare(strict_types=1);

/**
 * AICA API credentials — server-side only. Never expose in HTML or JavaScript.
 *
 * WHERE TO PUT THIS FILE
 * ----------------------
 * Recommended: same folder as aica-proxy.php
 *   your-site/api/aica-config.php
 *   your-site/api/aica-proxy.php
 *
 * Also works (proxy searches automatically):
 *   your-site/aica-config.php
 *   your-site/config/aica-config.php
 *   or set env AICA_CONFIG_PATH=/full/path/to/aica-config.php
 *
 * WIDGET HTML (any page on your site)
 * -----------------------------------
 * <div id="aica-address" data-api-base="/api/aica-proxy.php"></div>
 *
 * The browser JS (hosted by AICA) reads data-api-base and calls:
 *   /api/aica-proxy.php?search=...
 *   /api/aica-proxy.php/units?parent_id=...
 *   /api/aica-proxy.php/session-complete
 *
 * site_origin — your public site URL. The proxy sends this to AICA (never the
 * browser Origin header) so callers cannot spoof your domain.
 */
return [
    'api_key' => 'class="tok-key">aica_live_REPLACE_WITH_YOUR_KEY',
    'api_base' => 'class="tok-url">https://aica-apis.quebecstore.ca/v1/address/autocomplete/canada',
    'site_origin' => 'class="tok-url">https://example.com',
];

2. aica-proxy.php

The widget JS calls this file via data-api-base. It loads config from the same folder and forwards requests to AICA with your key and fixed site_origin.

<?php

declare(strict_types=1);

/**
 * AICA Address Autocomplete — server-side proxy for the embed widget.
 *
 * UPLOAD
 * ------
 * Put this file on your PHP host, e.g. public_html/api/aica-proxy.php
 * Put aica-config.php in the same folder (or see loadAicaConfig() below).
 *
 * WIDGET → THIS FILE (browser, from your HTML)
 * --------------------------------------------
 * Your page includes:
 *   <link  href="class="tok-url">https://aica-apis.quebecstore.ca/.../aica-autocomplete.css" />
 *   <div   id="aica-address" data-api-base="/api/aica-proxy.php"></div>
 *   <script src="class="tok-url">https://aica-apis.quebecstore.ca/.../aica-autocomplete.js"></script>
 *   <script>AicaAutocomplete.mount('#aica-address');</script>
 *
 * The widget JS calls data-api-base + query string, for example:
 *   GET /api/aica-proxy.php?search=657
 *   GET /api/aica-proxy.php/units?parent_id=...
 *   GET /api/aica-proxy.php/session-complete
 *
 * Change data-api-base to match where YOU uploaded this file.
 * Examples:
 *   /api/aica-proxy.php
 *   /shop/api/aica-proxy.php
 *   class="tok-url">https://yourdomain.com/api/aica-proxy.php
 *
 * THIS FILE → aica-config.php (server only)
 * -----------------------------------------
 * Loads your API key from aica-config.php (never sent to the browser).
 *
 * DOMAIN TIP
 * ----------
 * In the AICA dashboard add only example.com — www.example.com is added automatically.
 *
 * SECURITY
 * --------
 * Set site_origin in aica-config.php. This proxy sends that fixed Origin to AICA
 * and ignores the browser Origin header (prevents domain spoofing).
 */

/**
 * @return array{api_key: string, api_base: string, site_origin?: string}
 */
function loadAicaConfig(): array
{
    $candidates = array_filter([
        getenv('AICA_CONFIG_PATH') ?: null,
        __DIR__ . '/aica-config.php',
        dirname(__DIR__) . '/aica-config.php',
        dirname(__DIR__) . '/config/aica-config.php',
        isset($_SERVER['DOCUMENT_ROOT']) ? rtrim((string) $_SERVER['DOCUMENT_ROOT'], '/') . '/config/aica-config.php' : null,
    ]);

    foreach ($candidates as $path) {
        if (is_file($path)) {
            $loaded = require $path;
            if (is_array($loaded)) {
                return $loaded;
            }
        }
    }

    return [
        'api_key' => getenv('AICA_API_KEY') ?: '',
        'api_base' => getenv('AICA_API_BASE') ?: 'class="tok-url">https://aica-apis.quebecstore.ca/v1/address/autocomplete/canada',
    ];
}

$config = loadAicaConfig();
$apiKey = (string) ($config['api_key'] ?? '');
$apiBase = rtrim((string) ($config['api_base'] ?? ''), '/');
$siteOrigin = rtrim((string) ($config['site_origin'] ?? ''), '/');

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    header('Access-Control-Allow-Origin: ' . ($_SERVER['HTTP_ORIGIN'] ?? '*'));
    header('Access-Control-Allow-Headers: X-AICA-Session-Id, X-AICA-Interaction-Complete, Content-Type');
    header('Access-Control-Allow-Methods: GET, OPTIONS');
    http_response_code(204);
    exit;
}

if ($apiKey === '' || str_contains($apiKey, 'REPLACE_WITH_YOUR_KEY')) {
    http_response_code(503);
    header('Content-Type: application/json');
    echo json_encode(['error' => 'Proxy not configured — edit api_key in aica-config.php next to this file']);
    exit;
}

if ($siteOrigin === '' || str_contains($siteOrigin, 'example.com')) {
    http_response_code(503);
    header('Content-Type: application/json');
    echo json_encode(['error' => 'Proxy not configured — set site_origin in aica-config.php to your site URL']);
    exit;
}

$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$suffix = '';

if ($script !== '' && str_contains($uri, $script)) {
    $suffix = substr($uri, strpos($uri, $script) + strlen($script));
}
$suffix = ($suffix === '' || $suffix === '/') ? '' : $suffix;

$allowed = ['', '/session-complete', '/units'];
if (!in_array($suffix, $allowed, true) && !str_starts_with($suffix, '/units')) {
    http_response_code(404);
    header('Content-Type: application/json');
    echo json_encode(['error' => 'Proxy path not found']);
    exit;
}

$query = $_SERVER['QUERY_STRING'] ?? '';
$target = $apiBase . $suffix . ($query !== '' ? '?' . $query : '');

$headers = [
    'Accept: application/json',
    'X-AICA-API-Key: ' . $apiKey,
    'Origin: ' . $siteOrigin,
];
foreach (['HTTP_X_AICA_SESSION_ID', 'HTTP_X_AICA_INTERACTION_COMPLETE'] as $h) {
    if (!empty($_SERVER[$h])) {
        $name = str_replace(' ', '-', ucwords(strtolower(str_replace('HTTP_', '', $h)), '_'));
        $headers[] = $name . ': ' . $_SERVER[$h];
    }
}

$ch = curl_init($target);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => $headers,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_FOLLOWLOCATION => false,
]);
$body = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

http_response_code($status > 0 ? $status : 502);
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: ' . ($_SERVER['HTTP_ORIGIN'] ?? '*'));
header('Access-Control-Allow-Headers: X-AICA-Session-Id, X-AICA-Interaction-Complete, Content-Type');
echo $body === false ? '{}' : $body;

3. Protect config (Apache)

Optional .htaccess so aica-config.php cannot be downloaded over HTTP.

# .htaccess in api/ — block direct HTTP access to config
<Files "aica-config.php">
    Require all denied
</Files>

4. Point the widget at your proxy

<div id="address-field" data-api-base="/api/aica-proxy.php"></div>

Endpoints

GET

Autocomplete search

Standard URL that returns JSON when called correctly (server key via ?api_key= or header):

GET /v1/address/autocomplete?search={query}&api_key={key}

Header auth alias (widget / proxy / cURL with X-AICA-API-Key):

GET /v1/address/autocomplete/canada?search={query}
GET

Building units

Use the building addr_id from autocomplete results. Both query names work:

Primary (on main autocomplete route)

GET /v1/address/autocomplete/canada?complex-search={building_addr_id}

Alias (dedicated units route)

GET /v1/address/autocomplete/canada/units?parent_id={building_addr_id}
GET

Address validation

Validate a Canadian address (Canada Post style or partial) and return the best official match with a confidence score. Prefer header auth; ?api_key= is for docs/testing only.

GET /v1/address/validate?address={address}

Standard URL that returns JSON when called correctly (server key via ?api_key= or header):

GET /v1/address/validate?address={address}&api_key={key}

Parameters: address (required). Accepts spaced or compact postal codes (G0W 2H0 / G0W2H0), missing commas, and abbreviated street types.

GET

Reverse geocode

Find the closest Canadian building address from latitude and longitude. Apartment unit rows are excluded — the building is returned.

GET /v1/address/reverse-geocode?lat={lat}&lon={lon}

Standard URL that returns JSON when called correctly (server key via ?api_key= or header):

GET /v1/address/reverse-geocode?lat={lat}&lon={lon}&api_key={key}

Parameters: lat and lon (required, full precision — do not round). Search expands 50m → 100m → 250m. Exact matches return one result; otherwise up to 3 closest.

POST

Session complete

POST /v1/address/autocomplete/canada/session-complete

Responses

While the user types, GET ?search=… returns suggestions:

{
  "results": [{ "id": "f6aefa64-f190-4b67-a1f0-3a2c39303dcb", "label": "1001-1080 BAY ST, TORONTO, ON, M5S 0A5" }],
  "meta": { "count": 1, "search": "1080 bay" }
}

Selected address (widget onSelect)

When the visitor picks an address, your onSelect callback receives the full record (JSON is not shown in the widget UI):

{
  "result": "1001-1080 BAY ST, TORONTO, ON, M5S 0A5",
  "other_meta": {
    "addr_id": "f6aefa64-f190-4b67-a1f0-3a2c39303dcb",
    "is_address_building": false,
    "unit_count": 0,
    "unit": "1001",
    "building_line": "1080 BAY ST",
    "relation_complex": "c45f2597-88a2-4b3d-bfa8-73541f98a845",
    "full_addr": "1001-1080 BAY ST",
    "street_line": "1001-1080 BAY ST",
    "mail_line": "TORONTO ON  M5S 0A5",
    "city": "TORONTO",
    "pruid": 35,
    "postal_code": "M5S 0A5",
    "provice": "Ontario",
    "provice_abbr": "ON",
    "location": {
      "lat": 43.666825,
      "lon": -79.388364
    },
    "display_line": "1001-1080 BAY ST, TORONTO, ON, M5S 0A5"
  }
}

Validation response

When confidence is high and one match clearly wins:

{
  "status": "success",
  "validated": true,
  "confidence": 100,
  "input": "24 RUE UINISHK MASHTEUIATSH QC G0W 2H0",
  "match": {
    "address": "24 RUE UINISHK",
    "city": "MASHTEUIATSH",
    "province": "QC",
    "postal_code": "G0W 2H0",
    "addr_id": "38d303dc-70a1-4b6f-8f2d-3c753cf0490a"
  },
  "alternatives": [
    {
      "address": "1815 RUE NISHK",
      "city": "MASHTEUIATSH",
      "province": "QC",
      "postal_code": "G0W 2H0",
      "addr_id": "cf88d74a-011c-4eda-bba3-0f0092c1509f"
    },
    {
      "address": "1740 RUE NISHK",
      "city": "MASHTEUIATSH",
      "province": "QC",
      "postal_code": "G0W 2H0",
      "addr_id": "b5a0c7bd-a9b3-4d3f-a6aa-b79b5623c83a"
    }
  ]
}

When several addresses score similarly, validation is not automatic:

{
  "status": "success",
  "validated": false,
  "confidence": 72,
  "input": "24 UINISH",
  "reason": "multiple_matches",
  "choices": [
    {
      "address": "24 RUE UINISHK",
      "city": "MASHTEUIATSH",
      "province": "QC",
      "postal_code": "G0W 2H0",
      "addr_id": "38d303dc-70a1-4b6f-8f2d-3c753cf0490a"
    }
  ]
}

Reverse geocode response

Coordinates are never rounded. Results include distance in meters and building unit metadata when available.

{
  "status": "success",
  "type": "reverse-geocode",
  "query": {
    "lat": 43.642085,
    "lon": -79.374717
  },
  "results": [
    {
      "confidence": 99,
      "distance_meters": 0,
      "address": "1 YONGE ST",
      "city": "TORONTO",
      "province": "ON",
      "postal_code": "M5E 1E5",
      "coordinates": {
        "lat": 43.642085,
        "lon": -79.374717
      },
      "addr_id": "c7073342-4d12-4bfc-9baf-7277310d8c62",
      "building": {
        "has_units": true,
        "unit_count": 22
      }
    }
  ]
}

Error

{
  "status": "ok",
  "service": "AICA Address Autocomplete",
  "message": "Hi there, if you are seeing this it means your API request was misconfigured...",
  "documentation": "https://aica-apis.quebecstore.ca/aica-adresse-auto-complete/documentation",
  "instruction": "Use GET /v1/address/autocomplete/canada?search=YOUR_QUERY...",
  "autocomplete": "/v1/address/autocomplete/canada?search=",
  "note": "Service is reachable. See documentation for authentication...",
  "error_reason": "misconfigured api-call",
  "error_code": "AICA-E001"
}

Misconfigured or unknown routes return JSON that includes service status (status, service, note) plus error_code — no separate health page.

{
  "error": "The API key is invalid or unknown.",
  "error_reason": "invalid or unknown api-key",
  "error_code": "AICA-E003"
}

Quick cURL examples

URL-style GET (JSON)

-keyword">curl -sG "-url">https://aica-apis.quebecstore.ca/v1/address/autocomplete" \
  --data-urlencode "search=123 main" \
  --data-urlencode "api_key=$AICA_API_KEY"
https://aica-apis.quebecstore.ca/v1/address/validate?address=24%20RUE%20UINISHK%20MASHTEUIATSH%20QC%20G0W%202H0&api_key=YOUR_API_KEY
https://aica-apis.quebecstore.ca/v1/address/reverse-geocode?lat=43.642085&lon=-79.374717&api_key=YOUR_API_KEY

Header auth alias (widget / proxy / cURL with X-AICA-API-Key):

-keyword">curl -sG "-url">https://aica-apis.quebecstore.ca/v1/address/autocomplete/canada" \
  --data-urlencode "search=123 main" \
  -H "X-AICA-API-Key: $AICA_API_KEY"
-keyword">curl -sG "-url">https://aica-apis.quebecstore.ca/v1/address/autocomplete/canada" \
  --data-urlencode "complex-search=BUILDING_ADDR_ID" \
  -H "X-AICA-API-Key: $AICA_API_KEY"
-keyword">curl -sG "-url">https://aica-apis.quebecstore.ca/v1/address/validate" \
  --data-urlencode "address=24 RUE UINISHK MASHTEUIATSH QC G0W2H0" \
  -H "X-AICA-API-Key: $AICA_API_KEY"
-keyword">curl -sG "-url">https://aica-apis.quebecstore.ca/v1/address/reverse-geocode" \
  --data-urlencode "lat=43.642085" \
  --data-urlencode "lon=-79.374717" \
  -H "X-AICA-API-Key: $AICA_API_KEY"

Error codes

CodeHTTP
AICA-E002401Missing API key
AICA-E003401Invalid API key
AICA-E004403Origin not allowed
AICA-E005429Monthly quota exceeded
AICA-E016429Rate limit exceeded

Session billing

Usage is billed per interaction session, not per HTTP request.

Quota reset: 1st of each month at midnight Quebec time.

Usage logs

Each API request is logged in your dashboard under API Usage. Two fields help you understand what happened:

  • Search query — Search query — what the user typed (e.g. h0h 0h0).
  • Results / Response — Results / Response — a short summary of what was returned.

When the user selects a final address, the log shows a readable line such as SANTA CLAUS, NORTH POLE, H0H 0H0.

LONG RESPONSE means the API returned more than one address in a single response. We do not store the full JSON list — only that multiple results were returned. This includes building complexes where many unit sub-addresses exist; the individual apartment line appears only after the user picks a unit.

In the dashboard, long previews and LONG RESPONSE rows show a View button. Click it to open a centered panel with the stored preview (and an explanation for LONG RESPONSE).

When exactly one address is returned and the user has not finished selecting yet, a single-line preview may appear instead.

Origin allowlist

Domain restrictions per key — AICA-E004.

Plans & limits

PlanMonthly interactionsAPI keysDomainsOverage
Free 100 1 3 No
Starter 1,000 5 5 Yes
Pro 3,000 10 10 Yes
Pro Plus 7,000 15 15 Yes
Business 20,000 30 30 Yes
Enterprise 50,000 45 45 Yes
Plan/ second/ minute/ hour
Free 2 60 100
Starter 10 300 1,000
Pro 6 180 3,000
Pro Plus 8 225 7,000
Business 13 400 20,000
Enterprise 17 500 50,000

Quota exceeded → AICA-E005. Rate limit → AICA-E016 (HTTP 429).

Support

If your system uses our APIs and you encounter issues, contact Quebecstore before modifying integration code.

[email protected] · Contact