# Spanish Invoice Extractor (Facturas ES) (`delmiro/factura-es-extractor`) Actor

Tool for AI agents: Spanish invoices (facturas, text PDF) to JSON via pdfUrls or pdfBase64. Returns date, number, issuer NIF/CIF, base/VAT/recargo by rate, IRPF, total, status OK/REVISAR/ERROR + reason. NIF and totals validated. No OCR. $0.02/invoice, failures free.

- **URL**: https://apify.com/delmiro/factura-es-extractor.md
- **Developed by:** [Delmiro](https://apify.com/delmiro) (community)
- **Categories:** AI, Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 90.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 invoice processeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

Spanish Invoice Extractor (Facturas ES) turns **Spanish PDF invoices (facturas) with selectable text** into structured data: one dataset item per invoice (issuer NIF/CIF, VAT bases and amounts by rate, recargo de equivalencia, IRPF, total) plus an **Excel-ready CSV**. It also checks the NIF/CIF control digit and that the totals add up, and flags every invoice **OK** or **REVISAR** (review). It is a rules-based PDF invoice parser: no OCR and no AI service, so it only reads PDFs that contain text. [Español más abajo](#espanol).

[English](#english) | [Español](#espanol)

### English

#### What is the Spanish Invoice Extractor for?

Use this Actor as the "invoice PDF -> fields" step of a bookkeeping, ERP or reconciliation workflow. Typical searches it answers: *spanish invoice parser*, *extract data from factura PDF*, *PDF to CSV invoices Spain*, *validate NIF CIF on an invoice*, *VAT Spain IVA extraction*.

Who uses it:

- **Accounting firms (gestorías)** with technical staff who pre-process supplier invoices.
- **Developers of accounting / ERP software** who need an API call that returns invoice fields.
- **Ecommerce and operations teams** reconciling supplier invoices in Excel.

#### What data does it extract?

Issue date, invoice number, issuer NIF/CIF/NIE and name, **base, VAT amount and surcharge (recargo de equivalencia) for each VAT rate (0 / 4 / 5 / 10 / 21 %)**, IRPF withholding (% and amount) and total.

#### Built-in checks (why OK / REVISAR matters)

- The NIF/CIF/NIE control letter or digit is valid.
- Each VAT amount matches base x rate, and **base + VAT + surcharge - IRPF = total** (+/- 0.02 EUR).
- Valid Spanish VAT rate, plausible date, invoice number present.
- If the engine had to derive a base from the VAT amount or total instead of reading it, the invoice is always marked REVISAR.

#### How to use it (step by step)

1. Open the Actor and go to the **Input** tab.
2. Add the public URL of each PDF invoice under **PDF invoice URLs**. For private files, upload them to a key-value store in your account and list their keys under **Key-value store keys** (pick the store in the next field).
3. Choose **CSV format**: `es` for Spanish Excel (`;` separator, decimal comma, dd/mm/yyyy, UTF-8 BOM) or `intl` for `,` and ISO dates.
4. Click **Start**. When the run finishes, open the **Output** tab: the dataset has one item per PDF, and the key-value store has `facturas.csv` and `RESUMEN`.
5. Review every REVISAR row before posting anything to your books.

#### Input

| Field | Meaning |
|---|---|
| `pdfUrls` | Public http(s) URLs of PDF invoices (strings or `{url}` objects). |
| `pdfBase64` | Base64-encoded PDF invoices (for agents without a public URL). |
| `kvStoreKeys` | Keys of PDFs you uploaded to a key-value store (private files). |
| `kvStoreName` | Store holding those keys (empty = the run's default store). |
| `csvFormat` | `es` = `;` separator, decimal comma, dd/mm/yyyy, UTF-8 BOM (Spanish Excel). `intl` = `,` separator, decimal point, ISO date. |
| `language` | `es` / `en`: language of the run summary. Validation reasons are always in Spanish. |
| `maxInvoices` | Safety cap (default 100). |

Example input:

```json
{
  "pdfUrls": ["https://example.com/your-invoice.pdf"],
  "csvFormat": "es",
  "language": "en",
  "maxInvoices": 100
}
```

#### Output

- **Dataset**: one item per input file (`fuente`, `fecha`, `numero`, `nif_emisor`, `nombre_emisor`, `desglose[]`, `base_21`/`cuota_21` and the same for 0/4/5/10, `base_total`, `cuota_total`, `recargo_total`, `irpf_pct`, `irpf`, `total`, `estado`, `motivo`, `cobrado`). Files that cannot be read appear with `estado: "ERROR"` and the reason.
- **Key-value store**: `facturas.csv` (one row per VAT rate) and `RESUMEN` (counts of OK / REVISAR / ERROR / charged).

Example item (**fictitious** invoice, made-up company):

```json
{
  "fuente": "https://example.com/demo_01.pdf",
  "fecha": "2026-03-12", "numero": "2026-0124",
  "nif_emisor": "B76207117", "nombre_emisor": "Suministros Industriales Valdepeñas S.L.",
  "desglose": [
    { "tipo_iva": 21, "base": 38.24, "cuota_iva": 8.03, "recargo": 0 },
    { "tipo_iva": 4, "base": 3001.48, "cuota_iva": 120.06, "recargo": 0 }
  ],
  "base_total": 3039.72, "cuota_total": 128.09, "irpf": 0, "total": 3167.81,
  "estado": "OK", "motivo": "", "cobrado": true
}
```

#### For AI agents

Tool summary: *Spanish invoice (factura) PDF in, structured JSON out.* Accepts text PDFs only (no scans). Run it with the Apify MCP server (`call-actor` with `delmiro/factura-es-extractor`) or the REST API (`POST /v2/acts/delmiro~factura-es-extractor/run-sync-get-dataset-items`). Charged 0.02 USD per invoice with extracted data; failed files are free.

Input rules: give at least one of `pdfUrls` (array of public URL strings; `{"url": "..."}` objects are also accepted), `pdfBase64` (array of base64 PDF strings, or `data:application/pdf;base64,...` URIs, max 20 MB each; use when you have the file but no public URL) or `kvStoreKeys`. **Empty input = free demo** on 2 bundled fictitious invoices (not charged).

Minimal input with URLs:

```json
{ "pdfUrls": ["https://example.com/factura-1.pdf"] }
```

Minimal input with file content:

```json
{ "pdfBase64": ["JVBERi0xLjQK...<base64 of the PDF>..."] }
```

Output (one dataset item per input file, fictitious invoice shown). Check `estado`: `OK` = checks passed, `REVISAR` = read but a check failed (see `motivo`), `ERROR` = nothing extracted (not charged):

```json
{
  "fuente": "base64:1", "fecha": "2026-03-12", "numero": "2026-0124",
  "nif_emisor": "B76207117", "nombre_emisor": "Suministros Industriales Valdepeñas S.L.",
  "desglose": [
    { "tipo_iva": 21, "base": 38.24, "cuota_iva": 8.03, "recargo": 0 },
    { "tipo_iva": 4, "base": 3001.48, "cuota_iva": 120.06, "recargo": 0 }
  ],
  "base_total": 3039.72, "cuota_total": 128.09, "recargo_total": 0, "irpf_pct": null, "irpf": 0, "total": 3167.81,
  "estado": "OK", "motivo": "", "cobrado": true
}
```

#### How much does it cost?

Pay per event: **0.02 USD per invoice processed** (event `invoice-processed`), plus Apify's standard start event (0.00005 USD per run). Example: 100 invoices = 2.00 USD (+ 0.00005 USD start). Roughly, 5 USD of Apify credit covers up to about 250 invoices.

- **Files that fail** (not a PDF, no extractable text, download error) **are not charged**.
- Invoices delivered with status REVISAR **are charged**: data was extracted and validated, and the flag tells you to check it.
- Use `maxInvoices` and Apify's per-run maximum cost to cap spending. See the Pricing tab for the current price.

#### Integrations and API

Like every Apify Actor, it can be run from the Apify API and client libraries (JavaScript, Python), scheduled, and connected to **Make, Zapier, n8n, Airtable** and Google Drive through [Apify integrations](https://docs.apify.com/platform/integrations), or called by AI agents through the [Apify MCP server](https://docs.apify.com/mcp). Python example:

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("delmiro/factura-es-extractor").call(run_input={
    "pdfUrls": [{"url": "https://example.com/your-invoice.pdf"}], "csvFormat": "intl"})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["numero"], item["total"], item["estado"])
```

#### Limitations (honest)

- **Text PDFs only.** No OCR: scans and photos are not supported (they come back as ERROR and are not charged).
- One issuer NIF per invoice; no multi-page invoices, credit notes (rectificativas) or foreign-currency amounts. Layouts the rules have not seen may be read wrongly or flagged REVISAR.
- Validation detects inconsistencies; a consistent-looking value can still be wrong. **Always review before posting to your books.** This tool does not replace your accountant's judgment.
- Accuracy: on 30 **synthetic** test invoices generated by the same author as the extractor, all fiscal fields matched. That is not a measure of real-world accuracy, which will be lower and has not been measured on real invoices.

#### FAQ

**Does it work with scanned invoices or photos?** No. It reads the text layer of the PDF. Scans come back as ERROR and are not charged.

**Which invoices does it handle?** Spanish invoices (facturas) in PDF with selectable text, one issuer NIF, a single page, in euros. Credit notes (rectificativas), multi-page invoices and foreign currency are not supported yet.

**Why is my invoice marked REVISAR?** A check failed (invalid NIF/CIF, totals that do not add up, unusual VAT rate or date) or a value had to be derived instead of read. The `motivo` field says which one.

**Can I open the CSV in Spanish Excel directly?** Yes, with `csvFormat: "es"` (`;` separator, decimal comma, UTF-8 BOM).

**Are my invoices sent to an AI service?** No. See Privacy below.

**Something fails or a layout is not read correctly?** Open an issue from the Actor page with the (anonymised) layout description; unseen layouts are the main source of REVISAR.

#### Privacy (GDPR)

Invoices can contain personal data (names, NIFs). The Actor processes files in memory and a temporary file that is deleted right after reading; it does not send them to third parties or any AI service and does not keep them beyond your run. Results live in your run's dataset / key-value store under your account's retention settings. You are the data controller for the invoices you submit.

***

<a id="espanol"></a>

### Español

#### Qué hace el extractor de facturas españolas

Convierte **facturas españolas en PDF con texto seleccionable** en datos estructurados: un item de dataset por factura y un **CSV listo para Excel** en el key-value store. Es un lector de facturas por reglas (sin OCR ni IA): extrae datos y valida NIF/CIF e IVA/IRPF.

#### Qué extrae

Fecha, número de factura, NIF/CIF/NIE y nombre del emisor, **base, cuota de IVA y recargo de equivalencia por cada tipo (0/4/5/10/21 %)**, retención de IRPF (% e importe) y total.

#### Validación incluida

Cada factura sale como **OK** o **REVISAR** (con el motivo):

- Letra/dígito de control del NIF/CIF/NIE.
- Cada cuota = base x tipo; **base + IVA + recargo - IRPF = total** (+/- 0,02 EUR).
- Tipo de IVA válido en España, fecha verosímil, número de factura presente.
- Si el motor tuvo que derivar la base a partir de la cuota/total en vez de leerla, la factura sale siempre como REVISAR.

#### Para quién

- **Gestorías** con perfil técnico que preprocesan facturas de proveedores.
- **Desarrolladores de software contable / ERP** que necesitan el paso "PDF de factura -> campos" por API.
- **Ecommerce y operaciones** que concilian facturas de proveedores en Excel.

#### Cómo usarlo, paso a paso

1. Abra el Actor y vaya a la pestaña **Input**.
2. Añada la URL pública de cada PDF en **PDF invoice URLs**. Para archivos privados, súbalos a un key-value store de su cuenta e indique las claves en **Key-value store keys**.
3. Elija el **formato CSV**: `es` para Excel en español (`;`, coma decimal, dd/mm/aaaa, UTF-8 con BOM) o `intl`.
4. Pulse **Start**. Al terminar, el dataset tiene un item por PDF y el key-value store contiene `facturas.csv` y `RESUMEN`.
5. Revise las filas REVISAR antes de contabilizar nada.

#### Entrada

`pdfUrls` (URLs públicas de PDFs), `kvStoreKeys` (claves de PDFs subidos a un key-value store, para archivos privados), `kvStoreName`, `csvFormat` (`es` / `intl`), `language` (`es`/`en`, idioma del resumen; los motivos de validación van siempre en español) y `maxInvoices` (tope de seguridad, 100 por defecto).

#### Salida

- **Dataset**: un item por archivo con los campos del ejemplo JSON de arriba (factura **FICTICIA** de una empresa inventada). Los archivos ilegibles salen con `estado: "ERROR"` y el motivo.
- **Key-value store**: `facturas.csv` (una fila por tipo de IVA) y `RESUMEN` (recuentos OK / REVISAR / ERROR / cobradas).

#### Precio (pago por evento)

**0,02 USD por factura procesada** (evento `invoice-processed`) más el evento de inicio estándar de Apify. Ejemplo: 100 facturas = 2,00 USD. **Los archivos fallidos (no es PDF, sin texto extraíble, error de descarga) no se cobran.** Las facturas entregadas como REVISAR sí se cobran: se extrajeron y validaron datos, y la marca indica que debe revisarlas. Precio vigente en la pestaña Pricing.

#### Integraciones

API de Apify, planificación de ejecuciones y conexión con Make, Zapier, n8n y Airtable mediante las integraciones de Apify.

#### Limitaciones, sin adornos

- **Solo PDF con texto.** Sin OCR: escaneos y fotos no se admiten (salen como ERROR y no se cobran).
- Un NIF de emisor por factura; sin facturas multipágina, rectificativas ni importes en divisa. Diseños que las reglas no conocen pueden leerse mal o salir como REVISAR.
- La validación detecta incoherencias; un dato coherente puede seguir siendo incorrecto. **Revise siempre antes de contabilizar.** No sustituye el criterio de su gestor.
- Precisión: sobre 30 facturas de prueba **sintéticas** generadas por el mismo autor que el extractor, todos los campos fiscales coincidieron. No es una medida de precisión real, que será menor y no se ha medido con facturas reales.

#### Preguntas frecuentes

**¿Lee facturas escaneadas o fotos?** No. Lee la capa de texto del PDF.

**¿Por qué sale REVISAR?** Falló una comprobación (NIF/CIF, cuadre, tipo de IVA, fecha) o se tuvo que derivar un valor. El campo `motivo` lo indica.

**¿Se envían mis facturas a una IA?** No. Se procesan en memoria y no se envían a terceros.

#### Privacidad (RGPD)

Las facturas pueden contener datos personales (nombres, NIF). El Actor las procesa en memoria y en un archivo temporal que se borra justo tras leerlo; no las envía a terceros ni a ningún servicio de IA ni las conserva más allá de su ejecución. Los resultados quedan en el dataset / key-value store de su run, con la retención de su cuenta. Usted es el responsable del tratamiento de las facturas que envía.

# Actor input Schema

## `pdfUrls` (type: `array`):

Array of public http(s) URLs, each pointing to ONE Spanish PDF invoice with selectable text (max 20 MB). Preferred format: plain strings, e.g. \["https://example.com/factura.pdf"]; objects like {"url": "..."} are also accepted. Optional: if pdfUrls, pdfBase64 and kvStoreKeys are all empty, the Actor runs a free DEMO on 2 bundled fictitious invoices.

## `pdfBase64` (type: `array`):

Array of base64-encoded PDF files (plain base64 or a data:application/pdf;base64,... URI), one invoice per string, max 20 MB decoded each. Use this when you are an agent/script with the file content but no public URL. Same price and rules as pdfUrls. Prefer a few files per run.

## `kvStoreKeys` (type: `array`):

Keys of PDF records already uploaded to the key-value store (see 'Key-value store name'). Use this for private files you uploaded yourself.

## `kvStoreName` (type: `string`):

Pick one of your key-value stores that contains the PDF records listed above (read-only access is granted only to the store you pick). Empty = this run's default store.

## `csvFormat` (type: `string`):

'es': separator ';', decimal comma, date dd/mm/yyyy, UTF-8 with BOM (opens correctly in Spanish Excel). 'intl': separator ',', decimal point, ISO date.

## `language` (type: `string`):

Language of the run summary and status messages. Validation reasons ('motivo') are always in Spanish.

## `maxInvoices` (type: `integer`):

Safety cap on the number of PDFs processed in this run.

## Actor input object example

```json
{
  "pdfUrls": [],
  "pdfBase64": [],
  "kvStoreKeys": [],
  "csvFormat": "es",
  "language": "es",
  "maxInvoices": 100
}
```

# Actor output Schema

## `invoices` (type: `string`):

Extracted invoice data, one item per PDF: date, number, issuer NIF and name, VAT bases and quotas by rate, surcharge, IRPF, total, status (OK / REVISAR / ERROR) and reason.

## `csv` (type: `string`):

All invoices as CSV (format chosen in the input: Excel-ES with ';' and decimal comma, or international).

## `summary` (type: `string`):

Counts of OK / REVISAR / ERROR invoices and charged events for this run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "pdfUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("delmiro/factura-es-extractor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "pdfUrls": [] }

# Run the Actor and wait for it to finish
run = client.actor("delmiro/factura-es-extractor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "pdfUrls": []
}' |
apify call delmiro/factura-es-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,delmiro/factura-es-extractor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/UZXIC0SVIM1GIBV3B/builds/j6owMs20a5zkriBS7/openapi.json
