# Generador de QR Verifactu — QR tributario por lotes (`angel.carreira/qr-verifactu`) Actor

Genera por lotes el código QR tributario Verifactu de la AEAT (SVG y PNG al tamaño reglamentario) a partir de NIF, número de serie, fecha e importe, con validación de datos.

- **URL**: https://apify.com/angel.carreira/qr-verifactu.md
- **Developed by:** [Ángel Carreira](https://apify.com/angel.carreira) (community)
- **Categories:** Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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

## Verifactu QR code generator: batch SVG and PNG

Generate the AEAT "QR tributario" for Spanish invoices in batches: build the official verification URL, validate the invoice fields and get print-ready SVG and PNG files.

Run it on Apify, or use the same generator on the web: <https://cuadrafactura.es/generador-qr-verifactu/>.

### Who it is for

- Invoicing SaaS products and ERPs that issue Verifactu invoices in Spain and need the QR on every invoice.
- Accounting and billing teams that print or archive invoices and want consistent, correctly sized QR files.
- Developers integrating the AEAT QR specification into a batch job, a document pipeline or a printing workflow.

### What it does/checks

- Builds the **AEAT verification URL** with the four mandatory parameters: `nif`, `numserie`, `fecha` and `importe`. Dates are encoded as `DD-MM-AAAA`; values are URL-encoded.
- Uses `ValidarQR` for **VERI\*FACTU** invoices and `ValidarQRNoVerifactu` for the others, against either the production service or the AEAT External Test Portal.
- Validates the **NIF format and check digit** (not whether it exists in the AEAT census), the invoice series/number, the issue date and the amount.
- Generates the QR at **error-correction level M**, between **30 and 40 mm** per side including the quiet zone, with the same **6 mm** quiet zone as the web generator.
- Produces **SVG** with the text "QR tributario:" above the code and "VERI*FACTU" below it for VERI*FACTU invoices.
- Produces **PNG** as an 8-bit grayscale image with a `pHYs` chunk carrying the requested resolution, so Word, Illustrator or a printer respects the millimetre size. The PNG does not contain the typographic labels.
- Reports **per-invoice errors** with the AEAT error codes and an actionable message in Spanish. A bad invoice does not stop the batch.
- Stores the generated files in the run's **default key-value store** and links each invoice to its file keys in the dataset.
- **Nothing is sent to the AEAT.** The Actor makes no network requests and does not check the invoice against AEAT servers.

### Input

```json
{
  "facturas": [
    {
      "nif": "89890001K",
      "numserie": "12345678&G33",
      "fecha": "2024-01-01",
      "importe": 241.4,
      "verifactu": true
    },
    {
      "nif": "89890001K",
      "numserie": "F-2026-0042",
      "fecha": "2026-01-15",
      "importe": 1250,
      "verifactu": false
    }
  ],
  "tamanoMm": 35,
  "ppp": 300,
  "formatos": ["svg", "png"],
  "entorno": "produccion"
}
```

| Field | Type | Default | Description |
|---|---|---|---|
| `facturas` | array | — | 1 to 1000 invoices. |
| `facturas[].nif` | string | — | Issuer NIF. Normalised to uppercase and without an `ES` prefix. |
| `facturas[].numserie` | string | — | Series and invoice number, up to 60 printable ASCII characters (32–126). |
| `facturas[].fecha` | string | — | Issue date as `AAAA-MM-DD`; `DD-MM-AAAA` is also accepted. |
| `facturas[].importe` | number | — | Total amount, with at most 2 decimal places and up to 12 integer digits. |
| `facturas[].verifactu` | boolean | `true` | `true` uses `ValidarQR`; `false` uses `ValidarQRNoVerifactu` and omits the "VERI\*FACTU" label. |
| `tamanoMm` | number | `35` | Total QR side in millimetres, quiet zone included: 30 to 40 mm. |
| `ppp` | integer | `300` | PNG resolution, 72 to 1200 dpi, stored in the `pHYs` chunk. |
| `formatos` | array | `["svg", "png"]` | Files generated per valid invoice: `svg` and/or `png`. |
| `entorno` | string | `produccion` | `produccion` or `pruebas` (AEAT External Test Portal). |

### Output

One dataset item per invoice, including invalid ones, with a **Resumen** view.

```json
[
  {
    "nif": "89890001K",
    "numserie": "12345678&G33",
    "fecha": "2024-01-01",
    "importe": 241.4,
    "url": "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=89890001K&numserie=12345678%26G33&fecha=01-01-2024&importe=241.4",
    "valido": true,
    "errores": [],
    "claveSvg": "qr-verifactu-12345678-G33-h1a2b3c.svg",
    "clavePng": "qr-verifactu-12345678-G33-h1a2b3c.png"
  },
  {
    "nif": "891K",
    "numserie": "F-2026-0043",
    "fecha": "2026-01-16",
    "importe": 99.5,
    "url": null,
    "valido": false,
    "errores": [
      { "codigo": "2001", "mensaje": "El NIF no es válido: revisa la letra" }
    ]
  }
]
```

`valido` is `true` when the verification URL was built and the requested files were generated. `url` is `null` for invalid invoices. `error` appears when generation or storage fails for an otherwise valid invoice.

#### File names

The generated files live in the default key-value store:

- `qr-verifactu-<sanitised numserie>[-h<hash>][-N].svg` with `contentType: image/svg+xml`.
- `qr-verifactu-<sanitised numserie>[-h<hash>][-N].png` with `contentType: image/png`.

Sanitisation keeps letters, digits, hyphens and underscores; other characters become a hyphen, repeated hyphens collapse and the base is capped at 60 characters. If sanitisation changes the original series (`A/1` → `A-1`), `-h` plus 6 hexadecimal characters of the FNV-1a hash of the original is appended, so `A/1` and `A-1` never share a file. Repeated series in the same batch get `-2`, `-3`… before the extension.

#### Error codes

| Code | Meaning |
|---|---|
| 1001 | Missing NIF. |
| 1002 | Missing series/invoice number. |
| 1003 | Missing issue date. |
| 1004 | Missing total amount. |
| 2001 | NIF with the wrong format or check digit. |
| 2002 | Series/invoice number longer than 60 characters. |
| 2003 | Series/invoice number with characters outside printable ASCII (32–126). |
| 2004 | Invalid or non-existent date. |
| 2005 | Malformed amount (thousands separator, decimal comma, etc.). |
| 2006 | Amount with more than 2 decimals or more than 12 integer digits. |

An invoice with validation errors generates no files and is **not charged**. If generation fails for a valid invoice (for example, the resolution is too low for the requested size), the item is published with `valido: false`, `url: null` and an `error` field; the rest of the batch continues and that invoice is not charged.

### Use cases

- **Verifactu compliance:** add the official AEAT QR to every invoice generated by your billing system.
- **Print and archive-ready files:** obtain SVG for designers and PDFs, and PNG with the correct physical size for Word or a printer.
- **Bulk migration:** generate QRs for an existing batch of invoices when moving to a Verifactu-compliant system.

### Limits

- **1000 invoices per run.**
- **60 printable ASCII characters** for the series/number and **at most 2 decimal places** with up to 12 integer digits for the amount, per AEAT rules.
- **30 to 40 mm** per side and **72 to 1200 dpi** for the PNG. At 72 dpi and 30 mm a long series may not leave one pixel per module; that invoice is reported with `error` and is not charged.
- The QR uses error-correction level M. If the URL does not fit at the requested size, the invoice is reported with an `error`.
- Dates must be real civil dates; only `AAAA-MM-DD` and `DD-MM-AAAA` are accepted.
- The Actor does not contact the AEAT, does not check the invoice against its servers and does not verify that the NIF exists in the census.

### Privacy

The Actor **sends no data to the AEAT or any third party** and downloads nothing from the network. The values you enter (NIF, series/number, date and amount) are stored only in the run's Apify dataset and key-value store, with the retention configured in your account. Generated files contain the verification URL, which includes those same values. No secrets or credentials are stored.

### Pricing

**Free during the launch period.** Later, one event per valid invoice whose files were generated (`qr-generado`); rejected/failed items are never charged.

### Disclaimer

This Actor generates the QR code and the verification URL, but it is **not tax, legal or accounting advice**. Always check the result with your invoicing software and the applicable regulations before issuing an invoice. Responsibility for Verifactu compliance lies with the issuer.

### Español

### Generador de QR Verifactu: SVG y PNG por lotes

Genera el «QR tributario» de la AEAT para facturas españolas por lotes: construye la URL oficial de cotejo, valida los campos de la factura y devuelve ficheros SVG y PNG listos para imprimir.

Puedes ejecutarlo en Apify o usar el mismo generador en la web: <https://cuadrafactura.es/generador-qr-verifactu/>.

### Para quién es

- Productos de facturación y ERP que emiten facturas Verifactu en España y necesitan el QR en cada factura.
- Equipos de contabilidad y facturación que imprimen o archivan facturas y quieren ficheros QR coherentes y con el tamaño correcto.
- Desarrolladores que integran la especificación del QR de la AEAT en un proceso por lotes, un flujo documental o una imprenta.

### Qué comprueba

- Construye la **URL de cotejo de la AEAT** con los cuatro parámetros obligatorios: `nif`, `numserie`, `fecha` e `importe`. Las fechas se codifican como `DD-MM-AAAA` y los valores se codifican para URL.
- Usa `ValidarQR` para facturas **VERI\*FACTU** y `ValidarQRNoVerifactu` para las demás, contra producción o el Portal de Pruebas Externas de la AEAT.
- Valida el **formato y el dígito de control del NIF** (no si existe en el censo de la AEAT), el número de serie y factura, la fecha de expedición y el importe.
- Genera el QR con **nivel M de corrección de errores**, entre **30 y 40 mm** de lado con el margen incluido y la misma zona en blanco de **6 mm** que el generador web.
- Produce **SVG** con el texto «QR tributario:» encima del código y «VERI*FACTU» debajo en las facturas VERI*FACTU.
- Produce **PNG** como imagen en escala de grises de 8 bits con un trozo `pHYs` que lleva la resolución pedida, para que Word, Illustrator o la impresora respeten los milímetros. El PNG no lleva los textos tipográficos.
- Devuelve **errores por factura** con los códigos de la AEAT y un mensaje accionable en español. Una factura mala no detiene el lote.
- Guarda los ficheros en el **almacén clave-valor por defecto** de la ejecución y enlaza cada factura con sus claves en el dataset.
- **No envía nada a la AEAT.** El actor no hace peticiones de red ni comprueba la factura contra los servidores de la AEAT.

### Entrada

El ejemplo JSON completo está en la versión inglesa. Los campos son:

| Campo | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `facturas` | array | — | De 1 a 1000 facturas. |
| `facturas[].nif` | texto | — | NIF del emisor. Se normaliza a mayúsculas y sin prefijo `ES`. |
| `facturas[].numserie` | texto | — | Serie y número de factura, hasta 60 caracteres ASCII imprimibles (32–126). |
| `facturas[].fecha` | texto | — | Fecha de expedición como `AAAA-MM-DD`; también se acepta `DD-MM-AAAA`. |
| `facturas[].importe` | número | — | Importe total, con 2 decimales como máximo y hasta 12 cifras enteras. |
| `facturas[].verifactu` | booleano | `true` | `true` usa `ValidarQR`; `false`, `ValidarQRNoVerifactu`, y omite la etiqueta «VERI\*FACTU». |
| `tamanoMm` | número | `35` | Lado total del QR en milímetros, margen incluido: de 30 a 40 mm. |
| `ppp` | entero | `300` | Resolución del PNG, de 72 a 1200 ppp, guardada en el trozo `pHYs`. |
| `formatos` | array | `["svg", "png"]` | Ficheros que se generan por factura válida: `svg` y/o `png`. |
| `entorno` | texto | `produccion` | `produccion` o `pruebas` (Portal de Pruebas Externas de la AEAT). |

### Salida

Un elemento del dataset por factura, también para las inválidas, con una vista **Resumen**. El ejemplo JSON completo está en la versión inglesa.

- `valido` es `true` cuando se construyó la URL de cotejo y se generaron los ficheros pedidos.
- `url` es `null` en las facturas inválidas.
- `error` aparece cuando falla la generación o el guardado de una factura que por lo demás era válida.

#### Nombres de fichero

Los ficheros quedan en el almacén clave-valor por defecto:

- `qr-verifactu-<numserie saneado>[-h<hash>][-N].svg` con `contentType: image/svg+xml`.
- `qr-verifactu-<numserie saneado>[-h<hash>][-N].png` con `contentType: image/png`.

El saneado conserva letras, cifras, guiones y guiones bajos; el resto pasa a guion, los guiones repetidos se funden y la base se recorta a 60 caracteres. Si el saneado cambia la serie original (`A/1` → `A-1`), se añade `-h` más 6 caracteres hexadecimales del hash FNV-1a del original, de modo que `A/1` y `A-1` nunca comparten fichero. Las series repetidas en el mismo lote reciben `-2`, `-3`… antes de la extensión.

#### Códigos de error

| Código | Significado |
|---|---|
| 1001 | Falta el NIF. |
| 1002 | Falta la serie o el número de factura. |
| 1003 | Falta la fecha de expedición. |
| 1004 | Falta el importe total. |
| 2001 | NIF con formato o dígito de control incorrectos. |
| 2002 | Serie o número de factura de más de 60 caracteres. |
| 2003 | Serie o número con caracteres fuera del ASCII imprimible (32–126). |
| 2004 | Fecha inválida o inexistente. |
| 2005 | Importe mal formado (separador de miles, coma decimal…). |
| 2006 | Importe con más de 2 decimales o con más de 12 cifras enteras. |

Una factura con errores de validación no genera ficheros y **no se cobra**. Si falla la generación de una factura válida (por ejemplo, porque la resolución es demasiado baja para el tamaño pedido), el elemento se publica con `valido: false`, `url: null` y un campo `error`; el resto del lote continúa y esa factura tampoco se cobra.

### Casos de uso

- **Cumplimiento de Verifactu:** añadir el QR oficial de la AEAT a cada factura que genere tu sistema de facturación.
- **Ficheros listos para imprimir y archivar:** obtener SVG para diseñadores y PDF, y PNG con el tamaño físico correcto para Word o una impresora.
- **Migración por lotes:** generar los QR de un conjunto de facturas existente al pasar a un sistema compatible con Verifactu.

### Límites

- **1000 facturas por ejecución.**
- **60 caracteres ASCII imprimibles** para la serie y el número, y **2 decimales como máximo** con hasta 12 cifras enteras para el importe, según las reglas de la AEAT.
- **30 a 40 mm** de lado y **72 a 1200 ppp** para el PNG. Con 72 ppp y 30 mm, una serie larga puede no dejar un píxel por módulo; esa factura se publica con `error` y no se cobra.
- El QR usa nivel M de corrección de errores. Si la URL no cabe con el tamaño pedido, la factura se publica con un `error`.
- Las fechas deben ser días civiles reales; solo se aceptan `AAAA-MM-DD` y `DD-MM-AAAA`.
- El actor no contacta con la AEAT, no comprueba la factura contra sus servidores y no verifica que el NIF exista en el censo.

### Privacidad

El actor **no envía datos a la AEAT ni a terceros** y no descarga nada de la red. Los datos que introduces (NIF, serie y número, fecha e importe) se guardan únicamente en el dataset y el almacén clave-valor de la ejecución de Apify, con la retención configurada en tu cuenta. Los ficheros generados contienen la URL de cotejo, que incluye esos mismos valores. No se guardan secretos ni credenciales.

### Precios

**Gratis durante el periodo de lanzamiento.** Después, un evento por cada factura válida cuyos ficheros se hayan generado (`qr-generado`); las facturas rechazadas o fallidas nunca se cobran.

### Aviso

Este actor genera el código QR y la URL de cotejo, pero **no es asesoramiento fiscal, legal ni contable**. Comprueba siempre el resultado con tu software de facturación y con la normativa vigente antes de emitir una factura. La responsabilidad del cumplimiento de Verifactu es del obligado a expedir la factura.

# Actor input Schema

## `facturas` (type: `array`):

Hasta 1000 facturas. De cada una se genera la URL de cotejo de la AEAT y los ficheros SVG y/o PNG.

## `tamanoMm` (type: `number`):

Lado total del QR en milímetros, margen incluido. La AEAT exige entre 30 y 40 mm.

## `ppp` (type: `integer`):

Puntos por pulgada del PNG. Se guarda en el trozo pHYs para que el fichero mida los milímetros pedidos al imprimirlo. Con 72 ppp y 30 mm un número de serie largo puede no dejar un píxel por módulo; esa factura se reporta con error y no se cobra.

## `formatos` (type: `array`):

Ficheros que se generan por factura válida. Por defecto, SVG y PNG.

## `entorno` (type: `string`):

Entorno al que apunta la URL del QR: producción o el Portal de Pruebas Externas.

## Actor input object example

```json
{
  "facturas": [
    {
      "nif": "89890001K",
      "numserie": "12345678&G33",
      "fecha": "2024-01-01",
      "importe": 241.4,
      "verifactu": true
    },
    {
      "nif": "89890001K",
      "numserie": "F-2026-0042",
      "fecha": "2026-01-15",
      "importe": 1250,
      "verifactu": false
    }
  ],
  "tamanoMm": 35,
  "ppp": 300,
  "formatos": [
    "svg",
    "png"
  ],
  "entorno": "produccion"
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset con un elemento por factura: URL de cotejo, validez, errores y claves de los ficheros.

## `keyValueStore` (type: `string`):

Almacén clave-valor con los SVG y PNG generados, con su tipo de contenido.

# 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 = {
    "facturas": [
        {
            "nif": "89890001K",
            "numserie": "12345678&G33",
            "fecha": "2024-01-01",
            "importe": 241.4,
            "verifactu": true
        },
        {
            "nif": "89890001K",
            "numserie": "F-2026-0042",
            "fecha": "2026-01-15",
            "importe": 1250,
            "verifactu": false
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("angel.carreira/qr-verifactu").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 = { "facturas": [
        {
            "nif": "89890001K",
            "numserie": "12345678&G33",
            "fecha": "2024-01-01",
            "importe": 241.4,
            "verifactu": True,
        },
        {
            "nif": "89890001K",
            "numserie": "F-2026-0042",
            "fecha": "2026-01-15",
            "importe": 1250,
            "verifactu": False,
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("angel.carreira/qr-verifactu").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 '{
  "facturas": [
    {
      "nif": "89890001K",
      "numserie": "12345678&G33",
      "fecha": "2024-01-01",
      "importe": 241.4,
      "verifactu": true
    },
    {
      "nif": "89890001K",
      "numserie": "F-2026-0042",
      "fecha": "2026-01-15",
      "importe": 1250,
      "verifactu": false
    }
  ]
}' |
apify call angel.carreira/qr-verifactu --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,angel.carreira/qr-verifactu"
        }
    }
}
```

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/asQvvvvLl2ddLtTZK/builds/EntHuBtHWOuZ4yeNH/openapi.json
