This workflow follows the Agent → HTTP Request 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 →
{
"name": "Recup_CV",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "cv-extraction",
"responseMode": "responseNode",
"options": {}
},
"id": "f2c11802-df82-49be-a076-b4f0b88fcfd6",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
-5740,
-420
],
"notesInFlow": false
},
{
"parameters": {
"jsCode": "const file = $binary.data;\nconst ext = file.fileExtension?.toLowerCase();\nconst mime = file.mimeType?.toLowerCase();\n\nconst allowedMime = [\n 'application/pdf',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'image/png',\n 'image/jpeg'\n];\nconst allowedExt = ['pdf', 'doc', 'docx', 'png', 'jpg','jpeg'];\n\nif (!allowedMime.includes(mime) || !allowedExt.includes(ext)) {\n throw new Error(`Invalid file type: ${mime} / .${ext}`);\n}\n\nreturn items;\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-4320,
-420
],
"id": "dca594ca-fc73-4c0b-98ef-828674541874",
"name": "Code"
},
{
"parameters": {
"url": "={{ $json.body.documentUrl }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-5520,
-420
],
"id": "158eb555-5f1a-4d1a-be2e-6a51e774718c",
"name": "Recuperation Supabase"
},
{
"parameters": {
"content": "## V\u00e9rification Mime Type\n",
"height": 300,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-4400,
-560
],
"id": "3355e324-a61b-4658-a0d0-283dd292fd2f",
"name": "Sticky Note6"
},
{
"parameters": {
"content": "## Verification de la qualit\u00e9 du document \nTrouver une grille d'evaluation de la qualit\u00e9 du fichier",
"height": 300,
"width": 860,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-5300,
-560
],
"typeVersion": 1,
"id": "37c12d46-ea6c-41c8-adb3-d058655979bb",
"name": "Sticky Note4"
},
{
"parameters": {
"jsCode": "// R\u00e9cup\u00e9ration des donn\u00e9es binaires du fichier depuis l'item\nconst base64 = items[0].binary.data.data; // contenu binaire du fichier (Buffer/base64)\nconst fichier = Buffer.from(base64, 'base64'); // vrai buffer\nconst nomFichier = items[0].binary.data.fileName; // nom original du fichier (ex: \"CV_JaneDoe.pdf\")\nconst tailleOctets = fichier.length; // taille en octets\nconst tailleKo = Math.round(tailleOctets / 1024);\n\n// Extension du fichier en minuscules (ex: \"pdf\" ou \"docx\")\nlet extension = \"inconnue\";\nif (nomFichier && nomFichier.includes('.')) {\n extension = nomFichier.split('.').pop().toLowerCase();\n}\n\n// \u00c9valuation du format et compatibilit\u00e9 basique\nlet scoreFormat = 0, noteCompat = \"\", scoreCompat = 0;\nif (extension === \"pdf\") {\n scoreFormat = 1; // Par exemple: 1 = format attendu\n noteCompat = \"Bonne compatibilit\u00e9 (PDF reconnu universellement)\";\n scoreCompat = 1; // PDF = plein score pour compatibilit\u00e9\n} else if (extension === \"docx\") {\n scoreFormat = 1; // Format accept\u00e9 (docx)\n noteCompat = \"Compatibilit\u00e9 correcte (format Word standard)\";\n scoreCompat = 0.8; // l\u00e9g\u00e8rement moindre que PDF en score, par ex.\n} else {\n scoreFormat = 0.5; // Autres formats moins souhaitables\n noteCompat = \"Compatibilit\u00e9 incertaine (format moins standard)\";\n scoreCompat = 0.5;\n}\n\n// \u00c9valuation du nom de fichier (s'il est explicite)\n// On consid\u00e8re le nom g\u00e9n\u00e9rique s'il contient \"Document\" sans personnalisation, par ex.\nlet scoreNom = 1;\nlet remarqueNom = \"Nom de fichier correct\";\nif (nomFichier.match(/document\\d*\\.?[^.]*$/i) || nomFichier.toLowerCase().startsWith(\"document\")) {\n scoreNom = 0;\n remarqueNom = \"Nom de fichier g\u00e9n\u00e9rique (ex: 'Document1' d\u00e9tect\u00e9)\";\n}\n\n// Ajout des informations et scores dans l'item JSON pour usage ult\u00e9rieur\nitems[0].json = {\n nomFichier: nomFichier,\n extension: extension,\n tailleKo: tailleKo,\n score_poids: 1, // on peut donner 1 (ou une note sur 5) si le fichier est d'un poids raisonnable\n score_format: scoreFormat,\n note_compatibilite: noteCompat,\n score_compatibilite: scoreCompat,\n score_nomFichier: scoreNom,\n remarque_nomFichier: remarqueNom\n};\nreturn items;\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-5240,
-420
],
"id": "1b070705-33ac-432c-8e2d-327f311e3c0f",
"name": "informations du fichier"
},
{
"parameters": {
"jsCode": "// Tentative 1 : r\u00e9cup\u00e9ration directe de l'extension si pr\u00e9sente\nlet extension = items[0].json.extension?.toLowerCase();\n\n// Tentative 2 : r\u00e9cup\u00e9ration depuis le nom de fichier\nif (!extension && items[0].binary?.data?.fileName) {\n const fileName = items[0].binary.data.fileName;\n extension = fileName.split('.').pop().toLowerCase();\n}\n\n// Lecture base64 du fichier\nconst base64Data = items[0].binary.data.data;\nlet integriteOK = false;\nlet remarqueIntegrite = \"\";\n\ntry {\n const buffer = Buffer.from(base64Data, 'base64');\n\n if (extension === \"pdf\") {\n const dataStr = buffer.toString(\"latin1\");\n const startOK = dataStr.startsWith(\"%PDF-\");\n const endOK = dataStr.includes(\"%%EOF\");\n\n if (startOK && endOK) {\n integriteOK = true;\n remarqueIntegrite = \"Fichier PDF valide (structure de base OK)\";\n } else {\n remarqueIntegrite = `PDF incomplet : ${!startOK ? \"d\u00e9but incorrect\" : \"\"}${!startOK && !endOK ? \" et \" : \"\"}${!endOK ? \"fin manquante (%%EOF)\" : \"\"}`;\n }\n\n } else if (extension === \"docx\") {\n const signature = buffer.toString('hex', 0, 2); // 2 premiers octets\n if (signature === \"504b\") {\n integriteOK = true;\n remarqueIntegrite = \"Fichier DOCX valide (signature ZIP d\u00e9tect\u00e9e)\";\n } else {\n remarqueIntegrite = \"DOCX invalide ou corrompu (signature ZIP absente)\";\n }\n\n } else {\n integriteOK = true;\n remarqueIntegrite = \"Format non reconnu\";\n }\n\n} catch (err) {\n integriteOK = false;\n remarqueIntegrite = `Erreur lors de l\u2019analyse : ${err.message}`;\n}\n\n// Ajout des champs en sortie\nitems[0].json.score_integrite = integriteOK ? 1 : 0;\nitems[0].json.remarque_integrite = remarqueIntegrite;\nitems[0].json.extension_detectee = extension;\n\nreturn items;\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-5000,
-420
],
"id": "6ee1f58f-6e7e-40bc-88e4-60acf8ae31f8",
"name": "int\u00e9grit\u00e9 du fichier"
},
{
"parameters": {
"jsCode": "const data = items[0].json;\nlet scoreGlobal = 0;\nlet totalCriteres = 0;\nconst lignes = [];\n\nfunction ajouteCritere(nom, score, remarque) {\n scoreGlobal += score;\n totalCriteres += 1;\n lignes.push(`- **${nom}** : ${remarque} \u2013 *Score: ${score}*`);\n}\najouteCritere(\"Compatibilit\u00e9 du format\", data.score_compatibilite, data.note_compatibilite);\najouteCritere(\"Score brut du format\", data.score_format, \"Format reconnu = PDF/PNG, moins bon = autres\");\najouteCritere(\"Poids du fichier (\" + data.tailleKo + \" Ko)\", data.score_poids, data.tailleKo + \" Ko (taille du fichier)\");\n\najouteCritere(\"Nom du fichier\", data.score_nomFichier, data.remarque_nomFichier);\najouteCritere(\"Int\u00e9grit\u00e9 du fichier\", data.score_integrite, data.remarque_integrite);\najouteCritere(\"Accessibilit\u00e9 du texte\", data.score_accessibilite, data.remarque_accessibilite);\n\nconst scorePourcent = Math.round((scoreGlobal / totalCriteres) * 100);\nlignes.push(`\\n<!--SCORE_GLOBAL--> **Score global** : ${scoreGlobal}/${totalCriteres} (${scorePourcent}%)`);\n\nitems[0].json.rapport = lignes.join(\"\\n\");\n\n// \u2705 Champ structur\u00e9 pour usage programmatique\nitems[0].json.score_global = {\n valeur: scoreGlobal,\n total: totalCriteres,\n pourcentage: scorePourcent\n};\n\nreturn items;\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-4600,
-420
],
"id": "2aa9764b-b55a-4930-b053-20852b1f4028",
"name": "Evaluation"
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.text}}",
"options": {
"systemMessage": "Tu es un assistant expert en structuration de donn\u00e9es issues de CV.\n\nTu dois remplir un JSON brut strictement conforme au format d\u00e9fini en dessous \u00e0 partir des donn\u00e9es fournis extraite d'un CV. \nLes donn\u00e9es ne sont pas forc\u00e9ment dans le bon ordre, tu dois trouver quelles informations appartiennent \u00e0 qui.\nTu dois faire en sorte que le plus de donn\u00e9es soient extraites et ins\u00e9r\u00e9es dans le JSON au bon endroit.\n\nCe JSON sera exploit\u00e9 par une application front via n8n. Aucun commentaire, texte, ou explication ne doit pr\u00e9c\u00e9der ou suivre l'objet JSON.\n\n\ud83c\udfaf R\u00c8GLES OBLIGATOIRES \u00c0 SUIVRE :\n\nStructure exacte \u00e0 respecter (hi\u00e9rarchie, noms, types, champs obligatoires ou nullables).\n\nEfforce toi de trouver des \u00e9l\u00e9ments dans chaque cat\u00e9gorie, elle peut avoir d'autres noms, d'autres appellations.\n\nAucune g\u00e9n\u00e9ration de valeur factice : ne cr\u00e9e jamais d\u2019identifiants arbitraires.\n\nRespect strict des types et formats : dates en YYYY-MM-DD ou null, valeurs num\u00e9riques, cha\u00eenes, bool\u00e9ens.\n\nSi plusieurs phrases, paragraphes ou \u00e9l\u00e9ments d\u00e9crivent une m\u00eame exp\u00e9rience, les concat\u00e9ner dans une cha\u00eene unique.\n\nAide toi de l'outil Think pour trouver les informations pertinente aux bons endroits.\n\nIl faut absolument que tu laisses les champs de \"jobHistory\" vide, focalise toi sur les autres cat\u00e9gories.\n\n\u27a4 Chaque \u00e9l\u00e9ment doit \u00eatre pr\u00e9c\u00e9d\u00e9 du caract\u00e8re \u2022 et s\u00e9par\u00e9 par un retour \u00e0 la ligne (\\n).\n\n\u27a4 Exemple de format final :\n\n\"description\": \"\u2022 T\u00e2che 1\\n\u2022 T\u00e2che 2\\n\u2022 T\u00e2che 3\"\nNe pas utiliser de crochets [] ni de guillemets multiples.\n\n\ud83d\udcc5 D\u00c9DUCTION DES DATES (\u00e0 appliquer syst\u00e9matiquement) :\n\nCas 1 : Une ann\u00e9e seule est mentionn\u00e9e\n\u27a4 Pour un d\u00e9but : ann\u00e9e-09-01\n\u27a4 Pour une fin : ann\u00e9e-06-30\n\nCas 2 : Seule une date de fin est donn\u00e9e\n\u27a4 D\u00e9duis la date de d\u00e9but comme : ann\u00e9e_fin - 1-09-01\n\nCas 3 : Aucune date identifiable\n\u27a4 Remplace la valeur par null\n\u26a0\ufe0f Ne jamais utiliser \"UNKNOWN\" ou un string \"null\" pour un champ de date.\n\n\ud83e\udde0 D\u00c9DUCTION DU CHAMP field :\n\nSi le domaine de formation ou sp\u00e9cialit\u00e9 n\u2019est pas explicitement donn\u00e9, d\u00e9duis le \u00e0 partir du dipl\u00f4me, de l\u2019\u00e9cole, ou de la description.\n\nN\u2019utilise \"UNKNOWN\" que si aucune d\u00e9duction n\u2019est raisonnablement possible.\n\n\ud83d\udd0d BALAYAGE COMPLET DU CV PAR CAT\u00c9GORIE :\n\n\u27a4 Pour chaque cat\u00e9gorie (educationEntries, workExperienceEntries, skills, languages, certifications), tu dois extraire TOUS les \u00e9l\u00e9ments du CV qui y sont li\u00e9s, m\u00eame si :\n\nL\u2019intitul\u00e9 est partiel ou implicite.\nL\u2019information est redondante ou r\u00e9p\u00e9t\u00e9e dans diff\u00e9rentes parties du CV.\nL\u2019intitul\u00e9 de la rubrique ne correspond pas litt\u00e9ralement (ex : \u201cformation\u201d au lieu d\u2019\u201c\u00e9ducation\u201d, \u201cparcours acad\u00e9mique\u201d, \u201clangues et certifications\u201d, etc.).\n\n\u27a4 Tu dois donc analyser TOUT le contenu du CV pour y d\u00e9tecter chaque formation, exp\u00e9rience, comp\u00e9tence, langue ou certification.\n\n\u27a4 Aucune donn\u00e9e ne doit \u00eatre oubli\u00e9e dans les sections educationEntries, workExperienceEntries, skills, languages et certifications. Ne jamais te contenter d\u2019un seul bloc du CV, mais balayer l\u2019ensemble du contenu pour trouver chaque occurrence potentielle.\n\nTu dois fournir un effort suppl\u00e9mentaire pour les cat\u00e9gories educationEntries, workExperienceEntries et skills. \n\nAides toi de l'outil Think pour y arriver.\n\n\ud83d\udce6 FORMAT JSON \u00c0 PRODUIRE :\n\n{\n \"personalInfo\": {\n \"name\": \"string\",\n \"email\": \"string\",\n \"phone\": \"string\",\n \"address\": \"string\",\n \"linkedin\": \"string\",\n \"created_at\": \"ISODate\",\n \"updated_at\": \"ISODate\"\n },\n \"educationEntries\": [\n {\n \"institution\": \"string\",\n \"degree\": \"string\",\n \"field\": \"string (mettre 'UNKNOWN' si inconnu ou null)\",\n \"start_date\": \"YYYY-MM-DD|null\",\n \"end_date\": \"YYYY-MM-DD|null\",\n \"ongoing\": true,\n \"description\": \"string|null\",\n \"created_at\": \"ISODate\",\n \"updated_at\": \"ISODate\"\n }\n ],\n \"workExperienceEntries\": [\n {\n \"company\": \"string\",\n \"position\": \"string\",\n \"start_date\": \"YYYY-MM-DD|null\",\n \"end_date\": \"YYYY-MM-DD|null\",\n \"ongoing\": true,\n \"description\": \"string\",\n \"location\": \"string\",\n \"created_at\": \"ISODate\",\n \"updated_at\": \"ISODate\"\n }\n ],\n \"skills\": [\n {\n \"name\": \"string\",\n \"category\": \"string\",\n \"source\": \"explicit|implicit\",\n \"confidence\": 0.0,\n \"experience_duration\": \"integer\",\n \"last_used\": \"ISODate\",\n \"created_at\": \"ISODate\",\n \"updated_at\": \"ISODate\"\n }\n ],\n \"languages\": [\n {\n \"language\": \"string\",\n \"proficiency\": \"string\",\n \"created_at\": \"ISODate\",\n \"updated_at\": \"ISODate\"\n }\n ],\n \"certifications\": [\n {\n \"name\": \"string\",\n \"issuer\": \"string\",\n \"date\": \"YYYY-MM-DD|null\",\n \"expiry_date\": \"YYYY-MM-DD|null\",\n \"created_at\": \"ISODate\",\n \"updated_at\": \"ISODate\"\n }\n ],\n \n}"
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 1.8,
"position": [
-3120,
-440
],
"id": "e7ea4682-56c3-4c68-b406-dd49f670d39c",
"name": "AI Agent"
},
{
"parameters": {
"model": {
"__rl": true,
"value": "gpt-4o-mini",
"mode": "list",
"cachedResultName": "gpt-4o-mini"
},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"position": [
-3200,
-220
],
"id": "6b98ebd5-b7b3-434e-8390-3ac9e264f8f5",
"name": "OpenAI Chat Model",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "e808e481-78d2-4172-9dac-9f40e3e92010",
"leftValue": "={{ $json.score_integrite }}",
"rightValue": 1,
"operator": {
"type": "number",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-4460,
-180
],
"id": "b051698f-3a75-4a6c-b8fc-99dd39864d8a",
"name": "If2"
},
{
"parameters": {
"errorMessage": "Votre CV n'est pas traitable"
},
"type": "n8n-nodes-base.stopAndError",
"typeVersion": 1,
"position": [
-4240,
-180
],
"id": "5d76bc66-30ac-4dd1-80a3-ee7e0a72f706",
"name": "Stop and Error2"
},
{
"parameters": {
"content": "## Gestions des erreurs\n\n \n",
"height": 200,
"width": 460,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-4540,
-240
],
"id": "fe052be2-9c27-41c9-9a5c-d882b354c5ad",
"name": "Sticky Note12"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{$binary.data.fileExtension}}",
"rightValue": "pdf",
"operator": {
"type": "string",
"operation": "equals"
},
"id": "f6568061-1574-4300-8f4c-3601fc51c01e"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "PDF"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "e48db1ae-2c9d-497f-ba1a-7b9ccae11d0d",
"leftValue": "={{$binary.data.fileExtension}}",
"rightValue": "doc",
"operator": {
"type": "string",
"operation": "contains"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "DOC"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "f2a6c467-b31c-4209-a438-de0ed4177fd7",
"leftValue": "={{$binary.data.fileExtension}}",
"rightValue": "jpeg",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "JPEG"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "e27615db-0965-4594-8201-7efe2ceaf479",
"leftValue": "={{$binary.data.fileExtension}}",
"rightValue": "jpg",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "JPG"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "61cb3f70-d9d4-4071-83a6-1310d9b4b8c8",
"leftValue": "={{$binary.data.fileExtension}}",
"rightValue": "png",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "PNG"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
-4020,
-460
],
"id": "eabfe504-4249-45a0-8332-70085e3af4cb",
"name": "Switch"
},
{
"parameters": {
"description": "\ud83d\udee0\ufe0f Aide \u00e0 l\u2019extraction structur\u00e9e de donn\u00e9es de CV :\n\nObjectif :\nAnalyser un texte issu d\u2019un CV et en extraire rigoureusement les informations personnelles, formations, exp\u00e9riences professionnelles, comp\u00e9tences, langues et certifications, selon des r\u00e8gles de structuration strictes.\n\nR\u00e8gles fondamentales \u00e0 respecter :\n\nNe jamais inventer ou compl\u00e9ter les champs manquants.\nLaisser explicitement null ou \"UNKNOWN\" uniquement si AUCUNE information claire ne permet de d\u00e9duire une valeur.\n\nExtraire le maximum d\u2019\u00e9l\u00e9ments r\u00e9els d\u00e9tectables dans les bonnes cat\u00e9gories :\n\nUne exp\u00e9rience de stage est une exp\u00e9rience professionnelle.\n\nUn dipl\u00f4me avec une \u00e9cole = une entr\u00e9e en formation.\n\nUne comp\u00e9tence mentionn\u00e9e dans une mission ou une section d\u00e9di\u00e9e = comp\u00e9tence.\n\nFormats obligatoires \u00e0 respecter :\n\nDates : YYYY-MM-DD\n\u27a4 Ann\u00e9e seule : d\u00e9but = ann\u00e9e-09-01 ; fin = ann\u00e9e-06-30\n\u27a4 Date de fin seule : d\u00e9but = ann\u00e9e_fin - 1-09-01\n\u27a4 Pas de date identifiable = null\n\nDescription : concat\u00e9ner toutes les phrases li\u00e9es \u00e0 une exp\u00e9rience ou une formation en une seule cha\u00eene, s\u00e9par\u00e9es par \\n et commen\u00e7ant par \u2022\n\nChamps created_at et updated_at : utiliser la date et l\u2019heure ISO actuelles.\n\nLogique de remplissage des cat\u00e9gories :\n\npersonalInfo : nom, e-mail, t\u00e9l\u00e9phone, adresse, LinkedIn\n\neducationEntries : \u00e9coles, dipl\u00f4mes, ann\u00e9es, domaine d\u2019\u00e9tude (ou d\u00e9duction), description\n\nworkExperienceEntries : entreprises, postes, p\u00e9riodes, missions, lieu\n\nskills : extraire toutes comp\u00e9tences techniques, outils, frameworks, langages\n\nlanguages : langues parl\u00e9es et leur niveau\n\ncertifications : nom, organisme, date d\u2019obtention, expiration\n\nNe jamais toucher \u00e0 jobHistory.\nCette section doit rester vierge : valeurs par d\u00e9faut (ex. cha\u00eenes vides, 0, null, etc.)\n\nConfidence dans les skills :\n\nSi comp\u00e9tence clairement cit\u00e9e = 0.9\n\nSi d\u00e9duite d\u2019un poste ou projet = 0.7\n\nSi implicite sans d\u00e9tails techniques = 0.5\n\nExp\u00e9rience en mois (experience_duration) :\n\n1 an = 12, 2 ans = 24, 6 mois = 6, etc.\n\nSi dur\u00e9e non indiqu\u00e9e, estimer de fa\u00e7on prudente ou mettre 0 si non d\u00e9ductible.\n\nBUT :\nFournir \u00e0 l'IA une analyse fiable, exploitable automatiquement, sans erreurs de typage ou structure.\n\n"
},
"type": "@n8n/n8n-nodes-langchain.toolThink",
"typeVersion": 1,
"position": [
-2920,
-220
],
"id": "e8998de4-4cd3-4e15-a4b9-b9d164c2d39d",
"name": "Think"
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "0p5SH6mccKOpGeVb",
"mode": "list",
"cachedResultName": "Recup_PDF"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {},
"matchingColumns": [],
"schema": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"mode": "each",
"options": {
"waitForSubWorkflow": true
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
-3600,
-720
],
"id": "1ce54e62-0dce-4a76-b604-d0305b2827cf",
"name": "PDF"
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "1uHJV1JyuxpmI4vq",
"mode": "list",
"cachedResultName": "Recup_DOC"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {},
"matchingColumns": [],
"schema": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"mode": "each",
"options": {
"waitForSubWorkflow": true
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
-3600,
-520
],
"id": "4b068797-709b-49be-b0fe-d149efcd1cf4",
"name": "DOC"
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "pKa2oqD7LPGRiRyh",
"mode": "list",
"cachedResultName": "Recup_JPG"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {},
"matchingColumns": [],
"schema": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"mode": "each",
"options": {
"waitForSubWorkflow": true
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
-3600,
-340
],
"id": "292b0201-d441-47e0-8d13-ffe9d3f5b976",
"name": "JPG"
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "KXXldtFuEtaQPcrU",
"mode": "list",
"cachedResultName": "Recup_PNG"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {},
"matchingColumns": [],
"schema": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"mode": "each",
"options": {
"waitForSubWorkflow": true
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
-3600,
-160
],
"id": "22ed0714-f827-42f1-9a03-ef7ab600942f",
"name": "PNG"
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "BKPiHgeoc8YAUX6k",
"mode": "list",
"cachedResultName": "Enregistrement_CV"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {},
"matchingColumns": [],
"schema": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"options": {
"waitForSubWorkflow": "=true"
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
-1580,
-440
],
"id": "7a4c92a5-4483-4370-b178-86ef5319256f",
"name": "Supabase",
"onError": "continueErrorOutput"
},
{
"parameters": {
"sessionIdType": "customKey",
"sessionKey": "={{ $('Webhook Trigger').item.json.body.userId }}"
},
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"typeVersion": 1.3,
"position": [
-3060,
-220
],
"id": "a985b7f7-241b-479a-9a13-5e5983d9ba1c",
"name": "Simple Memory1",
"disabled": true
},
{
"parameters": {
"jsCode": "let ext = items[0].json.extension?.toLowerCase();\nif (!ext && items[0].binary?.data?.fileName) {\n const fileName = items[0].binary.data.fileName;\n ext = fileName.split('.').pop().toLowerCase();\n}\n\nconst base64Data = items[0].binary.data.data;\nlet accessTexte = false;\nlet scoreAcces = 0;\nlet remarque = \"\";\n\ntry {\n const buffer = Buffer.from(base64Data, \"base64\");\n\n if (ext === \"pdf\") {\n const dataStr = buffer.toString(\"latin1\");\n\n // V\u00e9rifie s'il y a des fontes et pas que des images\n const hasFont = /\\/Font\\b/.test(dataStr);\n const hasImage = /\\/Image\\b/.test(dataStr);\n\n if (!hasFont && hasImage) {\n accessTexte = false;\n scoreAcces = 0;\n remarque = \"PDF uniquement compos\u00e9 d'images (aucune fonte d\u00e9tect\u00e9e)\";\n } else {\n // Nettoyage du contenu\n const cleaned = dataStr\n .replace(/<[^>]+>/g, '')\n .replace(/[^A-Za-z\u00c0-\u00ff\\s]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\n const words = cleaned.split(' ').filter(w => w.length >= 4);\n const uniqueWords = [...new Set(words)];\n const totalChars = words.join('').length;\n\n if (uniqueWords.length >= 20 && totalChars > 200) {\n accessTexte = true;\n scoreAcces = 1;\n remarque = `Texte exploitable d\u00e9tect\u00e9 : ${uniqueWords.length} mots uniques (~${totalChars} caract\u00e8res nettoy\u00e9s)`;\n } else {\n remarque = \"Pas de contenu textuel exploitable (texte insuffisant ou bruit OCR)\";\n }\n }\n } else if (ext === \"docx\") {\n accessTexte = true;\n scoreAcces = 1;\n remarque = \"Texte accessible (format Word)\";\n } else {\n remarque = \"Format non v\u00e9rifi\u00e9 automatiquement\";\n }\n\n} catch (err) {\n remarque = `Erreur d'analyse : ${err.message}`;\n}\n\n// R\u00e9sultat\nitems[0].json.accessibilite_textuelle = accessTexte;\nitems[0].json.score_accessibilite = scoreAcces;\nitems[0].json.remarque_accessibilite = remarque;\nitems[0].json.extension_detectee = ext;\n\nreturn items;\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-4800,
-420
],
"id": "d6b7043e-0208-4cdc-9d45-5a8f18fec705",
"name": "Texte accessible"
},
{
"parameters": {
"respondWith": "allIncomingItems",
"options": {
"responseCode": 200
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
80,
-560
],
"id": "ff291271-1bb9-4f83-9fb1-c7a47e29d2c0",
"name": "Respond to Webhook Missing"
},
{
"parameters": {
"options": {
"responseCode": 202
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
80,
-120
],
"id": "48f381ae-0643-4fa3-b0a7-5bbbf78baba2",
"name": "Respond to Webhook Errors Workflow"
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nconst errors = $input.all().map(item => item.json.Errors.errors);\nconst block = $input.all().map(item => item.json.Errors.block);\n\nreturn [{\n json: {\n status: \"error\",\n errors: errors,\n block : block\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-560,
-380
],
"id": "d485609b-2481-49ff-a014-04e21f656935",
"name": "Errors Parsing"
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nconst errors = $input.all().map(item => item.json.Missings);\nconst block = $input.all().map(item => item.json.Missings.block);\n\nreturn [{\n json: {\n status: \"warning\",\n errors: errors,\n block : block\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-560,
-560
],
"id": "46c2410c-fe7c-477b-acec-944d72d181b3",
"name": "Missing Parsing"
},
{
"parameters": {
"jsCode": "const errorMessage = $input.all().map(item => item.json.error) || \"Une erreur inconnue est survenue.\";\nreturn [{\n json: {\n status: \"error\",\n message: errorMessage\n }\n}];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-560,
-140
],
"id": "56db7757-5940-470e-b6cd-36ac018cf161",
"name": "Code1"
},
{
"parameters": {
"content": "## Envoi des Missings et erreurs des noeuds Sous-Workflow Supabase\n",
"height": 760,
"width": 1300,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1060,
-980
],
"id": "31b86904-ac13-48c2-ab97-16683bf2182f",
"name": "Sticky Note"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $json.Missings }}",
"rightValue": "",
"operator": {
"type": "object",
"operation": "exists",
"singleValue": true
},
"id": "d8e6c491-eec6-4df0-b62f-56c62d198ec1"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Missings"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "d2eae932-6281-41cf-ab0c-7d6625f09fdf",
"leftValue": "={{ $json.Errors }}",
"rightValue": "",
"operator": {
"type": "object",
"operation": "exists",
"singleValue": true
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Errors"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
-880,
-460
],
"id": "c0692fed-ab85-4342-a524-d1e6c25c74b3",
"name": "Switch1"
},
{
"parameters": {
"numberInputs": 3
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.1,
"position": [
-120,
-560
],
"id": "6f742e0b-b003-4306-b0ee-2f40768e4ee1",
"name": "Merge"
},
{
"parameters": {
"jsCode": "const rawJson = $('Recup ID').first().json.output; // adapte selon o\u00f9 est le JSON\nreturn [{\n json: JSON.parse(rawJson)\n}];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-560,
-740
],
"id": "74ad9c68-6535-4a6a-aa4f-dd5c1479ded0",
"name": "Parsing"
},
{
"parameters": {},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.1,
"position": [
-120,
-120
],
"id": "f6eeda25-27fe-40dc-98c9-8a5fd55681e9",
"name": "Merge1"
},
{
"parameters": {
"jsCode": "const rawJson = $('Recup ID').first().json.output; // adapte selon o\u00f9 est le JSON\nreturn [{\n json: JSON.parse(rawJson)\n}];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-580,
140
],
"id": "d6d13c8d-02ee-4d48-8237-c102a0dbc099",
"name": "Parsing1"
},
{
"parameters": {
"content": "## Envoi des erreurs workflow\n",
"height": 520,
"width": 1300,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1060,
-180
],
"id": "905f078f-0829-492d-902f-2bd4769a2319",
"name": "Sticky Note1"
},
{
"parameters": {
"content": "## Sous workflows de r\u00e9cup\u00e9ration de donn\u00e9es du CV en fonction de son mimetype et de son \u00e9valuation\n",
"height": 1180,
"width": 740,
"color": 2
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-4040,
-960
],
"id": "7b7a142b-f5b2-4d8d-b28a-6f5057d95acd",
"name": "Sticky Note7"
},
{
"parameters": {
"content": "## Extraction et structuration de la donn\u00e9e par l'IA\n",
"height": 540,
"width": 1020,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-3240,
-620
],
"id": "77e4c881-d348-4715-b199-4d87512cad2e",
"name": "Sticky Note8"
},
{
"parameters": {
"content": "## Sous workflow d'enregistrement de la donn\u00e9e structur\u00e9e dans supabase avec gestions des erreurs\n\n",
"height": 380,
"width": 360,
"color": 2
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1700,
-620
],
"id": "b6092c75-289d-4c64-be4a-2ac9662b0f7c",
"name": "Sticky Note9"
},
{
"parameters": {
"content": "## Envoie JSON de la donn\u00e9e pour appel API\n\n",
"height": 280,
"width": 220,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-620,
-860
],
"id": "fecef852-14f7-49a7-80c9-f004e6ec43df",
"name": "Sticky Note10"
},
{
"parameters": {
"content": "## Envoie JSON de la donn\u00e9e pour appel API\n\n",
"height": 280,
"width": 220,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-640,
20
],
"id": "0bfdbe70-dac0-45d9-ab64-14c2d735aaef",
"name": "Sticky Note11"
},
{
"parameters": {
"content": "## Workflow de r\u00e9cup\u00e9ration de CV \n+ v\u00e9rification et \u00e9valuation de la qualit\u00e9 du fichier\n+ extraction de la donn\u00e9e en fonction du mimetype et de la note du fichier\n+ structuration de la donn\u00e9e extraite\n+ \n",
"height": 1560,
"width": 6060,
"color": 7
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-5800,
-1100
],
"id": "c9fc2deb-2eb0-4444-9bca-3b8b619bfdc1",
"name": "Sticky Note13"
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.output }}",
"options": {
"systemMessage": "Tu dois, \u00e0 partir du JSON brut, ajouter les informations n\u00e9cessaires dans la cat\u00e9gorie jobHistory. Pour ce faire, tu dois analyser les exp\u00e9riences pr\u00e9sentes dans workExperienceEntries uniquement.\n\nRenvoie le tableau JSON brut, sans aucune modification ailleurs que dans jobHistory. Ne change ni l\u2019ordre, ni le format des autres donn\u00e9es.\nNe rajoute pas de balise comme json autour.\n\n\ud83d\udce6 CONSTRUCTION DE jobHistory (obligatoire) :\n\nG\u00e9n\u00e8re dynamiquement l\u2019objet jobHistory \u00e0 partir de toutes les workExperienceEntries, en respectant les consignes suivantes :\n\n\ud83c\udfaf Champs \u00e0 g\u00e9n\u00e9rer :\ntotal_experience : dur\u00e9e totale nette (en mois, entier), en excluant les chevauchements.\n\nlongest_position_company : nom de l'entreprise avec la p\u00e9riode la plus longue.\n\nlongest_position_title : poste correspondant \u00e0 cette p\u00e9riode.\n\nlongest_position_duration : dur\u00e9e en mois (entier) de cette p\u00e9riode.\n\ntime_gaps : liste de trous entre deux exp\u00e9riences cons\u00e9cutives (chaque trou = objet JSON avec start, end, duration_months).\n\ntime_overlaps : liste de chevauchements entre deux exp\u00e9riences (chaque chevauchement = objet JSON avec range_a, range_b, overlap_months).\n\n\ud83d\udd52 R\u00e8gles de traitement des dates :\nUtilise start_date et end_date pour chaque exp\u00e9rience.\n\nTrie d\u2019abord les exp\u00e9riences par date de d\u00e9but croissante.\n\nUne p\u00e9riode de chevauchement existe si deux exp\u00e9riences se superposent dans le temps.\n\nUn gap existe si une exp\u00e9rience d\u00e9bute apr\u00e8s la fin de la pr\u00e9c\u00e9dente (apr\u00e8s tri).\n\n\u2795 Important :\nIgnore les dates nulles ou invalides\n\nArrondis les dur\u00e9es en mois vers le bas si partiels (ex : 2021-01-15 \u00e0 2021-03-14 = 1 mois)\n\nNe cr\u00e9e aucun doublon de gap ou overlap\n\nNe g\u00e9n\u00e8re pas de gap entre deux exp\u00e9riences si elles se chevauchent\n\n\ud83d\udce6 FORMAT JSON ATTENDU :\n\n\"jobHistory\": {\n \"total_experience\": number,\n \"longest_position_company\": \"string\",\n \"longest_position_title\": \"string\",\n \"longest_position_duration\": number,\n \"created_at\": \"ISODate\",\n \"updated_at\": \"ISODate\",\n \"time_gaps\": [\n {\n \"start\": \"YYYY-MM-DD\",\n \"end\": \"YYYY-MM-DD\",\n \"duration_months\": number\n },\n ...\n ],\n \"time_overlaps\": [\n {\n \"range_a\": [\"YYYY-MM-DD\", \"YYYY-MM-DD\"],\n \"range_b\": [\"YYYY-MM-DD\", \"YYYY-MM-DD\"],\n \"overlap_months\": number\n },\n ...\n ]\n}"
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 2,
"position": [
-2620,
-440
],
"id": "4d52f233-142a-47b4-9709-73fd9f394ff9",
"name": "AI Agent1"
},
{
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"position": [
-2620,
-260
],
"id": "7b85d980-6860-4886-a597-4b3e5b518a68",
"name": "OpenAI Chat Model1",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "0af6eed1-c0df-4eed-aa3c-40c1d991641f",
"name": "id",
"value": "={{ $('Webhook Trigger').item.json.body.analysisId }}",
"type": "string"
},
{
"id": "4a55d39f-a668-4986-8725-d747a41f636c",
"name": "cv_document_id",
"value": "={{ $('Webhook Trigger').item.json.body.documentId }}",
"type": "string"
},
{
"id": "bd77a383-7aba-4b76-92f2-22ab8c5c5f5f",
"name": "advisor_id",
"value": "={{ $('Webhook Trigger').item.json.body.userId }}",
"type": "string"
}
]
},
"includeOtherFields": true,
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-2020,
-440
],
"id": "1185d379-8ff8-442b-937a-77c1327bc687",
"name": "Recup ID"
}
],
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Recuperation Supabase",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Recuperation Supabase": {
"main": [
[
{
"node": "informations du fichier",
"type": "main",
"index": 0
}
]
]
},
"informations du fichier": {
"main": [
[
{
"node": "int\u00e9grit\u00e9 du fichier",
"type": "main",
"index": 0
}
]
]
},
"int\u00e9grit\u00e9 du fichier": {
"main": [
[
{
"node": "Texte accessible",
"type": "main",
"index": 0
}
]
]
},
"Evaluation": {
"main": [
[
{
"node": "If2",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"If2": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
],
[
{
"node": "Stop and Error2",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "PDF",
"type": "main",
"index": 0
}
],
[
{
"node": "DOC",
"type": "main",
"index": 0
}
],
[
{
"node": "JPG",
"type": "main",
"index": 0
}
],
[
{
"node": "JPG",
"type": "main",
"index": 0
}
],
[
{
"node": "PNG",
"type": "main",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[
{
"node": "AI Agent1",
"type": "main",
"index": 0
}
]
]
},
"Think": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"PDF": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"DOC": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"JPG": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"PNG": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Texte accessible": {
"main": [
[
{
"node": "Evaluation",
"type": "main",
"index": 0
}
]
]
},
"Simple Memory1": {
"ai_memory": [
[
{
"node": "AI Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Supabase": {
"main": [
[
{
"node": "Switch1",
"type": "main",
"index": 0
},
{
"node": "Parsing",
"type": "main",
"index": 0
}
],
[
{
"node": "Code1",
"type": "main",
"index": 0
},
{
"node": "Parsing1",
"type": "main",
"index": 0
}
]
]
},
"Errors Parsing": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 2
}
]
]
},
"Missing Parsing": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Code1": {
"main": [
[
{
"node": "Merge1",
"type": "main",
"index": 0
}
]
]
},
"Switch1": {
"main": [
[
{
"node": "Missing Parsing",
"type": "main",
"index": 0
}
],
[
{
"node": "Errors Parsing",
"type": "main",
"index": 0
}
]
]
},
"Merge": {
"main": [
[
{
"node": "Respond to Webhook Missing",
"type": "main",
"index": 0
}
]
]
},
"Parsing": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Merge1": {
"main": [
[
{
"node": "Respond to Webhook Errors Workflow",
"type": "main",
"index": 0
}
]
]
},
"Parsing1": {
"main": [
[
{
"node": "Merge1",
"type": "main",
"index": 1
}
]
]
},
"AI Agent1": {
"main": [
[
{
"node": "Recup ID",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model1": {
"ai_languageModel": [
[
{
"node": "AI Agent1",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Recup ID": {
"main": [
[
{
"node": "Supabase",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"callerPolicy": "workflowsFromSameOwner",
"errorWorkflow": "8GUEZAnQX5HR4Ict"
},
"versionId": "a25394cd-2587-43f6-998e-b1c6c2629f9a",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "0fJmPApvJ9PmqZ83",
"tags": []
}
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.
openAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Recup_CV. Uses httpRequest, agent, lmChatOpenAi, stopAndError. Webhook trigger; 43 nodes.
Source: https://github.com/anassrh/AI-CV-ATS-Analyzer-n8n-Supabase-React/blob/main/Recup_CV.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.
🧠 Gwen – The AI Voice Marketing Agent Gwen is your intelligent voice-powered marketing assistant built in n8n. She combines the power of OpenAI, ElevenLabs, and automation workflows to handle content
This workflow is designed for professionals, companies, and agencies that want to automate competitive analysis based on public reviews and opinions found on the Internet. It is especially useful for:
VEP WAPP. Uses openAi, lmChatOpenAi, toolCalculator, agent. Webhook trigger; 100 nodes.
L&D_AgentsAI_ATIVO. Uses httpRequest, agent, googleCalendarTool, toolSerpApi. Webhook trigger; 93 nodes.
CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.