Skip to content
AI Content Repurposer

Module 10 · ai · intermediate

Lesson 10 · AI Content Repurposer

Goal

Use an LLM as a reliable function: text in, strictly structured JSON out. You'll build the engine that turns each module's blog post into short-video scripts and social posts, the content side of "learn → demo → sell".

Prerequisites

  • Module 06 (OpenAI credential), Module 02 (HTTP Request)

Concepts

  • Switch: like IF, but with any number of named outputs, plus a fallback for "none matched".
  • HTML → Markdown: fetched pages are mostly markup. Converting (and ignoring script, style, nav, footer…) gives the model clean text and saves tokens.
  • Basic LLM Chain vs AI Agent: a chain is a single prompt → answer, with no tools and no loop. Use it whenever the task is fixed.
  • Structured Output Parser: give a JSON example; n8n tells the model the schema and parses the result into $json.output. Downstream nodes get real objects and arrays, not a string to parse.
  • Prompt as spec: audience, voice rules, banned words, hard limits (words, characters, counts), and "use only facts from the article". The test checks these limits automatically.

Build it

Step 1: Input

  1. Webhook Repurpose API: POST, path repurpose, respond via node.
  2. Switch Input Type (Rules mode), with renamed outputs:
    • url: {{ String($json.body.url ?? '') }} matches regex ^https?://
    • markdown: {{ String($json.body.markdown ?? '').trim().length }} ≥ 200
    • Options → Fallback Output: Extra Output, renamed invalid → Respond to Webhook 400.

Step 2: Get clean text

  • url → HTTP Request Fetch Article (GET {{ $json.body.url }}, Response Format Text, timeout 15s) → Markdown HTML to Markdown (HTML {{ $json.data }}, destination markdown, Options → Ignore: script,style,nav,footer,header,aside,form,svg) → Edit Fields Source from URL: source_text = {{ $json.markdown.slice(0, 15000) }}, source = the URL.
  • markdown → Edit Fields Source from Markdown: source_text = {{ $json.body.markdown.slice(0, 15000) }}, source = pasted markdown.

Step 3: Write

Basic LLM Chain Write Content, prompt Define below:

  • Audience and CTA from the request (with defaults)
  • Voice: practitioner sharing what they built; concrete; no hype words
  • Hooks under 12 words; only facts from the article
  • Exactly 3 shorts with different angles; 80–120-word scripts; LinkedIn 120–200 words; X thread 4–6 posts under 270 characters; newsletter subject under 60 characters
  • ARTICLE: followed by {{ $json.source_text }}

Turn on Require Specific Output Format → connect Structured Output Parser Content Format (Generate From JSON Example) with an example containing shorts[] (angle, hook, script, on_screen, caption), linkedin_post, x_thread[], newsletter {subject, blurb}. Connect OpenAI Chat Model (gpt-5-mini).

Step 4: Respond

Respond to Webhook: {{ { source: $("Source from URL").isExecuted ? $("Source from URL").item.json.source : "pasted markdown", generated_at: $now.toISO(), ...$json.output } }}

Test it

./test/test.sh                 # input validation
WITH_CREDS=1 ./test/test.sh    # repurpose Module 01's blog + a URL, checking counts and length limits

Use it for the course:

jq -n --rawfile md modules/02-api-digest/sell/blog.md '{markdown: $md}' \
  | curl -s -X POST http://localhost:5678/webhook/repurpose -H 'Content-Type: application/json' -d @- | jq .

Common problems

  • "Could not parse LLM output": the model returned extra text. Keep the JSON example small and valid. Exercise 3 adds automatic fixing.
  • Hooks too long / thread posts over 280 chars: limits in the prompt aren't guarantees. The test catches it; tighten the wording or validate and retry (Exercise 3).
  • URL returns garbage: some sites need JavaScript or block bots. Paste the markdown instead.
  • Hallucinated stats: keep "use only facts from the article" and review before posting.