Watch-IP

이메일 검사 API 레퍼런스

POST/v1/email/validate

이메일 주소 하나를 검사합니다: 구문, 알려진 일회용/임시 도메인, 도메인의 메일 서버(MX), 가능성 있는 오타와 역할 주소를 확인하고, 그 사유와 함께 accept/review/reject 판정을 반환합니다. { "email": "user@example.com" } 형태의 JSON 본문을 전송하세요. GET /v1/geo와 동일한 공개 가능하고 오리진에 고정된 키 모델을 사용해 가입 양식 자체의 JavaScript에서 호출하도록 설계되었습니다. 여러 주소를 검사하거나 서버 사이드에서 가입을 결정하려면 이 페이지 아래에 있는 일괄 검사, 목록 작업, 가입 검사 엔드포인트를 사용하세요. 모든 검사는 도메인 및 주소 수준에서 이루어집니다. 어떤 메일함에도 접속하지 않으므로, 어떤 결과도 메일함이 존재하는지, 누가 소유하는지, 메일이 도착할지를 입증하지 않습니다.

인증

POST /v1/email/validate는 X-Api-Key 헤더로 오리진에 고정된 공개 가능 키를 받습니다 — GET /v1/geo와 동일한 키 유형 및 허용 오리진 모델입니다. 이 페이지의 다른 모든 엔드포인트(일괄 검사, 목록 작업, 가입 검사)는 여러 주소나 임의의 IP를 받으므로 Authorization: Bearer로 시크릿 키가 필요하며 반드시 백엔드에서만 호출해야 합니다. 공개 가능 키를 사용하면 403을 받습니다.

응답 필드

email
전송한 주소로, 제출된 그대로 반환됩니다.
domain
소문자로 변환한 도메인(국제화 도메인은 punycode), syntaxValid가 false이면 null.
syntaxValid
주소가 구문상 유효한지 여부. 메일함 존재 여부나 전달 가능성 검사가 아닙니다.
isDisposable
도메인이 알려진 일회용/임시 이메일 제공자와 일치하는지 여부 — 직접, 상위 도메인을 통해, 또는 일회용 도메인을 처리하는 것으로 알려진 메일 서버를 통해 판단합니다. syntaxValid가 false이면 항상 false.
normalizedEmail
공백을 제거하고 소문자로 변환하며 도메인을 punycode로 표기한 주소, syntaxValid가 false이면 null.
canonicalEmail
normalizedEmail에서 +태그를 제거한 값(Gmail은 점도 제거하고 googlemail.com을 gmail.com으로 통일) — 같은 사람이 두 번 가입하는 것을 찾아내도록 저장하세요. syntaxValid가 false이면 null.
disposableSource
isDisposable이 true인 이유: list(도메인 자체가 목록에 있음), parent_domain(상위 도메인이 목록에 있음, 예: a.b.mailinator.com), mx_host(도메인의 메일 서버가 알려진 일회용 도메인을 처리함). isDisposable이 false이면 null.
isFreeProvider
도메인이 gmail.com이나 outlook.com 같은 일반 소비자용 메일함 제공자인지 여부. 참고용이며 판정을 바꾸지 않습니다.
isRoleAccount
로컬 부분이 admin@, info@, noreply@ 같은 역할 주소인지 여부.
didYouMean
주소 전체에 대한 가능성 있는 오타 수정(john@gmial.com → john@gmail.com), 또는 null. 자체적으로 작동하는 메일 서버가 있는 도메인은 흔한 제공자와 한 글자 차이가 아닌 한 “수정”되지 않습니다.
mx.status
found(MX 레코드 있음), implicit(MX는 없지만 A/AAAA 레코드 있음), none(둘 다 없음), null_mx(도메인이 메일을 받지 않는다고 선언), no_domain(도메인이 존재하지 않음), unknown(조회 실패 또는 시간 초과) 중 하나. syntaxValid가 false이면 mx 객체 전체가 null입니다.
mx.host
status가 found일 때 우선순위 값이 가장 낮은 MX 호스트, 그 외에는 null. 개별 메일함의 존재 여부에 대해서는 아무것도 알려주지 않습니다.
verdict
accept, review, reject — reasons에 있는 조건 중 가장 심각한 것. 최종 결정이 아니라 자체 정책을 위한 권고입니다.
reasons
해당된 모든 조건(심각한 순). verdict가 accept이면 비어 있습니다. 아래 “판정 및 사유 코드”를 참고하세요.

오류

400
잘못된 형식의 요청 본문. 일괄 검사: empty_batch 또는 batch_too_large(100개 초과). 가입 검사: invalid_ip. 목록 작업: invalid_body, empty_job 또는 job_too_large(100,000개 초과). 유효하지 않은 이메일 주소는 400이 되지 않으며, 판정 reject와 사유 invalid_syntax가 담긴 결과로 반환됩니다.
401
API 키가 없거나 유효하지 않습니다.
403
POST /v1/email/validate: 요청의 Origin/Referer가 키의 허용 오리진 목록에 없음(CORS 헤더가 설정되지 않으므로 JSON 본문과 관계없이 브라우저가 응답을 차단함). 시크릿 키 엔드포인트: 공개 가능 키가 사용됨.
404
job_not_found — 계정에 해당 ID의 목록 작업이 없습니다.
409
job_not_completed — 작업이 아직 대기 중이거나 처리 중이거나 실패했습니다. 결과는 작업이 완료된 후에만 다운로드할 수 있습니다.
410
job_expired — 작업의 업로드된 주소와 결과가 삭제되었습니다(생성 7일 후).
413
body_too_large — 10 MB를 초과하는 작업 CSV 업로드.
415
JSON도 text/csv도 아닌 목록 작업 본문.
429
분당 속도 제한, 또는 요금제의 월간 요청 한도를 초과하게 될 때의 monthly_limit_exceeded — 구분 방법은 오류 및 속도 제한을 참고하세요. 한도를 초과하게 될 일괄 검사나 목록 작업은 처리를 시작하기 전에 전체가 거부됩니다.
503
job_storage_unavailable — 목록 작업을 저장할 수 없었습니다. 과금되지 않았으니 다시 시도하세요.

예시

요청

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

응답

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

판정 및 사유 코드

각 조건은 최소 판정을 정하며 가장 심각한 것이 적용되고, reasons에는 해당된 모든 조건이 심각한 순으로 나열됩니다. 무료 제공자 주소를 허용할지는 사용자가 결정하며, 판정을 바꾸지 않습니다.

invalid_syntax
Reject. 주소가 구문상 유효하지 않습니다(길이 제한, 허용 문자, 점 위치, IP 리터럴이 아닌 실제 도메인). 따옴표로 묶인 로컬 부분도 거부됩니다 — 규격상 허용되지만 가입에서는 사실상 쓰이지 않습니다.
domain_not_found
Reject. 도메인이 DNS에 존재하지 않습니다.
no_mail_server
Reject. 도메인이 메일을 받지 않는다고 선언했거나(“null MX”), MX 레코드와 A/AAAA 레코드가 모두 없습니다.
disposable_domain
Reject. 도메인, 상위 도메인 또는 도메인의 메일 서버가 알려진 일회용/임시 이메일 제공자와 일치합니다 — disposableSource를 참고하세요.
typo_suspected
Review. didYouMean에 gmial.com → gmail.com 같은 가능성 있는 수정이 들어 있습니다.
role_account
Review. 로컬 부분이 admin@, info@, support@, noreply@ 같은 역할 주소로, 보통 개인용이 아니라 공유됩니다.
implicit_mx
Review. 도메인에 MX 레코드가 없어 메일이 A/AAAA 주소로 대체 전달되며, 이는 실제 메일 도메인에서는 드문 구성입니다.
dns_unavailable
Review. DNS 조회가 실패하거나 시간 초과되어 도메인의 메일 설정을 알 수 없습니다(mx.status: unknown). 주소에 불리한 증거가 아닙니다.

일괄 검사

POST/v1/email/batch

요청 한 번으로 1–100개 주소를 POST /v1/email/validate와 동일한 검사 및 결과 형식으로 검사하며, 입력 순서대로 주소마다 결과 하나를 반환합니다. { "emails": [...] }를 전송하세요. 같은 도메인의 주소는 DNS 조회 한 번을 공유합니다. 주소당 요청 1건으로 월간 요금제 한도에 계산되며, 한도를 초과하게 될 일괄 검사는 429 monthly_limit_exceeded로 전체가 거부됩니다. 분당 속도 제한에서는 호출 전체가 1건의 요청으로 계산됩니다. 시크릿 키 전용.

요청

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

응답

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

이메일 목록 작업

POST/v1/email/jobs

최대 100,000개 주소를 백그라운드에서 검사합니다. JSON { "emails": [...], "filename": "..." } 또는 최대 10 MB의 text/csv 본문을 전송하세요. 머리글이 email(e-mail, email address, email_address, mail도 가능, 대소문자 구분 없음)인 열이 사용되며, 이를 지정하는 머리글이 없으면 첫 번째 열이 사용됩니다. 쉼표, 세미콜론, 탭 구분자는 자동으로 감지됩니다. 작업은 생성 시점에 과금됩니다 — 고유 주소(완전 일치)당 요청 1건이므로 중복 주소도 검사되지만 한 번만 과금됩니다 — 월간 한도를 초과하게 될 목록은 전체가 거부됩니다. 재시도 후에도 작업의 일부를 처리할 수 없으면 작업이 failed로 표시되고 처리되지 않은 부분의 크레딧이 반환됩니다. 작업 객체와 함께 202를 반환합니다. 시크릿 키 전용이며, 같은 작업을 대시보드의 Email lists 페이지에서 업로드하고 다운로드할 수도 있습니다.

요청

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

응답

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

작업 상태

GET/v1/email/jobs/{id}

같은 작업 객체를 반환합니다. status가 completed가 될 때까지 폴링하세요. 폴링은 과금되지 않습니다.

status
queued → processing → completed. failed: 재시도 후에도 목록의 일부를 처리할 수 없었습니다. 결과가 없으며 처리되지 않은 부분의 크레딧은 반환되었습니다. expired: 업로드된 주소와 결과가 생성 7일 후 삭제되었습니다.
total
제출된 행 수(중복 포함).
credits
과금된 요청 수: 고유 주소당 1건.
processed
지금까지 검사된 행 수.
summary
완료 후: 판정(accept/review/reject)별, 그리고 발생한 사유 코드별 행 수. 그 전에는 null.
expiresAt
업로드된 주소와 결과가 삭제되는 시점.

작업 결과

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

제출된 행마다 한 행씩, 제출 순서대로(중복 포함) CSV를 다운로드합니다. 열: 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는 세미콜론으로 구분되고, 빈 셀은 null을 뜻하며, 구문상 유효하지 않은 주소는 조회를 하지 않았으므로 mx_status가 비어 있습니다. =, +, - 또는 @로 시작하는 유효하지 않은 주소에는 스프레드시트 앱이 수식으로 실행하지 않도록 앞에 아포스트로피가 붙습니다. 작업이 완료될 때까지는 409, 만료된 후에는 410. 다운로드는 과금되지 않습니다.

요청

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

가입 검사

POST/v1/signup/check

가입 양식이 제출될 때 백엔드에서 한 번의 호출로 이메일 주소와 IP를 기반으로 가입을 점수화합니다(브라우저에서 계산한 판정은 위조될 수 있습니다). { "email": "...", "ip": "...", "userAgent": "..." }를 전송하세요 — userAgent는 선택 사항입니다. 응답에는 전체 이메일 검사 결과(email), IP의 국가와 ASN 및 — 보안 데이터가 포함된 요금제에서는 — VPN/Tor/데이터센터/위협 지표(ip), 공개 등록(RDAP) 데이터 기준 이메일 도메인의 경과 일수(emailDomainAgeDays, 알 수 없거나 잘 알려진 소비자용 제공자인 경우 null), 0–100 점수(아래 사유 가중치의 합, 최대 100), 판정(60 이상 reject, 25 이상 review, 그 외 accept)이 포함됩니다. 보안 데이터가 없는 요금제에서는 ip.security가 null이고, 점수는 이메일 신호만 사용하며, reasons에 ip_security_unavailable이 포함됩니다. 요청 1건으로 계산됩니다. 시크릿 키 전용.

invalid_syntax
70
domain_not_found · no_mail_server · disposable_domain
각 60
ip_tor · ip_threat
각 40
ip_vpn
25
new_domain
25 — 이메일 도메인이 등록된 지 30일 미만
ip_datacenter
20 — IP가 이미 VPN 또는 Tor 출구가 아닌 경우에만
automated_user_agent
20 — 제공된 userAgent가 봇이나 스크립트처럼 보임
typo_suspected
15
role_account · implicit_mx
각 10
dns_unavailable
5
ip_security_unavailable
0 — 참고용: 이 요금제에는 보안 데이터가 없음

요청

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

응답

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

제한 사항

검사한 모든 주소는 요금제의 공유 월간 한도에서 요청 1건으로 계산됩니다 — 단일 검사, 일괄 검사의 각 주소, 목록 작업의 각 고유 주소 — 가입 검사는 요청 1건입니다. 일괄 검사와 목록 작업은 전체가 승인되거나 거부되며, 부분적으로 처리되지 않습니다. 분당 속도 제한은 항목 수가 아니라 HTTP 호출 수를 셉니다. 최대 100개의 IP 또는 이메일 주소를 담은 배치는 1건의 요청으로 계산되며, 각 항목은 여전히 월간 요청 한도에 개별적으로 계산됩니다. 목록 작업의 업로드와 결과는 작업 생성 7일 후 삭제됩니다. DNS 조회에는 짧은 시간 예산이 있어, 실패하거나 시간 초과되면 오류 대신 mx.status unknown과 사유 dns_unavailable로 저하됩니다. 일회용 도메인 데이터는 커뮤니티가 관리하는 세 가지 오픈 소스 목록을 병합한 것으로, 몇 시간마다 갱신되며 목록에 있는 도메인을 처리하는 것으로 관찰된 메일 서버로 확장됩니다 — 데이터 소스를 참고하세요. 아주 새로운 일회용 제공자는 아직 표시되지 않을 수 있습니다. 모든 검사는 도메인 및 주소 수준에서 이루어집니다. 어떤 메일함에도 접속하지 않으므로, 어떤 결과도 메일함이 존재하는지, 누가 소유하는지, 메일이 도착할지를 입증하지 않습니다.

관련 페이지