API reference
Duplicate pre-check
POST /api/v1/engine/dupe-check: ask whether ICON would call a lead a duplicate, before you post it.
POST /api/v1/engine/dupe-checkOptional. Answers one thing: duplicate_likely, true or false. No score, no matched fields. Submit always runs ICON's full duplicate check, whatever the pre-check answered.
- Server to server only. The endpoint has no CORS: call it from your server, never from a web page.
- Rate limit: 30 per minute per key (bursts of up to 10), separate from your other limits.
- Body: at most 16 KB.
#Request
Send any of the lead's fields, raw (the same names as a lead), or hashed under hashed, or both. A hashed key wins over the same raw key. The more fields you send, the more accurate the answer.
| Field | Type | Required | Description |
|---|---|---|---|
offer_id | string (uuid) | — | Optional: one of your offers (for an offer-scoped policy). |
email | string | — | — |
phone | string | — | — |
first_name | string | — | — |
last_name | string | — | — |
zip | string | — | — |
address | string | — | — |
dob | string | — | YYYY-MM-DD or MM/DD/YYYY. |
hashed | object | — | — |
Raw:
curl -X POST 'https://iconroute.io/api/v1/engine/dupe-check' \
-H "Authorization: Bearer $ICON_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"email": "ada.lovelace@mail.test",
"phone": "(602) 555-2368",
"zip": "85004",
"last_name": "Lovelace"
}'Hashed:
{
"hashed": {
"email": "d6117306485ed0e50afab3ac871e98f81699151f30281527d63ff5f233656c69",
"phone": "2f83685e66d4cb4d1bcff5f422ff9b0d7ce748a6f3103269d1adad16ab2e279b",
"zip": "918abeeaae3a90ad25f3a5e45408af4cdd71ac92accc3638461dc2f76e39cc01"
}
}#Hashing
hashed.<key> is the lower-case hex SHA-256 of the key's normalized form, encoded as UTF-8. Normalize first, then hash. Each example below was computed by ICON's own normalizer:
| Key | Normalized form | Example |
|---|---|---|
email | Trimmed and lower-case. At gmail.com and googlemail.com the dots and any +tag are removed from the local part and the domain is gmail.com. | Jane.Doe+promo@GoogleMail.com → janedoe@gmail.com |
phone | The 10 US digits: no country code, no punctuation. | +1 (602) 555-2368 → 6025552368 |
last_name | Lower-case a–z only: accents removed (ł ø đ ð þ æ œ ß ı ħ ŧ written l o d d th ae oe ss i h t); spaces, hyphens and apostrophes dropped. | Núñez-O'Brien → nunezobrien |
zip | The 5 digits (a +4 is dropped). | 85001-1234 → 85001 |
dob | YYYY-MM-DD. Send birth_year with it when you hash a date of birth. | 04/07/1990 → 1990-04-07 |
birth_year | Four digits (from a year-born answer or the date of birth). | 1990-04-07 → 1990 |
grad_year | Four digits. | 2008 → 2008 |
ip | The client address: the first of a forwarded list; IPv4 as a dotted quad without leading zeros; IPv6 compressed and lower-case, an IPv4-mapped address written as IPv4. | 2001:0DB8:0000:0000:0000:0000:0000:0001 → 2001:db8::1 |
The SHA-256 of each normalized example above:
email d6117306485ed0e50afab3ac871e98f81699151f30281527d63ff5f233656c69
phone 2f83685e66d4cb4d1bcff5f422ff9b0d7ce748a6f3103269d1adad16ab2e279b
last_name 36a69f8b745a87f4314b2dbcb8906ab78c03d4e2595f6b694f7af49b31f51242
zip 918abeeaae3a90ad25f3a5e45408af4cdd71ac92accc3638461dc2f76e39cc01
dob 8b44df2e1ab8ebe9eb4704090cf1524944141f66c7ab6dbed65391681a0d4878
birth_year a7be8e1fe282a37cd666e0632b17d933fa13f21addf4798fc0455bc166e2488c
grad_year e5e53c784d5d49de1cabb6e904bf3380026aadcb9769775a268dd304dd9aa2df
ip 5afd19e856d1c18d17d600dfd2b5f534992333985e126c2a951047102c1ed536Send the first name, the street address and the consent certificate raw, under their usual lead names (first_name, address, trusted_form_cert_url or universal_leadid): their matching forms are ICON's.
import { createHash } from 'node:crypto';
const sha256 = (value) => createHash('sha256').update(value, 'utf8').digest('hex');
// Normalize first (see the table), then hash.
const body = {
hashed: {
email: sha256('janedoe@gmail.com'),
phone: sha256('6025552368'),
zip: sha256('85001'),
},
};import hashlib
def sha256(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()
body = {"hashed": {"email": sha256("janedoe@gmail.com")}}#Response
{
"code": "OK",
"status": "accepted",
"reason": "OK",
"success": true,
"duplicate_likely": false,
"retryable": false
}duplicate_likely: true means a submit of this lead would very likely be refused as DUPLICATE. false is not a promise: the submit's own check decides.
#Errors
| HTTP | Meaning |
|---|---|
| 400 | MALFORMED_REQUEST: no usable lead field was sent (raw or hashed); INVALID_FIELD: offer_id is not a UUID. |
| 401 | AUTH_REQUIRED (no API key) or AUTH_INVALID (a key ICON does not know); status invalid. |
| 403 | FORBIDDEN: the offer_id is not yours, or this key is not permitted to use the pre-check. |
| 404 | Not found: the pre-check is not available on your account; ask your ICON contact. |
| 405 | MALFORMED_REQUEST: use POST. |
| 413 | The body is over 16 KB. |
| 429 | RATE_LIMITED (retryable): this key is over its pre-check limit. Wait the Retry-After seconds and retry. |
| 503 | UNAVAILABLE (retryable): the check could not run. Retry, or post the lead: submit runs the full check. |
A 404 answers a plain {"error": "Not found"} body.