ChartQuery

Validar números de IVA de la UE

Verifica números de IVA europeos en el checkout para habilitar exenciones fiscales B2B y prevenir fraude usando la API de validación de IVA de Veille.

Descripción general

Las empresas de la UE están exentas de IVA al comprar a otras empresas de la UE — pero solo con un número de IVA válido. Esta guía muestra cómo validar un número de IVA en el checkout y recuperar los datos de la empresa asociada (nombre y dirección) de la base de datos VIES.

Prerrequisitos

  • Una clave de API de Veille — consigue una en app.veille.io
  • Instala las dependencias para tu lenguaje:
pip install requests

Sin dependencias adicionales — usa la API nativa fetch (Node 18+).

Extensión curl habilitada (activa por defecto).

Sin dependencias adicionales — usa net/http (Go 1.18+).

Sin dependencias adicionales — usa java.net.http (Java 11+).

Sin dependencias adicionales — usa System.Net.Http (.NET 6+).

# Cargo.toml
[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde_json = "1"

Pasos

Valida un número de IVA

Llama a GET /v1/vat con el parámetro vat. El número debe incluir el prefijo del país (p. ej. ES12345678A).

import requests

API_KEY  = "YOUR_API_KEY"
response = requests.get(
    "https://api.veille.io/v1/vat",
    headers={"x-api-key": API_KEY},
    params={"vat": "ES12345678A"},
)
result = response.json()
print(result)
const API_KEY = "YOUR_API_KEY";

const params   = new URLSearchParams({ vat: "ES12345678A" });
const response = await fetch(`https://api.veille.io/v1/vat?${params}`, {
  headers: { "x-api-key": API_KEY },
});
const result = await response.json();
console.log(result);
<?php
$apiKey = "YOUR_API_KEY";
$params = http_build_query(["vat" => "ES12345678A"]);

$ch = curl_init("https://api.veille.io/v1/vat?{$params}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-api-key: {$apiKey}"]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($result);
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

func main() {
    req, _ := http.NewRequest("GET", "https://api.veille.io/v1/vat?vat=ES12345678A", nil)
    req.Header.Set("x-api-key", "YOUR_API_KEY")

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)

    var result map[string]any
    json.Unmarshal(body, &result)
    fmt.Println(result)
}
import java.net.URI;
import java.net.http.*;

var client  = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.veille.io/v1/vat?vat=ES12345678A"))
    .header("x-api-key", "YOUR_API_KEY")
    .GET().build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
using System.Net.Http;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "YOUR_API_KEY");

var body = await client.GetStringAsync("https://api.veille.io/v1/vat?vat=ES12345678A");
Console.WriteLine(body);
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let result = reqwest::Client::new()
        .get("https://api.veille.io/v1/vat")
        .header("x-api-key", "YOUR_API_KEY")
        .query(&[("vat", "ES12345678A")])
        .send().await?.json::<serde_json::Value>().await?;

    println!("{:#?}", result);
    Ok(())
}

Lee los datos de la empresa

Un número válido devuelve is_valid: true, company_name, company_address y country_code.

if result.get("is_valid"):
    print(f"✅ Número de IVA válido")
    print(f"   Empresa  : {result.get('company_name')}")
    print(f"   Dirección: {result.get('company_address')}")
    print(f"   País     : {result.get('country_code')}")
else:
    print(f"❌ Número de IVA inválido: {result.get('error', 'No encontrado en VIES')}")
if (result.is_valid) {
  console.log("✅ Número de IVA válido");
  console.log(`   Empresa  : ${result.company_name}`);
  console.log(`   Dirección: ${result.company_address}`);
  console.log(`   País     : ${result.country_code}`);
} else {
  console.log(`❌ Número de IVA inválido: ${result.error ?? "No encontrado en VIES"}`);
}
if ($result["is_valid"] ?? false) {
    echo "✅ Número de IVA válido\n";
    echo "   Empresa  : {$result['company_name']}\n";
    echo "   Dirección: {$result['company_address']}\n";
    echo "   País     : {$result['country_code']}\n";
} else {
    echo "❌ Número de IVA inválido: " . ($result["error"] ?? "No encontrado en VIES") . "\n";
}
if result["is_valid"].(bool) {
    fmt.Printf("✅ Número de IVA válido\n   Empresa  : %v\n   Dirección: %v\n   País     : %v\n",
        result["company_name"], result["company_address"], result["country_code"])
} else {
    errMsg := "No encontrado en VIES"
    if e, ok := result["error"].(string); ok { errMsg = e }
    fmt.Println("❌ Número de IVA inválido:", errMsg)
}
import org.json.*;

var r = new JSONObject(response.body());
if (r.getBoolean("is_valid")) {
    System.out.printf("✅ Número de IVA válido%n   Empresa  : %s%n   Dirección: %s%n   País     : %s%n",
        r.getString("company_name"), r.getString("company_address"), r.getString("country_code"));
} else {
    System.out.println("❌ Número de IVA inválido: " + r.optString("error", "No encontrado en VIES"));
}
using System.Text.Json;

var r = JsonDocument.Parse(body).RootElement;
if (r.GetProperty("is_valid").GetBoolean())
{
    Console.WriteLine("✅ Número de IVA válido");
    Console.WriteLine($"   Empresa  : {r.GetProperty("company_name")}");
    Console.WriteLine($"   Dirección: {r.GetProperty("company_address")}");
    Console.WriteLine($"   País     : {r.GetProperty("country_code")}");
}
else
{
    var error = r.TryGetProperty("error", out var e) ? e.GetString() : "No encontrado en VIES";
    Console.WriteLine($"❌ Número de IVA inválido: {error}");
}
if result["is_valid"].as_bool().unwrap_or(false) {
    println!("✅ Número de IVA válido");
    println!("   Empresa  : {}", result["company_name"].as_str().unwrap_or(""));
    println!("   Dirección: {}", result["company_address"].as_str().unwrap_or(""));
    println!("   País     : {}", result["country_code"].as_str().unwrap_or(""));
} else {
    let err = result["error"].as_str().unwrap_or("No encontrado en VIES");
    println!("❌ Número de IVA inválido: {}", err);
}

Crea un handler de checkout B2B con IVA

Elimina el IVA del total del pedido cuando se proporciona un número de IVA de empresa de la UE verificado.

import requests

API_KEY  = "YOUR_API_KEY"
IVA_RATE = 0.21  # 21% IVA (España)

def lookup_vat(number: str) -> dict:
    r = requests.get("https://api.veille.io/v1/vat",
        headers={"x-api-key": API_KEY}, params={"vat": number})
    return r.json()

def calculate_checkout(subtotal: float, vat_number: str | None = None) -> dict:
    iva_amount = subtotal * IVA_RATE
    vat_info   = None

    if vat_number:
        result = lookup_vat(vat_number)
        if result.get("is_valid"):
            iva_amount = 0  # Inversión del sujeto pasivo B2B — sin IVA
            vat_info   = {"empresa": result["company_name"], "país": result["country_code"]}
        else:
            return {"error": "El número de IVA proporcionado no es válido."}

    total = subtotal + iva_amount
    return {"subtotal": subtotal, "iva": iva_amount, "total": total, "empresa": vat_info}

print(calculate_checkout(100.00))
print(calculate_checkout(100.00, vat_number="ES12345678A"))
print(calculate_checkout(100.00, vat_number="INVALIDO123"))
const API_KEY  = "YOUR_API_KEY";
const IVA_RATE = 0.21;

async function lookupVat(number: string) {
  const params = new URLSearchParams({ vat: number });
  const res    = await fetch(`https://api.veille.io/v1/vat?${params}`, {
    headers: { "x-api-key": API_KEY },
  });
  return res.json();
}

async function calculateCheckout(subtotal: number, vatNumber?: string) {
  let ivaAmount = subtotal * IVA_RATE;
  let companyInfo: any = null;

  if (vatNumber) {
    const result = await lookupVat(vatNumber);
    if (result.is_valid) {
      ivaAmount   = 0;
      companyInfo = { empresa: result.company_name, pais: result.country_code };
    } else {
      return { error: "El número de IVA proporcionado no es válido." };
    }
  }

  return { subtotal, iva: ivaAmount, total: subtotal + ivaAmount, empresa: companyInfo };
}

console.log(await calculateCheckout(100));
console.log(await calculateCheckout(100, "ES12345678A"));
console.log(await calculateCheckout(100, "INVALIDO123"));
<?php
const IVA_RATE = 0.21;

function lookupVat(string $apiKey, string $number): array {
    $params = http_build_query(["vat" => $number]);
    $ch = curl_init("https://api.veille.io/v1/vat?{$params}");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-api-key: {$apiKey}"]);
    $result = json_decode(curl_exec($ch), true);
    curl_close($ch);
    return $result;
}

function calculateCheckout(string $apiKey, float $subtotal, ?string $vatNumber = null): array {
    $ivaAmount = $subtotal * IVA_RATE;
    $empresa   = null;

    if ($vatNumber) {
        $result = lookupVat($apiKey, $vatNumber);
        if ($result["is_valid"] ?? false) {
            $ivaAmount = 0;
            $empresa   = ["empresa" => $result["company_name"], "pais" => $result["country_code"]];
        } else {
            return ["error" => "Número de IVA inválido."];
        }
    }
    return ["subtotal" => $subtotal, "iva" => $ivaAmount, "total" => $subtotal + $ivaAmount, "empresa" => $empresa];
}

print_r(calculateCheckout("YOUR_API_KEY", 100.0));
print_r(calculateCheckout("YOUR_API_KEY", 100.0, "ES12345678A"));
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

const APIKey  = "YOUR_API_KEY"
const IVARate = 0.21

func lookupVAT(number string) map[string]any {
    req, _ := http.NewRequest("GET", "https://api.veille.io/v1/vat?vat="+number, nil)
    req.Header.Set("x-api-key", APIKey)
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    var r map[string]any
    json.Unmarshal(body, &r)
    return r
}

func calculateCheckout(subtotal float64, vatNumber string) map[string]any {
    ivaAmount := subtotal * IVARate
    var empresa map[string]any

    if vatNumber != "" {
        result := lookupVAT(vatNumber)
        if result["is_valid"].(bool) {
            ivaAmount = 0
            empresa   = map[string]any{"empresa": result["company_name"], "pais": result["country_code"]}
        } else {
            return map[string]any{"error": "Número de IVA inválido."}
        }
    }
    return map[string]any{"subtotal": subtotal, "iva": ivaAmount, "total": subtotal + ivaAmount, "empresa": empresa}
}

func main() {
    fmt.Println(calculateCheckout(100, ""))
    fmt.Println(calculateCheckout(100, "ES12345678A"))
    fmt.Println(calculateCheckout(100, "INVALIDO123"))
}
import java.net.URI;
import java.net.http.*;
import org.json.*;

public class Main {
    static HttpClient client   = HttpClient.newHttpClient();
    static String     API_KEY  = "YOUR_API_KEY";
    static double     IVA_RATE = 0.21;

    static JSONObject lookupVat(String number) throws Exception {
        var req  = HttpRequest.newBuilder()
            .uri(URI.create("https://api.veille.io/v1/vat?vat=" + number))
            .header("x-api-key", API_KEY).GET().build();
        var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
        return new JSONObject(resp.body());
    }

    static JSONObject calculateCheckout(double subtotal, String vatNumber) throws Exception {
        double ivaAmount = subtotal * IVA_RATE;
        JSONObject empresa = null;

        if (vatNumber != null && !vatNumber.isEmpty()) {
            var result = lookupVat(vatNumber);
            if (result.getBoolean("is_valid")) {
                ivaAmount = 0;
                empresa   = new JSONObject().put("empresa", result.getString("company_name"))
                    .put("pais", result.getString("country_code"));
            } else {
                return new JSONObject().put("error", "Número de IVA inválido.");
            }
        }
        return new JSONObject().put("subtotal", subtotal).put("iva", ivaAmount)
            .put("total", subtotal + ivaAmount).put("empresa", empresa);
    }

    public static void main(String[] args) throws Exception {
        System.out.println(calculateCheckout(100, null));
        System.out.println(calculateCheckout(100, "ES12345678A"));
        System.out.println(calculateCheckout(100, "INVALIDO123"));
    }
}
using System.Net.Http;
using System.Text.Json;

const double IVA_RATE = 0.21;
var apiKey = "YOUR_API_KEY";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", apiKey);

async Task<JsonElement> LookupVat(string number)
{
    var body = await client.GetStringAsync($"https://api.veille.io/v1/vat?vat={number}");
    return JsonDocument.Parse(body).RootElement;
}

async Task<string> CalculateCheckout(double subtotal, string? vatNumber = null)
{
    double ivaAmount = subtotal * IVA_RATE;
    string? empresa  = null;

    if (vatNumber != null)
    {
        var result = await LookupVat(vatNumber);
        if (result.GetProperty("is_valid").GetBoolean())
        { ivaAmount = 0; empresa = result.GetProperty("company_name").GetString(); }
        else return """{"error":"Número de IVA inválido."}""";
    }
    return $"""{{ "subtotal": {subtotal}, "iva": {ivaAmount}, "total": {subtotal + ivaAmount}, "empresa": "{empresa}" }}""";
}

Console.WriteLine(await CalculateCheckout(100));
Console.WriteLine(await CalculateCheckout(100, "ES12345678A"));
Console.WriteLine(await CalculateCheckout(100, "INVALIDO123"));
use reqwest::Client;
use serde_json::{json, Value};

const IVA_RATE: f64 = 0.21;
const API_KEY: &str = "YOUR_API_KEY";

async fn lookup_vat(client: &Client, number: &str) -> Value {
    client.get("https://api.veille.io/v1/vat")
        .header("x-api-key", API_KEY)
        .query(&[("vat", number)])
        .send().await.unwrap().json::<Value>().await.unwrap()
}

async fn calculate_checkout(client: &Client, subtotal: f64, vat_number: Option<&str>) -> Value {
    let mut iva_amount = subtotal * IVA_RATE;
    let mut empresa    = json!(null);

    if let Some(number) = vat_number {
        let result = lookup_vat(client, number).await;
        if result["is_valid"].as_bool().unwrap_or(false) {
            iva_amount = 0.0;
            empresa    = json!({"empresa": result["company_name"], "pais": result["country_code"]});
        } else {
            return json!({ "error": "Número de IVA inválido." });
        }
    }
    json!({ "subtotal": subtotal, "iva": iva_amount, "total": subtotal + iva_amount, "empresa": empresa })
}

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client = Client::new();
    println!("{}", calculate_checkout(&client, 100.0, None).await);
    println!("{}", calculate_checkout(&client, 100.0, Some("ES12345678A")).await);
    println!("{}", calculate_checkout(&client, 100.0, Some("INVALIDO123")).await);
    Ok(())
}

Siempre almacena el nombre y la dirección de la empresa validada junto al registro del pedido. Las autoridades fiscales de la UE exigen prueba de que el mecanismo de inversión del sujeto pasivo fue correctamente aplicado.

En esta página