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": "\ud83d\udd0d Recherche Alternance Cybers\u00e9curit\u00e9 - Complet",
"nodes": [
{
"parameters": {},
"id": "start-node-001",
"name": "\ud83d\ude80 Start",
"type": "n8n-nodes-base.start",
"typeVersion": 1,
"position": [
120,
300
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "search-terms-id",
"name": "recherche",
"value": "alternance cybers\u00e9curit\u00e9",
"type": "string"
},
{
"id": "location-id",
"name": "localisation",
"value": "France",
"type": "string"
},
{
"id": "sites-id",
"name": "sites",
"value": "[\n \"https://www.apec.fr/candidat/recherche-emploi.html/emploi?motsCles=alternance%20cybers\u00e9curit\u00e9\",\n \"https://fr.indeed.com/jobs?q=alternance+cybers\u00e9curit\u00e9\",\n \"https://labonnealternance.pole-emploi.fr/recherche-apprentissage?&job=cybers\u00e9curit\u00e9\",\n \"https://www.francetravail.fr/candidat/rechercheoffres/resultats/recherche?offresPartenaires=true&range=0-19&sort=0&nature=1&q=alternance%20cybers\u00e9curit\u00e9\",\n \"https://walt.community/jobs?search=cybers\u00e9curit\u00e9%20alternance\",\n \"https://www.bloom-alternance.fr/recherche?q=cybers\u00e9curit\u00e9\"\n]",
"type": "string"
}
]
},
"options": {}
},
"id": "config-node-002",
"name": "\u2699\ufe0f Configuration Recherche",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
320,
300
]
},
{
"parameters": {
"jsCode": "// SCRAPER MULTI-SITES POUR ALTERNANCES CYBERS\u00c9CURIT\u00c9\n\nconst config = $input.item.json;\nconst sites = JSON.parse(config.sites);\n\nconsole.log('\ud83d\udd0d === D\u00c9BUT SCRAPING MULTI-SITES ===');\nconsole.log('\ud83d\udcdd Recherche:', config.recherche);\nconsole.log('\ud83d\udccd Localisation:', config.localisation);\nconsole.log('\ud83c\udf10 Sites \u00e0 scraper:', sites.length);\n\nconst offresCollectees = [];\n\n// Fonction de scraping pour chaque site\nasync function scraperSite(url, siteName) {\n try {\n console.log(`\ud83c\udf10 Scraping ${siteName}: ${url}`);\n \n const response = await fetch(url, {\n headers: {\n 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',\n 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',\n 'Accept-Language': 'fr-FR,fr;q=0.5',\n 'Accept-Encoding': 'gzip, deflate',\n 'Connection': 'keep-alive',\n 'Upgrade-Insecure-Requests': '1'\n }\n });\n\n if (!response.ok) {\n console.log(`\u274c Erreur ${siteName}: HTTP ${response.status}`);\n return [];\n }\n\n const html = await response.text();\n console.log(`\ud83d\udcc4 HTML re\u00e7u de ${siteName}: ${html.length} caract\u00e8res`);\n \n // Simulation d'extraction (\u00e0 adapter selon chaque site)\n const offres = extraireOffres(html, siteName, url);\n console.log(`\u2705 ${siteName}: ${offres.length} offres trouv\u00e9es`);\n \n return offres;\n \n } catch (error) {\n console.log(`\ud83d\udea8 Erreur scraping ${siteName}:`, error.message);\n return [];\n }\n}\n\n// Extraction des offres selon le site\nfunction extraireOffres(html, siteName, url) {\n // Patterns de recherche pour chaque site\n const patterns = {\n 'apec.fr': {\n titleRegex: /<h3[^>]*class=\"[^\"]*title[^\"]*\"[^>]*>([^<]+)<\\/h3>/gi,\n companyRegex: /<span[^>]*class=\"[^\"]*company[^\"]*\"[^>]*>([^<]+)<\\/span>/gi,\n linkRegex: /<a[^>]*href=\"([^\"]*offre[^\"]*)\"/gi\n },\n 'indeed.com': {\n titleRegex: /<h2[^>]*class=\"[^\"]*jobTitle[^\"]*\"[^>]*>.*?<span[^>]*>([^<]+)<\\/span>/gi,\n companyRegex: /<span[^>]*class=\"[^\"]*companyName[^\"]*\"[^>]*>([^<]+)<\\/span>/gi,\n linkRegex: /<a[^>]*href=\"(\\/viewjob[^\"]*)\"/gi\n },\n 'pole-emploi.fr': {\n titleRegex: /<h4[^>]*class=\"[^\"]*media-heading[^\"]*\"[^>]*>([^<]+)<\\/h4>/gi,\n companyRegex: /<p[^>]*class=\"[^\"]*subtitle[^\"]*\"[^>]*>([^<]+)<\\/p>/gi,\n linkRegex: /<a[^>]*href=\"([^\"]*offre[^\"]*)\"/gi\n }\n };\n\n const siteKey = Object.keys(patterns).find(key => url.includes(key)) || 'default';\n const pattern = patterns[siteKey] || patterns['indeed.com'];\n \n const offres = [];\n let match;\n \n // Extraction des titres\n const titres = [];\n while ((match = pattern.titleRegex.exec(html)) !== null) {\n titres.push(match[1].trim());\n if (titres.length >= 20) break; // Limite \u00e0 20 offres par site\n }\n \n // G\u00e9n\u00e9ration d'offres simul\u00e9es (\u00e0 remplacer par vraie extraction)\n for (let i = 0; i < Math.min(titres.length, 5); i++) {\n const titre = titres[i] || `Offre ${i + 1} - ${siteName}`;\n \n offres.push({\n id: `${siteName}-${Date.now()}-${i}`,\n title: titre,\n company: `Entreprise ${i + 1}`,\n description: `Description pour ${titre}. Alternance en cybers\u00e9curit\u00e9 avec formation et missions pratiques.`,\n location: config.localisation,\n source: siteName,\n url: url,\n date_scraped: new Date().toISOString(),\n keywords: ['alternance', 'cybers\u00e9curit\u00e9', 's\u00e9curit\u00e9 informatique']\n });\n }\n \n return offres;\n}\n\n// Scraping de tous les sites\nconst promises = sites.map((url, index) => {\n const siteName = extractSiteName(url);\n return scraperSite(url, siteName);\n});\n\nfunction extractSiteName(url) {\n if (url.includes('apec.fr')) return 'APEC';\n if (url.includes('indeed.com')) return 'Indeed';\n if (url.includes('pole-emploi.fr') || url.includes('labonnealternance')) return 'France Travail';\n if (url.includes('walt.community')) return 'Walt';\n if (url.includes('bloom-alternance.fr')) return 'Bloom Alternance';\n return 'Site Inconnu';\n}\n\ntry {\n const resultats = await Promise.all(promises);\n \n // Consolidation des offres\n resultats.forEach(offres => {\n offresCollectees.push(...offres);\n });\n \n console.log(`\ud83c\udfaf === SCRAPING TERMIN\u00c9 ===`);\n console.log(`\ud83d\udcca Total offres collect\u00e9es: ${offresCollectees.length}`);\n \n // D\u00e9doublonnage basique\n const offresUniques = offresCollectees.filter((offre, index, self) => \n index === self.findIndex(o => o.title === offre.title && o.company === offre.company)\n );\n \n console.log(`\ud83e\uddf9 Apr\u00e8s d\u00e9doublonnage: ${offresUniques.length} offres`);\n \n return offresUniques.map(offre => ({ json: offre }));\n \n} catch (error) {\n console.log('\ud83d\udea8 Erreur globale scraping:', error.message);\n \n // Retour d'offres de test en cas d'erreur\n const offresTest = [\n {\n id: 'test-1',\n title: 'Alternant Cybers\u00e9curit\u00e9 - SOC Analyst',\n company: 'SecureTech Solutions',\n description: 'Rejoignez notre \u00e9quipe SOC pour une alternance en cybers\u00e9curit\u00e9. Missions: surveillance, analyse d\\'incidents, r\u00e9ponse aux menaces.',\n location: 'Paris, France',\n source: 'TEST',\n url: 'https://example.com/offre1',\n date_scraped: new Date().toISOString(),\n keywords: ['alternance', 'cybers\u00e9curit\u00e9', 'SOC']\n },\n {\n id: 'test-2',\n title: 'Stage - Marketing Digital', \n company: 'AgenceWeb',\n description: 'Stage en marketing digital, cr\u00e9ation de contenu et gestion des r\u00e9seaux sociaux.',\n location: 'Lyon, France',\n source: 'TEST',\n url: 'https://example.com/offre2',\n date_scraped: new Date().toISOString(),\n keywords: ['stage', 'marketing', 'digital']\n },\n {\n id: 'test-3',\n title: 'Alternant Pentesteur Junior',\n company: 'CyberAudit Pro',\n description: 'Alternance en tests d\\'intrusion et audit de s\u00e9curit\u00e9. Formation compl\u00e8te aux outils de pentest.',\n location: 'Marseille, France', \n source: 'TEST',\n url: 'https://example.com/offre3',\n date_scraped: new Date().toISOString(),\n keywords: ['alternance', 'pentest', 's\u00e9curit\u00e9']\n }\n ];\n \n console.log('\ud83d\udd04 Utilisation des offres de test');\n return offresTest.map(offre => ({ json: offre }));\n}"
},
"id": "scraper-node-003",
"name": "\ud83d\udd77\ufe0f Scraper Multi-Sites",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
520,
300
]
},
{
"parameters": {
"jsCode": "// CLASSIFICATION MISTRAL LARGE - OPTIMIS\u00c9E POUR ALTERNANCES CYBERS\u00c9CURIT\u00c9\n\n// Configuration avec mod\u00e8le performant\nconst config = {\n apiKey: process.env.MISTRAL_API_KEY || 'fe8GdBIIBwYk8Dj1GvclASPE3j0Zbt95',\n apiUrl: 'https://api.mistral.ai/v1/chat/completions',\n model: 'mistral-large-latest', // Mod\u00e8le le plus performant\n temperature: 0.1,\n max_tokens: 200\n};\n\nconst offre = $input.item.json;\n\nconsole.log(`\ud83e\udd16 === CLASSIFICATION MISTRAL LARGE: ${offre.title} ===`);\nconsole.log('\ud83c\udfe2 Entreprise:', offre.company);\nconsole.log('\ud83d\udccd Source:', offre.source);\nconsole.log('\ud83d\udd17 URL:', offre.url);\n\n// Prompt expert optimis\u00e9 pour la d\u00e9tection d'alternances cybers\u00e9curit\u00e9\nconst prompt = `Tu es un expert RH sp\u00e9cialis\u00e9 en cybers\u00e9curit\u00e9 et alternance.\n\nAnalyse cette offre d'emploi :\n\n**TITRE:** ${offre.title}\n**ENTREPRISE:** ${offre.company}\n**DESCRIPTION:** ${offre.description || 'Non sp\u00e9cifi\u00e9e'}\n**LOCALISATION:** ${offre.location}\n**MOTS-CL\u00c9S:** ${offre.keywords ? offre.keywords.join(', ') : 'Aucun'}\n\n**MISSION:** D\u00e9termine si cette offre correspond EXACTEMENT \u00e0 une ALTERNANCE en CYBERS\u00c9CURIT\u00c9.\n\n**CRIT\u00c8RES OBLIGATOIRES:**\n\n1. **TYPE DE CONTRAT:**\n \u2705 ALTERNANCE (contrat d'apprentissage ou professionnalisation)\n \u274c Stage, CDI, CDD, freelance, mission\n\n2. **DOMAINE CYBERS\u00c9CURIT\u00c9:**\n \u2705 S\u00e9curit\u00e9 informatique, SOC, SIEM, pentest, audit s\u00e9curit\u00e9, forensic, GRC s\u00e9curit\u00e9\n \u2705 Analyste s\u00e9curit\u00e9, ing\u00e9nieur cybers\u00e9curit\u00e9, consultant s\u00e9curit\u00e9\n \u274c D\u00e9veloppement web, marketing, RH, comptabilit\u00e9\n\n3. **NIVEAU:**\n \u2705 Junior, d\u00e9butant, \u00e9tudiant, formation\n \u274c Senior, expert, 5+ ans d'exp\u00e9rience\n\n**ANALYSE:**\nExamine attentivement le titre et la description.\nRecherche les mots-cl\u00e9s sp\u00e9cifiques : \"alternance\", \"apprentissage\", \"cybers\u00e9curit\u00e9\", \"s\u00e9curit\u00e9 informatique\".\n\n**R\u00c9PONSE:**\nR\u00e9ponds UNIQUEMENT par:\n- **VALIDE** si l'offre respecte TOUS les crit\u00e8res\n- **INVALIDE** si un seul crit\u00e8re n'est pas respect\u00e9\n\nAjoute en une ligne la raison principale de ton choix.`;\n\n// Payload optimis\u00e9\nconst payload = {\n model: config.model,\n messages: [\n {\n role: \"system\",\n content: \"Tu es un expert RH sp\u00e9cialis\u00e9 en cybers\u00e9curit\u00e9. Tu analyses les offres d'emploi avec une pr\u00e9cision chirurgicale pour identifier les vraies alternances en cybers\u00e9curit\u00e9.\"\n },\n {\n role: \"user\", \n content: prompt\n }\n ],\n temperature: config.temperature,\n max_tokens: config.max_tokens\n};\n\nconsole.log('\ud83d\udce6 Payload Mistral Large pr\u00e9par\u00e9');\nconsole.log('\ud83c\udfaf Mod\u00e8le utilis\u00e9:', config.model);\nconsole.log('\ud83d\udccf Taille prompt:', prompt.length, 'caract\u00e8res');\n\ntry {\n // V\u00e9rification fetch\n const fetchFn = typeof fetch !== 'undefined' ? fetch : \n (typeof globalThis.fetch !== 'undefined' ? globalThis.fetch : null);\n \n if (!fetchFn) {\n throw new Error('Fetch API non disponible');\n }\n\n console.log('\ud83c\udf10 Appel API Mistral Large...');\n \n const response = await fetchFn(config.apiUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`\n },\n body: JSON.stringify(payload)\n });\n\n console.log('\ud83d\udcca Status HTTP:', response.status);\n\n if (!response.ok) {\n const errorText = await response.text();\n console.log('\u274c Erreur API Mistral:', response.status, errorText);\n \n return {\n json: {\n ...offre,\n mistral_response: 'ERREUR_API',\n classification: 'ERREUR',\n is_valid: false,\n confidence: 0,\n error: `HTTP ${response.status}: ${errorText}`,\n model_used: config.model,\n processed_at: new Date().toISOString()\n }\n };\n }\n\n const data = await response.json();\n console.log('\ud83d\udce5 R\u00e9ponse Mistral Large re\u00e7ue');\n\n if (data?.choices?.[0]?.message?.content) {\n const content = data.choices[0].message.content.trim();\n \n console.log('\u2705 === MISTRAL LARGE SUCCESS ===');\n console.log('\ud83d\udcdd R\u00e9ponse compl\u00e8te:', content);\n \n // Analyse intelligente de la r\u00e9ponse\n const contentUpper = content.toUpperCase();\n let classification, isValid, confidence, raison;\n \n // Extraction de la raison\n const lignes = content.split('\\n').filter(l => l.trim());\n raison = lignes.length > 1 ? lignes[1] : 'Analyse automatique';\n \n if (contentUpper.includes('VALIDE') && !contentUpper.includes('INVALIDE')) {\n classification = 'VALIDE';\n isValid = true;\n confidence = 0.95; // Confiance \u00e9lev\u00e9e avec Mistral Large\n console.log('\u2705 OFFRE VALID\u00c9E - Alternance cybers\u00e9curit\u00e9 confirm\u00e9e');\n } else if (contentUpper.includes('INVALIDE')) {\n classification = 'INVALIDE';\n isValid = false;\n confidence = 0.95;\n console.log('\u274c OFFRE REJET\u00c9E - Ne correspond pas aux crit\u00e8res');\n } else {\n classification = 'INCERTAIN';\n isValid = false;\n confidence = 0.3;\n console.log('\u26a0\ufe0f R\u00e9ponse ambigu\u00eb de Mistral Large');\n }\n\n console.log('\ud83c\udfaf Classification finale:', classification);\n console.log('\ud83d\udcad Raison:', raison);\n console.log('\ud83d\udcca Usage tokens:', JSON.stringify(data.usage || {}));\n\n return {\n json: {\n ...offre,\n mistral_response: content,\n classification: classification,\n is_valid: isValid,\n confidence: confidence,\n raison: raison,\n model_used: data.model || config.model,\n usage: data.usage || {},\n processed_at: new Date().toISOString(),\n method: 'mistral_large_expert'\n }\n };\n\n } else {\n console.log('\u274c Structure r\u00e9ponse Mistral invalide');\n \n return {\n json: {\n ...offre,\n mistral_response: 'STRUCTURE_INVALIDE',\n classification: 'ERREUR',\n is_valid: false,\n confidence: 0,\n error: 'Structure r\u00e9ponse Mistral invalide',\n model_used: config.model,\n processed_at: new Date().toISOString()\n }\n };\n }\n\n} catch (error) {\n console.log('\ud83d\udea8 === ERREUR CRITIQUE ===');\n console.log('Type:', error.constructor.name);\n console.log('Message:', error.message);\n \n return {\n json: {\n ...offre,\n mistral_response: 'ERREUR_RESEAU',\n classification: 'ERREUR',\n is_valid: false,\n confidence: 0,\n error: `${error.constructor.name}: ${error.message}`,\n model_used: config.model,\n processed_at: new Date().toISOString()\n }\n };\n}"
},
"id": "mistral-node-004",
"name": "\ud83e\udde0 Classification Mistral Large",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
740,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "condition-valid-id",
"leftValue": "={{ $json.classification }}",
"rightValue": "VALIDE",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "filter-node-005",
"name": "\u2705 Offre Valide ?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
960,
300
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "valid-action-id",
"name": "action",
"value": "OFFRE_ALTERNANCE_ACCEPTEE",
"type": "string"
},
{
"id": "valid-message-id",
"name": "message",
"value": "\ud83c\udfaf Alternance cybers\u00e9curit\u00e9 VALID\u00c9E !",
"type": "string"
},
{
"id": "valid-score-id",
"name": "score_pertinence",
"value": "={{ $json.confidence }}",
"type": "number"
}
]
},
"options": {}
},
"id": "accept-node-006",
"name": "\ud83c\udfaf Traitement Offre Valide",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1180,
180
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "invalid-action-id",
"name": "action",
"value": "OFFRE_REJETEE",
"type": "string"
},
{
"id": "invalid-message-id",
"name": "message",
"value": "\u274c Ne correspond pas: {{ $json.raison || 'Crit\u00e8res non respect\u00e9s' }}",
"type": "string"
},
{
"id": "invalid-reason-id",
"name": "raison_rejet",
"value": "={{ $json.raison }}",
"type": "string"
}
]
},
"options": {}
},
"id": "reject-node-007",
"name": "\u274c Traitement Offre Invalide",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1180,
420
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "notify-condition-id",
"leftValue": "={{ $json.action }}",
"rightValue": "OFFRE_ALTERNANCE_ACCEPTEE",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "notify-filter-008",
"name": "\ud83d\udd14 Notification ?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1400,
180
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "notif-id",
"name": "notification",
"value": "\ud83d\udea8 ALERTE: Nouvelle alternance cybers\u00e9curit\u00e9 trouv\u00e9e !\n\n\ud83d\udccb Titre: {{ $json.title }}\n\ud83c\udfe2 Entreprise: {{ $json.company }}\n\ud83d\udccd Lieu: {{ $json.location }}\n\ud83c\udf10 Source: {{ $json.source }}\n\ud83c\udfaf Score: {{ $json.score_pertinence }}\n\ud83d\udd17 Lien: {{ $json.url }}\n\n\u2705 Valid\u00e9e par Mistral Large",
"type": "string"
}
]
},
"options": {}
},
"id": "notification-009",
"name": "\ud83d\udcec Pr\u00e9parer Notification",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1620,
180
]
}
],
"connections": {
"\ud83d\ude80 Start": {
"main": [
[
{
"node": "\u2699\ufe0f Configuration Recherche",
"type": "main",
"index": 0
}
]
]
},
"\u2699\ufe0f Configuration Recherche": {
"main": [
[
{
"node": "\ud83d\udd77\ufe0f Scraper Multi-Sites",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd77\ufe0f Scraper Multi-Sites": {
"main": [
[
{
"node": "\ud83e\udde0 Classification Mistral Large",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udde0 Classification Mistral Large": {
"main": [
[
{
"node": "\u2705 Offre Valide ?",
"type": "main",
"index": 0
}
]
]
},
"\u2705 Offre Valide ?": {
"main": [
[
{
"node": "\ud83c\udfaf Traitement Offre Valide",
"type": "main",
"index": 0
}
],
[
{
"node": "\u274c Traitement Offre Invalide",
"type": "main",
"index": 0
}
]
]
},
"\ud83c\udfaf Traitement Offre Valide": {
"main": [
[
{
"node": "\ud83d\udd14 Notification ?",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd14 Notification ?": {
"main": [
[
{
"node": "\ud83d\udcec Pr\u00e9parer Notification",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "workflow-alternance-v1",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "alternance-cybersec-workflow",
"tags": [
"alternance",
"cybers\u00e9curit\u00e9",
"scraping",
"mistral"
]
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
🔍 Recherche Alternance Cybersécurité - Complet. Uses start. Manual trigger; 9 nodes.
Source: https://github.com/bigmoletos/recherche_offre_emploi/blob/b4c4d2d0b97c534167365e440130f27c98586c1c/agent_n8n/workflows/old/workflow_alternance_complet.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.
NextCloud:NextCloud:Folder:create move copy delete list:File:upload move copy download delete. Uses start, nextCloud, readBinaryFile. Manual trigger; 28 nodes.
Transporeon - orders - step 3 - process single. Uses start, functionItem, httpRequest, microsoftSql. Manual trigger; 26 nodes.
Orbit:Member:upsert get update delete getAll lookup:Note:create update getAll:Activity:create getAll:Post:create getAll delete. Uses start, orbit. Manual trigger; 16 nodes.
Raindrop:User:get:Collection:create get update getAll delete:Bookmark:create get update getAll delete:Tag:getAll delete. Uses start, raindrop. Manual trigger; 14 nodes.
Backup workflows & credentials. Uses start, executeCommand. Manual trigger; 13 nodes.