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": "Szybkie Kursiki - Course Generator (Simple)",
"nodes": [
{
"parameters": {},
"id": "manual-trigger",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
250,
300
]
},
{
"parameters": {
"url": "https://api.anthropic.com/v1/messages",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "anthropicApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "anthropic-version",
"value": "2023-06-01"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({\n model: 'claude-sonnet-4-5-20250929',\n max_tokens: 32000,\n messages: [\n {\n role: 'user',\n content: $json.coursePrompt\n }\n ],\n system: \"Jeste\u015b ekspertem w tworzeniu kurs\u00f3w edukacyjnych online. Twoim zadaniem jest wygenerowanie kompletnej struktury kursu w formacie JSON.\\n\\nKurs musi zawiera\u0107:\\n1. Podstawowe informacje o kursie (tytu\u0142, kr\u00f3tki opis, pe\u0142ny opis, ikona Font Awesome, tagi)\\n2. DOK\u0141ADNIE 3 lekcje (nie wi\u0119cej, nie mniej)\\n3. Ka\u017cda lekcja MUSI zawiera\u0107:\\n - Tytu\u0142\\n - Kolejno\u015b\u0107 (order)\\n - Tre\u015b\u0107 w formacie Markdown (content_markdown) - szczeg\u00f3\u0142owa, merytoryczna tre\u015b\u0107 lekcji z przyk\u0142adami kodu\\n - Quiz z 3-5 pytaniami wielokrotnego wyboru\\n - Zadanie praktyczne (practical_task) z przyk\u0142adami i rozwi\u0105zaniem\\n\\nFormat JSON:\\n```json\\n{\\n \\\"course\\\": {\\n \\\"title\\\": \\\"Nazwa kursu\\\",\\n \\\"short_description\\\": \\\"Kr\u00f3tki opis kursu (max 200 znak\u00f3w)\\\",\\n \\\"description\\\": \\\"Pe\u0142ny opis kursu - czego si\u0119 nauczysz, dla kogo jest kurs\\\",\\n \\\"icon\\\": \\\"fas fa-code\\\",\\n \\\"tags\\\": [\\\"Python\\\", \\\"Backend\\\", \\\"API\\\"]\\n },\\n \\\"lessons\\\": [\\n {\\n \\\"title\\\": \\\"Tytu\u0142 lekcji\\\",\\n \\\"order\\\": 0,\\n \\\"content_markdown\\\": \\\"# Wprowadzenie\\\\n\\\\nTre\u015b\u0107 lekcji...\\\",\\n \\\"quiz\\\": {\\n \\\"title\\\": \\\"Quiz - Tytu\u0142 lekcji\\\",\\n \\\"description\\\": \\\"Sprawd\u017a swoj\u0105 wiedz\u0119\\\",\\n \\\"questions\\\": [\\n {\\n \\\"text\\\": \\\"Tre\u015b\u0107 pytania?\\\",\\n \\\"order\\\": 0,\\n \\\"explanation\\\": \\\"Wyja\u015bnienie poprawnej odpowiedzi\\\",\\n \\\"answers\\\": [\\n {\\n \\\"text\\\": \\\"Odpowied\u017a 1\\\",\\n \\\"is_correct\\\": true,\\n \\\"order\\\": 0\\n },\\n {\\n \\\"text\\\": \\\"Odpowied\u017a 2\\\",\\n \\\"is_correct\\\": false,\\n \\\"order\\\": 1\\n }\\n ]\\n }\\n ]\\n },\\n \\\"practical_task\\\": {\\n \\\"title\\\": \\\"Zadanie praktyczne - Tytu\u0142\\\",\\n \\\"content_markdown\\\": \\\"# Zadanie\\\\n\\\\nOpis zadania do wykonania...\\\",\\n \\\"instructions_markdown\\\": \\\"## Instrukcje\\\\n\\\\n1. Krok 1\\\\n2. Krok 2\\\",\\n \\\"example_markdown\\\": \\\"## Przyk\u0142ad\\\\n\\\\n```python\\\\nprzyk\u0142adowy_kod()\\\\n```\\\",\\n \\\"hints_markdown\\\": \\\"## Wskaz\u00f3wki\\\\n\\\\n- Wskaz\u00f3wka 1\\\\n- Wskaz\u00f3wka 2\\\",\\n \\\"solution_markdown\\\": \\\"## Rozwi\u0105zanie\\\\n\\\\n```python\\\\nrozwiazanie_zadania()\\\\n```\\\\n\\\\nWyja\u015bnienie...\\\"\\n }\\n }\\n ]\\n}\\n```\\n\\nKRYTYCZNE WYMAGANIA JSON:\\n- Wszystkie cudzys\u0142owy w tre\u015bci markdown MUSZ\u0104 zosta\u0107 poprawnie escape'owane\\n- Znaki nowej linii w tre\u015bci markdown zapisuj jako \\\\n (podw\u00f3jny backslash + n)\\n- Upewnij si\u0119, \u017ce ka\u017cdy otwarty cudzys\u0142\u00f3w ma swoje zamkni\u0119cie\\n- Nie u\u017cywaj pojedynczych backslash przed cudzys\u0142owami w warto\u015bciach string\\n- Zweryfikuj, \u017ce JSON jest poprawnie sformatowany przed zwr\u00f3ceniem\\n\\nWA\u017bNE:\\n- Stw\u00f3rz DOK\u0141ADNIE 3 lekcje (nie wi\u0119cej, nie mniej)\\n- Tre\u015b\u0107 lekcji (content_markdown) powinna by\u0107 zwi\u0119z\u0142a ale merytoryczna (200-400 s\u0142\u00f3w)\\n- U\u017cywaj prawid\u0142owego formatowania Markdown\\n- Bloki kodu musz\u0105 mie\u0107 okre\u015blony j\u0119zyk (```python, ```javascript, itp.)\\n- Ka\u017cde pytanie w quizie musi mie\u0107 co najmniej 4 odpowiedzi\\n- Tylko jedna odpowied\u017a mo\u017ce by\u0107 poprawna (is_correct: true)\\n- W quizie dodaj 3-4 pytania (nie wi\u0119cej)\\n- Zadania praktyczne musz\u0105 by\u0107 konkretne ale zwi\u0119z\u0142e\\n- U\u017cywaj polskich znak\u00f3w (\u0105, \u0107, \u0119, \u0142, \u0144, \u00f3, \u015b, \u017a, \u017c)\\n\\nODPOWIED\u0179 WY\u0141\u0104CZNIE POPRAWNYM, ZWALIDOWANYM JSON-em w bloku ```json, bez dodatkowych komentarzy ani wyja\u015bnie\u0144.\"\n}) }}",
"options": {}
},
"id": "anthropic-request",
"name": "Call Claude API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
450,
300
],
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Extract JSON from Claude's response\nconst response = $input.first().json;\n\n// Get text from content array\nlet text = '';\nif (response.content && Array.isArray(response.content)) {\n text = response.content[0].text;\n} else {\n throw new Error('No content in Claude response');\n}\n\nconsole.log('Raw Claude response length:', text.length);\nconsole.log('First 300 chars:', text.substring(0, 300));\n\n// Function to repair common JSON issues\nfunction repairJSON(jsonStr) {\n // Remove any text before the first { or [\n const start = jsonStr.search(/[{\\[]/);\n if (start > 0) {\n jsonStr = jsonStr.substring(start);\n }\n \n // Remove any text after the last } or ]\n const end = jsonStr.lastIndexOf('}') > jsonStr.lastIndexOf(']') \n ? jsonStr.lastIndexOf('}') + 1 \n : jsonStr.lastIndexOf(']') + 1;\n if (end > 0) {\n jsonStr = jsonStr.substring(0, end);\n }\n \n return jsonStr;\n}\n\n// Try to parse the response\nlet courseData;\nlet jsonText = '';\n\ntry {\n // Check if response contains markdown code blocks\n const jsonMatch = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n \n if (jsonMatch) {\n // Extract JSON from code block\n jsonText = jsonMatch[1].trim();\n console.log('Extracted JSON from code block, length:', jsonText.length);\n } else {\n // Try to parse directly\n jsonText = text.trim();\n console.log('Using raw text as JSON');\n }\n \n // Try to repair common JSON issues\n jsonText = repairJSON(jsonText);\n \n // Attempt to parse\n try {\n courseData = JSON.parse(jsonText);\n console.log('Successfully parsed JSON');\n } catch (parseError) {\n console.error('Initial parse failed:', parseError.message);\n \n // Log context around the error\n if (parseError.message.includes('position')) {\n const match = parseError.message.match(/position (\\d+)/);\n if (match) {\n const pos = parseInt(match[1]);\n const start = Math.max(0, pos - 100);\n const end = Math.min(jsonText.length, pos + 100);\n console.error('\\n=== Context around error position ===');\n console.error('Position:', pos);\n console.error('Context:', jsonText.substring(start, end));\n console.error('Character at position:', jsonText[pos]);\n console.error('=====================================\\n');\n }\n }\n \n // Try one more time with more aggressive cleaning\n console.log('Attempting aggressive JSON repair...');\n let cleaned = jsonText\n .replace(/\\\\n/g, '\\\\\\\\n') // Double escape newlines\n .replace(/([^\\\\])\"([^\":,\\]}\\s])/g, '$1\\\\\"$2'); // Escape unescaped quotes\n \n try {\n courseData = JSON.parse(cleaned);\n console.log('Successfully parsed with aggressive repair');\n } catch (finalError) {\n // Save the problematic JSON to help debug\n console.error('\\n=== FULL PROBLEMATIC JSON ===');\n console.error(jsonText);\n console.error('=============================\\n');\n throw new Error(`JSON parsing failed after repair attempts: ${parseError.message}`);\n }\n }\n \n // Validate required fields\n if (!courseData.course || !courseData.course.title) {\n throw new Error('Missing course title');\n }\n \n if (!courseData.lessons || courseData.lessons.length === 0) {\n throw new Error('No lessons provided');\n }\n \n // Ensure all lessons have required fields\n courseData.lessons.forEach((lesson, index) => {\n if (!lesson.title) {\n throw new Error(`Lesson ${index} missing title`);\n }\n if (lesson.order === undefined) {\n lesson.order = index;\n }\n if (!lesson.content_markdown) {\n throw new Error(`Lesson ${index} missing content_markdown`);\n }\n });\n \n console.log('Successfully validated course data');\n console.log('Course:', courseData.course.title);\n console.log('Lessons:', courseData.lessons.length);\n \n return {\n json: courseData\n };\n \n} catch (error) {\n throw new Error(`Failed to parse Claude response: ${error.message}\\n\\nCheck execution logs for full details.`);\n}"
},
"id": "code-parser",
"name": "Parse Claude Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
650,
300
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.DJANGO_APP_URL.replace(/\\/$/, '') }}/api/import-course/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Import-Token",
"value": "={{ $env.COURSE_IMPORT_TOKEN }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json) }}",
"options": {}
},
"id": "http-import",
"name": "Import Course to Django",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
850,
300
]
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.status }}",
"operation": "equals",
"value2": "ok"
}
]
}
},
"id": "check-success",
"name": "Check Import Success",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1050,
300
]
},
{
"parameters": {
"values": {
"string": [
{
"name": "status",
"value": "success"
},
{
"name": "message",
"value": "Kurs zosta\u0142 pomy\u015blnie utworzony!"
},
{
"name": "course_title",
"value": "={{ $json.course_title }}"
},
{
"name": "course_slug",
"value": "={{ $json.course_slug }}"
},
{
"name": "lessons_count",
"value": "={{ $json.lessons_count }}"
},
{
"name": "admin_url",
"value": "={{ $env.DJANGO_APP_URL }}{{ $json.admin_url }}"
},
{
"name": "preview_url",
"value": "={{ $env.DJANGO_APP_URL }}/course/{{ $json.course_slug }}/"
}
]
},
"options": {}
},
"id": "success-output",
"name": "Success Output",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
1250,
200
]
},
{
"parameters": {
"values": {
"string": [
{
"name": "status",
"value": "error"
},
{
"name": "message",
"value": "Nie uda\u0142o si\u0119 zaimportowa\u0107 kursu"
},
{
"name": "error",
"value": "={{ $json.error || 'Unknown error' }}"
}
]
},
"options": {}
},
"id": "error-output",
"name": "Error Output",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
1250,
400
]
}
],
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "Call Claude API",
"type": "main",
"index": 0
}
]
]
},
"Call Claude API": {
"main": [
[
{
"node": "Parse Claude Response",
"type": "main",
"index": 0
}
]
]
},
"Parse Claude Response": {
"main": [
[
{
"node": "Import Course to Django",
"type": "main",
"index": 0
}
]
]
},
"Import Course to Django": {
"main": [
[
{
"node": "Check Import Success",
"type": "main",
"index": 0
}
]
]
},
"Check Import Success": {
"main": [
[
{
"node": "Success Output",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Output",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [],
"triggerCount": 1,
"updatedAt": "2026-02-02T00:00:00.000Z",
"versionId": "1"
}
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.
anthropicApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Szybkie Kursiki - Course Generator (Simple). Uses httpRequest. Event-driven trigger; 7 nodes.
Source: https://github.com/Sul3j/szybkie-kursiki/blob/7a41c67048c38e3d720c52f72553d5ea0cd49935/n8n-workflows/course-generator-simple.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 workflow allows you to import any workflow from a file or another n8n instance and map the credentials easily. A multi-form setup guides you through the entire process At the beginning you have t
[n8n] Advanced URL Parsing and Shortening Workflow - Switchy.io Integration. Uses splitInBatches, stickyNote, httpRequest, html. Event-driven trigger; 56 nodes.
[](https://youtu.be/c7yCZhmMjtI)
This automation organizes your n8n workflows files into categorizes (Active, Template, Done, Archived) and uploads them directly to a categorized Google Drive folders. It is designed to help users man
Create Animated Stories using GPT-4o-mini, Midjourney, Kling and Creatomate API. Uses httpRequest. Event-driven trigger; 51 nodes.