AutomationFlowsWeb Scraping › N8n Ue0 PDF OCR

N8n Ue0 PDF OCR

N8N-Ue0-Pdf-Ocr. Uses httpRequest. Webhook trigger; 11 nodes.

Webhook trigger★★★★☆ complexity11 nodesHTTP Request
Web Scraping Trigger: Webhook Nodes: 11 Complexity: ★★★★☆ Added:

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 →

Download .json
{
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "ue0-antragspdf",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "dd676723-4a94-4478-9ba6-3cde6e8c982f",
      "name": "Webhook Trigger (Supabase NOTIFY)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [
        208,
        304
      ]
    },
    {
      "parameters": {
        "url": "=http://kong:8000/storage/v1/object/antragseingang-pdf/{{ $json.body.storage_path }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $env.SUPABASE_SERVICE_ROLE_KEY }}"
            },
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.SUPABASE_SERVICE_ROLE_KEY }}"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "id": "1226131a-fad0-430b-a0b3-becdbf7a4933",
      "name": "PDF aus Storage holen",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        416,
        304
      ]
    },
    {
      "parameters": {
        "jsCode": "// Holt das Binary-PDF als Buffer und gibt es als reinen Base64-String weiter.\n// Funktioniert mit n8n's binary storage modes 'default' (in-memory base64),\n// 'filesystem' (file-ref), und 's3' (URL).\nconst buffer = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst b64 = buffer.toString('base64');\nconst bin = $input.first().binary.data;\nreturn [{ json: { pdf_base64: b64, mime_type: bin.mimeType || 'application/pdf' } }];"
      },
      "id": "encode-1",
      "name": "PDF \u2192 Base64",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        560,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "={{ $env.ANTHROPIC_API_KEY }}"
            },
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"claude-sonnet-4-5\",\n  \"max_tokens\": 4096,\n  \"messages\": [\n    {\n      \"role\": \"user\",\n      \"content\": [\n        {\n          \"type\": \"document\",\n          \"source\": {\n            \"type\": \"base64\",\n            \"media_type\": \"application/pdf\",\n            \"data\": \"{{ $json.pdf_base64 }}\"\n          }\n        },\n        {\n          \"type\": \"text\",\n          \"text\": \"Du bekommst ein ausgef\u00fclltes Antragsformular der Stadt W\u00fcrzburg (Altenhilfeplan Nr. 2, Antrag auf Zuschuss f\u00fcr Altentagesst\u00e4tten). Das Formular kann maschinell ausgef\u00fcllt sein ODER handschriftlich. Extrahiere ALLE ausgef\u00fcllten Felder als striktes JSON nach folgendem Schema. Wenn ein Feld leer/nicht lesbar ist, setze null. Bei Betr\u00e4gen: nur die Zahl als float (z.B. 1250.50), keine W\u00e4hrung, kein Tausender-Punkt. IBAN/BIC trimmen, ohne Leerzeichen. Antwort NUR als JSON-Objekt, keine Erkl\u00e4rung davor oder danach, kein Code-Fence. WICHTIG zur Pflicht: die Verwaltungspraxis der Stadt W\u00fcrzburg verlangt Telefon, Bankverbindung (Bankname) und BIC IMMER \u2014 auch wenn das PDF kein Sternchen zeigt. Wenn du die Felder im PDF nicht findest, suche sorgf\u00e4ltig (h\u00e4ufig handschriftlich am Rand oder im Briefkopf).\\n\\nSchema:\\n{\\n  \\\"haushaltsjahr\\\": integer | null,\\n  \\\"name\\\": string | null,                    // Name der Einrichtung\\n  \\\"traeger\\\": string | null,                 // Tr\u00e4gerverein/Organisation\\n  \\\"strasse\\\": string | null,\\n  \\\"hausnummer\\\": string | null,\\n  \\\"plz\\\": string | null,                      // 5-stellig\\n  \\\"ort\\\": string | null,\\n  \\\"bankverbindung\\\": string | null,           // Bankname \u2014 Verwaltungspraxis: Pflicht\\n  \\\"iban\\\": string | null,\\n  \\\"bic\\\": string | null,                      // Verwaltungspraxis: Pflicht (auch DE-IBAN)\\n  \\\"ansprechpartner\\\": string | null,\\n  \\\"telefon\\\": string | null,                  // Verwaltungspraxis: Pflicht (f\u00fcr R\u00fcckfragen)\\n  \\\"email\\\": string | null,\\n  \\\"betriebskosten_vorjahr_euro\\\": float | null,\\n  \\\"personalkosten_vorjahr_euro\\\": float | null,\\n  \\\"monatliche_miete_euro\\\": float | null,     // PDF fragt MONATLICH ab; nicht selbst \u00d7 12 rechnen\\n  \\\"raeume_vorhanden\\\": \\\"ja\\\" | \\\"nein\\\" | null,\\n  \\\"raeume_unentgeltlich\\\": \\\"ja\\\" | \\\"nein\\\" | null,\\n  \\\"antragsdatum\\\": string | null              // ISO YYYY-MM-DD\\n}\"\n        }\n      ]\n    }\n  ]\n}",
        "options": {
          "timeout": 120000
        }
      },
      "id": "e6ccef34-12b8-4ccb-acbe-d2a7b6c2772a",
      "name": "Claude Vision \u2014 OCR + Extraktion",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        688,
        304
      ]
    },
    {
      "parameters": {
        "jsCode": "// Final-Sweep 2026-05-24: OCR-Daten werden NUR in antrag_einreichung.\n// extrahiert_jsonb geparkt. Apl2.antraege erst beim finalen B\u00fcrger-Submit\n// aus UE1 bef\u00fcllt \u2014 saubere Vorlage-vs-Antrag-Trennung.\nconst input = $input.first().json;\nconst content = input.content?.[0]?.text;\nif (!content) throw new Error('Claude lieferte keinen Text-Content: ' + JSON.stringify(input));\nlet extr;\ntry {\n  const cleaned = content.trim().replace(/^```json\\n?/, '').replace(/\\n?```$/, '');\n  extr = JSON.parse(cleaned);\n} catch (e) {\n  throw new Error('Claude-Antwort ist kein JSON: ' + content.slice(0, 500));\n}\n// Defaults f\u00fcr UE1-Prefill: Status + Sprache\nextr.submitted_language = 'de';\nif (!extr.antragsdatum) extr.antragsdatum = new Date().toISOString().slice(0, 10);\n// foerderbereich + geforderte_foerdersumme bleiben null \u2014 Sachbearbeitung beziffert\n// Bemessungsfelder bleiben null \u2014 PDF fragt sie nicht ab\n// oeffnungszeiten kommen aus dem Wochenplan-Branch (separater Node merged sie rein)\nreturn [{\n  json: {\n    extrahiert: extr,\n    einreichung_id: $('Webhook Trigger (Supabase NOTIFY)').first().json.body.einreichung_id\n  }\n}];"
      },
      "id": "aac56805-0923-4e36-b7dd-ab7388620f35",
      "name": "Parse JSON + Defaults",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        944,
        304
      ]
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=http://kong:8000/rest/v1/antrag_einreichung?id=eq.{{ $('Parse JSON + Defaults').first().json.einreichung_id }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $env.SUPABASE_SERVICE_ROLE_KEY }}"
            },
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.SUPABASE_SERVICE_ROLE_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Accept-Profile",
              "value": "apl2"
            },
            {
              "name": "Content-Profile",
              "value": "apl2"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"status\": \"fertig\",\n  \"extrahiert_jsonb\": {{ JSON.stringify($json.extrahiert) }},\n  \"verarbeitet_am\": \"{{ new Date().toISOString() }}\"\n}",
        "options": {}
      },
      "id": "26e94168-7d4c-4b44-95cb-82592bb064d8",
      "name": "UPDATE antrag_einreichung status=fertig",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2080,
        208
      ]
    },
    {
      "id": "if-anlage-exists",
      "name": "IF Anlage 1 vorhanden?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.1,
      "position": [
        928,
        504
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "cond-1",
              "leftValue": "={{ $('Webhook Trigger (Supabase NOTIFY)').first().json.body.anlage_1_storage_path }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "anlage-1-storage",
      "name": "Anlage-1 aus Storage holen",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1148,
        444
      ],
      "parameters": {
        "url": "=http://kong:8000/storage/v1/object/antragseingang-pdf/{{ $('Webhook Trigger (Supabase NOTIFY)').first().json.body.anlage_1_storage_path }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $env.SUPABASE_SERVICE_ROLE_KEY }}"
            },
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.SUPABASE_SERVICE_ROLE_KEY }}"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      }
    },
    {
      "id": "anlage-1-b64",
      "name": "Anlage-1 \u2192 Base64",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1368,
        444
      ],
      "parameters": {
        "jsCode": "// Gleich wie Hauptantrag-B64, aber f\u00fcr den Anlage-1-PDF-Buffer.\nconst buffer = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst b64 = buffer.toString('base64');\nconst bin = $input.first().binary.data;\nreturn [{ json: { pdf_base64: b64, mime_type: bin.mimeType || 'application/pdf' } }];"
      }
    },
    {
      "id": "anlage-1-claude",
      "name": "Claude Vision \u2014 Wochenplan-OCR",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1588,
        444
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "={{ $env.ANTHROPIC_API_KEY }}"
            },
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"claude-sonnet-4-5\",\n  \"max_tokens\": 2048,\n  \"messages\": [\n    {\n      \"role\": \"user\",\n      \"content\": [\n        {\n          \"type\": \"document\",\n          \"source\": {\n            \"type\": \"base64\",\n            \"media_type\": \"application/pdf\",\n            \"data\": \"{{ $json.pdf_base64 }}\"\n          }\n        },\n        {\n          \"type\": \"text\",\n          \"text\": \"Du bekommst ein ausgef\u00fclltes 'Anlage 1' zum Antrag Altenhilfeplan Nr. 2 der Stadt W\u00fcrzburg. Es ist eine Wochenplan-Tabelle mit den Spalten 'Wochentag', '\u00d6ffnungszeiten' und 'Angebot'. Die Eintr\u00e4ge k\u00f6nnen maschinell oder handschriftlich sein. Extrahiere ALLE ausgef\u00fcllten Zeilen als JSON-Array.\\n\\nFormat pro Zeile: {\\\"wochentag\\\": \\\"mo|di|mi|do|fr|sa|so\\\", \\\"oeffnungszeit\\\": \\\"<Original-Text z.B. '09:30 \u2013 11:30'>\\\", \\\"angebot\\\": \\\"<Original-Text>\\\"}\\n\\nWICHTIG:\\n- 'mo' f\u00fcr Montag, 'di' f\u00fcr Dienstag, 'mi' f\u00fcr Mittwoch, 'do' f\u00fcr Donnerstag, 'fr' f\u00fcr Freitag, 'sa' f\u00fcr Samstag, 'so' f\u00fcr Sonntag.\\n- Wenn eine Zelle (\u00d6ffnungszeit ODER Angebot) leer ist, lass den Eintrag WEG (nicht 'null' ausgeben, sondern komplett \u00fcberspringen).\\n- Bei handschriftlichen Eintr\u00e4gen: erkenne so gut wie m\u00f6glich. Bei totaler Unlesbarkeit kann das Feld leer bleiben (und somit die Zeile wegfallen).\\n- Erfinde NICHTS. Wenn das PDF keine Wochenplan-Tabelle enth\u00e4lt, gib [] zur\u00fcck.\\n\\nAntworte mit REINEM JSON (Array). Kein Flie\u00dftext, keine Markdown-Code-Fence.\"\n        }\n      ]\n    }\n  ]\n}",
        "options": {
          "timeout": 120000
        }
      }
    },
    {
      "id": "parse-wochenplan-merge",
      "name": "Parse Wochenplan + Merge in extrahiert",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1808,
        444
      ],
      "parameters": {
        "jsCode": "// Parsed Claude-Antwort (JSON-Array von Wochenplan-Eintr\u00e4gen) und merged\n// sie in extrahiert.oeffnungszeiten. Die Hauptantrag-Daten kommen vom\n// Parse-JSON-Defaults-Node (\u00fcber $()-Referenz), nicht vom direkten Input.\nconst claude = $input.first().json;\nconst content = claude.content?.[0]?.text;\nif (!content) throw new Error('Claude(Wochenplan) lieferte keinen Text: ' + JSON.stringify(claude));\nlet oeffnungszeiten;\ntry {\n  const cleaned = content.trim().replace(/^```json\\n?/, '').replace(/\\n?```$/, '');\n  oeffnungszeiten = JSON.parse(cleaned);\n  if (!Array.isArray(oeffnungszeiten)) {\n    throw new Error('Claude lieferte kein Array, sondern: ' + typeof oeffnungszeiten);\n  }\n} catch (e) {\n  // Soft-Fail: leerer Wochenplan, Hauptantrag-Daten gehen trotzdem durch.\n  console.error('Wochenplan-Parse fehlgeschlagen: ' + e.message + ' \u2014 Content: ' + content.slice(0, 300));\n  oeffnungszeiten = [];\n}\nconst parseNode = $('Parse JSON + Defaults').first().json;\nconst extrahiert = { ...parseNode.extrahiert, oeffnungszeiten };\nreturn [{\n  json: {\n    extrahiert,\n    einreichung_id: parseNode.einreichung_id,\n  }\n}];"
      }
    }
  ],
  "connections": {
    "Webhook Trigger (Supabase NOTIFY)": {
      "main": [
        [
          {
            "node": "PDF aus Storage holen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDF aus Storage holen": {
      "main": [
        [
          {
            "node": "PDF \u2192 Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDF \u2192 Base64": {
      "main": [
        [
          {
            "node": "Claude Vision \u2014 OCR + Extraktion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Vision \u2014 OCR + Extraktion": {
      "main": [
        [
          {
            "node": "Parse JSON + Defaults",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse JSON + Defaults": {
      "main": [
        [
          {
            "node": "IF Anlage 1 vorhanden?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Anlage 1 vorhanden?": {
      "main": [
        [
          {
            "node": "Anlage-1 aus Storage holen",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "UPDATE antrag_einreichung status=fertig",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anlage-1 aus Storage holen": {
      "main": [
        [
          {
            "node": "Anlage-1 \u2192 Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anlage-1 \u2192 Base64": {
      "main": [
        [
          {
            "node": "Claude Vision \u2014 Wochenplan-OCR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Vision \u2014 Wochenplan-OCR": {
      "main": [
        [
          {
            "node": "Parse Wochenplan + Merge in extrahiert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Wochenplan + Merge in extrahiert": {
      "main": [
        [
          {
            "node": "UPDATE antrag_einreichung status=fertig",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

N8N-Ue0-Pdf-Ocr. Uses httpRequest. Webhook trigger; 11 nodes.

Source: https://github.com/swrobuts/DigitalisierungVerwaltung/blob/4d3186f9418632b60dd11d5a7c1bc889b09a506e/supabase/webhooks/n8n-ue0-pdf-ocr.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

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

n8n, Execute Workflow Trigger, HTTP Request +1
Web Scraping

This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .

HTTP Request, Ssh
Web Scraping

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

HTTP Request
Web Scraping

Sign PDF documents with legally-compliant digital signatures using X.509 certificates. Supports multiple PAdES signature levels (B, T, LT, LTA) with optional visible stamps.

Execute Command, HTTP Request, Read Write File +1
Web Scraping

📡 This workflow serves as the central Alpha Vantage API fetcher for Tesla trading indicators, delivering cleaned 20-point JSON outputs for three timeframes: , , and . It is required by the following a

HTTP Request