Borradores
Un borrador es una factura emitida que todavía no tiene número ni fecha. Lo guardas, lo validas y, cuando está listo, lo emites.
Todas las llamadas usan la URL base https://app.faxtal.com/api/v1.1 y la cabecera X-API-Key (ver Autenticación). El cuerpo de una factura es el formato canónico json_1_1.
| Operación | Scope de la API key |
|---|---|
| Consultar, listar, validar y ver eventos | invoices:read |
| Crear, actualizar y eliminar | invoices:write |
| Emitir | invoices:issue |
Sin el scope necesario, la API responde 403 con el código scope_missing.
En la API, un borrador tiene siempre el estado draft, tenga o no errores de validación.
Qué lleva un borrador
meta.direction:issued.meta.source_system: el nombre de tu sistema.meta.external_ref: tu referencia de la factura. Es obligatoria: identifica el borrador cuando lo vuelves a enviar.invoice.supplier: tu empresa. Si informassupplier.tax_id, tiene que ser el NIF de la empresa de la API key.invoice.header.invoice_series_codeeinvoice.header.invoice_type_code.- Ni
header.invoice_numberniissue_data.issue_date: los asigna la emisión.
Una factura usa un solo grupo de impuesto: el de issue_data.tax_group_code. Si no lo envías, se usa el de tu empresa, y los tipos de las líneas (tax_output_rate) se interpretan dentro de ese grupo.
:::info Aún no disponible
El grupo IGIC (Canarias) todavía no está disponible. Una factura con IGIC se rechaza con 422 y el código tax_group_not_available en errors. Hoy el grupo es siempre IVA.
:::
Campos que no se envían
El sistema deduce estos campos. Si llegan en el cuerpo, la petición se rechaza con 422 inferred_fields_not_allowed y la lista de rutas en fields.
| Bloque | Campos |
|---|---|
header | is_simplified, is_corrective |
lines[] | taxable, exempt, tax_category_code, tax_output_code, tax_withheld_code |
taxes_outputs[] y taxes_withheld[] | taxable, exempt, tax_category_code, tax_type_code |
country_subdivision de una dirección | subdivision_type |
El tratamiento fiscal de una línea se declara con tax_treatment_code y el porcentaje (tax_output_rate).
Crear o actualizar un borrador
PUT /api/v1.1/drafts
- Sin parámetros, crea un borrador o actualiza el que tenga la misma
meta.external_ref. - Con
?invoice_id=<id>, actualiza ese borrador. - Si la factura encontrada ya no es un borrador (emitida, importada o recibida), responde
409invoice_update_not_allowedy no la cambia.
borrador.json:
{
"schema_version": "json_1_1",
"meta": {
"direction": "issued",
"source_system": "mi-erp",
"external_ref": "PEDIDO-2026-001"
},
"invoice": {
"supplier": {
"person_type_code": "J",
"tax_id": "B10000008",
"name": "Empresa Demo S.L."
},
"customer": {
"person_type_code": "J",
"tax_id": "B10000016",
"name": "Cliente Prueba S.L.",
"company": {
"legal_name": "Cliente Prueba S.L.",
"address": {
"address": "Av. Cliente 5",
"post_code": "08001",
"town": "Barcelona",
"country_code": "ES",
"country_subdivision": { "code": "ES-B" }
}
}
},
"header": {
"invoice_series_code": "A",
"invoice_type_code": "01"
},
"issue_data": {
"tax_currency_code": "EUR",
"invoice_description": "Consultoría noviembre 2026"
},
"totals": {
"total_gross_amount_before_taxes": 1000.0,
"total_tax_outputs": 210.0,
"invoice_total": 1210.0
},
"lines": [
{
"item_type": "service",
"item_description": "Consultoría senior",
"quantity": 10,
"unit_of_measure": "HUR",
"unit_price_without_tax": 100.0,
"tax_treatment_code": "TAXABLE_NON_EXEMPT",
"tax_output_rate": 21.0,
"article_code": "CONSULT-SENIOR"
}
]
}
}
curl -X PUT "https://app.faxtal.com/api/v1.1/drafts" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY" \
-H "Content-Type: application/json" \
-d @borrador.json
Respuesta 201, también cuando actualiza:
{
"success": true,
"id": "00000000-0000-0000-0000-000000000001",
"errors": []
}
id: el identificador del borrador. Lo usas en el resto de operaciones.success:falsesi el borrador se guardó con errores de negocio. Los tienes enerrors, cada uno consource,code,message,pathyseverity(errorimpide emitir;warningsolo informa).
Un error de estructura (esquema) no guarda nada. Un error de negocio sí guarda el borrador, y tendrás que corregirlo antes de emitir.
Errores
| HTTP | code | Causa |
|---|---|---|
422 | missing_required_fields | Falta invoice_series_code o meta.external_ref. El detalle trae missing. |
422 | invoice_number_not_allowed | Has enviado header.invoice_number. |
422 | issue_date_not_allowed | Has enviado issue_data.issue_date. |
422 | inferred_fields_not_allowed | Has enviado campos que deduce el sistema. |
422 | issuer_tax_id_mismatch | supplier.tax_id no es el NIF de tu empresa. |
422 | schema_validation_failed | El cuerpo no cumple el esquema. El detalle trae errors. |
409 | invoice_update_not_allowed | La factura que intentas actualizar ya no es un borrador. |
409 | invoice_race_condition | Otra petición ha escrito la misma factura a la vez. Reintenta. |
Crear o actualizar en bloque
POST /api/v1.1/drafts/batch
{
"batch_id": "lote-2026-11-01",
"invoices": [
{
"schema_version": "json_1_1",
"meta": { "direction": "issued", "source_system": "mi-erp", "external_ref": "PEDIDO-2026-001" },
"invoice": { "...": "igual que en el alta individual" }
},
{
"schema_version": "json_1_1",
"meta": { "direction": "issued", "source_system": "mi-erp", "external_ref": "PEDIDO-2026-002" },
"invoice": { "...": "igual que en el alta individual" }
}
]
}
invoices: al menos una factura. Cada elemento llevainvoiceymeta.external_ref.batch_idyfile_name: opcionales.- De
metasolo se usanexternal_refysource_system.
curl -X POST "https://app.faxtal.com/api/v1.1/drafts/batch" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY" \
-H "Content-Type: application/json" \
-d @lote.json
Cómo se procesa:
- La llamada espera a que termine todo el lote.
- Cada factura va por separado: si una se rechaza, las demás se guardan igual.
- Si alguna factura trae campos que deduce el sistema, se rechaza el lote entero con
422inferred_fields_not_allowedy la listaitems(index,fields). - La respuesta detalla como máximo 500 facturas. Envía lotes de 500 o menos.
Respuesta 200:
{
"batch_id": "000000000000000000000001",
"status": "completed_with_errors",
"total": 2,
"accepted": 1,
"rejected": 1,
"items": [
{
"index": 0,
"status": "accepted",
"id": "00000000-0000-0000-0000-000000000001",
"number": null,
"errors": []
},
{
"index": 1,
"status": "rejected",
"id": null,
"number": null,
"errors": [
{ "code": "missing_required_fields", "missing": ["invoice.header.invoice_series_code"] }
]
}
]
}
batch_id: el identificador que asigna la plataforma, no el que envías.status:completedsi se aceptan todas,completed_with_errorssi se rechaza alguna,failedsi el lote no se pudo procesar.items[].status:acceptedorejected.indexes la posición eninvoices.
:::warning
Un elemento accepted puede haberse guardado con errores de negocio: errors solo recoge los rechazos. Antes de emitir, valida cada borrador con POST /api/v1.1/drafts/{invoice_id}/validate.
:::
Validar
Un JSON, sin guardarlo
POST /api/v1.1/drafts/validate
Envía el mismo cuerpo que en el alta. No guarda nada.
curl -X POST "https://app.faxtal.com/api/v1.1/drafts/validate" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY" \
-H "Content-Type: application/json" \
-d @borrador.json
Respuesta 200:
{
"is_valid": false,
"errors": [
{
"source": "business",
"code": "missing_required_field",
"message": "...",
"path": "/invoice/customer/tax_id",
"extra": null,
"severity": "error"
}
],
"data": { "...": "canónico normalizado" }
}
data: el canónico tal como lo normaliza el sistema. Esnullsi hay errores de esquema.- Los campos que deduce el sistema aparecen en
errorsconinferred_fields_not_allowed, en lugar de rechazar la petición.
Esta validación no aplica lo que depende de tu empresa (el grupo de impuesto por defecto y las reglas de VeriFactu). El alta y la emisión pueden devolver errores o avisos que aquí no aparecen.
Un borrador guardado
POST /api/v1.1/drafts/{invoice_id}/validate
Vuelve a validar el borrador y actualiza su resultado de validación. Sin cuerpo.
curl -X POST "https://app.faxtal.com/api/v1.1/drafts/00000000-0000-0000-0000-000000000001/validate" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY"
Respuesta 200:
{
"id": "00000000-0000-0000-0000-000000000001",
"is_valid": true,
"status": "draft",
"errors": []
}
Consultar y listar
Un borrador
GET /api/v1.1/drafts/{invoice_id}
curl "https://app.faxtal.com/api/v1.1/drafts/00000000-0000-0000-0000-000000000001" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY"
Respuesta 200, resumida:
{
"id": "00000000-0000-0000-0000-000000000001",
"direction": "issued",
"status": "draft",
"series": "A",
"number": null,
"issue_date": null,
"external_id": "PEDIDO-2026-001",
"customer_tax_id": "B10000016",
"customer_name": "Cliente Prueba S.L.",
"currency": "EUR",
"total_base": 1000.0,
"total_tax": 210.0,
"total": 1210.0,
"created_at": "2026-11-02T09:15:00",
"updated_at": "2026-11-02T09:15:00",
"corrections": null
}
external_id es tu meta.external_ref. Si la factura ya se emitió, esta ruta responde 404 invoice_not_found: consúltala en Facturas emitidas.
El canónico del borrador
GET /api/v1.1/drafts/{invoice_id}/canonical
Devuelve el JSON canónico tal y como lo guardó Faxtal: normalizado y con los campos que el sistema infiere (clasificación fiscal, taxable, exempt, is_simplified…). Es lo que se declara a Hacienda.
curl "https://app.faxtal.com/api/v1.1/api/v1.1/drafts/00000000-0000-0000-0000-000000000001/canonical" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY"
:::warning No se reenvía tal cual
La respuesta no vale como cuerpo de PUT /api/v1.1/drafts: ese endpoint rechaza los campos inferidos con 422 inferred_fields_not_allowed. Úsalo para leer y para conciliar, no para reeditar.
:::
Si la factura no tiene canónico guardado, responde 409 canonical_not_available.
La lista
GET /api/v1.1/drafts
| Parámetro | Descripción |
|---|---|
series | Código de serie. |
customer_tax_id | NIF del cliente. |
number | Número de factura exacto. Sirve para recuperar una factura cuyo id has perdido. |
external_ref | Tu referencia (meta.external_ref), exacta. |
issue_date_from, issue_date_to | Rango de fecha de emisión (YYYY-MM-DD). Un borrador no tiene fecha, así que con estos filtros no aparece. |
limit | De 1 a 500. Por defecto, 50. |
offset | Desplazamiento. Por defecto, 0. |
curl -G "https://app.faxtal.com/api/v1.1/drafts" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY" \
-d series=A -d limit=100
Respuesta 200: items (borradores con la forma anterior), total, limit y offset.
Documentos adjuntos
Un borrador admite adjuntos sin efecto fiscal (un albarán, un pedido firmado). Siguen con la factura al emitirla, porque el id no cambia.
| Operación | Ruta | Permiso |
|---|---|---|
| Listar | GET /api/v1.1/drafts/{invoice_id}/documents | invoices:read |
| Subir | POST /api/v1.1/drafts/{invoice_id}/documents (multipart, campo file) | invoices:write |
| Descargar | GET /api/v1.1/drafts/{invoice_id}/documents/{doc_id} | invoices:read |
| Eliminar | DELETE /api/v1.1/drafts/{invoice_id}/documents/{doc_id} | invoices:write |
curl -X POST "https://app.faxtal.com/api/v1.1/drafts/00000000-0000-0000-0000-000000000001/documents" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY" \
-F "file=@albaran.pdf"
El fichero no puede pasar de 16 MB (413 file_too_large) ni estar vacío (422 empty_file). Una vez emitida, los mismos documentos se gestionan desde Facturas emitidas.
Eliminar
DELETE /api/v1.1/drafts/{invoice_id}
curl -X DELETE "https://app.faxtal.com/api/v1.1/drafts/00000000-0000-0000-0000-000000000001" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY"
Respuesta 204, sin cuerpo. Se borran también los documentos adjuntos del borrador.
Si la factura ya no es un borrador, responde 409 invoice_delete_not_allowed. Una factura emitida se anula, no se borra: ver Facturas emitidas.
Ver el histórico
GET /api/v1.1/drafts/{invoice_id}/events
curl "https://app.faxtal.com/api/v1.1/drafts/00000000-0000-0000-0000-000000000001/events" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY"
Devuelve una lista de eventos. Cada uno trae event_type, event_code, occurred_at, actor (user_id o api_key_id), from_status y to_status cuando hay cambio de estado, y details. Cuando el borrador se emite, sus eventos pasan a consultarse en GET /api/v1.1/issued-invoices/{invoice_id}/events.
Emitir un borrador
POST /api/v1.1/issued-invoices/{invoice_id}/issue, con el scope invoices:issue y sin cuerpo.
curl -X POST "https://app.faxtal.com/api/v1.1/issued-invoices/00000000-0000-0000-0000-000000000001/issue" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY"
La emisión vuelve a validar el borrador, le asigna el número de la serie y la fecha de hoy y lo registra en los sistemas fiscales que tengas activos. Si hay errores, no emite y el borrador sigue en draft. La respuesta y los errores están en Facturas emitidas.
Ver también
- Formato canónico: todos los campos de
json_1_1. - Facturas emitidas: emisión, anulación y rectificativas.
- Importación: alta masiva por fichero.
- Errores: formato común de las respuestas de error.
- Referencia interactiva.
Facturas recibidas
Registrar facturas de proveedores con JSON canónico o XML, reconocerlas desde un PDF o una imagen, validarlas, consultarlas y gestionar sus documentos.
Cobros y pagos
Informar desde tu programa los cobros de las facturas emitidas y los pagos de las recibidas, corregirlos sin borrarlos y consultarlos. Es lo que usa el criterio de caja.