Watch-IP

Referencia de la API de verificación de correo

POST/v1/email/validate

Verifica una dirección de correo: sintaxis, dominios desechables/temporales conocidos, el servidor de correo (MX) del dominio, erratas probables y direcciones de rol, y devuelve un veredicto accept/review/reject con los motivos. Envía un cuerpo JSON { "email": "user@example.com" }. Está pensado para llamarse desde el propio JavaScript de un formulario de registro, con el mismo modelo de clave publicable restringida por origen que GET /v1/geo. Para muchas direcciones o una decisión de registro en el servidor, usa los endpoints por lotes, de trabajos de lista y de verificación de registro que se describen más abajo. Todas las comprobaciones son a nivel de dominio y de dirección: no se contacta ningún buzón, así que ningún resultado establece que un buzón exista, quién es su titular ni que el correo vaya a llegar.

Autenticación

POST /v1/email/validate acepta una clave publicable restringida por origen mediante la cabecera X-Api-Key: el mismo tipo de clave y el mismo modelo de orígenes permitidos que GET /v1/geo. Todos los demás endpoints de esta página (lotes, trabajos de lista, verificación de registro) aceptan muchas direcciones o una IP arbitraria, por lo que requieren tu clave secreta mediante Authorization: Bearer y solo deben llamarse desde tu backend; una clave publicable recibe un 403 en ellos.

Campos de la respuesta

email
La dirección que enviaste, devuelta exactamente como se envió.
domain
El dominio en minúsculas (dominios internacionalizados en punycode), o null cuando syntaxValid es false.
syntaxValid
Si la dirección es sintácticamente válida. No es una comprobación de existencia del buzón ni de entregabilidad.
isDisposable
Si el dominio coincide con un proveedor de correo desechable/temporal conocido: directamente, a través de un dominio padre o a través de un servidor de correo que se sabe que atiende dominios desechables. Siempre false cuando syntaxValid es false.
normalizedEmail
Dirección sin espacios y en minúsculas con el dominio en punycode, o null cuando syntaxValid es false.
canonicalEmail
normalizedEmail sin ningún +tag (y, para Gmail, sin puntos y con googlemail.com convertido en gmail.com): guárdalo para detectar a la misma persona registrándose dos veces. Null cuando syntaxValid es false.
disposableSource
Por qué isDisposable es true: list (el propio dominio está en la lista), parent_domain (un dominio padre está en la lista, p. ej. a.b.mailinator.com) o mx_host (el servidor de correo del dominio atiende dominios desechables conocidos). Null cuando isDisposable es false.
isFreeProvider
Si el dominio es un proveedor de correo de consumo como gmail.com u outlook.com. Solo informativo: nunca cambia el veredicto.
isRoleAccount
Si la parte local es una dirección de rol como admin@, info@ o noreply@.
didYouMean
Una corrección probable de errata de la dirección completa (john@gmial.com → john@gmail.com), o null. Un dominio con su propio servidor de correo operativo no se «corrige» salvo que esté a una edición de un proveedor común.
mx.status
found (existen registros MX), implicit (sin MX, pero con un registro A/AAAA), none (ninguno de los dos), null_mx (el dominio declara que no acepta correo), no_domain (el dominio no existe) o unknown (la consulta falló o agotó el tiempo). El objeto mx completo es null cuando syntaxValid es false.
mx.host
El host MX de menor preferencia cuando status es found; en caso contrario, null. No indica nada sobre si existe el buzón concreto.
verdict
accept, review o reject: la más grave de las condiciones de reasons. Una recomendación para tu propia política, no una decisión final.
reasons
Cada condición que se cumplió, de la más grave a la menos grave; vacío cuando verdict es accept. Consulta «Veredictos y códigos de motivo» más abajo.

Errores

400
Cuerpo de solicitud mal formado. Lotes: empty_batch o batch_too_large (más de 100 direcciones). Verificación de registro: invalid_ip. Trabajos de lista: invalid_body, empty_job o job_too_large (más de 100.000 direcciones). Una dirección de correo no válida nunca produce un 400: es un resultado con veredicto reject y motivo invalid_syntax.
401
Clave de API faltante o no válida.
403
POST /v1/email/validate: el Origin/Referer de la solicitud no está en la lista de orígenes permitidos de la clave (no se establece cabecera CORS, así que el navegador bloquea la respuesta sea cual sea el cuerpo JSON). Endpoints con clave secreta: se usó una clave publicable.
404
job_not_found: no existe ningún trabajo de lista con este id en tu cuenta.
409
job_not_completed: el trabajo sigue en cola o en proceso, o ha fallado; los resultados solo se pueden descargar cuando ha terminado.
410
job_expired: las direcciones subidas y los resultados del trabajo se han eliminado (7 días después de su creación).
413
body_too_large: una subida CSV de un trabajo de más de 10 MB.
415
Un cuerpo de trabajo de lista que no es JSON ni text/csv.
429
El límite de tasa por minuto, o monthly_limit_exceeded cuando se superaría el límite mensual de solicitudes del plan; consulta errores y límites de tasa para saber cuál es. Un lote o trabajo de lista que superaría el límite se rechaza entero, antes de hacer ningún trabajo.
503
job_storage_unavailable: no se pudo almacenar un trabajo de lista. No se cobró nada; vuelve a intentarlo.

Ejemplo

Solicitud

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"}'

Respuesta

{
  "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"
  ]
}

Veredictos y códigos de motivo

Cada condición fija un veredicto mínimo; gana la más grave, y reasons las enumera todas, de la más grave a la menos grave. Si una dirección de un proveedor gratuito es aceptable lo decides tú: nunca cambia el veredicto.

invalid_syntax
Reject. La dirección no es sintácticamente válida (límites de longitud, caracteres permitidos, posición de los puntos, un dominio real en lugar de una IP literal). También se rechazan las partes locales entre comillas: son legales, pero en la práctica nunca se usan en un registro.
domain_not_found
Reject. El dominio no existe en el DNS.
no_mail_server
Reject. El dominio declara que no acepta correo (un «null MX») o no tiene registros MX ni A/AAAA.
disposable_domain
Reject. El dominio, un dominio padre o el servidor de correo del dominio coincide con proveedores de correo desechable/temporal conocidos; consulta disposableSource.
typo_suspected
Review. didYouMean contiene una corrección probable, como gmial.com → gmail.com.
role_account
Review. La parte local es una dirección de rol como admin@, info@, support@ o noreply@, normalmente compartida y no personal.
implicit_mx
Review. El dominio no tiene registro MX; el correo recurriría a su dirección A/AAAA, algo poco habitual en dominios de correo reales.
dns_unavailable
Review. La consulta DNS falló o agotó el tiempo, así que la configuración de correo del dominio es desconocida (mx.status: unknown). No es una prueba en contra de la dirección.

Verificación por lotes

POST/v1/email/batch

Verifica de 1 a 100 direcciones en una sola solicitud, con las mismas comprobaciones y la misma forma de resultado que POST /v1/email/validate, un resultado por dirección en el orden de entrada. Envía { "emails": [...] }. Las direcciones del mismo dominio comparten una única consulta DNS. Cuenta una solicitud por dirección contra el límite mensual de tu plan; un lote que superaría el límite se rechaza entero con 429 monthly_limit_exceeded. Para el límite de tasa por minuto, la llamada completa cuenta como una sola solicitud. Solo con clave secreta.

Solicitud

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"]}'

Respuesta

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

Trabajos de lista de correo

POST/v1/email/jobs

Verifica hasta 100.000 direcciones en segundo plano. Envía JSON { "emails": [...], "filename": "..." } o un cuerpo text/csv de 10 MB como máximo: se usa la columna titulada email (también e-mail, email address, email_address o mail, sin distinguir mayúsculas), o la primera columna si ninguna cabecera la nombra; se detectan los separadores coma, punto y coma y tabulación. El trabajo se cobra al crearse (una solicitud por dirección distinta, coincidencia exacta, así que los duplicados se verifican pero se cobran una vez) y una lista que superaría tu límite mensual se rechaza entera. Si parte de un trabajo no se puede procesar tras varios reintentos, el trabajo se marca como failed y se devuelven los créditos de la parte no procesada. Responde 202 con el objeto del trabajo. Solo con clave secreta; los mismos trabajos también se pueden subir y descargar desde la página Email lists del panel.

Solicitud

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

Respuesta

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

Estado del trabajo

GET/v1/email/jobs/{id}

Devuelve el mismo objeto del trabajo. Consúltalo periódicamente hasta que status sea completed; las consultas no se cobran.

status
queued → processing → completed. failed: parte de la lista no se pudo procesar tras varios reintentos; no hay resultados disponibles y se devolvieron los créditos de la parte no procesada. expired: las direcciones subidas y los resultados se eliminaron 7 días después de la creación.
total
Filas enviadas, duplicados incluidos.
credits
Solicitudes cobradas: una por dirección distinta.
processed
Filas verificadas hasta ahora.
summary
Una vez completado: número de filas por veredicto (accept/review/reject) y por cada código de motivo que apareció. Null hasta entonces.
expiresAt
Cuándo se eliminan las direcciones subidas y los resultados.

Resultados del trabajo

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

Descarga un CSV con una fila por cada fila enviada, en el orden de envío, duplicados incluidos. Columnas: 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 va separado por punto y coma, las celdas vacías significan null y mx_status está vacío para las direcciones sintácticamente no válidas (no se consultó nada). Una dirección no válida que empieza por =, +, - o @ lleva delante un apóstrofo para que las hojas de cálculo no la ejecuten como fórmula. 409 hasta que el trabajo termine, 410 cuando haya caducado. La descarga no se cobra.

Solicitud

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

Verificación de registro

POST/v1/signup/check

Puntúa un registro a partir de su dirección de correo y su IP en una sola llamada, desde tu backend cuando se envía el formulario de registro (un veredicto calculado en el navegador se puede falsificar). Envía { "email": "...", "ip": "...", "userAgent": "..." }; userAgent es opcional. La respuesta contiene el resultado completo de la verificación de correo (email), el país y el ASN de la IP y, en los planes que incluyen datos de seguridad, sus indicadores de VPN/Tor/centro de datos/amenaza (ip), la antigüedad del dominio del correo en días según los datos públicos de registro (RDAP) (emailDomainAgeDays, null cuando se desconoce y para los grandes proveedores de consumo), una puntuación de 0 a 100 (la suma de los pesos de los motivos siguientes, con un máximo de 100) y un veredicto: reject a partir de 60, review a partir de 25 y, en otro caso, accept. En un plan sin datos de seguridad, ip.security es null, la puntuación usa solo las señales del correo y reasons incluye ip_security_unavailable. Cuenta como una solicitud. Solo con clave secreta.

invalid_syntax
70
domain_not_found · no_mail_server · disposable_domain
60 cada uno
ip_tor · ip_threat
40 cada uno
ip_vpn
25
new_domain
25: el dominio del correo se registró hace menos de 30 días
ip_datacenter
20: solo cuando la IP no es ya una salida de VPN o Tor
automated_user_agent
20: el userAgent proporcionado parece un bot o un script
typo_suspected
15
role_account · implicit_mx
10 cada uno
dns_unavailable
5
ip_security_unavailable
0: informativo, este plan no incluye datos de seguridad

Solicitud

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"}'

Respuesta

{
  "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"]
}

Límites

Cada dirección verificada cuenta como una solicitud contra el límite mensual compartido de tu plan (una verificación individual, cada dirección de un lote, cada dirección distinta de un trabajo de lista) y una verificación de registro es una solicitud. Los lotes y los trabajos de lista se admiten o se rechazan enteros, nunca se atienden parcialmente. El límite de tasa por minuto cuenta llamadas HTTP, no elementos: un lote de hasta 100 IP o direcciones de correo cuenta como una sola solicitud, mientras que cada elemento sigue contando por separado en el límite mensual de solicitudes. Las subidas y los resultados de los trabajos de lista se eliminan 7 días después de crear el trabajo. Las consultas DNS tienen un margen de tiempo corto: cuando una falla o agota el tiempo, el resultado se degrada a mx.status unknown y motivo dns_unavailable en lugar de un error. Los datos de dominios desechables se combinan a partir de tres listas de código abierto mantenidas por la comunidad, se actualizan cada pocas horas y se amplían con los servidores de correo observados atendiendo dominios de las listas; consulta fuentes de datos. Es posible que un proveedor desechable muy reciente aún no se marque. Todas las comprobaciones son a nivel de dominio y de dirección: no se contacta ningún buzón, así que ningún resultado establece que un buzón exista, quién es su titular ni que el correo vaya a llegar.

Páginas relacionadas