Importación masiva de cuentas gestionadas
Previsualiza y aplica un CSV de clientes de una vez: columnas, modalidades, estados por fila, límites y qué hace una reimportación.
La importación masiva carga muchos clientes de una vez desde un fichero CSV. Cada fila es una de dos cosas, según su columna mode: una cuenta gestionada, una cuenta separada que aprovisionas para un cliente, o un NIF propio, un NIF que das de alta en tu propia cuenta como NIF adicional (la app lo muestra como «NIF en mi cuenta»). Primero envías el fichero a una vista previa, que clasifica y tarifica cada fila sin escribir ni cobrar nada, y después envías el mismo fichero para aplicarlo. Esta guía está escrita para la cuenta que aprovisiona, el proveedor de cuentas gestionadas: mira cuentas gestionadas. Explica las tres operaciones, el fichero y sus columnas, cómo lo lee el parser, los límites, los estados por fila, la vista previa y su coste, qué hace una segunda importación del mismo fichero, los clientes que puedes cargar con él, la aplicación y su idempotencia, y qué hace una clave de pruebas.
La importación es declarativa y no atómica: el fichero dice lo que debe existir, cada fila que se puede aplicar se aplica por separado y una fila que falla nunca deshace las demás. No tiene herramienta MCP ni opción de series de documentos, como explica el final de esta guía.
Las tres operaciones
| Operación | Scope | Idempotency-Key | Qué hace |
|---|---|---|---|
GET /v1/accounts/imports/template | accounts:read | No | Descarga la plantilla CSV del fichero de cuentas. |
POST /v1/accounts/imports/preview | accounts:write | No | Clasifica y tarifica cada fila. No se escribe, aprovisiona ni cobra nada. |
POST /v1/accounts/imports | accounts:write | Obligatoria | Aplica la importación y responde 201 Created con el resultado de la ejecución. |
accounts:read y accounts:write son scopes privilegiados: * no los cubre, y solo pueden usarlos las cuentas a las que hemos concedido el privilegio de proveedor. Sin el privilegio, o cuando la persona que emitió la clave ya no es titular ni administrador de tu cuenta, la respuesta es 403 privileged_scope_required. La vista previa también necesita accounts:write, aunque no escribe nada. Ninguna de las tres rutas lleva el segmento {account}: el proveedor es siempre la cuenta de la clave y el entorno es el de la clave, y ninguno de los dos va en la petición.
El orden habitual es este: descarga la plantilla, rellénala, envíala a la vista previa, corrige las filas que salgan BLOCKED o ERROR y vuelve a previsualizar, y después aplica los mismos ficheros.
La petición
La vista previa y la aplicación reciben el mismo cuerpo multipart/form-data.
| Campo | Obligatorio | Qué es |
|---|---|---|
accounts_file | Sí | El fichero de cuentas: una fila por cuenta gestionada o por NIF propio, hasta 100 filas. |
customers_file | No | El fichero de clientes: los clientes de esas cuentas y NIF, hasta 1.000 filas. |
options | No | Un objeto con tres opciones, todas false por defecto. |
Los dos ficheros son CSV, preferiblemente en UTF-8, separados por comas, punto y coma o tabuladores, y de hasta 5 MB cada uno. El parser es más tolerante que la plantilla. Para previsualizar el fichero del ejemplo de más abajo, envía solo el fichero de cuentas:
curl -X POST https://api.factuarea.com/v1/accounts/imports/preview \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-F "accounts_file=@accounts.csv"No fijes Content-Type tú mismo: tu cliente HTTP añade la cabecera multipart/form-data con su boundary. Para aplicar, envía los mismos campos a POST /v1/accounts/imports con una Idempotency-Key.
Opciones
| Opción | Por defecto | Qué hace |
|---|---|---|
send_email | false | Enviamos el enlace de reclamación al titular de cada cuenta gestionada nueva de producción, y la respuesta lleva claim_token y claim_url a null. Con false, cada fila nueva de producción devuelve su vale una sola vez, en el 201. Mira quién entrega el vale. |
apply_customers_to_own_company | false | Importa también, en tu propia empresa, los clientes de customers_file que no tienen account_ref. La clave necesita contacts:write. Mira clientes. |
confirm_plan_changes | false | Aplica el cambio de plan que pide una fila sobre una cuenta gestionada que ya existe y que pagas tú. Sin ella, esa fila sale WARNING y el plan no cambia. Mira importar otra vez el mismo fichero. |
options va como campos de formulario anidados (options[send_email]=true) o como un único campo JSON (options={"send_email":true}), que es como un cliente OpenAPI serializa un objeto en multipart/form-data. Un booleano acepta true, false, 1, 0, on, off, yes y no. Cualquier otra clave responde 422 invalid_param_value, así que una opción mal escrita nunca se ignora en silencio.
El fichero de cuentas
El fichero de cuentas tiene una fila de cabecera y una fila por cuenta gestionada o por NIF propio, hasta 100. Una fila lleva un NIF, y un NIF aparece una sola vez en el fichero.
La plantilla
GET /v1/accounts/imports/template devuelve text/csv como adjunto con el nombre plantilla-importacion-cuentas.csv. Es la misma para todos los proveedores y no lee nada de tu cuenta. El fichero es UTF-8 con marca de orden de bytes (BOM), ; como separador y fin de línea CRLF, que es lo que una hoja de cálculo en español abre bien. Trae la fila de cabecera y una fila de ejemplo:
mode;external_ref;plan;tax_id;legal_name;address;postal_code;city;province;country_code;owner_email;display_name;language;access_level
# ejemplo: managed_account;CLIENTE-0001;empresario;B12345674;Cliente Ejemplo SL;Calle Mayor 1;28013;Madrid;Madrid;ES;titular@example.com;Cliente Ejemplo;es;operateLa primera celda de la fila de ejemplo empieza por # ejemplo, y la importación ignora siempre una fila así. Sin esa marca sería una fila managed_account válida, así que quítala solo cuando lo pretendas. El conjunto de columnas es cerrado: una columna que no está en la plantilla deja cada fila en ERROR en esa columna, y lo mismo hace una columna que aparece dos veces.
Columnas
| Columna | managed_account | own_nif | Notas |
|---|---|---|---|
mode | Obligatoria | Obligatoria | own_nif o managed_account, sin valor por defecto: un valor vacío o desconocido deja la fila en ERROR. |
external_ref | Obligatoria | No se usa | Tu propia referencia del cliente, hasta 128 caracteres, única en el fichero y en tu cuenta. Es lo que hace que una segunda importación concilie en lugar de duplicar. |
plan | Obligatoria | No se admite | emprendedor, empresario o integral. enterprise solo se asigna por contrato y su fila queda BLOCKED. En una fila own_nif, un plan es un ERROR: el NIF toma el plan de tu cuenta. |
tax_id | Obligatoria | Obligatoria | El NIF, CIF o NIE, hasta 20 caracteres, validado con su carácter de control. Uno por fila, nunca repetido en el fichero. |
legal_name | Obligatoria | Obligatoria | La razón social, hasta 100 caracteres, y al menos 2 en una cuenta gestionada. |
address, postal_code, city, province, country_code | Opcionales | Opcionales | El domicilio fiscal, del cliente o de tu NIF propio. Hasta 255, 10, 100, 100 y 100 caracteres. |
owner_email | Obligatoria en una cuenta nueva de producción | No se usa | La persona que será titular de la cuenta y recibirá el vale de reclamación, hasta 255 caracteres. Una cuenta de pruebas no la necesita. |
display_name | Opcional | Opcional | El nombre visible. En una cuenta gestionada es también el nombre de la cuenta, de 2 a 100 caracteres. En un NIF propio son hasta 255 caracteres, y es la legal_name si está vacío. |
language | Opcional | No se usa | es, en o ca: el idioma de la cuenta y de su vale. |
access_level | Opcional | No se usa | El nivel que conserva tu cuenta sobre la nueva cuenta gestionada: none, view u operate. Es operate si está vacío. |
mode, plan, access_level y language se leen en minúsculas, y cada valor se recorta. Una celda vacía cuenta como ausente. owner_email, language y access_level se comprueban en su forma en todas las filas, aunque la modalidad no las use.
Cómo lee el fichero el parser
El parser lee lo que exporta una hoja de cálculo, así que es más tolerante que la plantilla. Decide el tipo del fichero por su contenido y nunca por su nombre ni su extensión.
| Qué | Cómo lo lee |
|---|---|
| Codificación | UTF-8 con o sin BOM, UTF-16 con BOM (el «texto Unicode» de Excel) y, si el fichero no es UTF-8 válido, Windows-1252. |
| Separador | Un punto y coma, una coma o un tabulador, detectado en la fila de cabecera. Si encajan varios, el que da más columnas, y en caso de empate el primero de esos tres. |
| Preámbulo | Salta hasta 20 líneas antes de la fila de cabecera. La fila de cabecera es la primera línea con una celda mode (account_ref en el fichero de clientes). |
| Cabecera | En minúsculas y recortada, con los espacios y guiones interiores convertidos en _: Tax ID y external-ref se leen como tax_id y external_ref. |
| Filas | Se saltan las líneas en blanco y la fila # ejemplo. Las celdas se recortan, espacios de no separación incluidos. Un valor entre comillas dobles puede contener el separador y saltos de línea. |
| Texto | Las celdas son texto literal y no se evalúa nada: una fórmula se lee como el texto que contiene. |
Lo que no lee: una hoja de cálculo (.xlsx o .xls), un PDF, una imagen, un archivo comprimido o cualquier fichero binario es 422 import_file_invalid aunque se llame .csv, y lo mismo un fichero sin fila de cabecera tras el preámbulo, con unas comillas dobles sin cerrar o con caracteres de control. Guarda la hoja como CSV antes de subirla.
Límites
| Límite | Valor | Cuando se supera |
|---|---|---|
| Tamaño de cada fichero | 5 MB | 413 import_file_too_large. error.param es accounts_file o customers_file. |
| Tamaño de toda la petición | 11 MB | 413 payload_too_large, antes de inspeccionar ningún fichero. |
Filas de accounts_file | 100 | 422 import_row_limit_exceeded. error.param nombra el fichero. |
Filas de customers_file | 1.000 | 422 import_row_limit_exceeded. error.param nombra el fichero. |
| Un fichero ilegible, sin fila de cabecera o al que le falta una columna | — | 422 import_file_invalid. |
| Bytes de los ficheros que se suben a la vez, en tu cuenta | — | 429 upload_in_flight_budget_exceeded, con Retry-After. |
| Vistas previas | 10 por minuto por clave | 429, con Retry-After. |
| Aplicaciones | 5 por minuto por clave y 20 al día en tu cuenta | 429, con Retry-After. |
| Aplicaciones a la vez | Una en tu cuenta | 409 import_in_progress. |
El tamaño y las filas se comprueban antes de leer ninguna fila, así que cuando falla uno de ellos no se clasifica, escribe ni cobra nada. Las filas se cuentan sin la cabecera, las líneas en blanco ni la fila de ejemplo. Para cargar más de 100 clientes, parte el fichero y envía las partes una tras otra: cada parte concilia por external_ref, así que puedes volver a enviar una parte.
Estados por fila
La vista previa y la aplicación clasifican cada fila con las mismas reglas y le dan uno de cinco estados. Solo los tres primeros se aplican.
status | Se aplica | Qué significa |
|---|---|---|
VALID | Sí | La fila es correcta y nada en tu cuenta lo impide. Aplicarla crea la cuenta o da de alta el NIF. |
WARNING | Sí | Se aplica, pero algo de ella no cambia. code dice qué. |
ALREADY_EXISTS | Sí | La cuenta o el NIF ya existe. La fila lo concilia: nunca lo duplica ni cobra dos veces. |
BLOCKED | No | Una regla de negocio impide la fila. code nombra el motivo. |
ERROR | No | La fila está mal. errors nombra las columnas, o code el motivo. |
El code de un WARNING dice qué no cambió:
code | Qué significa |
|---|---|
managed_account_claimed | El cliente ya reclamó la cuenta, así que sus datos son suyos: la fila no cambia nada. |
access_level_below_relation | La fila pide un nivel inferior al tuyo. La importación nunca baja un nivel, así que se queda. |
plan_change_not_confirmed | La fila pide otro plan y no enviaste confirm_plan_changes. El plan no cambia. |
plan_change_failed | Los datos se actualizaron, pero el cambio de plan confirmado falló. errors.plan lleva el motivo. |
Las filas se clasifican en el orden del fichero, así que las filas válidas anteriores a una agotan los topes que esta necesitaría: un fichero con más cuentas nuevas de producción que tu tope deja las últimas BLOCKED. El code de una fila es un motivo del informe de la importación y no siempre un código del catálogo de errores: los que tienen página enlazan a ella.
Errores por columna
Una fila mal formada sale ERROR con code null y un objeto errors. Su clave es la columna, o row para un error de la fila entera, y su valor es un mensaje en español. El objeto siempre está, y es {} cuando la fila no tiene nada mal. Una fila falla cuando:
- una columna obligatoria está vacía:
mode,tax_idylegal_name, yexternal_refyplanen una cuenta gestionada; modeno esown_nifnimanaged_account;tax_idno es un NIF, CIF o NIE válido;planno esemprendedor,empresarioniintegral(enterprisequedaBLOCKED, no es un error), o una filaown_niflo lleva;languageno eses,ennica,access_levelno esnone,viewnioperate, oowner_emailno es una dirección de correo;- un texto es más largo que su máximo, o una
legal_nameodisplay_namede una cuenta gestionada tiene menos de 2 caracteres; external_refotax_idya apareció en una fila anterior del fichero: el error va en la segunda fila y en las siguientes;tax_iddifiere del de la cuenta gestionada que elexternal_refya nombra;- una cuenta gestionada nueva de producción no tiene
owner_email; - la cabecera tiene una columna que no está en la plantilla o repite una (el error va en esa columna, en todas las filas), o una fila tiene más valores que columnas tiene la cabecera (el error va en
row).
Un valor que la cuenta o el NIF rechaza mientras se aplica la fila también es un ERROR, con code null y su mensaje en errors.row. Un fallo de la propia ejecución, como un error de la base de datos o de la pasarela de pago, es un ERROR con code row_apply_failed. Ninguno de los dos detiene el resto de la importación.
Códigos de los errores
Cada entrada de errors tiene su código estable en error_codes, un objeto con las mismas claves: {columna: código}. El mensaje de errors está en español y puede reformularse; el código no se traduce ni cambia, así que reacciona al código, o tradúcelo, y enseña el mensaje solo a una persona. Igual que errors, siempre está y es {} cuando la fila no tiene nada mal; también en una importación creada antes de que existiera el campo. Una fila BLOCKED sale con los dos objetos vacíos: su motivo es el code de la fila. El campo es aditivo: errors no cambia.
| Código | Clave | Cuándo |
|---|---|---|
required | mode, tax_id, legal_name, external_ref, plan, owner_email | Una columna obligatoria está vacía. |
invalid_mode | mode | No es own_nif ni managed_account. |
invalid_plan | plan | No es emprendedor, empresario ni integral. |
plan_not_allowed | plan | Una fila own_nif lleva plan. |
invalid_tax_id | tax_id | No es un NIF, CIF o NIE válido. |
invalid_email | owner_email | No es una dirección de correo. |
invalid_language | language | No es es, en ni ca. |
invalid_access_level | access_level | No es none, view ni operate. |
too_long | Cualquier columna de texto | El texto es más largo que su máximo. |
too_short | legal_name, display_name | Una cuenta gestionada lleva menos de 2 caracteres. |
duplicate_in_file | external_ref, tax_id | Ya apareció en una fila anterior. |
tax_id_mismatch | tax_id | Difiere del de la cuenta que el external_ref ya nombra. |
unknown_column | La columna desconocida | La cabecera tiene una columna que no está en la plantilla. |
duplicate_column | La columna repetida | La cabecera repite una columna. |
unnamed_column | row | Una columna de la cabecera no tiene nombre. |
column_count_mismatch | row | La fila tiene más valores que columnas tiene la cabecera. |
invalid_value | row | La cuenta o el NIF rechaza un valor de la fila. |
row_rejected | row | El dominio rechaza la fila mientras se aplica. El motivo es el code de la fila. |
row_apply_failed | row | Falló la ejecución de la fila. |
plan_change_rejected | plan | En una fila WARNING, se rechaza el cambio de plan confirmado. El motivo es el code de la fila. |
La vista previa
POST /v1/accounts/imports/preview clasifica y tarifica los ficheros sin ningún efecto: no escribe nada, no aprovisiona ninguna cuenta y no cobra nada. Para tarifar las filas solo consulta a Stripe, por el fin de tu periodo de facturación y, por cada cambio de plan confirmado, el importe prorrateado de ese cambio, así que tiene su propio límite de 10 por minuto. No necesita Idempotency-Key: es una consulta que usa POST porque sube ficheros.
Toma este fichero de cuentas:
mode;external_ref;plan;tax_id;legal_name;address;postal_code;city;province;country_code;owner_email;display_name;language;access_level
managed_account;cliente-0042;empresario;B12345674;Talleres García SL;Calle Mayor 1;28013;Madrid;Madrid;ES;laura@talleresgarcia.example;Talleres García;es;operate
own_nif;;;B87654323;Distribuciones Norte SL;;;;;;;;;
managed_account;cliente-0043;emprendedor;A58818501;Panadería Sol SA;;;;;;marta@panaderiasol.example;;es;
managed_account;cliente-0044;emprendedor;;Gráficas Levante SL;;;;;;ivan@graficaslevante.example;;;Tiene cuatro filas: una cuenta gestionada, un NIF propio, una segunda cuenta gestionada que no cabe en tu tope y otra que olvidó su NIF. La vista previa responde 200:
{
"data": {
"object": "managed_account_import_preview",
"rows": [
{
"line": 2,
"mode": "managed_account",
"external_ref": "cliente-0042",
"tax_id": "B12345674",
"status": "VALID",
"code": null,
"errors": {},
"error_codes": {},
"plan": "empresario",
"monthly_amount_cents": 3500,
"proration_amount_cents": 1750,
"account_id": null,
"customers_status": "none",
"customers_queued": 0,
"customers_code": null
},
{
"line": 3,
"mode": "own_nif",
"external_ref": null,
"tax_id": "B87654323",
"status": "VALID",
"code": null,
"errors": {},
"error_codes": {},
"plan": null,
"monthly_amount_cents": 1500,
"proration_amount_cents": 750,
"account_id": null,
"customers_status": "none",
"customers_queued": 0,
"customers_code": null
},
{
"line": 4,
"mode": "managed_account",
"external_ref": "cliente-0043",
"tax_id": "A58818501",
"status": "BLOCKED",
"code": "managed_accounts_limit_reached",
"errors": {},
"error_codes": {},
"plan": "emprendedor",
"monthly_amount_cents": 0,
"proration_amount_cents": 0,
"account_id": null,
"customers_status": "none",
"customers_queued": 0,
"customers_code": null
},
{
"line": 5,
"mode": "managed_account",
"external_ref": "cliente-0044",
"tax_id": null,
"status": "ERROR",
"code": null,
"errors": {
"tax_id": "La columna tax_id es obligatoria: cada fila da de alta un NIF."
},
"error_codes": {
"tax_id": "required"
},
"plan": null,
"monthly_amount_cents": 0,
"proration_amount_cents": 0,
"account_id": null,
"customers_status": "none",
"customers_queued": 0,
"customers_code": null
}
],
"totals": {
"total_rows": 4,
"valid": 2,
"warning": 0,
"already_exists": 0,
"blocked": 1,
"error": 1
},
"cost": {
"currency": "EUR",
"managed_account": {
"monthly_amount_cents": 3500,
"proration_amount_cents": 1750
},
"own_nif": {
"monthly_amount_cents": 1500,
"proration_amount_cents": 750
},
"total": {
"monthly_amount_cents": 5000,
"proration_amount_cents": 2500
}
},
"own_company_customers": null
}
}El cuerpo tiene estos campos:
| Campo | Qué es |
|---|---|
object | Siempre managed_account_import_preview. |
rows | Las filas del fichero de cuentas, en el orden del fichero. |
totals | Las filas por estado: total_rows, valid, warning, already_exists, blocked y error. Los cinco estados están siempre, 0 cuando no hay ninguna. |
cost | El coste de las filas que se aplicarían, por modalidad y en total. |
own_company_customers | El resultado de apply_customers_to_own_company, o null cuando la opción está apagada. Mira clientes. |
Y cada fila tiene estos:
| Campo | Qué es |
|---|---|
line | La línea de la fila en el fichero de cuentas, contando desde 1. |
mode | managed_account u own_nif. Es null cuando la fila no tiene un mode válido, y entonces es ERROR. |
external_ref | Tu referencia en una fila managed_account, y null en una fila own_nif. |
tax_id | El NIF tal como se lee del fichero, o null cuando la fila no lo tiene. |
status | Uno de los cinco estados. |
code | El motivo de un WARNING, un BLOCKED o un ERROR que no es un error de columna, o null. |
errors | Los errores por columna. |
error_codes | El código de cada error de errors, con las mismas claves. Mira los códigos de los errores. |
plan | El plan de una fila managed_account, y null en una fila own_nif: toma el plan de tu cuenta. |
monthly_amount_cents | El importe mensual que la fila añade a tu suscripción, en céntimos. |
proration_amount_cents | Lo que cobra de inmediato aplicar la fila, en céntimos. |
account_id | El id de la cuenta gestionada que la fila creó o concilió, o null mientras no existe. |
customers_status | none, queued o blocked. Mira clientes. |
customers_queued | Los clientes encolados para el NIF de la fila. |
customers_code | El motivo de customers_status: blocked, o null. |
Qué cuesta la importación
Cada fila lleva monthly_amount_cents, el importe mensual que añade a tu suscripción, y proration_amount_cents, lo que cobra de inmediato aplicarla por lo que queda de tu periodo de facturación actual. cost los suma para las filas managed_account, para las filas own_nif y en total, con la currency. Los importes son céntimos enteros de base imponible, sin IVA: el nombre de la clave dice la unidad.
En el ejemplo, las dos filas VALID añaden 3500 y 1500 céntimos al mes, 50,00 EUR, y aplicar el fichero cobra de inmediato 2500 céntimos, 25,00 EUR. El prorrateo es la mitad del importe mensual, como cuando queda la mitad de tu periodo de facturación. Las filas BLOCKED y ERROR no añaden nada.
- Solo cuenta lo que se cobra. Solo añaden importe mensual las filas nuevas que son
VALID: las cuentas gestionadas nuevas, que se cobran en la suscripciónmanaged-accountsde tu cuenta, y los NIF propios nuevos, que se cobran como NIF adicionales. Una fila que ya existe añade 0. - Un cambio de plan confirmado solo añade su prorrateo. Con
confirm_plan_changes, la fila de una cuenta existente no tiene importe mensual, pero sí el prorrateo que cobraría un cambio de plan suelto. Una subida se cobra de inmediato. Una bajada surte efecto al final de tu periodo y añade 0 ahora. - Algunas importaciones cuestan 0. Es 0 en una importación de pruebas, mientras tu cuenta está en su trial y cuando a tu cuenta no se le cobra.
- La aplicación cobra lo que anunció la vista previa. El prorrateo anunciado para un fichero es lo que cobra aplicar los mismos ficheros. La aplicación vuelve a clasificar el fichero en el momento en que lo envías, con el estado que tenga entonces tu cuenta, así que una vista previa de hace horas puede diferir.
Filas que salen BLOCKED
El code de una fila BLOCKED es el motivo. Una fila BLOCKED no cambia nada, ni campos ni plan.
code | Cuándo | Qué hacer |
|---|---|---|
managed_account_plan_not_self_serve | La fila pide enterprise, o un cambio sobre una cuenta con contrato enterprise. | enterprise lo asignamos por contrato: pide emprendedor, empresario o integral. |
managed_account_provider_in_trial | Tu cuenta está en su trial y la fila crearía una cuenta gestionada de producción, que se cobra desde el primer día. | Usa una clave fact_test_ para crear cuentas de pruebas, o espera a empezar a pagar. Mira tu cuenta en trial. |
managed_accounts_limit_reached | Tu cuenta está en su tope de cuentas gestionadas de producción, contando las filas válidas anteriores a esta. | Termina la gestión de una cuenta que ya no llevas, o pídenos que subamos el tope. |
billing_not_current | La suscripción que paga tus cuentas gestionadas no está al día, por ejemplo tras una renovación fallida. | Pon tu facturación al día y vuelve a previsualizar. |
payment_method_required | Algo de la fila se cobra y tu cuenta no tiene método de pago. La importación nunca abre un checkout. | Registra un método de pago en la app y vuelve a previsualizar. |
nif_transfer_required | El NIF ya está en una cuenta. Una cuenta gestionada necesita un NIF que no esté en ninguna, la tuya incluida, y un NIF propio no puede estar en otra cuenta. Nunca decimos en cuál. | Mueve el NIF con un traspaso de NIF. |
additional_nif_not_in_plan | Una fila own_nif y tu plan no admite NIF adicionales: empiezan en empresario. | Mira la puerta y sus cuatro códigos. |
additional_nif_limit_reached | Una fila own_nif y tu cuenta está en su tope de NIF adicionales, contando las filas válidas anteriores a esta. | Mira el tope. |
managed_account_external_ref_in_other_environment | El external_ref pertenece a una cuenta del otro entorno, producción o pruebas. | Usa otra referencia. |
managed_account_no_longer_managed | El external_ref pertenece a una cuenta que ya no gestionas, porque su titular asumió el pago. | Usa otra referencia. |
management_ended | La gestión de la cuenta con ese external_ref terminó, y la importación no reactiva una gestión. | Antes de que el cliente la reclame, aprovisiona ese external_ref con POST /v1/accounts. |
access_level_raise_forbidden | El cliente ya reclamó la cuenta y la fila pide un nivel superior al tuyo. | Solo el cliente puede subir tu nivel. |
access_level_above_relation | La cuenta no está reclamada y la fila pide un nivel superior al tuyo. | La importación nunca sube un nivel. Mira tu nivel de acceso. |
plan_change_not_paid_by_provider | Con confirm_plan_changes, el cliente asumió el pago, así que ya no pagas la cuenta. | Solo la cuenta que paga puede cambiar el plan. |
plan_change_requires_operate | Con confirm_plan_changes, tu nivel sobre la cuenta no es operate. | Mira tu nivel de acceso. |
forbidden_action | Una fila own_nif y la clave no alcanza la empresa titular de tu cuenta, que paga los NIF adicionales. | Usa una clave que la alcance. |
Una fila que el dominio rechaza mientras se aplica, por ejemplo un tope que se llenó entre la vista previa y la aplicación, también queda BLOCKED, con el código público de ese rechazo. Trata un code que no conozcas como el motivo de una fila BLOCKED.
Una fila own_nif necesita además companies:write en la clave, aparte de accounts:write: sin él, la fila es ERROR con code insufficient_scope. Si la clave está limitada a algunos NIF, cada NIF que da de alta la importación se añade a ese límite, como cuando das de alta un NIF con una sola llamada.
Importar otra vez el mismo fichero
Aplicar otra vez el mismo fichero nunca duplica una cuenta ni cobra dos veces. El external_ref de una fila managed_account identifica la cuenta, y el tax_id identifica una fila own_nif. Una fila que nombra algo que ya existe sale ALREADY_EXISTS, o WARNING o BLOCKED cuando lo que pide no se puede aplicar.
| La fila trata de… | Qué hace la importación |
|---|---|
| Un NIF propio que ya está en tu cuenta | Nada. La fila es ALREADY_EXISTS y solo se importan sus clientes. |
| Una cuenta gestionada que aprovisionaste, en el mismo entorno y sin reclamar | Actualiza los campos de la lista de abajo que la fila trae, y nada más. |
| Una cuenta gestionada que su titular ya reclamó | WARNING managed_account_claimed. No cambia nada: esos datos son ahora del titular. |
| Una cuenta gestionada cuya gestión terminó | BLOCKED management_ended. |
Un external_ref del otro entorno, o de una cuenta que ya no gestionas | BLOCKED, sin escribir nada. |
Qué actualiza una segunda importación
Solo se actualiza una lista de campos, y solo mientras la cuenta no esté reclamada: display_name (el nombre visible, que es también el nombre de la cuenta y de su NIF), language, legal_name, address, postal_code, city, province y country_code. Un campo cambia solo cuando su celda tiene valor, y un valor igual al actual no se reescribe. Todo lo demás queda fuera de su alcance:
- El NIF de una cuenta existente no puede cambiar: un
tax_iddistinto es unERROR. - El nivel de acceso nunca cambia. Un nivel inferior en el fichero es un
WARNINGy uno superior esBLOCKED. - El plan cambia solo como dice el párrafo siguiente.
Una fila que pide un plan distinto sobre una cuenta existente es WARNING plan_change_not_confirmed y no cambia nada, salvo que envíes confirm_plan_changes. Con ella, el cambio se aplica cuando sigues pagando la cuenta, tu nivel sobre ella es operate y no tiene contrato enterprise. Si no, la fila es BLOCKED con plan_change_not_paid_by_provider, plan_change_requires_operate o managed_account_plan_not_self_serve. El cambio se tarifica como un cambio de plan suelto. Si el perfil se actualizó y el cambio de plan falla después, la fila es WARNING con el motivo en errors.plan.
Clientes
customers_file carga los clientes de cada cuenta y NIF en la misma importación. Tiene una fila de cabecera con la columna account_ref y, en las demás columnas, las de la importación de contactos de una empresa: la cabecera de la plantilla de GET /v1/companies/{company}/contacts/import/template. Cada cliente va a una fila del fichero de cuentas a través de su account_ref:
| La fila del fichero de cuentas es… | Sus clientes llevan un account_ref igual a… |
|---|---|
managed_account | Su external_ref. |
own_nif | Su tax_id. |
Con el fichero de cuentas de arriba, este fichero de clientes envía tres clientes a cliente-0042:
account_ref;name;kind;tax_id;email
cliente-0042;Bar La Plaza SL;company;B22222228;compras@barlaplaza.example
cliente-0042;Ferretería Ortiz SL;company;B33333337;info@ferreteriaortiz.example
cliente-0042;Hotel Mirador SA;company;A44444446;reservas@hotelmirador.exampleLos clientes pasan por el importador de contactos que ya existe, con el rol customer y la estrategia de conflicto reject: un cliente que choca con un contacto existente se rechaza y el existente se queda como está. Exportar e importar explica qué acepta ese importador. Los clientes se encolan: customers_queued cuenta lo que se encoló, y los contactos se crean después, así que la cifra no prueba que ya existan.
Los clientes de una fila se importan solo en un NIF que la fila aplicó y donde puedes escribir. El estado de cada fila está en customers_status, que es none (la fila no tiene clientes), queued o blocked, y en el segundo caso customers_code dice por qué:
customers_code | Por qué no se importan sus clientes |
|---|---|
nif_not_applied | La fila no se aplicó: es BLOCKED o ERROR. |
operate_access_required | Tu cuenta no tiene operate sobre la cuenta gestionada. |
account_admin_required | Los clientes van a uno de tus NIF propios y la persona que emitió la clave no es titular ni administrador de tu cuenta. |
nif_not_in_key_scope | Tu NIF propio está fuera del ámbito de NIF de la clave y esta importación no lo dio de alta. |
insufficient_scope | La clave no tiene contacts:write. |
customers_import_failed | El importador de contactos falló. |
Con una clave, accounts:write no basta para los clientes: la clave necesita también contacts:write.
Clientes sin cuenta: apply_customers_to_own_company
Los clientes que no tienen account_ref no van a ninguna parte, salvo que envíes apply_customers_to_own_company. Con ella, se importan en tu propia empresa en foco: el único NIF del ámbito de la clave, o el único NIF de tu cuenta. El destino es siempre un NIF real de tu propia cuenta, nunca uno que otra cuenta te haya cedido. La clave necesita contacts:write, y el resultado está en own_company_customers:
code | status | Qué significa |
|---|---|---|
own_company_focus_required | ERROR | Tu cuenta tiene varios NIF y ninguno está en foco. |
insufficient_scope | ERROR | La clave no tiene contacts:write. |
live_credential_required | ERROR | La importación se ejecuta con una clave de pruebas. |
account_admin_required u operate_access_required | BLOCKED | La persona que emitió la clave no puede escribir en esa empresa. |
nif_not_in_key_scope | BLOCKED | Esa empresa está fuera del ámbito de NIF de la clave. |
customers_import_failed | BLOCKED | El importador de contactos falló. |
own_company_customers tiene status (VALID, BLOCKED o ERROR), code, customers_queued y unmatched_customers, que cuenta los clientes cuyo account_ref no corresponde a ninguna fila del fichero de cuentas: no se importan en ningún sitio, y así no se pierden en silencio.
Aplicar la importación
POST /v1/accounts/imports recibe el cuerpo de la petición y lo aplica. Aplica por separado cada fila VALID, WARNING o ALREADY_EXISTS, en el orden del fichero: una fila que falla sale BLOCKED o ERROR y las demás siguen. Nunca abre un checkout: cobra tu suscripción existente, y una fila que no se puede cobrar sale BLOCKED con su motivo.
curl -X POST https://api.factuarea.com/v1/accounts/imports \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "accounts_file=@accounts.csv" \
-F "customers_file=@customers.csv" \
-F "options[confirm_plan_changes]=false"La aplicación es síncrona: la respuesta llega cuando termina la última fila, y con un fichero lleno puede tardar unos minutos, así que deja al menos 5 minutos en tu cliente. Supón que quitaste las dos filas que salieron BLOCKED y ERROR y envías el fichero de clientes de arriba. La respuesta es 201 Created:
{
"data": {
"id": "0199a1f0-1c22-7a10-b111-8a8b8c8d8e8f",
"object": "managed_account_import",
"status": "completed",
"stats": {
"total_rows": 2,
"valid": 2,
"warning": 0,
"already_exists": 0,
"blocked": 0,
"error": 0,
"created": 2,
"reconciled": 0,
"customers_queued": 3,
"monthly_amount_cents": 5000,
"proration_amount_cents": 2500
},
"rows": [
{
"line": 2,
"mode": "managed_account",
"external_ref": "cliente-0042",
"tax_id": "B12345674",
"status": "VALID",
"code": null,
"errors": {},
"error_codes": {},
"plan": "empresario",
"monthly_amount_cents": 3500,
"proration_amount_cents": 1750,
"account_id": "0199a1f0-1c22-7a10-b111-6a6b6c6d6e6f",
"customers_status": "queued",
"customers_queued": 3,
"customers_code": null,
"claim_token": "e3c3e3c3e3c3e3c3e3c3e3c3e3c3e3c3e3c3e3c3e3c3e3c3",
"claim_url": "https://app.factuarea.com/account-claims/accept?token=e3c3e3c3e3c3e3c3e3c3e3c3e3c3e3c3e3c3e3c3e3c3e3c3"
},
{
"line": 3,
"mode": "own_nif",
"external_ref": null,
"tax_id": "B87654323",
"status": "VALID",
"code": null,
"errors": {},
"error_codes": {},
"plan": null,
"monthly_amount_cents": 1500,
"proration_amount_cents": 750,
"account_id": null,
"customers_status": "none",
"customers_queued": 0,
"customers_code": null,
"claim_token": null,
"claim_url": null
}
],
"own_company_customers": null
}
}| Campo | Qué es |
|---|---|
id | El identificador de la ejecución, un UUID v7. |
object | Siempre managed_account_import. |
status | El estado de la ejecución: completed cuando terminó. failed cuando no pudo terminar, y entonces las filas ya aplicadas se quedan aplicadas. |
stats | Las cifras de la ejecución: las filas por estado, created, reconciled, customers_queued, monthly_amount_cents y proration_amount_cents. |
rows | Las filas de la vista previa, en el orden del fichero, con claim_token y claim_url añadidos. |
own_company_customers | Como en la vista previa. |
Los importes de stats y de cada fila son los que cobró la aplicación: el prorrateo que anunció la vista previa, calculado de nuevo en ese momento. En el ejemplo, la cuenta gestionada de la línea 2 devuelve su claim_token y su claim_url, y el NIF propio devuelve null, porque no tiene vale.
El vale de cada fila managed_account nueva de producción vuelve una sola vez, en esta respuesta. Una repetición de la misma petición lo devuelve como null, y también una fila cuando send_email es true, porque el enlace fue por correo, y todas las filas de una importación de pruebas. Si pierdes uno, reemítelo. La respuesta no lleva cabecera Location, porque una ejecución no tiene operación pública de lectura. La app guarda el historial y el informe de cada importación, en Cuentas de clientes, Importar, Historial de importaciones, y cuando una importación termina publicamos account.import_completed en los webhooks de cuenta de tu cuenta, con el id de la ejecución y sus stats.
Idempotency-Key y una importación a la vez
La aplicación no se puede deshacer, así que exige una Idempotency-Key: sin ella la respuesta es 422 idempotency_key_required antes de leer nada. La vista previa no la necesita. Qué hace cada protección:
| Qué | Qué hace |
|---|---|
Idempotency-Key | Si se corta la conexión y repites la misma petición con la misma clave, reproducimos la primera respuesta, con Idempotent-Replayed: true, y no aplicamos nada dos veces. Cada vale vuelve como null: nunca lo guardamos en claro, ni siquiera en la caché de idempotencia. La misma clave con otra petición es 422 idempotency_key_reused. Mira idempotencia. |
external_ref y el NIF | Hacen que el fichero concilie: una nueva aplicación del mismo fichero, incluso con una clave nueva, no crea nada dos veces ni cobra nada dos veces. |
| Una importación a la vez | Las aplicaciones de tu cuenta se serializan. Mientras una se ejecuta, la siguiente responde 409 import_in_progress y no aplica nada. |
Tras un 409, espera a que termine la importación en curso y envía otra vez los mismos ficheros con una Idempotency-Key nueva: las filas que la primera ya aplicó vuelven ALREADY_EXISTS, nunca duplicadas ni cobradas dos veces. Una importación que nunca terminó deja de bloquear a los 15 minutos. Un 429 no aplica nada: espera lo que diga Retry-After y envía la petición otra vez.
Con una clave de pruebas
Con una clave fact_test_ la importación trabaja sobre tus cuentas de pruebas, y no se cobra nada:
- Las filas
managed_accountcrean o concilian cuentas de pruebas: sin cobro, sin Stripe y sin vale, así queclaim_tokenyclaim_urlsonnull. Una cuenta de pruebas no cuenta paramax_managed_accounts. - Las filas
own_nifsalenERRORconcodelive_credential_requiredy suman 0 al total: los NIF de tu propia cuenta solo se dan de alta en producción. Lo mismo vale para los clientes queapply_customers_to_own_companycargaría en tu propia empresa. - El entorno es el de la clave. Una importación de pruebas solo ve tus cuentas de pruebas: un
external_refque pertenece a una cuenta de producción saleBLOCKEDconmanaged_account_external_ref_in_other_environment, y al revés. - El coste es 0, en cada fila y en los totales.
El flujo completo con una clave de pruebas está en probar cuentas gestionadas.
Dos cosas que la importación no hace, por diseño
- Sin herramienta MCP. Las tres operaciones son privilegiadas y la importación lee dos ficheros subidos, que el servidor MCP no transporta. La alternativa es la API REST, o la importación de la app. Desde un agente, aprovisiona las cuentas de una en una con la herramienta
provision_managed_account, que reflejaPOST /v1/accounts: mira desde el servidor MCP. - Sin series. El fichero no tiene columna ni opción para series de documentos: da de alta cuentas y NIF, no su numeración. Crea o cambia las series de un NIF importado con las operaciones del eje de empresa, por ejemplo
POST /v1/companies/{company}/series/bootstrap, que crea en una llamada las series por defecto que faltan y necesitaseries:write.issuing-readinesste dice si a un NIF todavía le falta una:no_default_serieses uno de sus bloqueos, como explica qué impide emitir a un NIF. El{company}de un NIF importado está encompanies[].iddeGET /v1/accounts/{account}para una cuenta gestionada, cuyo{account}es elaccount_idde la fila, y enGET /v1/accounts/{account}/companiespara tus NIF propios.
Errores
Las reglas que rechazarían una provisión suelta rechazan aquí una fila, nunca la petición: llegan en el 201 o el 200, en el status y el code de la fila. Estos son los errores de la petición:
| Código | Status | Cuándo |
|---|---|---|
insufficient_scope | 403 | La clave no lista accounts:write, o accounts:read para la plantilla. * no cuenta. |
privileged_scope_required | 403 | Tu cuenta no tiene el privilegio de proveedor, o la persona que emitió la clave ya no es titular ni administrador de ella. |
invalid_param_value | 422 | Falta accounts_file o no es un fichero, o options lleva una clave desconocida o un valor que no es booleano. error.param lo nombra. |
import_file_too_large | 413 | Un fichero supera los 5 MB. error.param lo nombra. |
payload_too_large | 413 | Toda la petición supera los 11 MB. |
import_row_limit_exceeded | 422 | accounts_file tiene más de 100 filas, o customers_file más de 1.000. |
import_file_invalid | 422 | El fichero no se puede leer, no tiene fila de cabecera o le falta una columna. |
idempotency_key_required | 422 | La aplicación no lleva Idempotency-Key. |
idempotency_key_reused | 422 | Esa clave ya se usó con una petición distinta. |
import_in_progress | 409 | Otra importación de tu cuenta se está ejecutando. |
upload_in_flight_budget_exceeded | 429 | Tu cuenta ya está subiendo demasiados bytes a la vez. |
Un 429 sin un código de esta tabla es un límite de la importación: una vista previa por encima de 10 por minuto, o una aplicación por encima de 5 por minuto o de 20 al día. Espera lo que diga Retry-After.
Próximos pasos
aprovisiona una cuenta cada vez, el vale de reclamación, tu nivel de acceso y el fin de la gestión.
qué pagas por cada cuenta gestionada y cómo se cobra un cambio de plan.
recorre todo el flujo, con una importación, con una clave de pruebas y sin cobro.
lo que cuesta una fila own_nif, qué planes la admiten y el tope.
recibe account.import_completed y los eventos de las cuentas que aprovisionas.
todos los códigos del modelo de cuentas, con su causa y qué hacer.