Contactos
Los contactos son perfiles compartidos del workspace. Leer lista o detalle requiere contacts:read; crear, editar, archivar, restaurar o importar perfiles requiere contacts:write. Todas las mutaciones llevan Idempotency-Key.
Crea o sincroniza un perfil
POST /contacts crea un perfil con email, teléfono internacional o ambos; exige al menos uno. POST /contacts/upsert busca por email normalizado cuando se proporciona; sin email crea un contacto nuevo. Nunca identifica ni fusiona por teléfono. Ejemplo:
{
"email": "ana@example.com",
"phone": "+50688888888",
"firstName": "Ana",
"custom": {
"locale": {"type": "text", "value": "es-CR"},
"timezone": {"type": "text", "value": "America/Costa_Rica"}
}
}
La normalización elimina espacios exteriores y convierte a minúsculas; conserva puntos y alias +. Teléfono exige prefijo internacional explícito y se almacena como E.164; aceptar su formato no verifica titularidad ni capacidad de mensajería. El alta empieza sin suscripción de email Marketing. El servidor asigna identidad, fuente y fechas.
Upsert modifica solo los campos y claves custom aportados; no exige revisión y devuelve {contact, created} dentro de data. Usa PATCH /contacts/{id} con el ETag en If-Match si necesitas protegerte contra ediciones concurrentes. Omitir un campo lo conserva; null borra campos opcionales o valores custom. Quitar email o teléfono debe dejar la otra dirección. Para actualizar perfiles con solo teléfono usa PATCH por contactId, que permanece estable aunque cambien las direcciones. segmentIds añade asignaciones, conserva las anteriores y exige también segments:write.
GET /contacts admite email, emailSubscriptionStatus, archived, limit y cursor. Consulta el detalle con GET /contacts/{id}.
Importa en segundo plano
POST /contact-imports admite hasta 1000 filas y 700 KB:
{
"mode": "create",
"rows": [{"contact": {"email": "ana@example.com", "firstName": "Ana"}}]
}
create rechaza coincidencias; upsert, predeterminado si omites mode, sincroniza. Consulta progreso en GET /contact-imports/{id} y recibos paginados en GET /contact-imports/{id}/rows. No hay listado externo de importaciones. Cada fila puede tener un resultado distinto; importar perfiles no importa consentimiento de email. Cada fila con solo teléfono crea un contacto distinto, aunque el número se repita. Email, teléfono, nombre y apellidos son principales; empresa, cumpleaños, idioma y zona horaria usan sus definiciones personalizadas existentes.
Registra consentimiento explícito
Con contacts:email:consent:import, registra evidencia mediante POST /contacts/{id}/email-consent/evidence: method, email, occurredAt, source, sourceEventId, policyVersion, statement y affirmative: true. Debe acreditar una acción real, corresponder a la dirección actual y no tener fecha futura.
Usa la evidencia devuelta (method, email, occurredAt, evidenceRef) como emailConsent en POST /contacts/{id}/email-subscription con action: "request_subscription", contacts:email:subscribe e If-Match. La confirmación del destinatario completa la suscripción; el enlace caduca en 24 horas.
Para consentimiento ya establecido existe POST /contacts/{id}/email-consent/import con {emailConsent} y permiso explícito de importación. No restaura una baja ni elimina supresiones. Registrar evidencia solo no suscribe. Para dar de baja, usa /email-subscription con {action: "unsubscribe"} y revisión actual.
Un contacto con solo teléfono participa en segmentos y automatizaciones. Una acción de email registra una omisión por falta de email y continúa sin consumir cuota. Cambiar el email conserva identidad e historial, restablece la autorización para la nueva dirección y nunca redirige mensajes ya aceptados.
Conserva el historial restrictivo
POST /contacts/{id}/archive y /restore requieren revisión. Archivo conserva perfil y consentimiento; restaurar no revierte bajas. DELETE /contacts/{id} requiere contacts:delete y revisión: minimiza el perfil, pero retiene identidad e historial necesario para impedir recreaciones que eludan revocaciones. No representa borrado físico total.