API v1

Documentación de la API

Una petición por dirección, o lotes de hasta un millón. Mismo motor y mismos créditos que el panel. Las claves salen de tu cuenta.

Autenticación

Crea una clave en Panel → Claves API y envíala en la cabecera X-API-Key (o como Authorization: Bearer mk_live_…). Las claves empiezan por mk_live_, se muestran una sola vez y se pueden revocar en cualquier momento. URL base: https://api.mailok.io.

Verificar una dirección

POST /v1/verify con { "email" }, o GET /v1/verify?email=. Responde en unos segundos; una conversación SMTP real puede tardar hasta 60 s en servidores lentos.

curl https://api.mailok.io/v1/verify \
  -H "X-API-Key: mk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]"}'
Respuesta
{
  "email": "[email protected]",
  "status": "valid",
  "catch_all": false,
  "disposable": false,
  "role": false,
  "mailbox_full": false,
  "mx_found": true,
  "mx_record": "mx1.northwindtraders.com",
  "domain": "northwindtraders.com",
  "account": "sarah",
  "credits_remaining": 9999
}

Estados

VálidoEl servidor confirmó el buzón. Seguro para enviar.1 crédito
InválidoSintaxis incorrecta, sin registro MX, o el servidor rechazó el buzón.1 crédito
ArriesgadoAceptada pero no confirmable: dominio catch-all, buzón lleno o proveedor desechable. Mira los indicadores.1 crédito
DesconocidoEl servidor no respondió a tiempo (greylisting, tiempos de espera).0 créditos

Indicadores en cada resultado: catch_all, disposable, role, mailbox_full, mx_found, mx_record. credits_remaining es tu saldo tras la llamada.

Lotes

Envía una lista, consulta hasta ready y recorre los resultados por páginas. Los duplicados dentro de una lista se marcan y son gratis. Las listas también aparecen en tu panel.

POST /v1/batches
{ "emails": ["[email protected]", "[email protected]"], "name": "October import" }

→ { "uid": "k3d9x1qzpa", "name": "October import", "status": "pending", "total": 2, "completed": 0,
    "stats": { "valid": 0, "invalid": 0, "risky": 0, "unknown": 0, "duplicate": 0 }, "created_at": "…" }
GET /v1/batches/k3d9x1qzpa        → same shape plus "percent", "eta_seconds", "credits_used"; "status": "ready" when done
GET /v1/batches/k3d9x1qzpa/results?page_size=1000&status=valid,risky
→ { "items": [ { "id": 4102, "email": "[email protected]", "status": "valid", "checked_at": "…" } ], "total": 1, "next_after": 4102, … }
GET /v1/batches/k3d9x1qzpa/results?page_size=1000&after=4102      → the next page, in file order
GET /v1/batches/k3d9x1qzpa/download?format=xlsx&statuses=valid   → the file (your columns + MailStatus, or email,MailStatus)
POST /v1/batches/upload                     multipart: file (CSV or .xlsx), name, callback_url, auto_start (default true)
PUT  /v1/batches/k3d9x1qzpa/start           { "sample": 100 }   optional: verify the first 100 first, then start again for the rest
PUT  /v1/batches/k3d9x1qzpa/cancel          stops it; verified addresses stay, nothing else is charged
DELETE /v1/batches/k3d9x1qzpa               removes the batch, its results and its files

Estados: pending (leyendo) → parsed (solo con auto_start: false, esperando start) → processing → completed → ready; sampled tras una muestra; paused cuando la cuenta se queda sin créditos (continúa sola cuando llegan créditos); cancelled; failed con un mensaje error. Las direcciones desconocidas se vuelven a comprobar una vez gratis antes de ready. GET /v1/batches lista tus lotes.

Cuenta

GET /v1/account devuelve credits, subscription_credits, credits_remaining, plan y tu rate_limit_per_minute. Útil para alertas antes de una campaña.

Webhooks

Pasa una callback_url (host público https o http) al crear un lote y enviamos por POST un evento batch.ready cuando termina. Responde con cualquier 2xx en 10 s; si no, reintentamos tras 1, 5 y 30 minutos y luego desistimos. Cada intento se lista en Panel → Claves API, donde también vive el secreto de firma.

POST <your callback_url>
X-MailOK-Event: batch.ready
X-MailOK-Timestamp: 1760000000
X-MailOK-Signature: sha256=…
X-MailOK-Delivery: 42

{ "event": "batch.ready", "created_at": "…",
  "data": { "uid": "k3d9x1qzpa", "name": "October import", "status": "ready", "total": 2, "completed": 2,
            "stats": { "valid": 1, "invalid": 1, "risky": 0, "unknown": 0, "duplicate": 0 },
            "created_at": "…", "completed_at": "…", "error": null } }

Verifica la firma antes de confiar en el contenido: HMAC-SHA256 de "{timestamp}.{raw body}" con tu secreto, comparado en tiempo constante. Rechaza marcas de tiempo de más de unos minutos para bloquear repeticiones.

import crypto from 'node:crypto'

// signature = "sha256=" + HMAC_SHA256(secret, timestamp + "." + rawBody)
export function verify(rawBody, headers, secret) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret)
    .update(headers['x-mailok-timestamp'] + '.' + rawBody).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(headers['x-mailok-signature'] ?? ''))
}

Errores y límites

Cada error tiene la misma forma y un code estable:

{ "error": { "code": "insufficient_credits", "message": "No credits left. Buy credits or choose a plan in the dashboard." } }
400invalid_email, empty_list, list_too_large, invalid_callback_url, invalid_pageCorrige la petición.
401unauthorizedClave ausente, revocada o desconocida.
402insufficient_creditsSin créditos. No se verificó nada.
404not_foundEl lote no existe o pertenece a otra cuenta.
429rate_limitedSuperaste tu límite por minuto; espera a Retry-After.
503verifier_unavailableMotor de verificación inaccesible; reintenta en un minuto. No se cobró nada.

Límite: 60 peticiones por minuto y clave por defecto (cabecera X-RateLimit-Limit en cada respuesta); los planes mensuales pueden tener un límite mayor. ¿Necesitas más? Escríbenos.