The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Extrator de Im\u00f3veis \u2014 PDF para API",
"nodes": [
{
"id": "a1b2c3d4-0001",
"name": "Webhook \u2014 Receber PDF",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
300
],
"parameters": {
"httpMethod": "POST",
"path": "extrator-imoveis",
"responseMode": "responseNode",
"options": {
"binaryData": true,
"rawBody": false
}
}
},
{
"id": "a1b2c3d4-0002",
"name": "Extrair Texto do PDF",
"type": "n8n-nodes-base.extractFromFile",
"typeVersion": 1,
"position": [
460,
300
],
"parameters": {
"operation": "pdf",
"binaryPropertyName": "data",
"options": {}
}
},
{
"id": "a1b2c3d4-0003",
"name": "Preparar Prompt para OpenAI",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
680,
300
],
"parameters": {
"jsCode": "const text = $input.first().json.text || '';\n\n// Trunca para n\u00e3o ultrapassar o limite de tokens do modelo\nconst MAX_CHARS = 8000;\nconst truncated = text.slice(0, MAX_CHARS);\n\nif (!truncated.trim()) {\n throw new Error('PDF sem texto extra\u00edvel. Pode ser um PDF escaneado \u2014 use o fluxo de Vision API para esse caso.');\n}\n\nreturn [{\n json: {\n model: 'gpt-4o',\n response_format: { type: 'json_object' },\n temperature: 0.1,\n messages: [\n {\n role: 'system',\n content: 'Voc\u00ea \u00e9 um extrator especializado em documentos imobili\u00e1rios brasileiros. Retorne APENAS JSON v\u00e1lido e puro, sem markdown, sem texto adicional.'\n },\n {\n role: 'user',\n content: 'Extraia os seguintes dados deste documento imobili\u00e1rio:\\n- titulo: string descritiva do im\u00f3vel (ex: \"Apartamento 3 quartos em Copacabana\")\\n- valor: n\u00famero inteiro em reais SEM pontos ou v\u00edrgulas (ex: 850000)\\n- endereco: endere\u00e7o completo\\n- bairro: nome do bairro\\n- cidade: nome da cidade\\n- quartos: n\u00famero inteiro ou null\\n- suites: n\u00famero inteiro ou null\\n- vagas: n\u00famero inteiro ou null\\n- area_m2: n\u00famero decimal da \u00e1rea \u00fatil ou null\\n- iptu: valor anual do IPTU em reais como n\u00famero inteiro ou null\\n\\nSe o formato do documento for diferente do esperado, identifique os campos pelo contexto sem\u00e2ntico.\\nRetorne APENAS JSON com essas chaves exatas, sem campos extras.\\n\\nDocumento:\\n' + truncated\n }\n ]\n }\n}];"
}
},
{
"id": "a1b2c3d4-0004",
"name": "OpenAI \u2014 Extrair Dados Estruturados",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
900,
300
],
"parameters": {
"method": "POST",
"url": "https://api.openai.com/v1/chat/completions",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json) }}",
"options": {
"retry": {
"enabled": true,
"maxTries": 2,
"waitBetweenTries": 2000
},
"timeout": 30000
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"id": "a1b2c3d4-0005",
"name": "Mapear para IngestPropertyPayload",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1120,
300
],
"parameters": {
"jsCode": "// Extrai o conte\u00fado da resposta do OpenAI\nconst choice = $input.first().json.choices?.[0];\nconst content = choice?.message?.content || '{}';\n\nlet extracted;\ntry {\n extracted = JSON.parse(content);\n} catch (e) {\n throw new Error('OpenAI retornou JSON inv\u00e1lido: ' + content.slice(0, 200));\n}\n\n// Gera slug \u00fanico a partir do t\u00edtulo\nfunction toSlug(str) {\n return (str || 'imovel')\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^a-z0-9\\s-]/g, '')\n .trim()\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .slice(0, 70) + '-' + Date.now();\n}\n\n// Normaliza valor monet\u00e1rio (ex: \"R$ 1.200.000\" \u2192 1200000)\nfunction normalizePrice(val) {\n if (val === null || val === undefined) return 0;\n if (typeof val === 'number') return Math.round(val);\n const clean = String(val)\n .replace(/R\\$\\s*/gi, '')\n .replace(/\\./g, '')\n .replace(',', '.')\n .trim();\n return Math.round(parseFloat(clean)) || 0;\n}\n\n// Normaliza n\u00famero inteiro\nfunction toInt(val) {\n if (val === null || val === undefined || val === '') return undefined;\n const n = parseInt(String(val), 10);\n return isNaN(n) ? undefined : n;\n}\n\n// Normaliza decimal\nfunction toFloat(val) {\n if (val === null || val === undefined || val === '') return undefined;\n const n = parseFloat(String(val).replace(',', '.'));\n return isNaN(n) ? undefined : n;\n}\n\nconst title = (extracted.titulo || extracted.title || 'Im\u00f3vel').trim();\n\nconst payload = {\n slug: toSlug(title),\n title,\n price: normalizePrice(extracted.valor ?? extracted.value ?? extracted.price),\n address: extracted.endereco || extracted.address || null,\n neighborhood: extracted.bairro || extracted.neighborhood || null,\n city: extracted.cidade || extracted.city || null,\n status: 'disponivel',\n visibility: 'publico',\n features: {\n quartos: toInt(extracted.quartos),\n suites: toInt(extracted.suites),\n vagas: toInt(extracted.vagas),\n area_m2: toFloat(extracted.area_m2),\n iptu: toInt(extracted.iptu),\n },\n tags: [],\n};\n\n// Remove chaves undefined dentro de features\nObject.keys(payload.features).forEach(k => {\n if (payload.features[k] === undefined) delete payload.features[k];\n});\n\nreturn [{ json: payload }];"
}
},
{
"id": "a1b2c3d4-0006",
"name": "Validar Campos Obrigat\u00f3rios",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1340,
300
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond_title",
"leftValue": "={{ $json.title }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty"
}
},
{
"id": "cond_price",
"leftValue": "={{ $json.price }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
}
}
},
{
"id": "a1b2c3d4-0007",
"name": "Ingest API \u2014 Salvar Im\u00f3vel",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1560,
200
],
"parameters": {
"method": "POST",
"url": "={{ $vars.IMOB_API_URL }}/api/properties/ingest",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $vars.API_INGEST_TOKEN }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json) }}",
"options": {
"retry": {
"enabled": true,
"maxTries": 3,
"waitBetweenTries": 3000
},
"timeout": 15000
}
}
},
{
"id": "a1b2c3d4-0008",
"name": "Checar Resultado da Ingest\u00e3o",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1780,
200
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": false
},
"conditions": [
{
"id": "cond_errors",
"leftValue": "={{ $json.errors }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "equals"
}
}
],
"combinator": "and"
}
}
},
{
"id": "a1b2c3d4-0009",
"name": "Resposta \u2014 Sucesso",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
2000,
160
],
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ ok: true, action: $json.results?.[0]?.action, slug: $json.results?.[0]?.slug, processed: $json.processed }) }}",
"options": {
"responseCode": 200
}
}
},
{
"id": "a1b2c3d4-0010",
"name": "Resposta \u2014 Erro na Ingest\u00e3o",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
2000,
280
],
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ ok: false, error: 'Falha ao salvar no sistema', details: $json.results }) }}",
"options": {
"responseCode": 500
}
}
},
{
"id": "a1b2c3d4-0011",
"name": "Resposta \u2014 Dados Insuficientes",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1560,
420
],
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ ok: false, error: 'N\u00e3o foi poss\u00edvel extrair t\u00edtulo ou valor do documento', extracted: $json }) }}",
"options": {
"responseCode": 422
}
}
}
],
"connections": {
"Webhook \u2014 Receber PDF": {
"main": [
[
{
"node": "Extrair Texto do PDF",
"type": "main",
"index": 0
}
]
]
},
"Extrair Texto do PDF": {
"main": [
[
{
"node": "Preparar Prompt para OpenAI",
"type": "main",
"index": 0
}
]
]
},
"Preparar Prompt para OpenAI": {
"main": [
[
{
"node": "OpenAI \u2014 Extrair Dados Estruturados",
"type": "main",
"index": 0
}
]
]
},
"OpenAI \u2014 Extrair Dados Estruturados": {
"main": [
[
{
"node": "Mapear para IngestPropertyPayload",
"type": "main",
"index": 0
}
]
]
},
"Mapear para IngestPropertyPayload": {
"main": [
[
{
"node": "Validar Campos Obrigat\u00f3rios",
"type": "main",
"index": 0
}
]
]
},
"Validar Campos Obrigat\u00f3rios": {
"main": [
[
{
"node": "Ingest API \u2014 Salvar Im\u00f3vel",
"type": "main",
"index": 0
}
],
[
{
"node": "Resposta \u2014 Dados Insuficientes",
"type": "main",
"index": 0
}
]
]
},
"Ingest API \u2014 Salvar Im\u00f3vel": {
"main": [
[
{
"node": "Checar Resultado da Ingest\u00e3o",
"type": "main",
"index": 0
}
]
]
},
"Checar Resultado da Ingest\u00e3o": {
"main": [
[
{
"node": "Resposta \u2014 Sucesso",
"type": "main",
"index": 0
}
],
[
{
"node": "Resposta \u2014 Erro na Ingest\u00e3o",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true,
"callerPolicy": "workflowsFromSameOwner",
"errorWorkflow": ""
},
"staticData": null,
"tags": [
"im\u00f3veis",
"pdf",
"ingest"
],
"active": false
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
httpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Extrator de Imóveis — PDF para API. Uses httpRequest. Webhook trigger; 11 nodes.
Source: https://github.com/uilliamrd/Imob/blob/0ce742c2a39a4bd34cc4b782517f177ad55153c9/extrator_imoveis.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di
This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .
eek-Go v2 (Batch-Then-Review). Uses httpRequest. Webhook trigger; 75 nodes.
This workflow receives webhook requests from a content calendar and uses the X API v2 to publish text posts, threads, image/video posts, and polls, as well as delete existing posts and run a credentia
This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c