Watch-IP

Riferimento dell'API di verifica e-mail

POST/v1/email/validate

Verifica un indirizzo e-mail: sintassi, domini usa e getta/temporanei noti, il server di posta (MX) del dominio, probabili refusi e indirizzi di ruolo, e restituisce un verdetto accept/review/reject con i relativi motivi. Invia un body JSON { "email": "user@example.com" }. Pensato per essere chiamato dal JavaScript del modulo di registrazione, con lo stesso modello di chiave pubblicabile limitata per origine di GET /v1/geo. Per molti indirizzi o per una decisione di registrazione lato server, usa gli endpoint batch, dei job di lista e di verifica della registrazione descritti più in basso. Tutti i controlli sono a livello di dominio e di indirizzo: nessuna casella di posta viene contattata, quindi nessun risultato stabilisce che una casella esista, chi ne sia il titolare o che la posta vi arriverà.

Autenticazione

POST /v1/email/validate accetta una chiave pubblicabile limitata per origine tramite l'header X-Api-Key — lo stesso tipo di chiave e lo stesso modello di origini consentite di GET /v1/geo. Tutti gli altri endpoint di questa pagina (batch, job di lista, verifica della registrazione) accettano molti indirizzi o un IP arbitrario, quindi richiedono la tua chiave segreta tramite Authorization: Bearer e vanno chiamati solo dal tuo backend; una chiave pubblicabile riceve un 403.

Campi della risposta

email
L'indirizzo inviato, restituito esattamente come è stato inviato.
domain
Il dominio in minuscolo (domini internazionalizzati in punycode), oppure null quando syntaxValid è false.
syntaxValid
Se l'indirizzo è sintatticamente valido. Non è una verifica dell'esistenza della casella né della recapitabilità.
isDisposable
Se il dominio corrisponde a un provider di e-mail usa e getta/temporanee noto — direttamente, tramite un dominio padre o tramite un server di posta noto per servire domini usa e getta. Sempre false quando syntaxValid è false.
normalizedEmail
Indirizzo ripulito e in minuscolo con il dominio in punycode, oppure null quando syntaxValid è false.
canonicalEmail
normalizedEmail senza eventuale +tag (e, per Gmail, senza punti e con googlemail.com ricondotto a gmail.com) — conservalo per individuare la stessa persona che si registra due volte. Null quando syntaxValid è false.
disposableSource
Perché isDisposable è true: list (il dominio stesso è in lista), parent_domain (un dominio padre è in lista, ad es. a.b.mailinator.com) o mx_host (il server di posta del dominio serve domini usa e getta noti). Null quando isDisposable è false.
isFreeProvider
Se il dominio è un provider di posta per consumatori come gmail.com o outlook.com. Solo informativo — non modifica mai il verdetto.
isRoleAccount
Se la parte locale è un indirizzo di ruolo come admin@, info@ o noreply@.
didYouMean
Una probabile correzione del refuso sull'intero indirizzo (john@gmial.com → john@gmail.com), oppure null. Un dominio con un proprio server di posta funzionante non viene «corretto», a meno che non sia a una modifica di distanza da un provider comune.
mx.status
found (esistono record MX), implicit (nessun MX, ma un record A/AAAA), none (né l'uno né l'altro), null_mx (il dominio dichiara di non accettare posta), no_domain (il dominio non esiste) o unknown (la ricerca è fallita o è andata in timeout). L'intero oggetto mx è null quando syntaxValid è false.
mx.host
L'host MX con la preferenza più bassa quando status è found, altrimenti null. Non dice nulla sull'esistenza della singola casella di posta.
verdict
accept, review o reject — la più grave delle condizioni in reasons. Una raccomandazione per la tua policy, non una decisione definitiva.
reasons
Ogni condizione riscontrata, dalla più grave; vuoto quando verdict è accept. Vedi «Verdetti e codici motivo» più sotto.

Errori

400
Body della richiesta malformato. Batch: empty_batch o batch_too_large (più di 100 indirizzi). Verifica della registrazione: invalid_ip. Job di lista: invalid_body, empty_job o job_too_large (più di 100.000 indirizzi). Un indirizzo e-mail non valido non produce mai un 400 — è un risultato con verdetto reject e motivo invalid_syntax.
401
Chiave API mancante o non valida.
403
POST /v1/email/validate: l'Origin/Referer della richiesta non è nell'elenco delle origini consentite della chiave (non viene impostato alcun header CORS, quindi il browser blocca la risposta indipendentemente dal body JSON). Endpoint con chiave segreta: è stata usata una chiave pubblicabile.
404
job_not_found — nessun job di lista con questo id per il tuo account.
409
job_not_completed — il job è ancora in coda o in elaborazione, oppure è fallito; i risultati sono scaricabili solo a job completato.
410
job_expired — gli indirizzi caricati e i risultati del job sono stati eliminati (7 giorni dopo la creazione).
413
body_too_large — un caricamento CSV di un job superiore a 10 MB.
415
Un body di job di lista che non è né JSON né text/csv.
429
Il limite di frequenza al minuto, oppure monthly_limit_exceeded quando verrebbe superato il limite mensile di richieste del piano — vedi errori e limiti di frequenza per distinguerli. Un batch o un job di lista che supererebbe il limite viene rifiutato per intero, prima di qualsiasi elaborazione.
503
job_storage_unavailable — non è stato possibile memorizzare un job di lista. Non è stato addebitato nulla; riprova.

Esempio

Richiesta

curl https://api.watch-ip.com/v1/email/validate \
  -H "X-Api-Key: YOUR_PUBLISHABLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "John.Doe+news@Gmial.com"}'

Risposta

{
  "email": "John.Doe+news@Gmial.com",
  "domain": "gmial.com",
  "syntaxValid": true,
  "isDisposable": false,
  "normalizedEmail": "john.doe+news@gmial.com",
  "canonicalEmail": "john.doe@gmial.com",
  "disposableSource": null,
  "isFreeProvider": false,
  "isRoleAccount": false,
  "didYouMean": "john.doe+news@gmail.com",
  "mx": {
    "status": "none",
    "host": null
  },
  "verdict": "reject",
  "reasons": [
    "no_mail_server",
    "typo_suspected"
  ]
}

Verdetti e codici motivo

Ogni condizione imposta un verdetto minimo; prevale la più grave, e reasons le elenca tutte, dalla più grave. Se un indirizzo di un provider gratuito sia accettabile lo decidi tu — non modifica mai il verdetto.

invalid_syntax
Reject. L'indirizzo non è sintatticamente valido (limiti di lunghezza, caratteri consentiti, posizione dei punti, un dominio reale anziché un IP letterale). Anche le parti locali tra virgolette vengono rifiutate — lecite ma di fatto mai usate in registrazione.
domain_not_found
Reject. Il dominio non esiste nel DNS.
no_mail_server
Reject. Il dominio dichiara di non accettare posta (un «null MX»), oppure non ha né record MX né A/AAAA.
disposable_domain
Reject. Il dominio, un dominio padre o il server di posta del dominio corrisponde a provider di e-mail usa e getta/temporanee noti — vedi disposableSource.
typo_suspected
Review. didYouMean contiene una probabile correzione, ad esempio gmial.com → gmail.com.
role_account
Review. La parte locale è un indirizzo di ruolo come admin@, info@, support@ o noreply@, di solito condiviso anziché personale.
implicit_mx
Review. Il dominio non ha un record MX; la posta ripiegherebbe sul suo indirizzo A/AAAA, cosa rara per i domini di posta reali.
dns_unavailable
Review. La ricerca DNS è fallita o è andata in timeout, quindi la configurazione di posta del dominio è sconosciuta (mx.status: unknown). Non è un indizio contro l'indirizzo.

Verifica in batch

POST/v1/email/batch

Verifica da 1 a 100 indirizzi in un'unica richiesta con gli stessi controlli e la stessa forma di risultato di POST /v1/email/validate, un risultato per indirizzo nell'ordine di input. Invia { "emails": [...] }. Gli indirizzi dello stesso dominio condividono un'unica ricerca DNS. Conta una richiesta per indirizzo nel limite mensile del tuo piano; un batch che supererebbe il limite viene rifiutato per intero con 429 monthly_limit_exceeded. Per il limite di frequenza al minuto, l'intera chiamata conta come una sola richiesta. Solo chiave segreta.

Richiesta

curl https://api.watch-ip.com/v1/email/batch \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"emails": ["user@gmail.com", "someone@mailinator.com"]}'

Risposta

{
  "results": [
    { "email": "user@gmail.com", "verdict": "accept", "reasons": [], ... },
    { "email": "someone@mailinator.com", "isDisposable": true, "disposableSource": "list", "verdict": "reject", "reasons": ["disposable_domain"], ... }
  ]
}

Job di lista e-mail

POST/v1/email/jobs

Verifica fino a 100.000 indirizzi in background. Invia JSON { "emails": [...], "filename": "..." } oppure un body text/csv di al massimo 10 MB: viene usata la colonna intitolata email (anche e-mail, email address, email_address o mail, senza distinzione tra maiuscole e minuscole), oppure la prima colonna se nessuna intestazione la indica; i separatori virgola, punto e virgola e tabulazione vengono rilevati. Il job viene addebitato alla creazione — una richiesta per indirizzo distinto (corrispondenza esatta), quindi i duplicati vengono verificati ma addebitati una volta — e una lista che supererebbe il tuo limite mensile viene rifiutata per intero. Se una parte del job non può essere elaborata dopo i tentativi ripetuti, il job viene contrassegnato come failed e i crediti della parte non elaborata vengono restituiti. Risponde 202 con l'oggetto job. Solo chiave segreta; gli stessi job possono essere caricati e scaricati anche dalla pagina Email lists della dashboard.

Richiesta

curl https://api.watch-ip.com/v1/email/jobs \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @newsletter.csv

Risposta

{
  "id": "5b0d6f5e-8a51-4f7e-9d0e-2c1f3f7f8a11",
  "status": "queued",
  "source": "api",
  "filename": null,
  "total": 12840,
  "credits": 12512,
  "processed": 0,
  "summary": null,
  "createdAt": "2026-09-28T10:00:00.000Z",
  "completedAt": null,
  "expiresAt": "2026-10-05T10:00:00.000Z"
}

Stato del job

GET/v1/email/jobs/{id}

Restituisce lo stesso oggetto job. Interrogalo finché status non è completed; le interrogazioni non vengono addebitate.

status
queued → processing → completed. failed: una parte della lista non è stata elaborata dopo i tentativi ripetuti; nessun risultato disponibile e i crediti della parte non elaborata sono stati restituiti. expired: gli indirizzi caricati e i risultati sono stati eliminati 7 giorni dopo la creazione.
total
Righe inviate, duplicati inclusi.
credits
Richieste addebitate: una per indirizzo distinto.
processed
Righe verificate finora.
summary
A completamento: numero di righe per verdetto (accept/review/reject) e per ciascun codice motivo riscontrato. Null fino ad allora.
expiresAt
Quando vengono eliminati gli indirizzi caricati e i risultati.

Risultati del job

GET/v1/email/jobs/{id}/results

Scarica un CSV con una riga per ogni riga inviata, nell'ordine di invio — duplicati inclusi. Colonne: email, normalized_email, verdict, reasons, syntax_valid, is_disposable, disposable_source, is_free_provider, is_role_account, did_you_mean, mx_status, mx_host. reasons è separato da punto e virgola, le celle vuote indicano null e mx_status è vuoto per gli indirizzi sintatticamente non validi (non è stata fatta alcuna ricerca). Un indirizzo non valido che inizia con =, +, - o @ viene preceduto da un apostrofo, così i fogli di calcolo non lo eseguono come formula. 409 finché il job non è completato, 410 una volta scaduto. Il download non viene addebitato.

Richiesta

curl https://api.watch-ip.com/v1/email/jobs/5b0d6f5e-8a51-4f7e-9d0e-2c1f3f7f8a11/results \
  -H "Authorization: Bearer YOUR_SECRET_KEY" -o results.csv

Verifica della registrazione

POST/v1/signup/check

Assegna un punteggio a una registrazione a partire da indirizzo e-mail e IP in un'unica chiamata, dal tuo backend all'invio del modulo di registrazione (un verdetto calcolato nel browser può essere falsificato). Invia { "email": "...", "ip": "...", "userAgent": "..." } — userAgent è facoltativo. La risposta contiene il risultato completo della verifica e-mail (email), il paese e l'ASN dell'IP e — nei piani che includono i dati di sicurezza — i suoi indicatori VPN/Tor/data center/minaccia (ip), l'età del dominio e-mail in giorni secondo i dati pubblici di registrazione (RDAP) (emailDomainAgeDays, null se sconosciuta e per i grandi provider consumer), un punteggio da 0 a 100 (la somma dei pesi dei motivi qui sotto, con un massimo di 100) e un verdetto: reject da 60 in su, review da 25 in su, altrimenti accept. In un piano senza dati di sicurezza, ip.security è null, il punteggio usa solo i segnali dell'e-mail e reasons include ip_security_unavailable. Conta come una richiesta. Solo chiave segreta.

invalid_syntax
70
domain_not_found · no_mail_server · disposable_domain
60 ciascuno
ip_tor · ip_threat
40 ciascuno
ip_vpn
25
new_domain
25 — il dominio e-mail è stato registrato meno di 30 giorni fa
ip_datacenter
20 — solo se l'IP non è già un'uscita VPN o Tor
automated_user_agent
20 — lo userAgent fornito sembra un bot o uno script
typo_suspected
15
role_account · implicit_mx
10 ciascuno
dns_unavailable
5
ip_security_unavailable
0 — informativo: questo piano non include dati di sicurezza

Richiesta

curl https://api.watch-ip.com/v1/signup/check \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@new-startup.io", "ip": "203.0.113.1"}'

Risposta

{
  "email": { "email": "jane@new-startup.io", "verdict": "accept", "reasons": [], ... },
  "ip": {
    "ip": "203.0.113.1",
    "country": "US",
    "asn": 64500,
    "asOrganization": "Example Hosting",
    "security": { "isTor": false, "isVpn": false, "isDatacenter": true, "isThreat": false, "isSanctionedNetwork": false }
  },
  "emailDomainAgeDays": 12,
  "score": 45,
  "verdict": "review",
  "reasons": ["new_domain", "ip_datacenter"]
}

Limiti

Ogni indirizzo verificato conta come una richiesta nel limite mensile condiviso del tuo piano — una verifica singola, ogni indirizzo di un batch, ogni indirizzo distinto di un job di lista — e una verifica della registrazione è una richiesta. Batch e job di lista vengono accettati o rifiutati per intero, mai evasi parzialmente. Il limite di frequenza al minuto conta le chiamate HTTP, non gli elementi: un batch fino a 100 IP o indirizzi email conta come una sola richiesta, mentre ogni elemento continua a contare separatamente nel limite mensile di richieste. I caricamenti e i risultati dei job di lista vengono eliminati 7 giorni dopo la creazione del job. Le ricerche DNS hanno un breve budget di tempo: quando una fallisce o va in timeout, il risultato degrada a mx.status unknown e motivo dns_unavailable anziché a un errore. I dati sui domini usa e getta derivano dall'unione di tre liste open source mantenute dalla community, aggiornate ogni poche ore ed estese con i server di posta osservati servire domini in lista — vedi fonti dei dati. Un provider usa e getta molto recente potrebbe non essere ancora segnalato. Tutti i controlli sono a livello di dominio e di indirizzo: nessuna casella di posta viene contattata, quindi nessun risultato stabilisce che una casella esista, chi ne sia il titolare o che la posta vi arriverà.

Pagine correlate