Factuarea APIDevelopers

Facturación FACe (B2G)

Envía facturas FacturaE 3.2.2 a FACe — códigos DIR3, XML firmado XAdES-EPES conservado por intento, estados de tramitación, transmisiones inciertas y cómo recuperarte, anulación, simulación en sandbox y el scope facturae:write.

Facturar a una administración pública española (B2G) es obligatorio a través de FACe, el punto general de entrada de facturas electrónicas (Ley 25/2013). Factuarea genera el XML FacturaE 3.2.2 de cualquier factura emitida, lo firma XAdES-EPES con el certificado de tu empresa y lo presenta al web service de FACe — y después sigue rastreando el estado de tramitación que informa FACe hasta que la factura se paga (o se rechaza).

El ciclo de vida completo lo cubren las cinco operaciones del grupo FacturaE de la Referencia de la API:

Las lecturas usan el scope facturae:read; enviar y anular requieren facturae:write. El módulo FacturaE está incluido desde el plan Profesional.

Todas las rutas de esta guía cuelgan de /v1/companies/{company}/…: {company} es el id (UUID v7) del NIF con el que operas, y en los ejemplos curl es $COMPANY_ID. Alcanzar un NIF explica cómo resolverlo.

Antes de enviar

Configura los códigos DIR3 del cliente. Todo cliente administración pública lleva tres códigos del directorio DIR3, cada uno con el formato ^[A-Z][A-Z0-9]{8,9}$ (p. ej. L01280796), y opcionalmente un cuarto, el proponente. Configúralos al crear o actualizar el cliente:

curl -X PATCH https://api.factuarea.com/v1/companies/$COMPANY_ID/contacts/0197a2a8-4cf0-7a31-9a5e-3f2b8c1d6e42/customer-profile \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
  -H "Content-Type: application/json" \
  -d '{
    "dir3_accounting_office": "L01280796",
    "dir3_managing_body": "L01280796",
    "dir3_processing_unit": "L01280796"
  }'
CampoRol DIR3
dir3_accounting_officeOficina contable (01)
dir3_managing_bodyÓrgano gestor (02)
dir3_processing_unitUnidad tramitadora (03)
dir3_proponentProponente (04), opcional

La administración te indica los códigos (los tres primeros a menudo coinciden); también puedes consultarlos en el directorio público DIR3. La operación reemplaza el perfil, así que un código que dejes fuera se borra: envía todos los que quieras conservar.

La factura captura estos códigos cuando se crea, y puedes sustituirlos por factura o corregirlos tras emitir con una revisión administrativa. El encaminamiento que cuenta es el de la factura, no el del contacto en vivo; los roles 01 a 03 son obligatorios para enviar y el 04 es opcional. Si la factura tiene un encaminamiento conocido sin centros, el envío falla con client_missing_dir3_codes aunque el contacto tenga códigos.

Sube un certificado de firma activo. FACe solo acepta facturas firmadas, así que el envío requiere el certificado FNMT (PKCS#12) que tu empresa ya usa para VeriFactu (POST /v1/companies/{company}/verifactu/certificates). Sin certificado activo el envío falla con signing_certificate_required.

Emite la factura. Las facturas en borrador no pueden viajar a FACe — enviar o emitir antes la factura es lo que congela su contenido legal. Los borradores y las facturas programadas (aún sin número fiscal) responden invoice_not_emittable_for_facturae.

Descargar el XML FacturaE

Puedes descargar el XML en cualquier momento — para presentación manual, archivo o validación — sin involucrar a FACe. Cualquier factura que ya tenga número fiscal es exportable (también una cobrada, anulada o cancelada); solo las facturas draft y scheduled no lo son. Una factura anterior sin calendario de pagos capturado se exporta sin el bloque de vencimientos: Factuarea nunca lo inventa.

curl -OJ https://api.factuarea.com/v1/companies/$COMPANY_ID/invoices/0197b1c2-89ab-7def-8123-456789abcdef/facturae \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW"

Con un certificado activo el cuerpo va firmado XAdES-EPES (política de firma Facturae v3.1) y el fichero se llama .xsig; sin él, el XML vuelve sin firmar como .xml. El header de respuesta X-Facturae-Signed: true|false distingue ambos casos. Consulta la referencia del endpoint.

La descarga tolera la ausencia de certificado (obtienes el XML sin firmar); el envío a FACe no — FACe exige la firma.

Para obtener el fichero exacto que se presentó en un intento, añade submission_id:

curl -OJ "https://api.factuarea.com/v1/companies/$COMPANY_ID/invoices/0197b1c2-89ab-7def-8123-456789abcdef/facturae?submission_id=0197c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d" \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW"

Eso devuelve los bytes conservados de ese intento: nunca regenera, vuelve a firmar ni lee el certificado o el contacto actuales, así que es la evidencia de lo que recibió la administración aunque la factura, los códigos DIR3 o el certificado hayan cambiado después. La respuesta añade X-Facturae-Sha256 (y el mismo valor como ETag) cuando se conoce el hash del fichero conservado. Un fichero ausente o corrupto responde 500 facturae_artifact_unavailable; no se regenera. La factura y el intento deben corresponderse; si no, 404.

Enviar a FACe

La operación de envío no necesita cuerpo de petición: la factura viaja en la ruta. Cada llamada prepara un intento: congela la factura, el encaminamiento DIR3 efectivo (proponente incluido), el calendario de pagos, el certificado que firma y el XML firmado exacto, y solo entonces habla con FACe:

curl -X POST https://api.factuarea.com/v1/companies/$COMPANY_ID/invoices/0197b1c2-89ab-7def-8123-456789abcdef/face-submissions \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
  -H "Idempotency-Key: $(uuidgen)"

Respuesta (201 para un intento nuevo):

{
  "data": {
    "id": "0197c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "object": "face_submission",
    "invoice_id": "0197b1c2-89ab-7def-8123-456789abcdef",
    "status": "submitted",
    "registry_number": "202612345678",
    "dir3_accounting_office": "L01280796",
    "dir3_managing_body": "L01280796",
    "dir3_processing_unit": "L01280796",
    "error_code": null,
    "error_message": null,
    "status_updated_at": "2026-06-12T10:15:00Z",
    "last_polled_at": null,
    "created_at": "2026-06-12T10:15:00Z",
    "dir3_proponent": null,
    "administrative_version": 1,
    "administrative_revision_id": null,
    "payment_terms_version": 1,
    "source_fingerprint": "6d2f8a4c1b73e5b0a9c4d7e1f3a6b8c2d5e9f0a1b4c7d8e2f3a5b6c9d0e1f2a3",
    "artifact_sha256": "b4c7d8e2f3a5b6c9d0e1f2a36d2f8a4c1b73e5b0a9c4d7e1f3a6b8c2d5e9f0a1",
    "prepared_at": "2026-06-12T10:14:58Z",
    "transmission_started_at": "2026-06-12T10:14:59Z",
    "can_transmit": false,
    "requires_reconciliation": false,
    "signing_certificate_fingerprint": "e5b0a9c4d7e1f3a6b8c2d5e9f0a1b4c7d8e2f3a5b6c9d0e1f2a36d2f8a4c1b73",
    "artifact_status": "ready"
  }
}

registry_number es el asiento registral de FACe que acredita la presentación — consérvalo para cualquier disputa con la administración. Los campos adicionales identifican lo que congeló el intento: source_fingerprint, artifact_sha256 (hash del XML conservado), la administrative_version y la payment_terms_version usadas y la huella del certificado. artifact_status es ready, unverified (un intento anterior al hash), unavailable o corrupt.

El intento es la unidad, no la llamada

El cuerpo es opcional. Sin él, Factuarea crea la identidad del intento y responde 201. También puedes enviar tu propio submission_id (un UUID v7) en el cuerpo:

curl -X POST https://api.factuarea.com/v1/companies/$COMPANY_ID/invoices/0197b1c2-89ab-7def-8123-456789abcdef/face-submissions \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "submission_id": "0197c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d" }'
  • Un submission_id nuevo crea un intento nuevo: 201.
  • Uno existente de esa factura lo continúa: 200, con el mismo XML conservado y el mismo certificado, sin volver a mirar el certificado actual ni el contacto. Si FACe ya lo había aceptado, recibes el resultado guardado y no se envía nada dos veces.
  • submission_id: null se rechaza, y un id de otra factura o de otra empresa es un 404.
  • El Idempotency-Key no sustituye al submission_id: la clave reproduce una petición HTTP; el id identifica el intento duradero. Las operaciones de envío y cancelación están declaradas como no reintentables automáticamente (x-no-retry): los SDK no las reintentan, así que gestiona los fallos como se describe a continuación en lugar de envolver la llamada en un bucle de reintentos a ciegas.

Si la llamada termina con 502 face_transmission_failed (FACe caído, timeout, respuesta mal formada) o 500 face_submission_completion_failed, el intento existe y error.details.attempt_id te da su id. No envíes nunca un envío nuevo a ciegas: lista los intentos de la factura (historial) o recupera ese, y mira su status:

status tras el falloQué significaQué haces
preparedCongelado y firmado, nunca transmitido.Reintenta con el mismo submission_id.
transmission_uncertainLa petición salió hacia FACe y se desconoce el resultado. Un timeout no demuestra que FACe no la recibiera.No reenvíes. Las llamadas responden 409 face_transmission_uncertain con el mismo attempt_id hasta que el intento se concilie, algo que Factuarea hace con evidencia auditada (requires_reconciliation: true); contacta con soporte.
submitted y posterioresFACe lo tiene.Síguelo como cualquier envío.
errorEl intento falló. Solo se libera si FACe lo rechazó explícitamente (error_code: face_rejected) o se confirmó la no recepción (confirmed_not_received).En esos casos corrige la causa y envía de nuevo con una identidad nueva; si no, sigue bloqueado.

Un intento prepared o en cualquier estado de tramitación no final bloquea uno nuevo para la misma factura (409 face_submission_already_exists), y uno incierto responde 409 face_transmission_uncertain; rejected y cancelled lo liberan. Un 409 face_submission_attempt_conflict significa que otra petición ya tiene o ha terminado ese intento: léelo de nuevo.

Seguir el estado de tramitación

FACe informa de cómo la administración tramita la factura. Factuarea consulta FACe periódicamente y actualiza cada envío — recuperar el detalle del envío (o el historial de envíos de la factura) es la forma de seguir el progreso; no hay endpoint de refresco en v1:

curl https://api.factuarea.com/v1/companies/$COMPANY_ID/face-submissions/0197c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW"
statusSignificado
preparedXML firmado conservado, aún sin transmitir. Continúalo con su submission_id.
transmission_uncertainLa transmisión empezó y se desconoce el resultado. Bloquea nuevos envíos hasta que se concilie.
submittedPresentada en FACe; número de registro asignado.
registered_rcfRegistrada en el RCF (el registro contable de facturas de la administración).
accountedReconocida como obligación contable por la administración.
paidLa administración informa de la factura como pagada.
rejectedRechazada por la administración — consulta el motivo en FACe y emite una factura corregida.
cancellation_requestedHas solicitado la anulación; pendiente de confirmación de FACe.
cancelledAnulación confirmada por FACe.
errorError local de transmisión — error_code y error_message llevan el detalle.

¿Prefieres push a polling? Suscríbete a los eventos de webhook facturae.face_submitted, facturae.face_status_changed y facturae.face_cancellation_requested.

Solicitar la anulación

Mientras la factura no se haya pagado ni rechazado puedes solicitar su anulación (anulación 4200). El reason es obligatorio y viaja a FACe:

curl -X POST https://api.factuarea.com/v1/companies/$COMPANY_ID/face-submissions/0197c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d/cancel \
  -H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Factura emitida por error al organismo equivocado." }'

La anulación solo se permite en un estado anulable (submitted, registered_rcf, accounted); en caso contrario la llamada responde face_submission_not_cancellable. El envío pasa a cancellation_requested hasta que FACe confirma el estado final cancelled.

Modo de prueba

Con una clave de prueba (fact_test_) todo el flujo se simula: ninguna llamada SOAP llega a FACe y el envío recibe un número de registro sintético con prefijo FACE-SANDBOX-*. Las validaciones de firma y DIR3 siguen aplicando, así que el sandbox ejercita los mismos caminos de error que producción. Consulta el modo de prueba.

Errores

HTTPcode / subcodeCuándo
404resource_not_foundLa factura o el envío no existe o pertenece a otra empresa.
422business_rule_violation / invoice_not_emittable_for_facturaeLa factura está en borrador o programada, así que no tiene número fiscal — emítela primero.
422business_rule_violation / client_missing_dir3_codesAl cliente le falta uno o más códigos DIR3.
422business_rule_violation / signing_certificate_requiredSin certificado de firma activo — sube uno vía POST /v1/companies/{company}/verifactu/certificates.
422business_rule_violation / face_submission_not_cancellableEl envío no está en un estado anulable.
409resource_already_exists / face_submission_already_existsYa existe un envío activo para la factura.
422face_submission_rejectedFACe analizó el envío y lo rechazó con su propio código (código DIR3 erróneo, firma no válida, duplicado); no registró la factura. details.attempt_id identifica el intento (su error_code es face_rejected): corrige los datos y envía de nuevo con una identidad nueva.
409face_transmission_uncertainUn intento anterior tiene un resultado desconocido. No se transmite nada; details.attempt_id es el intento que debes revisar.
409face_submission_attempt_conflictOtra petición tiene o ya completó ese intento. Léelo de nuevo.
500face_submission_completion_failedNo se registró el resultado; el intento se conserva (details.attempt_id) y sigue bloqueado.
500facturae_artifact_unavailableEl XML conservado del intento falta o está corrupto; no se regenera.
403facturae_operation_forbiddenLa autoridad vigente del emisor no permite la operación (enviar exige autoridad de envío; cancelar exige acceso de edición al módulo).
403insufficient_scopeLa clave no tiene el scope facturae:write.
502face_transmission_failedEl web service de FACe falló o agotó el tiempo. El intento se conserva y details.attempt_id lo identifica: mira su status antes de hacer nada más.

En esta página

¿Te echamos una mano?Contactar con soporte