Factuarea APIDevelopers

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ónScopeIdempotency-KeyQué hace
GET /v1/accounts/imports/templateaccounts:readNoDescarga la plantilla CSV del fichero de cuentas.
POST /v1/accounts/imports/previewaccounts:writeNoClasifica y tarifica cada fila. No se escribe, aprovisiona ni cobra nada.
POST /v1/accounts/importsaccounts:writeObligatoriaAplica 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.

CampoObligatorioQué es
accounts_fileSíEl fichero de cuentas: una fila por cuenta gestionada o por NIF propio, hasta 100 filas.
customers_fileNoEl fichero de clientes: los clientes de esas cuentas y NIF, hasta 1.000 filas.
optionsNoUn 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ónPor defectoQué hace
send_emailfalseEnviamos 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_companyfalseImporta 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_changesfalseAplica 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;operate

La 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

Columnamanaged_accountown_nifNotas
modeObligatoriaObligatoriaown_nif o managed_account, sin valor por defecto: un valor vacío o desconocido deja la fila en ERROR.
external_refObligatoriaNo se usaTu 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.
planObligatoriaNo se admiteemprendedor, 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_idObligatoriaObligatoriaEl NIF, CIF o NIE, hasta 20 caracteres, validado con su carácter de control. Uno por fila, nunca repetido en el fichero.
legal_nameObligatoriaObligatoriaLa razón social, hasta 100 caracteres, y al menos 2 en una cuenta gestionada.
address, postal_code, city, province, country_codeOpcionalesOpcionalesEl domicilio fiscal, del cliente o de tu NIF propio. Hasta 255, 10, 100, 100 y 100 caracteres.
owner_emailObligatoria en una cuenta nueva de producciónNo se usaLa 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_nameOpcionalOpcionalEl 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.
languageOpcionalNo se usaes, en o ca: el idioma de la cuenta y de su vale.
access_levelOpcionalNo se usaEl 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ónUTF-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.
SeparadorUn 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ámbuloSalta 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).
CabeceraEn 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.
FilasSe 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.
TextoLas 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ímiteValorCuando se supera
Tamaño de cada fichero5 MB413 import_file_too_large. error.param es accounts_file o customers_file.
Tamaño de toda la petición11 MB413 payload_too_large, antes de inspeccionar ningún fichero.
Filas de accounts_file100422 import_row_limit_exceeded. error.param nombra el fichero.
Filas de customers_file1.000422 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 previas10 por minuto por clave429, con Retry-After.
Aplicaciones5 por minuto por clave y 20 al día en tu cuenta429, con Retry-After.
Aplicaciones a la vezUna en tu cuenta409 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.

statusSe aplicaQué significa
VALIDSíLa fila es correcta y nada en tu cuenta lo impide. Aplicarla crea la cuenta o da de alta el NIF.
WARNINGSíSe aplica, pero algo de ella no cambia. code dice qué.
ALREADY_EXISTSSíLa cuenta o el NIF ya existe. La fila lo concilia: nunca lo duplica ni cobra dos veces.
BLOCKEDNoUna regla de negocio impide la fila. code nombra el motivo.
ERRORNoLa fila está mal. errors nombra las columnas, o code el motivo.

El code de un WARNING dice qué no cambió:

codeQué significa
managed_account_claimedEl cliente ya reclamó la cuenta, así que sus datos son suyos: la fila no cambia nada.
access_level_below_relationLa fila pide un nivel inferior al tuyo. La importación nunca baja un nivel, así que se queda.
plan_change_not_confirmedLa fila pide otro plan y no enviaste confirm_plan_changes. El plan no cambia.
plan_change_failedLos 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_id y legal_name, y external_ref y plan en una cuenta gestionada;
  • mode no es own_nif ni managed_account;
  • tax_id no es un NIF, CIF o NIE válido;
  • plan no es emprendedor, empresario ni integral (enterprise queda BLOCKED, no es un error), o una fila own_nif lo lleva;
  • language no es es, en ni ca, access_level no es none, view ni operate, o owner_email no es una dirección de correo;
  • un texto es más largo que su máximo, o una legal_name o display_name de una cuenta gestionada tiene menos de 2 caracteres;
  • external_ref o tax_id ya apareció en una fila anterior del fichero: el error va en la segunda fila y en las siguientes;
  • tax_id difiere del de la cuenta gestionada que el external_ref ya 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ódigoClaveCuándo
requiredmode, tax_id, legal_name, external_ref, plan, owner_emailUna columna obligatoria está vacía.
invalid_modemodeNo es own_nif ni managed_account.
invalid_planplanNo es emprendedor, empresario ni integral.
plan_not_allowedplanUna fila own_nif lleva plan.
invalid_tax_idtax_idNo es un NIF, CIF o NIE válido.
invalid_emailowner_emailNo es una dirección de correo.
invalid_languagelanguageNo es es, en ni ca.
invalid_access_levelaccess_levelNo es none, view ni operate.
too_longCualquier columna de textoEl texto es más largo que su máximo.
too_shortlegal_name, display_nameUna cuenta gestionada lleva menos de 2 caracteres.
duplicate_in_fileexternal_ref, tax_idYa apareció en una fila anterior.
tax_id_mismatchtax_idDifiere del de la cuenta que el external_ref ya nombra.
unknown_columnLa columna desconocidaLa cabecera tiene una columna que no está en la plantilla.
duplicate_columnLa columna repetidaLa cabecera repite una columna.
unnamed_columnrowUna columna de la cabecera no tiene nombre.
column_count_mismatchrowLa fila tiene más valores que columnas tiene la cabecera.
invalid_valuerowLa cuenta o el NIF rechaza un valor de la fila.
row_rejectedrowEl dominio rechaza la fila mientras se aplica. El motivo es el code de la fila.
row_apply_failedrowFalló la ejecución de la fila.
plan_change_rejectedplanEn 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:

CampoQué es
objectSiempre managed_account_import_preview.
rowsLas filas del fichero de cuentas, en el orden del fichero.
totalsLas filas por estado: total_rows, valid, warning, already_exists, blocked y error. Los cinco estados están siempre, 0 cuando no hay ninguna.
costEl coste de las filas que se aplicarían, por modalidad y en total.
own_company_customersEl resultado de apply_customers_to_own_company, o null cuando la opción está apagada. Mira clientes.

Y cada fila tiene estos:

CampoQué es
lineLa línea de la fila en el fichero de cuentas, contando desde 1.
modemanaged_account u own_nif. Es null cuando la fila no tiene un mode válido, y entonces es ERROR.
external_refTu referencia en una fila managed_account, y null en una fila own_nif.
tax_idEl NIF tal como se lee del fichero, o null cuando la fila no lo tiene.
statusUno de los cinco estados.
codeEl motivo de un WARNING, un BLOCKED o un ERROR que no es un error de columna, o null.
errorsLos errores por columna.
error_codesEl código de cada error de errors, con las mismas claves. Mira los códigos de los errores.
planEl plan de una fila managed_account, y null en una fila own_nif: toma el plan de tu cuenta.
monthly_amount_centsEl importe mensual que la fila añade a tu suscripción, en céntimos.
proration_amount_centsLo que cobra de inmediato aplicar la fila, en céntimos.
account_idEl id de la cuenta gestionada que la fila creó o concilió, o null mientras no existe.
customers_statusnone, queued o blocked. Mira clientes.
customers_queuedLos clientes encolados para el NIF de la fila.
customers_codeEl 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ón managed-accounts de 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.

codeCuándoQué hacer
managed_account_plan_not_self_serveLa 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_trialTu 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_reachedTu 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_currentLa 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_requiredAlgo 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_requiredEl 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_planUna fila own_nif y tu plan no admite NIF adicionales: empiezan en empresario.Mira la puerta y sus cuatro códigos.
additional_nif_limit_reachedUna 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_environmentEl external_ref pertenece a una cuenta del otro entorno, producción o pruebas.Usa otra referencia.
managed_account_no_longer_managedEl external_ref pertenece a una cuenta que ya no gestionas, porque su titular asumió el pago.Usa otra referencia.
management_endedLa 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_forbiddenEl cliente ya reclamó la cuenta y la fila pide un nivel superior al tuyo.Solo el cliente puede subir tu nivel.
access_level_above_relationLa 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_providerCon 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_operateCon confirm_plan_changes, tu nivel sobre la cuenta no es operate.Mira tu nivel de acceso.
forbidden_actionUna 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 cuentaNada. La fila es ALREADY_EXISTS y solo se importan sus clientes.
Una cuenta gestionada que aprovisionaste, en el mismo entorno y sin reclamarActualiza 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 gestionasBLOCKED, 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_id distinto es un ERROR.
  • El nivel de acceso nunca cambia. Un nivel inferior en el fichero es un WARNING y uno superior es BLOCKED.
  • 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_accountSu external_ref.
own_nifSu 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.example

Los 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_codePor qué no se importan sus clientes
nif_not_appliedLa fila no se aplicó: es BLOCKED o ERROR.
operate_access_requiredTu cuenta no tiene operate sobre la cuenta gestionada.
account_admin_requiredLos 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_scopeTu NIF propio está fuera del ámbito de NIF de la clave y esta importación no lo dio de alta.
insufficient_scopeLa clave no tiene contacts:write.
customers_import_failedEl 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:

codestatusQué significa
own_company_focus_requiredERRORTu cuenta tiene varios NIF y ninguno está en foco.
insufficient_scopeERRORLa clave no tiene contacts:write.
live_credential_requiredERRORLa importación se ejecuta con una clave de pruebas.
account_admin_required u operate_access_requiredBLOCKEDLa persona que emitió la clave no puede escribir en esa empresa.
nif_not_in_key_scopeBLOCKEDEsa empresa está fuera del ámbito de NIF de la clave.
customers_import_failedBLOCKEDEl 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
  }
}
CampoQué es
idEl identificador de la ejecución, un UUID v7.
objectSiempre managed_account_import.
statusEl estado de la ejecución: completed cuando terminó. failed cuando no pudo terminar, y entonces las filas ya aplicadas se quedan aplicadas.
statsLas cifras de la ejecución: las filas por estado, created, reconciled, customers_queued, monthly_amount_cents y proration_amount_cents.
rowsLas filas de la vista previa, en el orden del fichero, con claim_token y claim_url añadidos.
own_company_customersComo 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-KeySi 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 NIFHacen 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 vezLas 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_account crean o concilian cuentas de pruebas: sin cobro, sin Stripe y sin vale, así que claim_token y claim_url son null. Una cuenta de pruebas no cuenta para max_managed_accounts.
  • Las filas own_nif salen ERROR con code live_credential_required y suman 0 al total: los NIF de tu propia cuenta solo se dan de alta en producción. Lo mismo vale para los clientes que apply_customers_to_own_company cargarí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_ref que pertenece a una cuenta de producción sale BLOCKED con managed_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 refleja POST /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 necesita series:write. issuing-readiness te dice si a un NIF todavía le falta una: no_default_series es uno de sus bloqueos, como explica qué impide emitir a un NIF. El {company} de un NIF importado está en companies[].id de GET /v1/accounts/{account} para una cuenta gestionada, cuyo {account} es el account_id de la fila, y en GET /v1/accounts/{account}/companies para 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ódigoStatusCuándo
insufficient_scope403La clave no lista accounts:write, o accounts:read para la plantilla. * no cuenta.
privileged_scope_required403Tu cuenta no tiene el privilegio de proveedor, o la persona que emitió la clave ya no es titular ni administrador de ella.
invalid_param_value422Falta 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_large413Un fichero supera los 5 MB. error.param lo nombra.
payload_too_large413Toda la petición supera los 11 MB.
import_row_limit_exceeded422accounts_file tiene más de 100 filas, o customers_file más de 1.000.
import_file_invalid422El fichero no se puede leer, no tiene fila de cabecera o le falta una columna.
idempotency_key_required422La aplicación no lleva Idempotency-Key.
idempotency_key_reused422Esa clave ya se usó con una petición distinta.
import_in_progress409Otra importación de tu cuenta se está ejecutando.
upload_in_flight_budget_exceeded429Tu 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

En esta página

¿Te echamos una mano?Contactar con soporte