REST API para integraciones B2B · auth Bearer · respuesta JSON · webhooks HMAC-firmados para eventos email_delivered / email_opened / email_clicked / email_bounced / email_complained / email_unsubscribed.
Ve a /ajustes/seguridad y click "Crear API key". Elige el scope:
read · solo lectura · subscribers, stats, campañaswrite · agregar/editar subscribers, tags, metadatasend · disparar envíos de campañasadmin · todo lo anterior + dominios + DELETELa key se muestra UNA SOLA VEZ. Guárdala en tu vault. Formato: ef_live_xxxx
Pasa la key como Bearer token en el header Authorization:
curl https://postari.io/api/v1/stats \
-H "Authorization: Bearer ef_live_xxxxxxxxxxxxxxxx"Alternativa: header x-api-key · mismo valor sin "Bearer ".
POST /api/v1/campaigns/send · scope send
curl -X POST https://postari.io/api/v1/campaigns/send \
-H "Authorization: Bearer ef_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"list_id": "uuid-de-tu-lista",
"subject": "🚀 Nuevo curso disponible",
"from_name": "Mara López",
"from_email": "mara@coachmara.com",
"html_body": "<h1>Hola {{first_name}}</h1><p>Tu curso ya está...</p>",
"send_to": {
"include_tags": ["tier:premium"],
"exclude_tags": ["unsubscribed_promos"]
},
"scheduled_at": "2026-05-12T15:00:00Z"
}'Variables soportadas en el HTML: {{first_name}} {{from_name}} {{tenant_name}} {{unsubscribe_url}}
# Agregar 1 subscriber
curl -X POST https://postari.io/api/v1/subscribers \
-H "Authorization: Bearer ef_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"email": "miembro@gmail.com",
"name": "Juan Pérez",
"tags": ["tier:premium", "joined:2026-04"],
"metadata": { "faroli_id": "cm456def", "ltv_cents": 29700 }
}'
# Bulk import (max 5000 por request)
curl -X POST https://postari.io/api/v1/subscribers/bulk \
-H "Authorization: Bearer ef_live_xxx" \
-d '{
"subscribers": [
{ "email": "uno@example.com", "tags": ["nuevo"] },
{ "email": "dos@example.com", "tags": ["nuevo"] }
],
"on_conflict": "ignore"
}'
# Listar paginado con cursor
curl "https://postari.io/api/v1/subscribers?limit=100&tag=tier:premium" \
-H "Authorization: Bearer ef_live_xxx"
# Update tags
curl -X PUT https://postari.io/api/v1/subscribers/miembro%40gmail.com \
-H "Authorization: Bearer ef_live_xxx" \
-d '{ "tags": ["tier:premium", "completed:lesson3"] }'
# Soft delete
curl -X DELETE https://postari.io/api/v1/subscribers/miembro%40gmail.com \
-H "Authorization: Bearer ef_live_xxx"Cada cuenta puede configurar 1 dominio sender. Postari devuelve los DNS records (DKIM CNAMEs + SPF + DMARC) que el creator pega en su DNS provider.
# Registrar dominio
curl -X POST https://postari.io/api/v1/domains \
-H "Authorization: Bearer ef_live_xxx" \
-d '{
"domain": "coachmara.com",
"from_email": "mara@coachmara.com",
"from_name": "Mara López"
}'
# Response incluye dns_records con CNAMEs DKIM, SPF y DMARC
# El creator pega esos records en Cloudflare/Hostinger/etc.
# Verificar (después de propagación DNS · típicamente 30 min - 24h)
curl -X POST "https://postari.io/api/v1/domains/coachmara.com?action=verify" \
-H "Authorization: Bearer ef_live_xxx"
# Estado actual
curl https://postari.io/api/v1/domains/coachmara.com \
-H "Authorization: Bearer ef_live_xxx"Configura un webhook en /ajustes/webhooks. Postari envía POST con HMAC-SHA256 signature en header X-Postari-Signature.
Eventos disponibles:
email_delivered · Resend confirma entregaemail_opened · pixel disparadoemail_clicked · click en linkemail_bounced · rebotó (hard/soft)email_complained · marcó como spamemail_unsubscribed · usó link unsub# Payload típico
{
"event": "email_opened",
"tenant_id": "uuid",
"fired_at": "2026-05-09T20:30:00Z",
"payload": {
"email_id": "uuid",
"contact_id": "uuid",
"campaign_id": "uuid",
"subject": "Tu asunto",
"device_type": "mobile",
"email_client": "apple_mail",
"opened_at": "2026-05-09T20:30:00Z"
}
}
# Verificar la firma en tu servidor (Node.js)
# El header es X-Postari-Signature: t=<unix_ts>,v1=<hmac_sha256_hex>
# y lo que se firma es `${t}.${rawBody}` (timestamp + punto + cuerpo crudo).
const [tPart, v1Part] = req.headers['x-postari-signature'].split(',');
const t = tPart.slice(2); // quita "t="
const v1 = v1Part.slice(3); // quita "v1="
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(`${t}.${rawBody}`)
.digest('hex');
// Comparación en tiempo constante · no uses !==
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
if (!ok) return res.status(401).end();
// Y rechaza lo viejo: sin esto, quien capture un webhook puede reenviarlo siempre.
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.status(401).end();# Resumen del período
curl "https://postari.io/api/v1/stats?from=2026-04-01&to=2026-05-01" \
-H "Authorization: Bearer ef_live_xxx"
# Stats por campaña (incluye by_link[])
curl https://postari.io/api/v1/campaigns/uuid-campaña/stats \
-H "Authorization: Bearer ef_live_xxx"by_link[]: cada URL clickeada con clicks totales y unique_clicks (1 por contact).
| Código | Cuándo |
|---|---|
| 400 | Body inválido o campos requeridos faltantes |
| 401 | API key inválida, expirada o revocada |
| 403 | API key sin scope suficiente para este endpoint |
| 404 | Recurso (subscriber, campaña, dominio) no encontrado |
| 409 | Conflicto · subscriber ya existe, dominio ya configurado |
| 422 | Validation error · ej. dominio no verificado al intentar enviar |
| 423 | Cuenta con envíos congelados (sending_frozen) |
| 429 | Rate limit excedido · header Retry-After indica cuándo reintentar |
| 502 | Error con provider externo (Resend, etc.) |
| 500 | Error interno · contacta soporte |
¿Dudas o reportar bug? Email hi@postari.io
Postari API v1 · base URL https://postari.io/api/v1