ICON Developer Center

API reference

Duplicate pre-check

POST /api/v1/engine/dupe-check: ask whether ICON would call a lead a duplicate, before you post it.

http
POST /api/v1/engine/dupe-check

Optional. 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.

FieldTypeRequiredDescription
offer_idstring (uuid)—Optional: one of your offers (for an offer-scoped policy).
emailstring——
phonestring——
first_namestring——
last_namestring——
zipstring——
addressstring——
dobstring—YYYY-MM-DD or MM/DD/YYYY.
hashedobject——

Raw:

bash
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:

json
{
  "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:

KeyNormalized formExample
emailTrimmed 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
phoneThe 10 US digits: no country code, no punctuation.+1 (602) 555-2368 → 6025552368
last_nameLower-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
zipThe 5 digits (a +4 is dropped).85001-1234 → 85001
dobYYYY-MM-DD. Send birth_year with it when you hash a date of birth.04/07/1990 → 1990-04-07
birth_yearFour digits (from a year-born answer or the date of birth).1990-04-07 → 1990
grad_yearFour digits.2008 → 2008
ipThe 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:

text
email      d6117306485ed0e50afab3ac871e98f81699151f30281527d63ff5f233656c69
phone      2f83685e66d4cb4d1bcff5f422ff9b0d7ce748a6f3103269d1adad16ab2e279b
last_name  36a69f8b745a87f4314b2dbcb8906ab78c03d4e2595f6b694f7af49b31f51242
zip        918abeeaae3a90ad25f3a5e45408af4cdd71ac92accc3638461dc2f76e39cc01
dob        8b44df2e1ab8ebe9eb4704090cf1524944141f66c7ab6dbed65391681a0d4878
birth_year a7be8e1fe282a37cd666e0632b17d933fa13f21addf4798fc0455bc166e2488c
grad_year  e5e53c784d5d49de1cabb6e904bf3380026aadcb9769775a268dd304dd9aa2df
ip         5afd19e856d1c18d17d600dfd2b5f534992333985e126c2a951047102c1ed536

Send 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.

javascript
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'),
  },
};
python
import hashlib

def sha256(value: str) -> str:
    return hashlib.sha256(value.encode("utf-8")).hexdigest()

body = {"hashed": {"email": sha256("janedoe@gmail.com")}}

#Response

json
{
  "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

HTTPMeaning
400MALFORMED_REQUEST: no usable lead field was sent (raw or hashed); INVALID_FIELD: offer_id is not a UUID.
401AUTH_REQUIRED (no API key) or AUTH_INVALID (a key ICON does not know); status invalid.
403FORBIDDEN: the offer_id is not yours, or this key is not permitted to use the pre-check.
404Not found: the pre-check is not available on your account; ask your ICON contact.
405MALFORMED_REQUEST: use POST.
413The body is over 16 KB.
429RATE_LIMITED (retryable): this key is over its pre-check limit. Wait the Retry-After seconds and retry.
503UNAVAILABLE (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.

Generated 2026-09-28 from ICON's API definitions. Every page is also available as Markdown; the index is llms.txt.