This workflow corresponds to n8n.io template #15942 — we link there as the canonical source.
This workflow follows the Agent → OpenRouter Chat recipe pattern — see all workflows that pair these two integrations.
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 →
{
"id": "yHmIg0ykCCKnbNLB",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Analyze resume PDF for ATS fit and score using OpenRouter and webhook",
"tags": [],
"nodes": [
{
"id": "f4927711-bcbc-457e-8497-f291580e4a70",
"name": "Receive Resume PDF",
"type": "n8n-nodes-base.webhook",
"position": [
-32,
48
],
"parameters": {
"path": "resume-analyzer",
"options": {
"binaryPropertyName": "resume_file"
},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "b0703cb3-d3d5-4bd4-bc07-e8e954e14f7e",
"name": "Extract Text from PDF",
"type": "n8n-nodes-base.extractFromFile",
"position": [
288,
48
],
"parameters": {
"options": {},
"operation": "pdf",
"binaryPropertyName": "resume_file0"
},
"typeVersion": 1.1
},
{
"id": "75af61fa-52a7-4110-87f6-2510da96f9dc",
"name": "Analyze Resume with AI",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
544,
48
],
"parameters": {
"text": "=Analyze this resume for the role:\n{{ $('Receive Resume PDF').item.json.body.job_role || 'General' }}\n\nResume Text:\n{{ $json.text }}",
"options": {
"systemMessage": "You are an expert ATS resume analyzer.\n\nAnalyze the resume carefully and return ONLY valid JSON.\n\nFormat:\n{\n \"ats_score\": number,\n \"best_suited_role\": \"string\",\n \"missing_skills\": [\"skill1\", \"skill2\"],\n \"improvement_suggestions\": [\"suggestion1\", \"suggestion2\"],\n \"summary\": \"short summary\"\n}\n\nRules:\n- Return only JSON\n- No markdown\n- No explanations\n- ats_score must be integer"
},
"promptType": "define"
},
"typeVersion": 3.1
},
{
"id": "ddba6329-e401-4dcc-a7e2-40fc83a68b9b",
"name": "OpenRouter Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
544,
320
],
"parameters": {
"model": "openai/gpt-4o-mini",
"options": {}
},
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "d5f341b3-d266-432d-9475-981d4c438f02",
"name": "Parse & Validate AI Response",
"type": "n8n-nodes-base.code",
"position": [
960,
48
],
"parameters": {
"jsCode": "const rawInput = $input.item.json.output;\n\n// Strip markdown code fences (```json ... ```)\nconst cleaned = rawInput\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/```\\s*$/i, '')\n .trim();\n\ntry {\n const parsed = JSON.parse(cleaned);\n\n return [{\n json: {\n ats_score: typeof parsed.ats_score === 'number' \n ? parsed.ats_score \n : parseInt(parsed.ats_score) || null,\n best_suited_role: parsed.best_suited_role || \"\",\n missing_skills: Array.isArray(parsed.missing_skills) \n ? parsed.missing_skills \n : [],\n improvement_suggestions: Array.isArray(parsed.improvement_suggestions) \n ? parsed.improvement_suggestions \n : [],\n summary: parsed.summary || \"\"\n }\n }];\n\n} catch (e) {\n return [{\n json: {\n ats_score: null,\n best_suited_role: \"\",\n missing_skills: [],\n improvement_suggestions: [],\n summary: \"\",\n error: \"Failed to parse AI response\",\n raw: rawInput\n }\n }];\n}"
},
"typeVersion": 2
},
{
"id": "8346032f-1afe-4e22-b8eb-3addf21da773",
"name": "Return ATS Report",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1296,
48
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ $json }}"
},
"typeVersion": 1.5
},
{
"id": "99a36856-bd07-42b0-80ae-b2f84d286c47",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-64,
-80
],
"parameters": {
"height": 112,
"content": "Step 1 \u2013 Receive Resume\nWebhook accepts a multipart POST with a PDF file and optional job_role field."
},
"typeVersion": 1
},
{
"id": "5707e820-7f7d-4ee0-a281-333a06e522db",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
224,
-80
],
"parameters": {
"width": 208,
"height": 112,
"content": "Step 2 \u2013 Extract Text\nPulls plain text out of the uploaded PDF for AI processing."
},
"typeVersion": 1
},
{
"id": "9d1b3028-6c7a-4aec-8d72-301ecc7cdfae",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
912,
-96
],
"parameters": {
"height": 128,
"content": "Step 4 \u2013 Parse & Validate\nJavaScript cleans any markdown formatting from the AI output and safely \nparses the JSON, with error handling fallback."
},
"typeVersion": 1
},
{
"id": "379c86ed-01f3-4a62-b2c0-ac78d766416c",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
544,
-96
],
"parameters": {
"width": 224,
"height": 128,
"content": "Step 3 \u2013 AI Analysis\nOpenRouter GPT analyzes the resume against the target role and returns \nstructured feedback as JSON."
},
"typeVersion": 1
},
{
"id": "84f0da7e-1a78-47d9-a8d5-ed1dc0505b1c",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1248,
-80
],
"parameters": {
"height": 96,
"content": "Step 5 \u2013 Respond\nReturns the final structured ATS report to the caller."
},
"typeVersion": 1
},
{
"id": "0b257e86-293f-4008-b37b-2c766f49cf6e",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-848,
-400
],
"parameters": {
"width": 608,
"height": 336,
"content": "# \ud83d\udcc4 Resume ATS Analyzer\n\nSend a PDF resume to this workflow via webhook and get back an instant \nATS compatibility report powered by OpenRouter GPT.\n\nPOST to: /resume-analyzer\nFields: resume_file (PDF), job_role (optional text)\n\nReturns JSON:\n- ats_score (0\u2013100)\n- best_suited_role\n- missing_skills\n- improvement_suggestions\n- summary\n\nSetup: Add your OpenRouter API key to the OpenRouter Chat Model node credential."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "bc023a7c-bddb-4905-8a69-57a2048b295e",
"connections": {
"Receive Resume PDF": {
"main": [
[
{
"node": "Extract Text from PDF",
"type": "main",
"index": 0
}
]
]
},
"Extract Text from PDF": {
"main": [
[
{
"node": "Analyze Resume with AI",
"type": "main",
"index": 0
}
]
]
},
"OpenRouter Chat Model": {
"ai_languageModel": [
[
{
"node": "Analyze Resume with AI",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Analyze Resume with AI": {
"main": [
[
{
"node": "Parse & Validate AI Response",
"type": "main",
"index": 0
}
]
]
},
"Parse & Validate AI Response": {
"main": [
[
{
"node": "Return ATS Report",
"type": "main",
"index": 0
}
]
]
}
}
}
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.
openRouterApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow accepts a resume PDF via webhook, extracts the text, and uses OpenRouter (OpenAI model) to generate an ATS-style analysis and score for a specified job role, returning a clean JSON response to the requester. Receives a POST webhook request containing a resume PDF…
Source: https://n8n.io/workflows/15942/ — 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.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCalculator. Webhook trigger; 63 nodes.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCalculator. Webhook trigger; 63 nodes.
leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCode. Webhook trigger; 62 nodes.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCode. Webhook trigger; 62 nodes.