NORA API Docs
↓ OpenAPI spec Obtener API key
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

# Guarda tu API key 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, // 6% ITP Madrid "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...", // guarda esto en tu CRM "region_version": "2025-01", "as_of": "2026-03-28" } } }

4. Con 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-MD', transmission: 'used', property_type: 'vivienda', price: 350_000, is_habitual: true, is_young: true, age_years: 30 } }); console.log(`ITP: ${result.taxes.itp} €`); // → ITP: 21000 €
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

PrefijoEntornoUso
nora_live_ProducciónCálculos reales. Se cobra según plan.
nora_test_TestPara desarrollo. No genera factura.

Cómo obtener una clave

Escribe a api@noraapp.es con:

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.

ScopePermite
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?

# Node.js import { randomUUID } from 'crypto'; const idempotencyKey = randomUUID(); # Python import uuid idempotency_key = str(uuid.uuid4()) # PHP $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", // código máquina-legible "message": "input fiscal invalido", "request_id": "req_abc123", // incluye esto en reportes de bug "details": { // presente en errores de validación "errors": [ { "path": "region_code", "code": "ERR_REQUIRED", "message": "region_code es obligatorio" } ], "missing_fields": { "blocking": ["region_code"], "recommended": [], "conditional": [] } } } }

Códigos HTTP

CódigoSituació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

codeCausa
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
GET /health Público

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 } }
GET /v1/version Público

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 } }
GET /v1/regions regions:read

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" } ] }
POST /v1/tax/validate tax:validate

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." } ] }
POST /v1/tax/calculate tax:calculate Idempotency-Key requerido

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

Campo
Tipo
Descripción
input.region_code *
string
Código ISO de la comunidad autónoma. Ver tabla de regiones.
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)

Campo
Tipo
Descripción
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

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, // tipo reducido 3,5% por joven + VH "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" } } }
GET /v1/calculations/:hash tax:calculate

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": { /* input original */ }, "result": { /* TaxResult completo */ }, "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" } } }
POST /v1/pdf/render tax:calculate · pdf:render Idempotency-Key requerido

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

Campo
Tipo
Descripción
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": { /* TaxResult completo */ }, "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" // sin expiración, guarda esta } }
Usa persistent_download_url para guardar la URL en tu CRM: no expira y no requiere autenticación.
GET /v1/pdf/jobs/:jobId/file pdf:render

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
GET /v1/usage usage:read

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

TipoCuá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

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; // Guarda pdfUrl en tu CRM... } 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 CRMCampo NORATipoEjemplo
nora_region_codeinput.region_codeSelecciónES-AN
nora_transmissioninput.transmissionSelecciónused
nora_property_typeinput.property_typeSelecciónvivienda
nora_priceinput.priceNúmero250000
nora_is_habitualinput.is_habitualBooleanotrue
nora_is_younginput.is_youngBooleanofalse
nora_age_yearsinput.age_yearsNúmero32
nora_itp_eurresult.taxes.itpNúmero (salida)8750
nora_total_taxes_eurresult.totals.impuestosNúmero (salida)11600
nora_calculation_hashresult.meta.calculation_hashTexto (salida)a1b2c3...
nora_pdf_urlpdf.persistent_download_urlURL (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

// Crea todas las propiedades NORA en tu portal HubSpot automáticamente 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)

  1. Setup → Integrations → External Services.
  2. Importa el fichero openapi.yaml de NORA.
  3. Salesforce genera automáticamente las acciones invocables.
  4. Ú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 campoTipo
NORA: Comunidad AutónomaOpciones (ES-AN, ES-MD…)
NORA: Tipo transmisiónOpciones (used / new)
NORA: Tipo inmuebleOpciones
NORA: Total impuestos (€)Número
NORA: ITP/IVA (€)Número
NORA: PDF informe fiscalURL
NORA: Hash cálculoTexto

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 }); // Calcular impuestos 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} €`); // Con PDF 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); // Validar antes de calcular 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); } // Manejo de errores tipados 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.'); }

TaxInput — Referencia de campos

Obligatorios *

CampoTipoValores
region_codestringVer tabla de regiones
transmissionstring"used" · "new"
property_typestring"vivienda" · "local" · "suelo" · "otros"
pricenumberEuros (1 – 100.000.000)

Perfil del comprador

CampoTipoDescripción
is_habitualboolean¿Vivienda habitual?
is_youngboolean¿Comprador joven?
age_yearsnumber|nullEdad del comprador (18–120)
is_large_familyboolean¿Familia numerosa?
has_disc_33boolean¿Discapacidad ≥33%?
has_disc_65boolean¿Discapacidad ≥65%?
is_first_homeboolean¿Primera vivienda?
is_single_parent_familyboolean¿Familia monoparental?
seller_non_residentboolean¿Vendedor no residente? → retención 3% art.211
municipality_inestring|nullCódigo INE del municipio
vponull|"general"|"special"¿VPO?
island_codestring|nullIsla (Canarias: TF, GC, LP, LZ, FV, HI, GO)

Hipoteca (cuando hasMortgage = true)

CampoTipoDescripción
hasMortgageboolean¿Lleva hipoteca?
mortgageStatestring"yes" · "no" · "in_process"
appraisalnumberTasación en €
downpaymentnumberEntrada en €
loan_amountnumberCapital hipotecario en €
ltv_pctnumberLTV en % (0–100)
fin_termnumberPlazo en meses (0–600)
rate_typestring"fixed" · "variable" · "mixed"
fixed_tin_pctnumberTIN fijo en %
index_value_pctnumberEuribor / índice en %
spread_pctnumberDiferencial en %
opening_fee_pctnumberComisión apertura en %
linked_products_bonusnumberBonificación vinculación en pp

TaxResult — Referencia de respuesta

result.taxes — Impuestos en €

CampoCuándoDescripción
itpSiempreITP — Impuesto de Transmisiones Patrimoniales
ajdSiempreAJD — Actos Jurídicos Documentados
ivaObra nuevaIVA 10% (o 4% VPO especial)
igicCanariasIGIC 7%
ipsiCeuta / MelillaIPSI
retencion211seller_non_resident = trueRetención 3% art. 211 LIRNR

result.totals — Totales en €

CampoDescripció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

CampoDescripció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ódigoComunidad / TerritorioImpuestoTipo
ES-ANAndalucíaITPccaa
ES-ARAragónITPccaa
ES-ASAsturiasITPccaa
ES-IBIlles BalearsITPccaa
ES-CNCanariasIGIC (no IVA)ccaa
ES-CBCantabriaITPccaa
ES-CMCastilla-La ManchaITPccaa
ES-CLCastilla y LeónITPccaa
ES-CTCataluñaITPccaa
ES-VCComunitat ValencianaITPccaa
ES-EXExtremaduraITPccaa
ES-GAGaliciaITPccaa
ES-MDComunidad de MadridITPccaa
ES-MCRegión de MurciaITPccaa
ES-NCNavarraITP (foral)ccaa
ES-RILa RiojaITPccaa
ES-CECeutaIPSIspecial
ES-MLMelillaIPSIspecial
ES-BIBizkaiaITP (foral)foral
ES-SSGipuzkoaITP (foral)foral
ES-VIAraba / ÁlavaITP (foral)foral