Skip to main content
Guides Scripts Library
Updated: September 2026 · updated biweekly

Scripts Library

Ready-made scripts and automations — n8n workflows, Python code for calling models and RAG, and Shell commands. Copy, download and run them yourself. All free.

— scripts

How to use

1. Download / copy

Every script can be copied or downloaded as a ready file (‎.json / .py / .sh‎).

2. Add your keys

Replace YOUR_API_KEY and the bracketed [details] with your own.

3. Run

Import n8n workflows via Import from File; run Python/Shell directly.

Safety: never put an API key into code you've shared. Use environment variables (OPENAI_API_KEY) or a .env file. Model IDs move fast, so the examples use [MODEL_ID] as a placeholder — fill in whichever model you are actually entitled to call, taken from your provider's own model list rather than from memory.

n8n Workflows

Webhook → AI → reply
n8nJSON
{
  "name": "Webhook to AI Reply",
  "nodes": [
    { "parameters": { "httpMethod": "POST", "path": "ai-reply" },
      "name": "Webhook", "type": "n8n-nodes-base.webhook",
      "typeVersion": 1, "position": [400, 300] },
    { "parameters": { "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST", "sendHeaders": true,
        "headerParameters": { "parameters": [
          { "name": "Authorization", "value": "Bearer YOUR_API_KEY" },
          { "name": "Content-Type", "value": "application/json" } ] },
        "sendBody": true, "specifyBody": "json",
        "jsonBody": "={\"model\":\"[MODEL_ID]\",\"messages\":[{\"role\":\"user\",\"content\":$json.body.message}]}" },
      "name": "OpenAI", "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4, "position": [640, 300] },
    { "parameters": { "respondWith": "json",
        "responseBody": "={{ $json.choices[0].message.content }}" },
      "name": "Respond", "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1, "position": [880, 300] }
  ],
  "connections": {
    "Webhook": { "main": [[{ "node": "OpenAI", "type": "main", "index": 0 }]] },
    "OpenAI": { "main": [[{ "node": "Respond", "type": "main", "index": 0 }]] }
  }
}
Scheduled — daily digest to email
n8nJSON
{
  "name": "Daily Digest to Email",
  "nodes": [
    { "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 8 * * *" }] } },
      "name": "Every day 08:00", "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1, "position": [400, 300] },
    { "parameters": { "url": "[YOUR_DATA_SOURCE_URL]" },
      "name": "Fetch Data", "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4, "position": [640, 300] },
    { "parameters": { "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST", "sendHeaders": true,
        "headerParameters": { "parameters": [
          { "name": "Authorization", "value": "Bearer YOUR_API_KEY" } ] },
        "sendBody": true, "specifyBody": "json",
        "jsonBody": "={\"model\":\"[MODEL_ID]\",\"messages\":[{\"role\":\"user\",\"content\":\"Summarize to 5 points: \"+ $json.data}]}" },
      "name": "Summarize", "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4, "position": [880, 300] },
    { "parameters": { "toEmail": "[YOUR_EMAIL]", "subject": "Daily Digest",
        "text": "={{ $json.choices[0].message.content }}" },
      "name": "Send Email", "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2, "position": [1120, 300] }
  ],
  "connections": {
    "Every day 08:00": { "main": [[{ "node": "Fetch Data", "type": "main", "index": 0 }]] },
    "Fetch Data": { "main": [[{ "node": "Summarize", "type": "main", "index": 0 }]] },
    "Summarize": { "main": [[{ "node": "Send Email", "type": "main", "index": 0 }]] }
  }
}

Python — AI

OpenAI — chat call
PythonOpenAI
# pip install openai   |   export OPENAI_API_KEY="sk-..."
from openai import OpenAI

client = OpenAI()  # reads OPENAI_API_KEY from env

resp = client.chat.completions.create(
    model="[MODEL_ID]",
    messages=[
        {"role": "system", "content": "Answer concisely."},
        {"role": "user", "content": "[your request here]"},
    ],
)
print(resp.choices[0].message.content)
Claude — message call
PythonAnthropic
# pip install anthropic   |   export ANTHROPIC_API_KEY="sk-ant-..."
import anthropic

client = anthropic.Anthropic()

msg = client.messages.create(
    model="[MODEL_ID]",
    max_tokens=1024,
    messages=[{"role": "user", "content": "[your request here]"}],
)
print(msg.content[0].text)
Batch processing of files
PythonBatch
# Process every text file in a folder with a cheap model
import os, glob
from openai import OpenAI

client = OpenAI()

for path in glob.glob("input/*.txt"):
    text = open(path, encoding="utf-8").read()
    resp = client.chat.completions.create(
        model="[MODEL_ID]",  # for high volume consider a cheaper model
        messages=[{"role": "user", "content": f"Summarize in 3 points:\n{text}"}],
    )
    out = path.replace("input/", "output/")
    os.makedirs("output", exist_ok=True)
    open(out, "w", encoding="utf-8").write(resp.choices[0].message.content)
    print("done:", out)
RAG — query over documents
PythonRAG
# Minimal RAG: embed -> retrieve -> answer
from openai import OpenAI
import numpy as np

client = OpenAI()
docs = ["[document 1]", "[document 2]", "[document 3]"]

def embed(texts):
    r = client.embeddings.create(model="text-embedding-3-small", input=texts)
    return np.array([e.embedding for e in r.data])

doc_vecs = embed(docs)

def ask(question):
    q = embed([question])[0]
    sims = doc_vecs @ q
    top = docs[int(sims.argmax())]  # the most relevant passage
    r = client.chat.completions.create(
        model="[MODEL_ID]",
        messages=[{"role": "system", "content": f"Answer only from:\n{top}\nIf there is no answer, say 'I don't know'."},
                  {"role": "user", "content": question}],
    )
    return r.choices[0].message.content

print(ask("[your question]"))

Shell / CLI

Run a local model (Ollama)
Shelllocal · free
# Install Ollama from https://ollama.com , then:
ollama run qwen3            # interactive chat, runs locally for free

# or via the local API (port 11434):
curl http://localhost:11434/api/generate -d '{
  "model": "qwen3",
  "prompt": "Write a Python function that returns Fibonacci numbers",
  "stream": false
}'
Call OpenAI from the CLI (curl)
Shellcurl
export OPENAI_API_KEY="sk-..."

curl https://api.openai.com/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "[MODEL_ID]",
    "messages": [{"role": "user", "content": "[your request]"}]
  }'

Taking a snippet to production

Everything above is deliberately short. A script you can read in one screen is a script you can understand before you run it, and that is the point of a library like this one. But short means things have been left out, and the things left out are precisely what determines whether the script survives its second week. What follows is what to add, in the order it usually starts to matter.

Keys: the rules that are not negotiable

The safety note at the top says use environment variables. Here is what that means in practice, because the failure modes are specific.

A key pasted into an n8n node header is stored in the workflow JSON. If you then export that workflow to share it — which is exactly what this page encourages — the key travels with it. n8n has a credentials system for this reason: create the credential once, reference it from the node, and the exported JSON carries the reference rather than the secret. It takes two extra minutes and removes an entire category of accident.

For scripts, .env plus a library that loads it is the standard answer, with one condition that gets forgotten: .env must be in .gitignore before the file exists, not after. Git does not forget, and a key committed once is a key that lives in the history whether or not the next commit removes it. If it happens anyway, the only real fix is to revoke the key at the provider and issue a new one — deleting the line does nothing, because anyone with the repository still has the old commit.

Scope keys down where the provider allows it. A key that can only call one model, with a spending cap attached, is a much smaller problem when it does leak. And put a calendar reminder to rotate them; keys that were created for a weekend experiment have a way of still being in production two years later.

Retries, and why the naive loop makes things worse

None of the snippets here handle a failed call, because handling it properly would double their length. In a one-off run that is fine — it fails, you see it, you run it again. In anything scheduled or user-facing it is not, and the reason is that API errors are overwhelmingly transient: a rate limit, a brief overload, a connection reset. Retrying is almost always the right response.

Retrying immediately is almost always the wrong one. If you got a rate limit, the thing that caused it is that you were sending too fast, and an instant retry sends faster. Back off exponentially — wait a second, then two, then four — and add a small random jitter to each wait. The jitter matters more than it sounds: without it, twenty parallel workers that all hit the same limit will all wake up at the same instant and hit it again together, which is a thundering herd of your own making.

Distinguish what is worth retrying. A rate limit, a timeout, a 5xx: retry. A malformed request, an invalid key, a model name that does not exist: retrying changes nothing and just burns time — fail loudly instead, because these are bugs and you want to see them. Cap the attempts at three or four and set a total deadline, so that a genuinely broken dependency fails in a minute rather than spinning quietly for an hour.

Set an explicit timeout on every HTTP call as well. The default in most clients is either very long or absent, and a request that hangs forever is worse than one that fails, because nothing alerts on it.

Scheduled jobs run more than once

The daily digest workflow above assumes it runs once a day. It will not. Sooner or later it runs twice — you tested it manually, or the schedule fired during a restart, or someone clicked execute to check something. If the job sends an email, that means two emails. If it writes to a database, two rows. If it posts to a channel, a duplicate that everyone sees.

The fix is to make the job idempotent: safe to run twice with the same result as running once. Usually this means deriving a key from the work itself — the date for a daily digest, the source record's id for an extraction — and checking whether that key has already been processed before doing anything with a side effect. A single small table, or even a file of keys, is enough. It is the cheapest insurance in automation and it is almost never there until after the first duplicate.

Two related habits. Give any scheduled job a way to run against yesterday's data on demand, so a missed run can be filled in without editing the code. And make it fail visibly: a job that errors silently at 08:00 every morning can be broken for a fortnight before anyone notices the digest stopped arriving, because absence is much harder to spot than a wrong answer.

Cost: the runaway is a loop, not a big prompt

A single model call is cheap enough that it is not worth thinking about. The bills that surprise people come from multiplication — a script that loops over ten thousand rows, an agent that retries without a cap, a webhook someone else can trigger, a scheduled job that was meant to be daily and is set to every minute because of a cron typo.

So before you run anything in a loop, do the arithmetic once, by hand: rows times calls per row times roughly what a call costs. If the number surprises you, that is the moment to find out, not after the run. Then test on ten rows, look at the reported usage, and multiply that measured number rather than your estimate.

Use the provider's spending limits — they exist, they are per-key, and they turn an unbounded mistake into a bounded one. Log token usage alongside your results rather than only the text, because a cost problem you cannot attribute to a particular job is a cost problem you cannot fix. And put a hard cap in your own code on anything that loops or recurses: a maximum number of iterations, checked and enforced, even where you are certain it cannot be reached.

A public webhook that calls a paid API deserves particular care. Without authentication and a rate limit, the cost of running it is set by whoever finds the URL.

Choosing between a workflow and a script

This page offers both, and which one fits is mostly a question about who maintains it rather than what it does.

An n8n workflow is the better choice when the job is mostly gluing services together, when the schedule and the connections are the substance, when someone non-technical may need to see or adjust it, and when you want retries, logging and credential storage without writing them. The visual history of executions is genuinely useful for debugging, and it is there without any effort on your part.

A script is the better choice when there is real logic — branching, transformation, anything that would become a dozen awkward nodes — when you want it in version control with a proper diff and review, when it needs tests, or when it has to run somewhere n8n is not. Complex logic expressed as boxes and arrows is harder to read than the same logic in fifteen lines of code, not easier.

The combination is often best: n8n handles the trigger, the schedule and the delivery, and calls out to a script or an endpoint for the part that is actually complicated. That keeps the orchestration visible and the logic reviewable, which is the right split for both.

Read a workflow before you import it

This applies to anything you download — from here, from a community forum, from a template gallery. An n8n workflow is executable, and importing one is closer to running a script than to opening a document. Two minutes of reading the JSON first is a reasonable habit.

Three things are worth looking for specifically. Every URL the workflow calls: an HTTP node pointing at a host you do not recognise, in a workflow that is supposed to talk to one API, is the thing to notice. Any code or function node, since that runs arbitrary JavaScript on your instance and is where anything interesting would hide. And which credentials it expects — a workflow that asks for broader access than its stated job needs is asking for a reason.

The same reading answers a more mundane question: what will this actually do the first time it fires? A workflow that sends email or writes to a live system on import is best pointed at a test address first. Disconnect the final node, run it once, look at what came out of the step before, and only then wire the delivery back up.

Before it counts as finished

A handful of things separate a snippet that ran once from something you can leave alone. Log enough to reconstruct a run: inputs, the model's raw response, timing, token usage. Storing only the parsed result guarantees that the first weird output will be impossible to investigate. Keep a fixture — a saved input and the output you expect — and run it after any change to the prompt or the model, because prompt edits that look harmless are exactly the ones that break something two fields over.

Pin the model id explicitly rather than accepting a default, and treat changing it as a change worth testing. Handle the case where the model returns something unparseable, because it eventually will. And write down, in a comment at the top, what the script is for and who to ask about it. Six months later that comment is the difference between fixing it and rewriting it.

Want more?

The library is updated biweekly with new scripts. You'll find the prompts in the prompt library, and the full explanation in the n8n guide.