Saltar al contenido principal

API de importaciones

Carga masiva de contactos, eventos o registros de entidades personalizadas desde un archivo CSV o JSON.

Todos los endpoints de esta página son relativos a la URL base: https://api-eu1.joryio.com/api - consulta Visión general de la API.

Visión general​

Una importación son tres llamadas, no una. Pides una ubicación de subida, subes el archivo directamente al almacenamiento y después le dices a Joryio que lo procese:

  1. POST /users/import/upload-url - devuelve una URL prefirmada y el fileKey que identifica tu subida.
  2. PUT del archivo a esa URL - directo al almacenamiento, de modo que un archivo de 200 MB nunca pasa por la API.
  3. POST /users/import - crea el trabajo. Responde de inmediato; el archivo se procesa en segundo plano.
  4. GET /users/import - consulta el progreso y los recuentos finales.

Que el paso 2 funcione no dice nada sobre el paso 3. La subida es solo un archivo aterrizando en un bucket; la validación ocurre al crear el trabajo y de nuevo fila por fila mientras se ejecuta.

Autenticación​

Authorization: Bearer YOUR_API_KEY

Todos los endpoints de esta página requieren users:write. Establecer overrideConsent requiere además compliance:write: con users:write a secas se obtiene un 403 y la importación no arranca.

1. Solicitar una URL de subida​

POST /users/import/upload-url
Content-Type: application/json

{
"filename": "customers-2026-08.csv"
}
{
"success": true,
"fileKey": "imports/<organizationId>/<workspaceId>/customers-2026-08.csv",
"uploadUrl": "https://storage.example.com/..."
}

El fileKey está acotado a tu organización y a tu espacio de trabajo. Devuélvelo tal cual en el paso 3: una clave fuera de tu propio prefijo se rechaza con un 400.

2. Subir el archivo​

curl -X PUT --upload-file customers-2026-08.csv "$UPLOAD_URL"

Un 200 aquí significa que los bytes están almacenados. No significa que la importación vaya a funcionar.

3. Crear el trabajo de importación​

POST /users/import
Content-Type: application/json

{
"format": "csv",
"fileKey": "imports/<organizationId>/<workspaceId>/customers-2026-08.csv",
"importType": "users",
"originalFilename": "customers-2026-08.csv",
"fieldMapping": {
"Email Address": "email",
"First Name": "firstName",
"Loyalty Tier": "attributes.loyaltyTier"
}
}
CampoTipoNotas
formatcsv | jsonObligatorio
fileKeystringObligatorio. Exactamente lo que devolvió el paso 1
importTypeusers | events | entitiesPor defecto users
originalFilenamestring?Se muestra en el historial de importaciones
fieldMappingobject?Columna de origen → ruta del campo destino. Omítelo para detectarlo desde la fila de cabecera
overrideConsentboolean?Ver más abajo. Requiere compliance:write
smsAlreadyConfirmedboolean?Solo para el doble opt-in por SMS. Ver más abajo. Requiere compliance:write
maxBackdatingDaysint?1-3650. Cuánto se puede retrodatar un evento importado
entityNamestring?Obligatorio cuando importType es entities

Los campos desconocidos se rechazan con un 400 en lugar de ignorarse, de modo que una errata en un nombre de campo falla de forma visible en vez de no hacer nada en silencio.

fieldMapping contiene columnas, y solo columnas​

Cada clave de fieldMapping es uno de tus propios nombres de columna. Las opciones de importación son campos tipados aparte - overrideConsent, entityName, maxBackdatingDays - y nunca claves de este mapa.

Esto importa si tienes una columna llamada literalmente _overrideConsent: es una columna, se mapea como cualquier otra y no activa la anulación de consentimiento. Solo lo hace el campo tipado.

Si omites fieldMapping por completo, las columnas se emparejan desde la fila de cabecera del archivo. Si lo indicas, se respeta exactamente tal como está: lo que no esté mapeado se ignora.

Sin fieldMapping, las grafías habituales de los encabezados se asignan a los campos estándar - por ejemplo first_name, First Name o given_name a attributes.firstName, surname o last_name a attributes.lastName, e-mail a email y mobile a phone; cualquier otra columna se convierte en un atributo personalizado con su propio nombre. La lista completa está en Importación masiva. full_name no se divide. Los archivos CSV pueden ir separados por comas, punto y coma o tabulaciones - el separador se lee de la fila de encabezados.

Archivos JSON​

Se aceptan ambas formas:

[ { "email": "[email protected]" }, { "email": "[email protected]" } ]
{ "users": [ { "email": "[email protected]" } ] }

overrideConsent​

Por defecto una importación puede suprimir pero no otorgar: una fila unsubscribed de tu archivo da de baja a ese contacto, mientras que una fila subscribed no vuelve a suscribir a alguien que se había dado de baja antes.

overrideConsent: true trata el archivo como autoritativo en ambos sentidos. Es una acción de cumplimiento, así que requiere compliance:write además de users:write.

Fíjate en la asimetría: las bajas se aplican de todos modos. No necesitas este indicador para sincronizar exclusiones desde un CRM, solo para volver a suscribir a personas que se habían dado de baja.

El valor debe ser un booleano JSON. Una cadena, un 1 o un "yes" se rechazan con un 400 en lugar de interpretarse silenciosamente como false: es mejor que te lo digan a creer que se aplicó una anulación que nunca ocurrió.

Incluso con overrideConsent: true, una nueva suscripción se aplica solo al contacto de la fila - los demás contactos con el mismo email o teléfono siguen dados de baja -, una baja por STOP se anula solo con el flag (registrado en el historial del contacto con el trabajo de importación), y una importación nunca levanta un rebote duro, una queja de spam ni un bloqueo a nivel de organización. Sin él, un valor subscribed para un contacto bloqueado se ignora. Un valor unsubscribed llega a todos los contactos del espacio de trabajo con el mismo email o teléfono. Contactos que comparten un email o un teléfono.

El trabajo terminado incluye consentSummary:

{ "overridden": 12, "overriddenSmsStop": 3, "keptUnsubscribed": 40, "keptDeliverability": 1, "keptOrganization": 0 }

overridden cuenta las nuevas suscripciones hechas con overrideConsent, y overriddenSmsStop cuántas de ellas eran contactos de SMS que habían enviado STOP (su proveedor de SMS puede seguir bloqueando mensajes hasta que envíen START); keptUnsubscribed cuenta los valores subscribed ignorados porque el flag estaba desactivado; keptDeliverability y keptOrganization cuentan los que ninguna importación puede levantar. consentSummary es null en las importaciones que terminaron antes de que existiera.

smsAlreadyConfirmed​

Solo importa cuando el doble opt-in por SMS del espacio de trabajo está activado. Entonces cada alta de SMS nueva del archivo (un contacto sin estado de SMS) se guarda como pending y recibe un mensaje "Reply YES", enviado en torno a un minuto después; consentSummary.awaitingSmsConfirmation las cuenta. smsAlreadyConfirmed: true declara que tienes prueba de que estas personas ya confirmaron su alta de SMS: se guardan como subscribed sin mensaje. Es una declaración de cumplimiento, así que requiere compliance:write como overrideConsent.

Con overrideConsent: true (y el doble opt-in por SMS activado), un contacto del archivo dado de baja de SMS se trata como un alta nueva: pending, UN mensaje de confirmación, sin recordatorio, suscrito solo tras YES y de vuelta a unsubscribed si no responde. Sin él, los contactos dados de baja no cambian y no reciben mensaje. smsAlreadyConfirmed: true los suscribe en su lugar (junto con overrideConsent). Un archivo nunca confirma a un contacto que ya está pending.

4. Consultar el progreso​

GET /users/import

Devuelve el historial de importaciones del espacio de trabajo, de más reciente a más antiguo, con status, totalRows, processedRows, createdCount, updatedCount, skippedCount, failedCount y, cuando hay filas fallidas, una muestra acotada de los errores con su número de fila de origen. matchedSeveralCount cuenta las filas con email pero sin externalId cuando varios contactos comparten ese email: cada una actualizó solo el contacto de actividad más reciente, una baja en ella llegó a todos, y el informe de errores la incluye como notice. Consulta Cuando varios contactos comparten un email; añade externalId a tus filas para que coincidan exactamente con un contacto.

status avanza pending → processing → completed o failed.

5. Listar las importaciones que un segmento puede usar​

GET /users/import/sources?limit=100

Requiere users:read. Devuelve las importaciones de contactos (users) finalizadas del espacio de trabajo, de la más reciente a la más antigua: la lista de la que elige la condición de segmento Vino de una importación:

{
"imports": [
{
"id": "6f1c2a9e-4b7d-4c1e-9a53-2f0d8e7b1c44",
"fileName": "customers-2026-08.csv",
"status": "completed",
"createdAt": "2026-10-08T09:00:00.000Z",
"completedAt": "2026-10-08T09:05:12.000Z",
"rowCount": 1200,
"createdCount": 1000,
"updatedCount": 150
}
]
}

limit vale 100 por defecto y como máximo 200. ids (separados por comas, hasta 100) devuelve exactamente esas importaciones, sea cual sea su antigüedad: sirve para nombrar las importaciones a las que apunta una condición guardada. status es completed, expired (finalizada; su informe de errores ya se eliminó) o failed: una importación fallida solo aparece si llegó a procesar filas, porque los contactos a los que llegó están marcados. Las importaciones de eventos y de entidades no aparecen.

Cada contacto que una importación de contactos crea o actualiza queda marcado con el id de la importación, así que un segmento puede apuntar a esa importación:

{ "type": "contact_import", "operator": "any_of", "value": ["6f1c2a9e-4b7d-4c1e-9a53-2f0d8e7b1c44"] }

Consulta el filtro contact_import en la Segments API. Los contactos importados antes de que se publicara esta función no llevan marca.

Una importación cada vez​

Un espacio de trabajo puede tener una importación pending o processing. Una segunda petición mientras hay una activa devuelve:

{ "statusCode": 400, "message": "An import is already in progress for this workspace. Please wait for it to complete." }

Consulta el paso 4 hasta que termine la primera en lugar de reintentar de inmediato.

Errores​

EstadoSignificado
400Campo desconocido, valor inválido de overrideConsent, fileKey fuera de tu prefijo, importación de entities sin entityName, o una importación ya en curso
403Se pidió overrideConsent sin compliance:write
403Se pidió smsAlreadyConfirmed sin compliance:write
404El fileKey no corresponde a ningún objeto subido: el trabajo se crea y después falla con "The specified key does not exist"

Las filas que fallan individualmente no hacen fallar el trabajo. Se cuentan en failedCount y se incluyen en el informe de errores, de modo que un archivo con tres filas erróneas de cincuenta mil importa igualmente las otras 49.997.