Intro to Spanish Suministro Inmediato de Información (SII) for developers cover image

Intro to Spanish Suministro Inmediato de Información (SII) for developers

Kane Cohen • January 6, 2024

python sii taxes spain invoices ai

A few weeks ago, I received a surprising task for one of the projects I work on. Within a very short period, the company had to comply with a relatively recent tax requirement for companies registered in the Canary Islands - any invoice issued or received by the company had to be submitted to the Canarian Tax Agency or, in the case of the mainland, the main Spanish Tax Administration. If you're reading this post, I imagine you're somewhat familiar with these requirements, also known by its abbreviation - SII. But here's a really rough overview:

Understanding Spain's SII

Spain's SII (Suministro Inmediato de Información) is a mandatory electronic reporting system for VAT data. Implemented in 2017, it requires specific companies (large businesses, REDEME users, and VAT groups) to report details of issued and received invoices to the tax authorities within four business days. This "real-time" VAT reporting aims to simplify compliance, automate tasks, and boost fiscal control. Failure to comply can lead to penalties. So, if you're a large taxpayer in Spain, remember, your VAT information goes straight to the taxman, almost instantly!

Navigating the Documentation

As any developer would, the first thing I did was open a page with the technical documentation of the whole process on the official website. The documentation is, of course, written entirely in Spanish. Despite diligently studying it for almost a year on Duolingo, I'm not yet ready to tackle it without external help. Thankfully, we live in a world where AI and translation apps exist. Using Google Translate alongside Google Bard and ChatGPT, I managed to extract all the necessary information to write a small microservice app in Python with a simple task - retrieve invoices from the database and send them to the tax authorities.

A Quick Solution for Developers

If you're in a position where you need a quick solution, I'm happy to provide some basic guidance with the commented code below:

# Make sure to install library `zeep` which will be used for communications with SII Soap services.
import zeep
from requests import Session
from zeep.transports import Transport
# A bit of pseudo-Django code
from models import CompanyInvoice

COMPANY_NAME = 'ACME'
COMPANY_NIF = '123456789'

def main(test_mode=True):
    # Define company information

    # Define WSDL endpoint where we'll retreinve available services and test/production environments
    wsdl = 'https://www3.gobiernodecanarias.org/tributos/atc/estatico/asistencia_contribuyente/wsdl/SuministroFactEmitidas.wsdl'

    # Define common header that will be sent to SII
    body_header = {
        'IDVersionSii': '1.0',
        'Titular': {
            'NombreRazon': COMPANY_NAME,
            'NIF': COMPANY_NIF
        },
        # A0 specifies that we want to register new invoice with SII
        'TipoComunicacion': 'A0'
    }

    if test_mode:
        print('Running in Test Mode')
    else:
        print('!>>> Running in Production Mode <<<!')

    # Initiate a session and provide company's digital certificate - it is required in order to submit data.
    session = Session()
    session.cert = ('cert.pem', 'key.pem')
    settings_zeep = zeep.Settings(force_https=False)
    transport = Transport(session=session)

    try:
        client = zeep.Client(wsdl=wsdl, settings=settings_zeep, transport=transport)
    except:
        print('SII WSDL Request Failure')
        return

    # Pick current active environment - testing or production.
    if test_mode:
        service = client.bind('siiService', 'SuministroFactEmitidasPruebas')
    else:
        service = client.bind('siiService', 'SuministroFactEmitidas')

    # Fetch Invoices
    company_invoices = CompanyInvoices.objects \
        .filter(is_submitted=False) \
        .select_related('company') \
        .order_by('number')[:1000]

    if len(company_invoices) < 0:
        print('No Invoices')
        return

    print('Submitting %d invoices' % len(company_invoices))

    body_value = {
        'Cabecera': body_header,
        # Map raw Invoice data to a format required by SII.
        'RegistroLRFacturasEmitidas': map(make_company_invoice, company_invoices)
    }
    response = service.SuministroLRFacturasEmitidas(**body_value)
    process_response(response, entries)

    items = response.RespuestaLinea

    correct = 0
    incorrect = 0
    for item in items:
        # Here you likely would want to retreive Invoice from DB based on number: item.IDFactura.NumSerieFacturaEmisor
        # and update its status, save time of submission and any errors if present.
        if item.EstadoRegistro == 'Correcto':
            correct += 1
        elif item.EstadoRegistro == 'Incorrecto':
            incorrect +=1

    print('Correct: %d | Incorrect: %d' % (correct, incorrect))


# Function to generate dictionary for invoice that was sent to a company.
# Direct client invoices have different format and less requirements.
def make_company_invoice(invoice):
    # Counterparty company details differ for Spain/Non-Spain countries.
    if invoice.company.country != 'Spain':
        company_id = {
            'NombreRazon': invoice.company.name,
            'IDOtro': {
                'CodigoPais': invoice.company.country_code, # Standard alpha(2) ISO desigation like FR or IT
                'IDType': '04', # Signifies that this is a general ID document
                'ID': invoice.company.cif, # Actual ID of the company
            }
        }
    else:
        company_id = {
            'NombreRazon': invoice.company.name,
            'NIF': invoice.company.cif
        }

    return {
        'PeriodoLiquidacion': {
            'Ejercicio': invoice.created_at.strftime('%Y'),
            'Periodo': invoice.created_at.strftime('%m')
        },
        'IDFactura': {
            'IDEmisorFactura': {
                'NIF': COMPANY_NIF
            },
            'NumSerieFacturaEmisor': invoice.number, # Invoice number registered in the system. But be sequential.
            'FechaExpedicionFacturaEmisor': invoice.created_at.strftime('%d-%m-%Y') # Date of issuance
        },
        'FacturaExpedida': {
            'TipoFactura': 'F1', # Invoice type. One of four. In most cases for simple invoices it should be F1 or F2 for direct client invoices
            'ClaveRegimenEspecialOTrascendencia': '01', # Designates invoice type. A lot of different types
            'ImporteTotal': invoice.total,
            'DescripcionOperacion': invoice.description,
            'Contraparte': company_id,
            'TipoDesglose': {
                'DesgloseFactura': {
                    'Sujeta': {
                        'NoExenta': {
                            'TipoNoExenta': 'S1',
                            'DesgloseIGIC': {
                                'DetalleIGIC': {
                                    'TipoImpositivo': invoice.igic_rate,
                                    'BaseImponible': invoice.base,
                                    'CuotaRepercutida': invoice.igic_amount
                                }
                            }
                        }
                    }
                }
            }
        }
    }

main()

I hope this bit of code will be helpful to those like me who have the task of upgrading business software to be compliant with SII requirements. Please note that the code above is a super-simplified version that focuses only on B2B-type invoices. There are many other types that companies usually have to deal with: B2C invoices, B2B Credit Invoices, B2C Credit Invoices, Invoice Cancellations, and many more.

Get in Touch

Feel free to ping me via mail if you have any questions or need further assistance.