Motor fiscal · v1
NORA API
Infraestructura fiscal para el mercado inmobiliario español.
Calcula ITP, AJD, IVA, IGIC, IPSI y beneficios autonómicos en todas las comunidades autónomas mediante una sola llamada REST.
✓ Producción
REST · JSON
19 CCAA + territorios forales
Webhooks
SDK JS/Node
⚡
Un endpoint, todas las CCAA
POST /v1/tax/calculate devuelve el cálculo completo: impuestos, gastos, hipoteca y beneficios aplicados en <200 ms.
🔌
Integra con cualquier CRM
REST puro con autenticación por Bearer token. Compatible con HubSpot, Salesforce, Pipedrive, Zoho, Frappe y cualquier sistema con HTTP.
📄
PDF de estudio fiscal
Genera el informe PDF profesional en la misma llamada pasando output.pdf: true. Descarga autenticada o por URL firmada sin expiración.
🔔
Webhooks firmados
Recibe cálculos y PDFs en tu sistema en tiempo real con firma HMAC-SHA256. Eventos: calculation.completed, pdf.ready, pdf.failed.
Base URL
https://api.noraapp.es
Flujo básico de integración
1
Obtén tu API key — Escribe a api@noraapp.es. Te enviamos una clave nora_live_XXXX con los scopes necesarios.
2
Llama a /v1/tax/calculate — Envía los datos de la operación (región, precio, tipo, perfil del comprador) y recibe los impuestos desglosados.
3
Guarda el calculation_hash — Úsalo para recuperar el cálculo, regenerar el PDF o como referencia en tu CRM.
4
Opcional: genera el PDF — Pasa output.pdf: true en el mismo request o llama a /v1/pdf/render con el hash.
Quickstart — 5 minutos
El ejemplo más simple posible: calcular los impuestos de comprar un piso de segunda mano en Madrid por 350.000 €.
1. Obtén tu API key
Escribe a api@noraapp.es indicando tu empresa y CRM. En menos de 24h recibirás tu clave nora_live_XXXX.
2. Primer cálculo con curl
export NORA_API_KEY="nora_live_XXXXXXXXXXXXXXXXXXXX"
curl -X POST https://api.noraapp.es/v1/tax/calculate \
-H "Authorization: Bearer $NORA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"input": {
"region_code": "ES-MD",
"transmission": "used",
"property_type": "vivienda",
"price": 350000
}
}'
3. Respuesta
{
"result": {
"region": { "code": "ES-MD", "name": "Comunidad de Madrid", "scope_ccaa": "ccaa" },
"price": 350000,
"transmission": "used",
"property_type": "vivienda",
"taxes": {
"itp": 21000,
"ajd": 0
},
"totals": {
"impuestos": 21000,
"gastos": 3200,
"coste_adquisicion": 24200
},
"costs": {
"notaria": 1350,
"registro": 700,
"gestoria": 650,
"tasacion": 500
},
"notes": {
"itp_rate": 0.06,
"tax_note": "Tipo general ITP 6% Comunidad de Madrid"
},
"meta": {
"engine_version": "compute_v1.0.0",
"calculation_hash": "a1b2c3d4e5f6...",
"region_version": "2025-01",
"as_of": "2026-03-28"
}
}
}
4. Con Node.js
import { NoraClient } from '@nora/sdk-js';
const nora = new NoraClient({ apiKey: process.env.NORA_API_KEY });
const { result } = await nora.tax.calculate({
input: {
region_code: 'ES-MD',
transmission: 'used',
property_type: 'vivienda',
price: 350_000,
is_habitual: true,
is_young: true,
age_years: 30
}
});
console.log(`ITP: ${result.taxes.itp} €`);
Nota sobre Idempotency-Key: El header Idempotency-Key es obligatorio en todos los POST de cálculo. Genera un UUID único por operación. Si repites la misma key en los próximos 5 minutos, la API devuelve el mismo resultado cacheado sin recalcular.
Autenticación
La API usa Bearer token en el header Authorization:
Authorization: Bearer nora_live_XXXXXXXXXXXXXXXXXXXX
Tipos de claves
Prefijo Entorno Uso
nora_live_Producción Cálculos reales. Se cobra según plan.
nora_test_Test Para desarrollo. No genera factura.
Cómo obtener una clave
Escribe a api@noraapp.es con:
Nombre de empresa
CRM o sistema que vas a integrar
Volumen estimado de cálculos/mes
Si necesitas PDF habilitado
Errores de autenticación
{
"error": {
"code": "ERR_UNAUTHORIZED",
"message": "Invalid or missing API key",
"request_id": "req_abc123"
}
}
Scopes
Cada API key tiene asignados uno o varios scopes que determinan qué endpoints puede usar.
Scope Permite
regions:readGET /v1/regions
tax:validatePOST /v1/tax/validate
tax:calculatePOST /v1/tax/calculate · GET /v1/calculations/:hash
pdf:renderPOST /v1/pdf/render · GET /v1/pdf/jobs/:id/file
usage:readGET /v1/usage
Una clave de partner estándar incluye todos los scopes excepto pdf:render, que se activa bajo demanda.
Idempotencia
Los endpoints POST /v1/tax/calculate y POST /v1/pdf/render requieren el header Idempotency-Key .
¿Cómo funciona?
Genera un UUID v4 único para cada operación nueva.
Si repites la misma key en los próximos 5 minutos , la API devuelve exactamente la misma respuesta sin recalcular.
Protege contra doble envío en workflows de CRM, automatizaciones o reintentos por timeout.
import { randomUUID } from 'crypto';
const idempotencyKey = randomUUID();
import uuid
idempotency_key = str(uuid.uuid4())
$idempotencyKey = Ramsey\Uuid\Uuid::uuid4()->toString();
Si no incluyes Idempotency-Key, recibirás 422 ERR_IDEMPOTENCY_REQUIRED.
Rate limits
60 peticiones por minuto por partner. Si lo superas:
HTTP/1.1 429 Too Many Requests
{
"error": {
"code": "ERR_RATE_LIMIT",
"message": "Rate limit exceeded. Retry after 42 seconds.",
"request_id": "req_xyz"
}
}
Implementa exponential backoff en tu integración: espera 1s, luego 2s, luego 4s antes de reintentar.
Errores
Todos los errores usan el mismo formato:
{
"error": {
"code": "ERR_INPUT_INVALID",
"message": "input fiscal invalido",
"request_id": "req_abc123",
"details": {
"errors": [
{ "path": "region_code", "code": "ERR_REQUIRED", "message": "region_code es obligatorio" }
],
"missing_fields": { "blocking": ["region_code"], "recommended": [], "conditional": [] }
}
}
}
Códigos HTTP
Código Situación
200OK
401API key inválida, expirada o sin scope
402Cuota de PDFs agotada
404Recurso no encontrado (cálculo, PDF job…)
405Método HTTP no permitido
415Content-Type debe ser application/json
422Input inválido o Idempotency-Key ausente
429Rate limit superado
503Renderizador de PDF no disponible
Códigos de error frecuentes
code Causa
ERR_UNAUTHORIZEDAPI key inválida o sin scope
ERR_INPUT_INVALIDEl input no pasa la validación del motor
ERR_INPUT_REQUIREDNi input ni calculation_hash enviado
ERR_IDEMPOTENCY_REQUIREDFalta el header Idempotency-Key
ERR_CALCULATION_NOT_FOUNDNo existe cálculo con ese hash
ERR_PDF_QUOTA_EXCEEDEDCuota de PDFs del plan agotada
ERR_PDF_RENDER_FAILEDError interno al renderizar el PDF
ERR_RATE_LIMITDemasiadas peticiones por minuto
ERR_NOT_FOUNDRecurso no encontrado
Comprueba si el servidor está en marcha. No requiere autenticación. Úsalo para monitoring.
Respuesta
{
"ok": true,
"service": "nora-api",
"api_version": "v1",
"engine_version": "compute_v1.0.0",
"pdf_renderer": {
"available": true,
"mode": "playwright",
"reason": null
}
}
Versión del motor fiscal y estado del renderizador de PDF.
{
"api_version": "v1",
"engine_version": "compute_v1.0.0",
"auth_model": "partner_api_keys_separate_from_spa_users",
"pdf_renderer": { "available": true, "mode": "playwright", "reason": null }
}
Catálogo de todas las comunidades autónomas y territorios soportados por el motor. Úsalo para poblar selectores en tu CRM.
curl https://api.noraapp.es/v1/regions \
-H "Authorization: Bearer $NORA_API_KEY"
{
"regions": [
{ "code": "ES-AN", "name": "Andalucia", "scope_ccaa": "ccaa" },
{ "code": "ES-AR", "name": "Aragon", "scope_ccaa": "ccaa" },
{ "code": "ES-AS", "name": "Principado de Asturias", "scope_ccaa": "ccaa" },
{ "code": "ES-IB", "name": "Illes Balears", "scope_ccaa": "ccaa" },
{ "code": "ES-CN", "name": "Canarias", "scope_ccaa": "ccaa" },
{ "code": "ES-CB", "name": "Cantabria", "scope_ccaa": "ccaa" },
{ "code": "ES-CM", "name": "Castilla-La Mancha", "scope_ccaa": "ccaa" },
{ "code": "ES-CL", "name": "Castilla y Leon", "scope_ccaa": "ccaa" },
{ "code": "ES-CT", "name": "Cataluna", "scope_ccaa": "ccaa" },
{ "code": "ES-VC", "name": "Comunitat Valenciana", "scope_ccaa": "ccaa" },
{ "code": "ES-EX", "name": "Extremadura", "scope_ccaa": "ccaa" },
{ "code": "ES-GA", "name": "Galicia", "scope_ccaa": "ccaa" },
{ "code": "ES-MD", "name": "Comunidad de Madrid", "scope_ccaa": "ccaa" },
{ "code": "ES-MC", "name": "Region de Murcia", "scope_ccaa": "ccaa" },
{ "code": "ES-NC", "name": "Comunidad Foral de Navarra", "scope_ccaa": "ccaa" },
{ "code": "ES-RI", "name": "La Rioja", "scope_ccaa": "ccaa" },
{ "code": "ES-CE", "name": "Ceuta", "scope_ccaa": "special"},
{ "code": "ES-ML", "name": "Melilla", "scope_ccaa": "special"},
{ "code": "ES-BI", "name": "Bizkaia", "scope_ccaa": "foral" },
{ "code": "ES-SS", "name": "Gipuzkoa", "scope_ccaa": "foral" },
{ "code": "ES-VI", "name": "Araba", "scope_ccaa": "foral" }
]
}
Valida el input sin ejecutar el cálculo. Devuelve qué campos faltan, cuáles son obligatorios y cuáles mejorarían la precisión.
curl -X POST https://api.noraapp.es/v1/tax/validate \
-H "Authorization: Bearer $NORA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": {"region_code": "ES-AN", "transmission": "used", "price": 250000}}'
{
"ok": false,
"errors": [
{ "path": "property_type", "code": "ERR_INVALID_ENUM", "message": "property_type invalido" }
],
"missing_fields": {
"blocking": ["property_type"],
"recommended": ["is_habitual", "hasMortgage", "age_years", "seller_non_resident"],
"conditional": []
},
"next_questions": [
{
"field": "property_type",
"level": "blocking",
"label": "Tipo de inmueble",
"question": "Indica si es vivienda, local, suelo u otro inmueble."
}
]
}
Endpoint principal. Envía los datos de la operación y recibe el desglose completo: ITP/IVA, AJD, retención art. 211, gastos de notaría/registro/gestoría, hipoteca y todos los beneficios autonómicos aplicables.
Campos obligatorios
input.region_code *
string
input.transmission *
"used"│"new"
used = segunda mano → ITP · new = obra nueva → IVA+AJD
input.property_type *
string
"vivienda" · "local" · "suelo" · "otros"
input.price *
number
Precio escriturado en euros (mín. 1, máx. 100.000.000)
Campos recomendados (mejoran la precisión y activan beneficios)
input.is_habitual
boolean
¿Será vivienda habitual? Activa bonificaciones en casi todas las CCAA.
input.is_young
boolean
¿Es comprador joven (<35 años)? Activa tipos reducidos.
input.age_years
number│null
Edad del comprador (18–120).
input.is_large_family
boolean
¿Familia numerosa?
input.has_disc_33
boolean
¿Discapacidad ≥33%?
input.seller_non_resident
boolean
¿El vendedor es no residente en España? Activa retención 3% art. 211 LIRNR.
input.hasMortgage
boolean
¿Lleva hipoteca? Si true, completa los campos financieros.
input.municipality_ine
string│null
Código INE del municipio (5 dígitos). Para beneficios municipales específicos.
input.vpo
null│"general"│"special"
¿Es VPO? null si no lo es.
Ejemplos
Mínimo
Con hipoteca y beneficios
Obra nueva Canarias
Con PDF
curl -X POST https://api.noraapp.es/v1/tax/calculate \
-H "Authorization: Bearer $NORA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"input": {
"region_code": "ES-MD",
"transmission": "used",
"property_type": "vivienda",
"price": 350000
}
}'
curl -X POST https://api.noraapp.es/v1/tax/calculate \
-H "Authorization: Bearer $NORA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 660f9511-f3ac-52e5-b827-557766551111" \
-d '{
"input": {
"region_code": "ES-AN",
"transmission": "used",
"property_type": "vivienda",
"price": 250000,
"is_habitual": true,
"is_young": true,
"age_years": 32,
"is_large_family": false,
"has_disc_33": false,
"seller_non_resident": false,
"hasMortgage": true,
"mortgageState": "yes",
"appraisal": 270000,
"downpayment": 50000,
"loan_amount": 200000,
"ltv_pct": 80,
"fin_term": 360,
"rate_type": "fixed",
"fixed_tin_pct": 3.2,
"opening_fee_pct": 0,
"linked_products_bonus": 0.3
}
}'
curl -X POST https://api.noraapp.es/v1/tax/calculate \
-H "Authorization: Bearer $NORA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 770a1622-g4bd-63f6-c938-668877662222" \
-d '{
"input": {
"region_code": "ES-CN",
"transmission": "new",
"property_type": "vivienda",
"price": 300000,
"is_habitual": true,
"island_code": "TF"
}
}'
curl -X POST https://api.noraapp.es/v1/tax/calculate \
-H "Authorization: Bearer $NORA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 880b2733-h5ce-74g7-d049-779988773333" \
-d '{
"input": {
"region_code": "ES-CT",
"transmission": "used",
"property_type": "vivienda",
"price": 420000,
"is_habitual": true
},
"output": {
"pdf": true,
"filename": "estudio-fiscal-cliente.pdf",
"pdf_options": {
"headerRightLogoUrl": "https://mi-agencia.com/logo.png"
}
}
}'
Respuesta
{
"result": {
"region": { "code": "ES-AN", "name": "Andalucia", "scope_ccaa": "ccaa" },
"price": 250000,
"transmission": "used",
"property_type": "vivienda",
"taxes": {
"itp": 8750,
"ajd": 0
},
"totals": {
"impuestos": 8750,
"gastos": 2850,
"downpayment": 50000,
"coste_adquisicion": 11600,
"fondos_totales": 61600,
"intereses": 114580,
"coste_total_con_intereses": 326180
},
"costs": {
"notaria": 1150,
"registro": 580,
"gestoria": 620,
"tasacion": 500
},
"notes": {
"itp_rate": 0.035,
"tax_note": "Tipo reducido 3.5% jóvenes <35 años VH ≤150.000 €"
},
"meta": {
"engine_version": "compute_v1.0.0",
"calculation_hash": "a1b2c3d4e5f6789012345678901234567890abcd",
"region_version": "2025-01",
"as_of": "2026-03-28"
}
}
}
Recupera un cálculo previo por su calculation_hash. Útil para mostrar el detalle de una operación guardada en tu CRM sin repetir el cálculo.
curl https://api.noraapp.es/v1/calculations/a1b2c3d4e5f6... \
-H "Authorization: Bearer $NORA_API_KEY"
{
"calculation": {
"calculation_hash": "a1b2c3d4e5f6...",
"input": { },
"result": { },
"latest_pdf": null,
"audit": {
"request_id": "req_abc123",
"route": "POST /v1/tax/calculate",
"created_at": "2026-03-28T10:30:00.000Z",
"engine_version": "compute_v1.0.0",
"region_version": "2025-01"
}
}
}
Genera el informe PDF de un estudio fiscal. Acepta un input nuevo o un calculation_hash previo. Puedes añadir el logo de tu empresa en la cabecera.
Opciones de PDF
output.filename
string
Nombre del fichero generado
output.lang
string
Idioma: es · en · fr · de · it · ru
output.pdf_options.headerRightLogoUrl
string
URL del logo de tu agencia para la cabecera
Respuesta PDF
{
"result": { },
"pdf": {
"requested": true,
"status": "ready",
"billable": true,
"filename": "estudio-fiscal-cliente.pdf",
"job_id": "job_xyz789",
"content_type": "application/pdf",
"size_bytes": 142350,
"expires_at": "2026-03-28T14:30:00.000Z",
"download_url": "/v1/pdf/jobs/job_xyz789/file",
"public_download_url": "/v1/pdf/public/job_xyz789?token=eyJ...",
"persistent_file_id": "file_pqr456",
"persistent_download_url": "/v1/pdf/files/file_pqr456"
}
}
Usa persistent_download_url para guardar la URL en tu CRM: no expira y no requiere autenticación.
Descarga el PDF autenticado por job_id. Solo el partner que lo generó puede descargarlo.
curl https://api.noraapp.es/v1/pdf/jobs/job_xyz789/file \
-H "Authorization: Bearer $NORA_API_KEY" \
--output estudio-fiscal.pdf
Uso actual del partner: PDFs generados, cuota incluida en el plan y precio por PDF adicional.
{
"partner": {
"partner_id": "partner_inmobiliaria_xyz",
"display_name": "Inmobiliaria XYZ"
},
"usage": {
"pdf_renders_used": 23,
"pdf_renders_included": 100,
"pdf_renders_remaining": 77,
"overage_enabled": true,
"price_per_pdf_cents": 200,
"currency": "EUR"
}
}
Webhooks
Configura una URL HTTPS en tu partner config para recibir eventos en tiempo real. Cada evento llega como un POST JSON firmado con HMAC-SHA256.
Eventos disponibles
Tipo Cuándo se dispara
calculation.completedCada vez que se completa un cálculo (con o sin PDF)
pdf.readyCuando el PDF se genera correctamente
pdf.failedCuando el PDF falla
Cabeceras del webhook
X-Nora-Webhook-Id: 550e8400-e29b-41d4-a716-446655440000
X-Nora-Webhook-Type: calculation.completed
X-Nora-Webhook-Timestamp: 2026-03-28T10:30:00.000Z
X-Nora-Webhook-Signature: sha256=a1b2c3d4e5f6...
Verificación de firma
Node.js
Python
PHP
import { createHmac } from 'crypto';
function verifyNoraWebhook(body, signature, timestamp, secret) {
const expected = createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex');
return `sha256=${expected}` === signature;
}
app.post('/webhooks/nora', (req, res) => {
const sig = req.headers['x-nora-webhook-signature'];
const ts = req.headers['x-nora-webhook-timestamp'];
const raw = JSON.stringify(req.body);
if (!verifyNoraWebhook(raw, sig, ts, process.env.NORA_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = req.body;
if (event.type === 'pdf.ready') {
const pdfUrl = event.data?.pdf?.persistent_download_url;
}
res.status(200).send('ok');
});
import hmac, hashlib
from flask import Flask, request, abort
def verify_nora_webhook(body, signature, timestamp, secret):
expected = hmac.new(
secret.encode(), f"{timestamp}.{body}".encode(), hashlib.sha256
).hexdigest()
return f"sha256={expected}" == signature
@app.post('/webhooks/nora')
def nora_webhook():
sig = request.headers.get('X-Nora-Webhook-Signature', '')
ts = request.headers.get('X-Nora-Webhook-Timestamp', '')
raw = request.get_data(as_text=True)
if not verify_nora_webhook(raw, sig, ts, NORA_WEBHOOK_SECRET):
abort(401)
event = request.json
if event['type'] == 'pdf.ready':
pdf_url = event['data']['pdf']['persistent_download_url']
return 'ok', 200
<?php
function verifyNoraWebhook($body, $sig, $ts, $secret) {
$expected = 'sha256=' . hash_hmac('sha256', "{$ts}.{$body}", $secret);
return hash_equals($expected, $sig);
}
$body = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_NORA_WEBHOOK_SIGNATURE'] ?? '';
$ts = $_SERVER['HTTP_X_NORA_WEBHOOK_TIMESTAMP'] ?? '';
if (!verifyNoraWebhook($body, $sig, $ts, $_ENV['NORA_WEBHOOK_SECRET'])) {
http_response_code(401); exit('Invalid signature');
}
echo 'ok';
Integración genérica con CRMs
Cualquier CRM que pueda hacer una llamada HTTP POST puede integrarse con NORA. El patrón general es siempre el mismo:
1
Mapea los campos de tu CRM a TaxInput. Crea campos personalizados en tu CRM: nora_region_code, nora_transmission, nora_price, etc.
2
Llama a POST /v1/tax/calculate desde tu workflow o automatización, construyendo el input con los valores de los campos del Deal/Operación.
3
Escribe los resultados de vuelta en tu CRM. Guarda result.taxes.itp, result.totals.impuestos, result.meta.calculation_hash y pdf.persistent_download_url.
Campos sugeridos en tu CRM
Campo en CRM Campo NORA Tipo Ejemplo
nora_region_code input.region_code Selección ES-AN
nora_transmission input.transmission Selección used
nora_property_type input.property_type Selección vivienda
nora_price input.price Número 250000
nora_is_habitual input.is_habitual Booleano true
nora_is_young input.is_young Booleano false
nora_age_years input.age_years Número 32
nora_itp_eur result.taxes.itp Número (salida) 8750
nora_total_taxes_eur result.totals.impuestos Número (salida) 11600
nora_calculation_hash result.meta.calculation_hash Texto (salida) a1b2c3...
nora_pdf_url pdf.persistent_download_url URL (salida) /v1/pdf/files/...
Integración HubSpot (nativa)
NORA tiene una integración nativa con HubSpot que automatiza la creación de propiedades y el writeback de resultados en los Deals.
Setup automático de propiedades
curl -X POST https://api.noraapp.es/v1/integrations/hubspot/setup/properties \
-H "Authorization: Bearer $NORA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"private_app_token": "pat-eu1-XXXXXXXX"}'
Calcular desde un Deal (formato plano HubSpot)
curl -X POST https://api.noraapp.es/v1/integrations/hubspot/tax/calculate \
-H "Authorization: Bearer $NORA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"nora_region_code": "ES-AN",
"nora_price": "250000",
"nora_transmission": "used",
"nora_is_habitual": "true",
"nora_is_young": "true",
"nora_age_years": "32",
"hs_object_id": "12345678"
}'
Deal Sync completo (todo-en-uno)
curl -X POST https://api.noraapp.es/v1/integrations/hubspot/deal-sync \
-H "Authorization: Bearer $NORA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"deal_id": "12345678"}'
Salesforce
Integra NORA en Salesforce usando External Services (sin código) o Apex Callouts .
External Services (sin código)
Setup → Integrations → External Services.
Importa el fichero openapi.yaml de NORA.
Salesforce genera automáticamente las acciones invocables.
Úsalas en tus Flows: Action → NORA API → calculateTax .
Apex Callout
public class NoraApiService {
private static final String API_KEY = 'nora_live_XXXX';
private static final String BASE_URL = 'https://api.noraapp.es';
public static Map<String, Object> calculateTax(
String regionCode, String transmission, Decimal price
) {
HttpRequest req = new HttpRequest();
req.setEndpoint(BASE_URL + '/v1/tax/calculate');
req.setMethod('POST');
req.setHeader('Authorization', 'Bearer ' + API_KEY);
req.setHeader('Content-Type', 'application/json');
req.setHeader('Idempotency-Key', generateUUID());
req.setBody(JSON.serialize(new Map<String, Object>{
'input' => new Map<String, Object>{
'region_code' => regionCode,
'transmission' => transmission,
'property_type' => 'vivienda',
'price' => price
}
}));
HttpResponse res = new Http().send(req);
return (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
}
private static String generateUUID() {
Blob b = Crypto.GenerateAESKey(128);
String h = EncodingUtil.ConvertTohex(b);
return h.substring(0,8)+'-'+h.substring(8,12)+'-'+
h.substring(12,16)+'-'+h.substring(16,20)+'-'+h.substring(20);
}
}
Pipedrive
Integra NORA vía Make (Integromat) / Zapier o con la API de Pipedrive directamente.
HTTP request para Make / Zapier
URL: POST https://api.noraapp.es/v1/tax/calculate
Headers:
Authorization: Bearer nora_live_XXXX
Content-Type: application/json
Idempotency-Key: {{uuid}}
Body:
{
"input": {
"region_code": "{{deal.nora_region_code}}",
"transmission": "{{deal.nora_transmission}}",
"property_type": "vivienda",
"price": {{deal.value}},
"is_habitual": true
}
}
Campos de Deal sugeridos en Pipedrive
Nombre del campo Tipo
NORA: Comunidad Autónoma Opciones (ES-AN, ES-MD…)
NORA: Tipo transmisión Opciones (used / new)
NORA: Tipo inmueble Opciones
NORA: Total impuestos (€) Número
NORA: ITP/IVA (€) Número
NORA: PDF informe fiscal URL
NORA: Hash cálculo Texto
Zoho CRM
Integra NORA con Deluge (función personalizada) o Zoho Flow .
Deluge — función personalizada
noraApiKey = "nora_live_XXXXXXXXXXXXXXXXXXXX";
idempotencyKey = zoho.encryption.uuid();
dealInfo = zoho.crm.getRecordById("Deals", dealId);
price = dealInfo.get("Amount");
regionCode = dealInfo.get("NORA_Region_Code");
requestBody = Map();
input = Map();
input.put("region_code", regionCode);
input.put("transmission", "used");
input.put("property_type", "vivienda");
input.put("price", price);
input.put("is_habitual", true);
requestBody.put("input", input);
headers = Map();
headers.put("Authorization", "Bearer " + noraApiKey);
headers.put("Content-Type", "application/json");
headers.put("Idempotency-Key", idempotencyKey);
response = invokeurl
[
url: "https://api.noraapp.es/v1/tax/calculate"
type: POST
parameters: requestBody.toString()
headers: headers
];
result = response.get("result");
itp = result.get("taxes").get("itp");
total = result.get("totals").get("impuestos");
hash = result.get("meta").get("calculation_hash");
updateData = Map();
updateData.put("NORA_ITP_EUR", itp);
updateData.put("NORA_Total_Taxes_EUR", total);
updateData.put("NORA_Calculation_Hash", hash);
zoho.crm.updateRecord("Deals", dealId, updateData);
Frappe / ERPNext
Server Script (Python)
import requests, uuid, frappe
def calculate_nora_taxes(doc, method=None):
api_key = frappe.conf.get("nora_api_key")
payload = {
"input": {
"region_code": doc.nora_region_code,
"transmission": doc.nora_transmission,
"property_type": doc.nora_property_type or "vivienda",
"price": float(doc.precio_compra),
"is_habitual": bool(doc.vivienda_habitual),
"is_young": bool(doc.comprador_joven),
"age_years": int(doc.edad_comprador) if doc.edad_comprador else None,
"seller_non_resident": bool(doc.vendedor_no_residente),
}
}
resp = requests.post(
"https://api.noraapp.es/v1/tax/calculate",
json=payload,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"Idempotency-Key": str(uuid.uuid4()),
},
timeout=10,
)
resp.raise_for_status()
data = resp.json()
result = data["result"]
doc.nora_itp_eur = result["taxes"].get("itp", 0)
doc.nora_total_taxes_eur = result["totals"]["impuestos"]
doc.nora_calculation_hash = result["meta"]["calculation_hash"]
Guarda tu API key en site_config.json como nora_api_key. Nunca la hardcodees en el script.
SDK JavaScript / Node.js
npm install @nora/sdk-js
import { NoraClient } from '@nora/sdk-js';
const nora = new NoraClient({ apiKey: process.env.NORA_API_KEY });
const { result } = await nora.tax.calculate({
input: {
region_code: 'ES-AN', transmission: 'used',
property_type: 'vivienda', price: 250_000,
is_habitual: true, is_young: true, age_years: 32
}
});
console.log(`ITP: ${result.taxes.itp} €`);
const { result: r2, pdf } = await nora.tax.calculate({
input: { region_code: 'ES-MD', transmission: 'used', property_type: 'vivienda', price: 350_000 },
output: { pdf: true, filename: 'informe-cliente.pdf' }
});
console.log('PDF URL:', pdf.persistent_download_url);
const validation = await nora.tax.validate({ input: { region_code: 'ES-AN', price: 250_000 } });
if (!validation.ok) {
console.log('Campos obligatorios:', validation.missing_fields.blocking);
}
import { ValidationError, RateLimitError, AuthError } from '@nora/sdk-js';
try {
const { result } = await nora.tax.calculate({ input: {} });
} catch (err) {
if (err instanceof ValidationError) console.error('Datos inválidos:', err.details);
if (err instanceof RateLimitError) console.error('Rate limit. Reintenta en unos segundos.');
if (err instanceof AuthError) console.error('API key inválida.');
}
TaxResult — Referencia de respuesta
result.taxes — Impuestos en €
Campo Cuándo Descripción
itpSiempre ITP — Impuesto de Transmisiones Patrimoniales
ajdSiempre AJD — Actos Jurídicos Documentados
ivaObra nueva IVA 10% (o 4% VPO especial)
igicCanarias IGIC 7%
ipsiCeuta / Melilla IPSI
retencion211seller_non_resident = true Retención 3% art. 211 LIRNR
result.totals — Totales en €
Campo Descripción
impuestosTotal de todos los impuestos
gastosTotal gastos tramitación (notaría + registro + gestoría + tasación)
downpaymentEntrada del comprador
coste_adquisicionImpuestos + gastos
fondos_totalesEntrada + impuestos + gastos (fondos propios totales)
interesesTotal intereses hipotecarios
coste_total_con_interesesCoste total incluyendo intereses
result.meta — Metadatos
Campo Descripción
engine_versionVersión del motor fiscal
calculation_hashHash único del cálculo. Guárdalo en tu CRM.
region_versionVersión de las reglas fiscales aplicadas
as_ofFecha de referencia del cálculo
Tabla de regiones
Código Comunidad / Territorio Impuesto Tipo
ES-ANAndalucía ITP ccaa
ES-ARAragón ITP ccaa
ES-ASAsturias ITP ccaa
ES-IBIlles Balears ITP ccaa
ES-CNCanarias IGIC (no IVA) ccaa
ES-CBCantabria ITP ccaa
ES-CMCastilla-La Mancha ITP ccaa
ES-CLCastilla y León ITP ccaa
ES-CTCataluña ITP ccaa
ES-VCComunitat Valenciana ITP ccaa
ES-EXExtremadura ITP ccaa
ES-GAGalicia ITP ccaa
ES-MDComunidad de Madrid ITP ccaa
ES-MCRegión de Murcia ITP ccaa
ES-NCNavarra ITP (foral) ccaa
ES-RILa Rioja ITP ccaa
ES-CECeuta IPSI special
ES-MLMelilla IPSI special
ES-BIBizkaia ITP (foral) foral
ES-SSGipuzkoa ITP (foral) foral
ES-VIAraba / Álava ITP (foral) foral