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.
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.
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.
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.
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.
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
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).
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).
*/
/**
* @returnarray{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 ($candidatesas$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>
For Microsoft stacks (.NET 6+). Same widget HTML on any .cshtml or Razor page; point data-api-base at /api/aica-proxy. Legacy .aspx sites can host the same endpoint via ASP.NET Core or reverse-proxy to PHP.
// ASP.NET Core minimal API proxy — same role as api/aica-proxy.php (IIS / Azure / Windows hosts).
// Add to Program.cs. Store secrets in appsettings or environment variables.
//
// Widget HTML (any .cshtml / .aspx page):
// <divid="aica-address"data-api-base="/api/aica-proxy"></div>
//
// Env / appsettings:
// AICA:ApiKey, AICA:ApiBase, AICA:SiteOrigin
using System.Net.Http.Headers;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var apiKey = builder.Configuration["AICA:ApiKey"] ?? "";
var apiBase = (builder.Configuration["AICA:ApiBase"] ?? "https://aica-apis.quebecstore.ca/v1/address/autocomplete/canada").TrimEnd('/');
var siteOrigin = (builder.Configuration["AICA:SiteOrigin"] ?? "").TrimEnd('/');
app.MapGet("/api/aica-proxy/{**path}", async (HttpContext ctx, string? path) =>
{
if (string.IsNullOrEmpty(apiKey))
return Results.Json(new { error = "Proxy not configured" }, statusCode: 503);
if (string.IsNullOrEmpty(siteOrigin))
return Results.Json(new { error = "Set AICA:SiteOrigin" }, statusCode: 503);
var suffix = string.IsNullOrEmpty(path) ? "" : "/" + path.TrimStart('/');
var allowed = new[] { "", "/session-complete", "/units" };
if (!allowed.Contains(suffix) && !suffix.StartsWith("/units", StringComparison.Ordinal))
return Results.Json(new { error = "Not found" }, statusCode: 404);
var query = ctx.Request.QueryString.HasValue ? ctx.Request.QueryString.Value : "";
var target = $"{apiBase}{suffix}{query}";
using var client = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Get, target);
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
req.Headers.Add("X-AICA-API-Key", apiKey);
req.Headers.Add("Origin", siteOrigin);
if (ctx.Request.Headers.TryGetValue("X-AICA-Session-Id", out var sid))
req.Headers.Add("X-AICA-Session-Id", sid.ToString());
if (ctx.Request.Headers.TryGetValue("X-AICA-Interaction-Complete", out var done))
req.Headers.Add("X-AICA-Interaction-Complete", done.ToString());
var res = await client.SendAsync(req);
var body = await res.Content.ReadAsStringAsync();
return Results.Content(body, "application/json", statusCode: (int)res.StatusCode);
});
app.Run();
Point the widget at your proxy
<%-- .aspx / Razor: same widget markup --%>
<divid="address-field"data-api-base="/api/aica-proxy"></div><scriptsrc="https://aica-apis.quebecstore.ca/v1/address/autocomplete/canada/interactive/aica-autocomplete.js"></script><script>AicaAutocomplete.mount("#address-field");</script>
cURL / raw HTTP
Raw HTTPS calls from any language. Never embed the key in front-end code.
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:
{
"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"
}
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.