TreeMap
무료 오픈소스 디스크 사용량 시각화 도구로, 사각형 트리맵·중복 파일 탐색·디스크 가득 예측·정리 시 파일을 휴지통으로만 이동한다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Affordable AI workflow automation platform with visual workflow builder, triggers, AI integrations, background execution, authentication, and subscriptions.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
RXJ is a modern AI-native workflow automation platform inspired by tools like n8n, Zapier, and Make.com. It enables users to visually build, automate, and execute workflows using AI models, APIs, messaging systems, productivity tools, webhooks, and realtime execution pipelines.
The platform is designed as a scalable SaaS product with:README 전문 보기 ↗RXJ is a modern AI-native workflow automation platform inspired by tools like n8n, Zapier, and Make.com. It enables users to visually build, automate, and execute workflows using AI models, APIs, messaging systems, productivity tools, webhooks, and realtime execution pipelines.
The platform is designed as a scalable SaaS product with:
* * * * * clock matching system){{variable}} interpolation using Handlebars, plus n8n-style {{ $json.field }} expressionsThe Wait node pauses a run and continues it later. While it is paused nothing runs and no request is held open: the pause is an Inngest step.sleepUntil or step.waitForEvent, and the execution has the status Waiting.
| Resume | The run continues |
|---|---|
| After Time Interval | after an amount of seconds, minutes, hours or days |
| At Specified Time | at a date and time (ISO, e.g. 2026-12-24T09:00:00Z; no time zone means UTC). A time in the past does not wait |
| On Webhook Call | when the execution's resume URL is called (GET, POST, PUT or PATCH) |
| On Form Submitted | when the form at {{execution.formUrl}} is submitted (see "Forms"). The answers are at {{wait.form.<field>}} |
<app>/api/webhooks/wait/<executionId>/<token>. The token is an HMAC of the execution id made with ENCRYPTION_KEY, so the URL cannot be guessed and nothing is stored for it. It is available from the first node on as {{execution.resumeUrl}} (or {{ $execution.resumeUrl }}), so a node before the Wait node can send it out, for example as an approval link in an email. Whoever has the URL can continue that one execution, so send it only to who should decide.wait): {{wait.body}}, {{wait.query}}, {{wait.headers}}, {{wait.method}}, and {{wait.resumedBy}} (webhook, timeout or timer).{{wait.timedOut}} is true) or Fail the workflow. Without a timeout it waits the longest the server allows.WAIT_MAX_DAYS on a plan that allows more.409 while the execution is not waiting for a call (it has not reached the Wait node yet, was already resumed, or has finished) and 404 for a wrong token. One URL serves every Wait node of the execution, one call per wait.[REDACTED] in stored node input and output, and "All Workflow Data" webhook responses leave the URL out.The Send and Wait for Approval node asks a person a yes/no question in a normal workflow and pauses until they answer. It sends a title and optional details with the same signed, single-use, expiring Approve / Reject links as the AI Agent's tool approvals (see "Human approval for tools"), through the same channels: the app's Approvals page, email, Slack, Telegram or WhatsApp.
{{approval.approved}}, {{approval.decision}} (approved, rejected or expired), {{approval.timedOut}} and {{approval.decidedVia}} (link or app).A workflow can have its own form: a public page hosted by the app, no sign-in needed.
<app>/forms/<id> (the id is the trigger node's). The page works while the workflow is active. A submission starts the workflow with the answers at {{form.<field>}} and {{formMeta.submittedAt}}.Your name becomes {{form.your_name}}); the settings show it under each field.Hello {{form.your_name}}. The answers are at {{formPage.<field>}}. The form ends with the After Submit setting of the last page that was submitted.{{execution.formUrl}} from an earlier node (an email, a chat message). It is different for every execution, cannot be guessed, and is not the resume URL. The execution's page shows it while the run waits.MAX_FILE_SIZE_MB), counted against the workflow owner's storage (MAX_USER_STORAGE_MB), removed after FILE_RETENTION_DAYS, and only ever downloaded as attachments. The value of a file field is a file variable: use form.cv in an email's Attachments, in Extract from File, and so on. Nothing is stored when the submission is not valid.FORM_RATE_LIMIT_PER_MINUTE), counted before anything else is done.Like n8n, nodes run once per item after a list node.
{{item}} or {{ $json }}, for example {{item.name}} or {{ $json.name }}.{ items, count }.Without a list node nothing changes: every node runs once. An HTTP Request or Code node that returns a list does not split into items by itself; add a Split Out after it. At most 100 items can reach one node (MAX_ITEMS_PER_NODE).
Both syntaxes work in every node field, side by side.
| Syntax | Meaning |
|---|---|
{{webhook.body.name}} | A workflow variable (Handlebars) |
{{json myApiCall}} | An object or list as JSON |
{{ $json.webhook.body.name }} | The same variable, n8n style. Inside a list, $json is the current item |
{{ $json.price * $json.quantity }} | Any JavaScript expression, run in a sandbox |
{{ $('myApiCall').item.json.id }} | Another node's result, by its variable name |
{{ $now }}, {{ $today }}, {{ $itemIndex }} | Current time, start of today, position in the list |
{{$fromAI "city" "The city"}} / {{ $fromAI('city') }} | A value the AI Agent fills in when the node is its tool |
{{shellQuote webhook.body.name}} | A value made safe to put into an SSH command |
Differences from n8n: $json is the whole set of workflow variables (not the previous node's output) unless the node is running for an item; $('name') looks up a variable name; $now is a plain date with toISO(), not a Luxon object.
RXJ includes a comprehensive execution monitoring system.
Two ways to test one part of a workflow in the editor without running the rest again. Both are on a node's toolbar, under the flask button (a pin once the node is pinned).
MAX_PINNED_DATA_KB). For a trigger it is everything the run started with.Every workflow keeps its earlier versions, so a change can be looked at and undone.
WORKFLOW_VERSION_LIMIT, WORKFLOW_VERSION_DAYS). The newest version always stays, however old. Versions are deleted with their workflow./settings/<section>; /settings opens Profile, and the old /api-keys opens Settings → API Keys.
Every account has a workspace, and can be a member of other people's. Workflows, credentials, executions, chatbots, evaluations, AI usage and budgets belong to the workspace you are working in; pick it in the sidebar. Your two-factor settings, sessions and API keys are your own.
Members. Under Settings → Members & roles an admin invites someone by email with a role. No email is sent: the invitation waits until that person signs in to RXJ with that address, sees it under Settings → General and accepts or declines it. Where email verification is on, only a verified address can take an invitation. A member can leave; an admin can change a role or remove a member. What a member built stays in the workspace.
Roles.
| Viewer | Editor | Admin | Owner | |
|---|---|---|---|---|
| See workflows, executions, usage and the names of credentials | yes | yes | yes | yes |
| Create, edit, run and delete workflows; chatbots, evaluations, approvals, templates | yes | yes | yes | |
| Use a credential in a node | yes | yes | yes | |
| Add, change and delete credentials; AI budgets; execution retention | yes | yes | ||
| Invite, remove and change editors and viewers; rename the workspace | yes | yes | ||
| Make or remove admins | yes |
Credentials are used, never shown. A member picks a credential by its name. Its value is not sent to any browser, the owner's included; only admins can replace or delete it.
Plan limits are per workspace. A workspace has its owner's plan: the number of workflows and credentials, the month's executions, locked nodes, AI credits and AI budgets are counted for the workspace, whoever of its members acts. Settings → Usage & plan shows the plan of the workspace you are in, its trial, what it used this month against each limit, its AI credits and AI spend; Billing is always about your own account.
Audit log. What members do in a workspace is in its owner's audit log, with who did it.
How it is stored. A workspace's data is kept under its owner's account: a workflow a member creates has the owner's user id and the workspace's id. That is why running a workflow, its credentials and its limits work the same for every member. Which workspace a request is for comes from a cookie that only picks among the workspaces the user is a member of; the membership and the role are read from the database on every request.
Three things for companies, under Settings → Enterprise (Git sync, External secrets, SSO). Git sync and external secrets belong to the workspace and are set up by its admins; they need the Pro plan on the workspace's owner (the free trial does not unlock them). What is already set up keeps working after a downgrade; it can no longer be changed.
A workspace's workflows can be kept as JSON files in a GitHub repository, one file per workflow (workflows/<id>.json).
owner/name), a branch that exists, a folder, and a GitHub credential of the workspace whose token can reach the repository ("Contents: read and write" to push). Say whether the workspace is development or production.A credential can read its value from HashiCorp Vault (KV engine, version 1 or 2) or AWS Secrets Manager instead of storing it.
vault, aws...), and what reaches it: for Vault the address, a token, the KV mount and optionally a namespace; for AWS a region and an access key. These are stored encrypted and never shown again. Use a token or key that can only read the secrets that are needed.# the field, in double curly braces starting with $secrets.. The External secrets settings show an example. For Vault a field is always named; an AWS secret that is one text is used whole when no field is named. A credential that is JSON (an SMTP server) can use references in any of its fields.EXTERNAL_SECRETS_CACHE_SECONDS), and never saved. A rotated secret is picked up after that time. Values that were read are removed from what is stored about a run, like every credential value.PRIVATE_NETWORK_ALLOWLIST). AWS is called at Amazon's own address for the region.People of a company can sign in through its identity provider with OpenID Connect (OIDC): Okta, Microsoft Entra ID, Google Workspace, Keycloak and others. On the login page they type their work email and press Sign in with SSO.
TRUSTED_ORIGINS, or registering fails with "not trusted".ENCRYPTION_KEY like credentials are.Dependencies. better-auth, @better-auth/sso, @better-auth/core, @better-auth/utils, @better-fetch/fetch and better-call are pinned to exact versions that belong together. Upgrade them together.
Test a workflow over a set of inputs, score what it answers, and compare runs. Open it with the Evaluations button in the editor's header (/workflows/<id>/evaluations).
[{ "input": "...", "expected": "..." }]).input). Pinned data is not used.agent.text. Empty means all of the run's data.```json block is unwrapped), and LLM judge: a model you pick grades each answer from 0 to 1 against the row's expected answer and the dataset's grading criteria, and 0.7 or more passes. The judge uses one of your model credentials or RXJ AI (included), like Build with AI.Every workflow execution stores:
Each node execution stores:
| Field | Description |
|---|---|
| nodeId | Unique node identifier |
| type | Node type |
| status | Current node state |
| startedAt | Execution start time |
| completedAt | Completion timestamp |
| input | Incoming execution data |
| output | Node output |
| error | Failure details |
| Status | Meaning |
|---|---|
| initial | Not run yet |
| loading | Executing, or paused at a Wait node |
| success | Completed |
| error | Failed |
An execution is Running, Waiting (paused at a Wait node), Success or Failed.
Realtime workflow updates use:
Works like the n8n AI Agent: a chat model that decides which tools to call and loops until it has an answer.
{{output}} becomes an object.{{$fromAI "name" "description"}}: write this in any field of a tool node to let the agent fill in that value.{{chatInput}}, set by the Chat, Telegram and WhatsApp triggers) or defined in the node with {{variables}}.The model decides which tools to call, and it acts on text it has read: a chat message, an email, a web page. That text can try to give it orders. These limits apply to every agent:
Risk levels: each connected tool is shown as Read, Write or Dangerous in the agent's settings.
| Level | What it covers |
|---|---|
| Read | Searching, getting and listing; Vector Store search; HTTP GET; Web Search and Read Web Page; calculations and text nodes |
| Write | Sending a message; creating or updating a record; HTTP POST/PUT/PATCH; MCP Client; anything not known to be harmless |
| Dangerous | SSH; raw SQL (Postgres and MySQL "Execute Query"); every delete operation and HTTP DELETE; Execute Workflow |
Dangerous tools are off by default. With one connected and Allow dangerous tools unchecked, the run fails at the agent with a message naming the tool. Agents saved before this option existed count as unchecked, so a workflow that already uses such a tool needs the option switched on once.
Max Tool Calls limits how many tool calls one run may make in total. The run fails with a clear error when the model asks for more.
SSH as a tool: every $fromAI text value in the command is quoted for the shell automatically (inside '...', "..." and $( ) too), so it always arrives as one argument. A value placed inside backticks, $(( )), a # comment, $'...' or after a heredoc cannot be quoted safely and fails the run.
Tool results are data: the system message tells the model that tool results are untrusted data and never instructions, and each result is handed to the model inside a marked <tool_result> block.
These limits reduce the risk; they do not remove it. Give an agent only the tools and credentials it needs.
Each tool in the agent's settings has a Require approval switch. When the model calls such a tool, the tool is not run: the run pauses (the execution shows as Waiting, using the same Inngest wait-for-event as the Wait node), a request with the tool's name and its arguments is sent to a person, and the run continues with their answer.
Default. On for Write and Dangerous tools, off for Read tools, on AI Agent nodes added to the canvas since this exists. An agent that was already in a workflow keeps running without approvals until a tool's switch is turned on, so nothing that works today starts waiting for a person.
Send approval requests through (one setting per agent):
| Channel | Needs |
|---|---|
| In the app | nothing: the request appears on the Approvals page and on the execution's page |
| an SMTP credential and the address to ask | |
| Slack | an incoming webhook URL |
| Telegram | a Telegram bot credential and the chat ID |
| a WhatsApp credential, the Phone Number ID and the number to ask. WhatsApp only delivers free text to someone who wrote to that number in the last 24 hours |
Whatever the channel, the workflow's owner can also answer on the Approvals page.
Approve / Reject links. <app>/api/approvals/<id>?decision=...&expires=...&signature=.... The signature (HMAC with ENCRYPTION_KEY) covers the request, the decision and the expiry, so a Reject link cannot be changed into an Approve link. A link opens a page that shows the tool and its arguments and asks for a confirmation; only that button answers, so a mail scanner or link preview that opens the link approves nothing. A request is answered once, by whoever is first, and the links stop working when it expires (default 24 hours, set per agent).
Approved: the tool runs with exactly the arguments that were shown, and the agent goes on with its result.
Rejected: the tool is not run, the agent is told The user rejected this action and goes on (it usually says so in its answer). A request that expires is treated the same way, with a message saying nobody answered in time.
Several calls that need approval in one turn are asked one after the other. Calls waiting for approval count toward Max Tool Calls. The tools of an MCP Client node share that node's switch.
The tool's arguments are shown as plain text everywhere: they are never rendered as HTML or evaluated as a {{template}}.
Cancelling a waiting execution withdraws its requests. If the request cannot be sent (a wrong credential, say), the run fails with that error instead of waiting.
Two nodes that only belong on an AI Agent's Tools port.
Think Tool lets the agent stop and write down its reasoning or a plan before it goes on, which helps on tasks with several steps or before an action that is hard to undo.
AI Agent Tool is an agent that another agent can hand a task to (multi-agent). It has a Model and a Tools port of its own, a Name and a Description the calling agent sees, and its own System Message.
prompt for it; nothing else of the conversation is passed on, and only its final answer comes back.Checks a text and sends the run on through its Pass or Fail output. Put it before an AI Agent to check what a user wrote (Text To Check {{chatInput}}), or after one to check the answer ({{output}}); The Text Is says which.
| Check | How | When it fails |
|---|---|---|
| Personal data | In the app, by pattern: email addresses, phone numbers, card numbers (13 to 19 digits that pass the card check digit) and national IDs (US Social Security number, Pakistan CNIC, India Aadhaar, UK National Insurance number). Each kind has a switch | Only with Block. With Mask each value becomes [EMAIL], [PHONE], [CARD] or [ID] and the text passes |
| Maximum length | In the app, in characters | The text is longer |
| Prompt injection | A chat model on the node's Model port judges the text: for a user's message, whether it tries to override the assistant's instructions or bypass its restrictions (a jailbreak); for an answer, whether the assistant was manipulated (it reveals its instructions, drops its rules) | The model says so |
| Allowed topics | The same model decides whether the text is about one of the topics you list; greetings and thanks pass | It is about something else |
{{guardrails.passed}}, {{guardrails.reason}} (one line, why it failed), {{json guardrails.violations}} (every failed check) and {{guardrails.text}}, the checked text with personal data masked. Give {{guardrails.text}} to the next node instead of the original. Only the kinds and counts of what was found are reported, never the values.A second node for the agent's Memory port, next to the simple Memory node. It keeps everything in the app's own database, so it needs no credential.
Chat history per session. Every turn is stored under the Session ID (by default {{sessionId}}, which the chat, Telegram, WhatsApp, Messenger and Instagram triggers set). Message Limit (default 20, up to 100) is how many of the latest messages the model is given; older ones stay stored.
Summarise Older Messages (off by default) keeps a running summary of what no longer fits in the limit and gives it to the model at the start of every turn. It is written by the agent's own model after a turn, once six messages have fallen out of the window, so not every turn pays for one. Its cost is recorded with the agent's. A summary that cannot be written never fails the run; it is tried again after a later turn.
Long-Term Facts (on by default) are key/value notes about a contact that outlive the conversation: a name, a preference, an order number. The agent gets two built-in tools:
recall_facts reads everything saved about the contact;remember_fact saves one fact under a short key, replaces it when the key exists, and removes it when the value is empty.What is saved is also shown to the model at the start of every turn, so it does not have to ask first. The two tools only touch the agent's own notes and never ask for approval.
Contact ID says who the facts are about. Empty means the session. Use something that stays the same for a person, for example {{whatsapp.from}} or a customer ID, so the facts are there in the next conversation too.
Limits: 50 facts per contact, a key of up to 64 characters (lower case letters, digits and _), a value of one line of up to 500 characters.
Facts belong to the account, not to one workflow. Every agent of the same account that uses the same Contact ID sees and changes the same facts. Another account never does.
What the model wrote is treated as notes. The summary and the facts are given to the model marked as data, with the instruction not to follow anything written inside them, and a fact cannot contain line breaks or angle brackets. This lowers, but cannot remove, the risk that a user talks the agent into "remembering" an instruction.
Facts and summaries hold what people told the agent. They are deleted with the account. There is no screen to view or delete them yet; the agent removes a fact when asked to forget it.
The older Memory node works as before.
Switch on Make chat public in the Chat Trigger's settings, save and activate the workflow, and anyone can chat with it:
<script src="<app>/widget.js" data-id="<id>" async></script>, shown in the settings to copy. It adds a floating chat button to a website; a click opens the chat window. On a phone the window fills the screen.<app>/chat/<id>, the same chat on a page of its own. The widget shows this page in a frame, so the website's styles and scripts cannot reach into the conversation.{{sessionId}}, in the form widget:<id>:<visitor>, so a Buffer Memory node remembers each visitor separately. A returning visitor sees their conversation again; New chat starts a fresh one.example.com, *.example.com). Browsers refuse to frame the chat anywhere else, and widget.js shows no button there. Empty means any site. The hosted chat page itself is always reachable by its link.CHAT_WIDGET_RATE_LIMIT_PER_MINUTE) and 10 per minute per visitor (CHAT_VISITOR_RATE_LIMIT_PER_MINUTE), counted both by session id and by address.output) is ever sent to the visitor, never the rest of the run's data. The workflow must keep "Save execution data" on: the answer is read from the run's saved output.| Node | Description |
|---|---|
| OpenAI | GPT models with dynamic prompts |
| Anthropic | Claude models (default claude-sonnet-5-5) |
| Gemini | Google Gemini models |
| DeepSeek | DeepSeek models (default deepseek-flash) |
| Kimi | Moonshot AI's Kimi models (default kimi-k2.6), international or China region |
| Qwen | Alibaba Cloud's Qwen models (default qwen-plus), international or China region |
| Chat Model | Any OpenAI-compatible API, with a preset per provider (below) or a custom base URL |
The Chat Model node's Provider dropdown fills in the provider's base URL and suggests its models. The list lives in src/features/executions/lib/chat-model-providers.ts.
| Provider | Base URL | Suggested models |
|---|---|---|
| OpenRouter | https://openrouter.ai/api/v1 | ~openai/gpt-sol-latest, ~anthropic/claude-sonnet-latest |
| Groq | https://api.groq.com/openai/v1 | use Load models |
| Together AI | https://api.together.ai/v1 | MiniMaxAI/MiniMax-M3 |
| Mistral | https://api.mistral.ai/v1 | mistral-small-latest, mistral-large-latest |
| DeepSeek | https://api.deepseek.com | deepseek-flash, deepseek-v4-pro |
| Zhipu Z.ai (international) | https://api.z.ai/api/paas/v4 | glm-5.3, glm-5.3-flash, glm-5.2, glm-4.7, glm-4.7-flash, glm-4.5-flash |
| Zhipu BigModel (China) | https://open.bigmodel.cn/api/paas/v4 | the same, and glm-4-flash-250414 |
| MiniMax (international) | https://api.minimax.io/v1 | MiniMax-M3, MiniMax-M2.7, MiniMax-M2.7-highspeed |
| MiniMax (China) | https://api.minimaxi.com/v1 | MiniMax-M2.7, MiniMax-M2.7-highspeed |
| ByteDance Volcengine Ark (Doubao) | https://ark.cn-beijing.volces.com/api/v3 | doubao-seed-2-0-pro-260215, doubao-seed-1-6-251015, or an endpoint ID (ep-...) from the console |
| Xiaomi MiMo | https://api.xiaomimimo.com/v1 | mimo-v2.6-pro |
| SiliconFlow (international) | https://api.siliconflow.com/v1 | Qwen/Qwen3-32B |
| SiliconFlow (China) | https://api.siliconflow.cn/v1 | deepseek-ai/DeepSeek-V4-Flash, moonshotai/Kimi-K2.7-Code |
GET <base URL>/models) and shows it to pick from. When that fails, for example because the provider has no such list or refuses the key, the reason is shown and the suggested models stay. The model field always accepts a typed ID. The request goes through the same guard against private addresses as the node itself.MAINLAND_CHINA_HOSTS in the same file), so a custom base URL that is not on that list shows nothing, whatever country it is in.glm-4.7-flash, glm-4.5-flash (and glm-4-flash-250414 on BigModel), and OpenRouter models whose ID ends in :free. The list is src/config/free-models.ts and nothing else: change that file to mark or unmark a model. Free tiers have rate limits, and providers change them: the provider's pricing page is what counts.moonshot-v1 models (the Kimi node's old default) on 31 August 2026, and DeepSeek announced the end of the deepseek-chat and deepseek-reasoner names for 24 July 2026. A node saved with one of those keeps it, and its dialog says so and names a current model; the node is not switched on its own, because another model has another price.A Chat Model node can name up to 5 Fallback Models: an ordered list of other models to try when its own model does not answer.
all 3 models failed (openrouter / a: HTTP 429; groq / b: timeout; ...).The DeepSeek, Kimi, Qwen, OpenAI, Anthropic and Gemini nodes have no fallback list. Put them behind a Model Router, or use a Chat Model node.
A sub-node for the AI Agent's Chat Model port. Plug one model node into its Cheap port and another into its Strong port, and connect the router to the agent.
Nodes that can switch models say so in their output:
| Field | Meaning |
|---|---|
modelUsed, providerUsed | The model that gave the (final) answer |
fallbackUsed | true when a model other than the first choice answered at least one call |
modelAttempts | Every attempt in order: { provider, model, outcome }, where outcome is answered, timeout, connection error, HTTP 429... The provider's error text is not kept here |
modelRoute | AI Agent with a Model Router only: { tier: "cheap" | "strong", reason } |
They are on the Chat Model, DeepSeek, Kimi and Qwen nodes ({{chatModel.modelUsed}}) and on the AI Agent ({{aiAgentOutput.modelUsed}}); the Text Classifier and Information Extractor have modelUsed and fallbackUsed. Token usage and cost (see AI token usage and cost) are recorded per model that answered, each at its own price.
| Node | Description |
|---|---|
| Model Router | Plugs into the AI Agent's Chat Model port: a cheap model first, a stronger one when it fails or its answer does not fit the parser |
| Text Classifier | Sorts text into your categories with a connected model and continues on that category's output |
| Information Extractor | Pulls named values (name (type): description) out of free text |
| Analyze Image / Document | Reads an image or a PDF, scans included, with a vision model: all its text (OCR), named fields, or a description. See "Analyze Image / Document" |
| Structured Output Parser | Plugs into the AI Agent's Parser port to force a JSON structure |
| Vector Store | Insert, search and delete documents by meaning (RAG), with OpenAI, Gemini or OpenAI-compatible embeddings. Text splitting, metadata filters, reranking, and pgvector when the database has it (see "Vector Store and Document Loader") |
| Document Loader | Loads a Google Drive folder or a Notion database into a Vector Store collection, skipping documents that have not changed |
| MCP Client | Gives an AI Agent the tools of an MCP server (Streamable HTTP, or SSE for older servers) |
| Think Tool | A tool for an AI Agent: it writes down its reasoning; nothing else happens (see "Think Tool and AI Agent Tool") |
| AI Agent Tool | An agent with its own model, tools and description on another agent's Tools port (multi-agent) |
| Guardrails | Checks text before or after an agent: personal data (mask or block), maximum length, prompt injection and allowed topics, with Pass and Fail outputs (see "Guardrails") |
MCP Client gives an AI Agent the tools of an MCP server. Its Transport is Streamable HTTP (the default: one URL, usually ending in /mcp) or SSE for older servers (one URL, usually ending in /sse, that the client keeps open while the answers arrive on it). With SSE the node opens a connection for each tool call and closes it again, so nothing stays open when the agent stops; the credential is only sent to the server's own site. Servers that only run locally over stdio are not supported.
MCP Server Trigger makes a workflow a tool that MCP clients (Claude, Cursor, another agent, or this app's own MCP Client node) can call.
<app>/api/mcp/<workflow id>, Streamable HTTP, shown in the node. The workflow has to be active.Authorization: Bearer <token>. The token is typed into the node, has to be at least 16 characters, is compared in constant time and is never accepted in the address. Requests are rate limited per workflow before the token is checked.name (type): description, with the types string, number, integer, boolean, list and object. A parameter is required unless it says optional: includeItems (boolean, optional): Also return the line items.{{mcp.arguments.<parameter>}}, {{json mcp.arguments}} and {{mcp.tool}}. Arguments are checked against the schema first (required ones present, the right type at the first level); a wrong call is answered to the model as a tool error and does not start a run.agent.output;MCP_TOOL_TIMEOUT_MS) answers that it is still running; it carries on, and its result is in the executions list.GET is answered 405, as the transport allows), tools only. It speaks protocol versions 2025-06-18, 2025-03-26 and 2024-11-05.The Vector Store keeps text in chunks with an embedding each, per user and collection, and finds the chunks closest in meaning to a query. The Document Loader fills a collection from a Google Drive folder or a Notion database.
Inserting
heading metadata. A section longer than Chunk Size is cut further; a # inside a code block is not a heading.unchanged: true), and a changed text replaces the old chunks instead of adding to them. Without it every insert adds chunks, as before.Searching
{"source": "faq"}. Only chunks whose metadata contains every field of it with the same value are searched (nested objects too; a list in the filter needs each of its entries in the stored list). Values are not converted: 3 does not match "3".rerankNote says so.score and gets a rerankScore; reranked says whether the order came from the reranker.engine in the result says who compared the embeddings: pgvector, pgvector-index or app.pgvector
vector extension, the migration creates it and adds an embeddingVector column next to the existing embedding array, with HNSW indexes for embeddings of 768, 1024, 1536 and 3072 dimensions (cosine distance). Searches are then answered by the database, with no limit on the size of a collection.VECTOR_STORE_ENGINE=app, or when a pgvector query fails, the collection's most recent 5000 chunks are read and compared in the app, as before. Every chunk keeps its plain array for that.embeddingVector column as missing.Document Loader
.docx and text file directly in the folder. Sub-folders are not opened; pictures, scanned PDFs and old Office formats are skipped with the reason.Name: value lines, and the page's blocks as Markdown. Share the database with your Notion integration first.documents without stopping the others.source, title, url and documentId metadata, plus your own, so a search can filter on them.OCR and vision for a file a workflow has: a Form Trigger upload, an HTTP Request download, a Google Drive or Gmail file.
Input: a file variable (form.invoice, download.file) that holds an image (JPEG, PNG, WebP, GIF, TIFF) or a PDF.
Modes:
| Mode | Result |
|---|---|
| Extract All Text | {{analysis.text}}: the document's text in reading order, tables as rows |
| Extract Fields | {{analysis.output.<field>}}, from lines of name (type): description, the same format and types as the Information Extractor. A field the document does not have is null |
| Describe | {{analysis.description}}; "What To Describe" can ask something specific |
Model: connect a model that can read images to the node's Model port: OpenAI, Gemini or Anthropic, or a Chat Model node with a vision model of its provider (for example GLM-4.6V on Zhipu, or a Qwen-VL model). A model that cannot read images fails with the provider's error and a hint to use a vision model. A Chat Model's fallback models are used as usual, and the call is counted in AI usage and cost.
PDFs: each page that has text is given to the model as that text. A page without text is a scan: the picture on it is taken out of the PDF and given to the model as an image. So a typed PDF costs text tokens, a scanned one image tokens, and a mixed one each page as it is.
Max Pages (PDF): how many pages are read, from the first; default 5. The server's limit is 20 (VISION_MAX_PDF_PAGES). The result says how much was read: pages, pagesRead, truncated and scannedPages.
Images are turned upright and scaled down to 1600 pixels on their longest side before they are sent.
Text inside the document is treated as content, never as instructions to the model. As an AI Agent tool the node counts as Read.
Limits: scanned pages are not redrawn, the scanner's picture is used as it is. A page that is only vector drawings without text gives nothing; a scan stored as many small strips is read from its first four pieces. Reading a PDF has the same time limit as Extract from File. HEIC photos are not supported: convert them to JPEG first.
Templates: two templates in the gallery use it: Invoice to Google Sheets (upload form, extract fields, append a row) and (upload form, extract text, score against your job description, email the result).
| Trigger | Description |
|---|---|
| Manual Trigger | Manual execution on user demand |
| Schedule Trigger | Time-based automatic triggers evaluated each minute using custom Cron intervals (e.g., */5 * * * *) |
| Webhook Trigger | Runs on any HTTP request to /api/webhooks/trigger/<workflowId>, protected by a per-node secret. Respond immediately, when the last node finishes, or with a Respond to Webhook node |
| Chat Trigger | Runs when a message is sent from the editor's chat panel, or (with "Make chat public") from the hosted chat page or the website widget; provides chatInput and sessionId |
| Telegram Trigger | Runs when your bot receives a message. The bot's webhook is registered when the workflow is activated. A voice message is given as a file at {{telegram.voice}} |
| WhatsApp Trigger | Runs when your WhatsApp Business number receives a message (verify token + signature check). With a WhatsApp credential selected, a voice message is given as a file at {{whatsapp.voice}} |
| Gmail Trigger | Runs when a new email arrives; polls every 1, 5, 15 or 60 minutes with an optional Gmail search filter. One poll starts at most 10 runs, oldest email first; when more arrived, the rest follow at 10 a minute until none are waiting (a burst of up to 500 emails is kept) |
| Messenger Trigger | Runs when someone messages your Facebook Page (verify token + signature check; see "Messenger and Instagram") |
| Instagram Trigger | Runs when your Instagram professional account receives a direct message (verify token + signature check) |
| Typeform Trigger | Runs when a typeform is submitted; answers are available by question title |
| Shopify Trigger | Runs when an order is created, paid, fulfilled or cancelled, a product is created or updated, a customer is created or a checkout changes (HMAC signature check; see "Shopify and WooCommerce") |
| WooCommerce Trigger | Runs when an order is created, updated or paid, or a product or customer changes (signature check; optional order status filter) |
| Form Trigger | Runs when someone submits the workflow's own hosted form at /forms/<id> (see "Forms") |
| MCP Server Trigger | Makes the workflow a tool of an MCP server at /api/mcp/<workflow id>, protected by a bearer token (see "MCP: client and server") |
Telegram, WhatsApp, Messenger, Instagram, Typeform, Shopify, WooCommerce and Stripe need NEXT_PUBLIC_APP_URL to be a public https address. Every webhook route is rate limited per workflow.
Providers re-send an event when they are unsure it arrived. Each trigger remembers the events it accepted for 7 days and ignores a repeat:
| Trigger | Recognised by |
|---|---|
| Stripe | The event id |
| The message id | |
| Messenger, Instagram | The message id (mid) |
| Telegram | The update id |
| Typeform | The submission's event id |
| Shopify | The event id Shopify sends with every attempt (X-Shopify-Event-Id), per event type |
| WooCommerce | The delivery id; for "Order paid" and the order status filter, the order itself, so an order starts one run however often it is updated |
| Google Form | The response id |
| Gmail, RSS Feed | The message or item id |
| Webhook | An optional Idempotency-Key (or X-Idempotency-Key) header sent by the caller; a repeat answers { "duplicate": true } |
Every execution also keeps the trigger and the data it started with, so it can be run again: Retry on a failed execution's page (or Run again on a successful one), or POST /api/v1/executions/:id/retry. A retry uses the workflow as it is now, which is how a failed webhook delivery is replayed after the workflow has been fixed.
| Node | Description |
|---|---|
| AI Agent | n8n-style Tools Agent with Chat Model, Parser, Memory and Tools sub-nodes |
| HTTP Request | Call any API: headers, query parameters, Basic / Bearer / Header / Query auth from credentials (Query Auth adds an API key to the URL when the request is sent, for APIs such as ZeroBounce that only take it there; the key is not kept in the node), JSON / form / raw bodies, timeout, Never Error. Response Format "File" stores a download. Private and local addresses are blocked |
| RSS Read | Read the items of an RSS or Atom feed; the following nodes run once per item |
| Web Search | Search the web with Tavily, Brave Search or your own SearXNG server; titles, links and snippets. Also an AI Agent tool. See "Web Search and Read Web Page" |
| Read Web Page | Fetch a page and return its main content as clean Markdown or text, cut to a length you set. Also an AI Agent tool. Private and local addresses are blocked |
| Transcribe Audio | Turn a voice message or audio file into text with OpenAI Whisper, Groq Whisper or any OpenAI-compatible server. See "Audio" |
| Text to Speech | Turn text into spoken audio with OpenAI TTS or an OpenAI-compatible server; the result is a file |
| Respond to Webhook | Answer the HTTP request that started the workflow (JSON, text, redirect, status code, headers) |
| Webhook Callback | POST the workflow's data to another URL |
| Execute Workflow | Run another workflow and use its result; also works as an AI Agent tool |
| SSH | Run a command on your own server and get its output and exit code. Pro plan only |
| JavaScript Code | Execute custom JavaScript in an isolated QuickJS (WebAssembly) sandbox |
| Set Variable | Store reusable variables |
| Filter | Stop the current branch unless a condition is true |
| IF | Route to a True or False output based on one or more conditions |
| Switch | Route to one of several outputs based on ordered rules, with a fallback |
| Merge | Join branches back into one path; optionally append or join two lists |
| Loop | Run the connected nodes once for every item in a list |
| Stop and Error | Fail the workflow with your own message |
| Node | Description |
|---|---|
| Edit Fields | Set, keep, remove and rename fields (name (type) = value per line). After a list node it reshapes every item, like a map |
| Split Out | Turn a list inside your data into separate items |
| Aggregate | Combine a field from many items into one list; ends a per-item section |
| Sort | Order the items of a list by a field |
| Limit | Keep only the first or last items |
| Remove Duplicates | Remove items that repeat an earlier item |
| Summarize | Count, sum, average, min, max per group, like a pivot table |
Files do not travel inside the workflow data. A node that makes or fetches a file stores it and puts a reference in its result: { id, fileName, mimeType, size, url }. To use the file in another node, type its variable into that node's File field, for example pdf.file or myApiCall.httpResponse.file. The url is a download link for the signed-in owner.
| Node | Description |
|---|---|
| PDF Generator | Make a PDF from text with headings (#), bullets (-) and rules (---). Supports Latin, Cyrillic, Greek, Arabic, Urdu and Persian text, including right-to-left layout and mixed lines |
| Convert to File | Turn a list into an Excel (XLSX) or CSV spreadsheet, any value into JSON, or text into a text file |
| Extract from File | Read a file back into workflow data: the rows of an Excel or CSV sheet (which continue as items), the text of a PDF or Word (DOCX) file, JSON, or plain text |
Files come from PDF Generator, Convert to File, Google Drive (Download) and HTTP Request (Response Format: File). They can be:
The PDF Generator uses the fonts built into PDF readers for plain Latin text and switches to the bundled Noto fonts in assets/fonts for other scripts. Text in Arabic script looks right on the page, but copying or searching it inside the PDF is unreliable.
Limits: 10 MB per file (MAX_FILE_SIZE_MB), 200 MB stored per user (MAX_USER_STORAGE_MB), and files are deleted after 7 days (FILE_RETENTION_DAYS).
Download links. The url of a file reference is a signed link, /api/files/<id>?expires=...&signature=.... It works for 15 minutes for anyone who has it, without signing in, so a workflow can hand a file to another service. After that it answers 403; the signed-in owner can still open it, and every node that loads the file gets a fresh link. Files are always sent as downloads (Content-Disposition: attachment), with their stored type and X-Content-Type-Options: nosniff, so a file a workflow made is never shown as a page of the app.
Limits when reading files (Extract from File). A file over a limit fails the node with a message that says which one:
| File | Limit | Setting |
|---|---|---|
| Excel (XLSX), Word (DOCX) | Unpacks to at most 50 MB, and has at most 2,000 parts. Checked by unpacking with a cap before the file is opened, so a "zip bomb" is refused | MAX_UNCOMPRESSED_FILE_MB, MAX_ZIP_ENTRIES |
The first 200 pages are read (<name>.truncated is true when there were more; <name>.pages is the real count), within 20 seconds | PDF_MAX_PAGES, PDF_EXTRACT_TIMEOUT_SECONDS | |
| CSV | At most 10,000 rows; the node's Max Rows field can lower it. <name>.truncated is true when the file had more | CSV_MAX_ROWS |
| Excel (XLSX) | The first 5,000 rows of the sheet |
Two nodes that speak the OpenAI audio API, and voice messages on the two chat triggers.
PRIVATE_NETWORK_ALLOWLIST.Transcribe Audio (/audio/transcriptions)
telegram.voice, whatsapp.voice, a Form Trigger upload, an HTTP Request download. MP3, M4A, WAV, OGG, WebM, FLAC or MP4, up to the workflow file size limit.whisper-1 on OpenAI and whisper-large-v3-turbo on Groq when left empty; required for a custom server.en, ur, ar) makes the result more accurate; Hint (optional) helps with names and unusual words.{{transcript.text}}, and language and duration when the server reports them.Text to Speech (/audio/speech)
tts-1 on OpenAI when left empty; required for Groq and custom servers), a Voice (alloy when left empty) and a Format: MP3, Opus (an Ogg file, the format of voice messages) or WAV.{{speech.file}}, stored like every other workflow file (same size limit and storage quota, removed after the retention period). Use speech.file in a Telegram or WhatsApp node's File field or in an email's Attachments.Voice messages on the Telegram and WhatsApp triggers
{{telegram.voice}}, with its duration.{{whatsapp.voice}}. Without it everything works as before and text messages need nothing.{{telegram.voiceError}} / {{whatsapp.voiceError}} says why there is no file.telegram.voice) → AI Agent (Source for Prompt "Define below", Prompt {{transcript.text}}) → Text to Speech (Text {{aiAgentOutput.output}}, Format Opus) → Telegram (File speech.file). The Telegram node sends the audio as a file, not as a voice note. A voice message has no text, so {{chatInput}} is empty for it: the agent's prompt has to come from the transcript. For a bot that takes both, put an IF on {{telegram.voice.id}} first.Two nodes that bring the web into a workflow. Both work as ordinary steps and as AI Agent tools: connect them to an agent's Tools port and let the agent fill in the query or the address with {{$fromAI "query"}} / {{$fromAI "url"}}. They count as Read tools, so they need neither "Allow dangerous tools" nor approval.
Web Search
json under search.formats in its settings.yml); a server on a private network also has to be on PRIVATE_NETWORK_ALLOWLIST.{{search.results}}, a list of { title, url, snippet }, plus {{search.count}}. The three providers come back in the same shape. Connect a Split Out node to handle the results one by one.Read Web Page
<article> or <main> if there is one, otherwise the block with the most real text. Menus, headers and footers, sidebars, cookie banners, share buttons, comments, scripts and hidden elements are left out. Output is Markdown (headings, lists, links with full addresses, bold, code, quotes, tables as rows) or plain text.{{page.content}}, {{page.title}}, {{page.description}}, {{page.url}} (after redirects), {{page.truncated}} and {{page.length}} (the length of the whole page).RXJ-PageReader; some sites refuse automated readers.<tool_result> block as every other tool result, marked as untrusted.| Node | Description |
|---|---|
| Google Sheets | Append rows (Google account or service account) |
| Google Calendar | Create, update and delete events (Google account or service account) |
| Google Drive | Search, upload, download, create folder, delete. Google Docs and Slides download as PDF, Sheets as CSV |
| Gmail | Send (with file attachments), reply, search, get and mark messages as read. A reply stays in the thread: to a message you received it goes to its sender, to a message you sent yourself (a follow-up) it goes to the people that message was sent to, with its CC only when CC of a Message You Sent is set to Keep |
| Notion | Create pages, query databases |
| GitHub | Create issues, comment on issues, list issues, read a repository |
| Airtable | List, create, update and delete records. List Records returns one page (50 by default, 100 at most); with Return All it follows Airtable's pages up to Max Records (1000 by default, 10,000 at most, and 3 MB). Its output then has truncated (true when the table had more) and a warning text when the list was cut or is longer than MAX_ITEMS_PER_NODE, the most items a node after Split Out handles |
| Jira | Create issue, get issue, search with JQL, add comment (Jira Cloud) |
| HubSpot | Create, get, update and search contacts; create deals |
| Salesforce | Query with SOQL, and create, get, update and delete records of any object |
| Shopify | Orders, products, customers, inventory levels and abandoned checkouts (see "Shopify and WooCommerce") |
| WooCommerce | Orders, order notes, products and customers through the REST API |
| Postgres | Run parameterised SQL, select rows, insert rows. Expressions go in Query Parameters, not in the query text |
| MySQL | Run parameterised SQL, select rows, insert rows (MySQL and MariaDB). Same rule for expressions |
Two store nodes and two triggers. The nodes can also be AI Agent tools: reading is "read", creating and updating is "write" (so it asks for approval by default).
| Node | Operations |
|---|---|
| Shopify | Get, list and update orders; get, list, create and update products; get, search, create and update customers; get, set and adjust an inventory level; list locations; list abandoned checkouts |
| WooCommerce | Get, list and update orders; add an order note; get, list, create and update products (with stock quantity); get, list, create and update customers |
myshopify.com address: the token is sent nowhere else. Shopify calls the REST API "legacy" and is moving to GraphQL, but still serves it; the node asks for version 2025-01, Shopify answers a retired version with its oldest supported one, and API Version can be set on the node.https. The key is never put in the address. Some hosts remove the Authorization header before WordPress sees it, and the store then answers 401 "cannot list resources" with a correct key; that has to be fixed on the host.{{json <name>.items}} and {{<name>.count}}, at most 100 (default 20). Put a Split Out node with Input List <name> after one to run the next nodes once per item. Fields limits what each item contains; an answer over 3 MB is refused with that advice.variants.0.inventory_item_id from Get Product and a location from List Locations.| Trigger | Set up in the store | Signature |
|---|---|---|
| Shopify Trigger | Settings > Notifications > Webhooks: one webhook per event, format JSON, the node's Webhook URL | X-Shopify-Hmac-Sha256, checked with the signing secret shown on that page (or the app's API secret key for a webhook an app created) |
| WooCommerce Trigger | WooCommerce > Settings > Advanced > Webhooks: status Active, a topic, the node's Webhook URL as Delivery URL, and a Secret | X-WC-Webhook-Signature, checked with the same Secret typed into the node |
200 and starts nothing, because both stores switch a webhook off when it keeps failing.{{shopify.event}}, {{shopify.id}}, {{shopify.email}}, {{shopify.phone}}, {{shopify.customerName}} and the store's whole payload at {{shopify.order}} (or .product, .customer, .checkout); the same names under {{woocommerce...}}. phone and email are taken from wherever the payload has them (an order has a phone number in up to five places).| Integration | Description |
|---|---|
| Discord | Discord automation |
| Slack | Slack notifications |
| Telegram | Bot messaging: text, or a file with a caption, and the Telegram Trigger for incoming messages |
| Text, template and file messages via the WhatsApp Business Cloud API, and the WhatsApp Trigger | |
| Messenger | Text and picture replies to people who wrote to your Facebook Page, and the Messenger Trigger |
| Text and picture direct messages, the Instagram Trigger, and photo posts (with the Instagram / Facebook Post node) | |
| Twilio | SMS and WhatsApp messages |
| SMTP email, with file attachments | |
| Gmail | Email through a connected Google account, with file attachments |
| Resend | Email through the Resend API, with file attachments |
| SendGrid | Email through the SendGrid API, with file attachments |
Three nodes and two triggers on Meta's Graph API. As AI Agent tools all three nodes are "write", so they ask for approval by default.
| Node | What it does |
|---|---|
| Messenger | Sends a text or a picture to someone who wrote to your Facebook Page |
| Sends a text or a picture as a direct message from your Instagram professional account | |
| Instagram / Facebook Post | Publishes a photo with a caption on the Instagram account or on the Facebook Page |
| Messenger Trigger, Instagram Trigger | Start a run for each incoming message, with chatInput and sessionId for an AI Agent |
{{messenger.from}}, {{instagram.from}}), not by name or phone number. A plain reply arrives within 24 hours of the person's last message. Message Type offers Meta's tags for later messages (human agent, post-purchase update and so on); they are not for promotion.graph.facebook.com) or with Instagram Login (an Instagram credential, calls go to graph.instagram.com). Messenger and Facebook Page posts always use the Facebook Page credential.Authorization header to those two hosts only, never in an address.https address, or a file variable. A file is handed over as its signed download link (valid 15 minutes), so the app has to run on a public https address for that. An address on a private network is refused.X-Hub-Signature-256 signature (made with the app secret) on every event. Echoes of the account's own messages, deleted messages, read and delivery receipts and reactions start nothing. Button taps and quick replies do, with their payload at {{messenger.payload}}. Attachments are given as a list of { type, url }; they are not downloaded.pages_messaging, instagram_manage_messages or instagram_business_manage_messages); posting needs or / .All credentials are encrypted before database storage using AES encryption.
Supported credential types:
| Type | Used By |
|---|---|
| OPENAI | OpenAI nodes, Vector Store embeddings |
| ANTHROPIC | Claude nodes |
| GEMINI | Gemini nodes, Vector Store embeddings |
| DEEPSEEK | DeepSeek node |
| KIMI | Kimi node |
| QWEN | Qwen node |
| OPENAI_COMPATIBLE | Chat Model node (the API key of whichever provider preset is picked: OpenRouter, Groq, Zhipu, MiniMax, Ollama...) |
| SMTP | Email node |
| GOOGLE_OAUTH2 | Gmail, Gmail Trigger, Google Drive, Google Sheets, Google Calendar ("Sign in with Google") |
| GOOGLE_SHEETS | Google Sheets (service account) |
| GOOGLE_CALENDAR | Google Calendar (service account) |
| NOTION | Notion nodes |
| TELEGRAM | Telegram node and Telegram Trigger |
| WhatsApp node (Cloud API access token) | |
| FACEBOOK_PAGE | Messenger node, Facebook Page posts, and Instagram through a Page (Page access token) |
| Instagram node and posts for an account that uses Instagram Login (Instagram access token) | |
| TWILIO | Twilio node (AccountSID:AuthToken) |
| RESEND | Resend node (API key) |
| SENDGRID | SendGrid node (API key) |
| TAVILY | Web Search node with Tavily (API key) |
| BRAVE_SEARCH | Web Search node with Brave Search (subscription token) |
| JINA_READER | Read Web Page node with Jina Reader, and the Vector Store's Jina reranker (API key) |
| COHERE | The Vector Store's Cohere reranker (API key) |
| FIRECRAWL | Read Web Page node with Firecrawl (API key) |
| GITHUB | GitHub node (personal access token) |
| AIRTABLE | Airtable node (personal access token) |
| JIRA | Jira node (email:api-token) |
| HUBSPOT | HubSpot node (private app access token) |
Outbound requests: URLs, database hosts and SSH hosts typed in by users cannot point at private or local addresses (SSRF guard, including redirects and DNS tricks). A self-hosted server that needs to reach something on its own network, for example Ollama, lists exactly that in PRIVATE_NETWORK_ALLOWLIST:
PRIVATE_NETWORK_ALLOWLIST=127.0.0.1:11434,10.0.0.0/24,db.internal:5432
| Entry | Allows |
|---|---|
127.0.0.1:11434 | that address on that port only |
10.0.0.5 | that address on any port |
10.0.0.0/24 | every address of the range, on any port |
ollama.lan:11434, db.internal | that host name (with or without a port), whatever private address it resolves to |
[::1]:11434, fd00:1234::/64 | the same for IPv6; an address with a port goes in brackets |
169.254.169.254) included.http://localhost:11434 is allowed by localhost:11434, not by 127.0.0.1:11434, because localhost can also resolve to ::1 and every address of a name has to be allowed. Use the same form in the node as in the list. The Chat Model node's default Ollama address is http://localhost:11434/v1, so for that the entry is localhost:11434.10.0.0.0/24:5432) is not supported. Entries that cannot be read are ignored and named in a warning at startup.ALLOW_PRIVATE_NETWORK_REQUESTS=true still works and opens every private address; the allowlist then has no effect. It logs a warning at startup recommending the allowlist, which is the safer choice.Webhooks: per-node secrets or provider signatures (Stripe, WhatsApp, Messenger, Instagram, Typeform, Telegram, Shopify, WooCommerce), a bearer token for the MCP Server Trigger, and a per-workflow rate limit.
secretsMatch in src/lib/webhook-security.ts, built on crypto.timingSafeEqual).Redaction. A run always works with the real data. What is stored about it is redacted first, with [REDACTED] in place of each secret:
| Stored | What is redacted |
|---|---|
| Each node's input, output, error and stack trace; the execution's error | The values of all your credentials (also inside a longer text, such as a URL or an error message), and the well-known shapes below |
| The execution's final output | Credential values only. This is also what a webhook that answers "when the last node finishes", the chat panel and GET /api/v1/executions/:id return, so a token a workflow made on purpose is not changed |
| The data the run started with (kept for Retry) | Nothing: a retry has to start from the same data |
The well-known shapes: sk-... keys, AKIA... AWS key ids, Slack xoxb-/xoxp-/xoxa- tokens, GitHub ghp_... tokens, JSON Web Tokens, Bearer ... tokens, the values of Authorization, Proxy-Authorization, Cookie, Set-Cookie and X-API-Key headers, and the password in connection strings like postgresql://user:password@host. Credential values shorter than 6 characters are not matched, and for credentials with several fields (SMTP, SSH, Google, Salesforce) the host, username and similar fields are left readable.
The same redaction of well-known shapes runs on everything sent to Sentry (errors, traces and logs).
Retention. On the Executions page, Execution data retention sets how long your execution data is kept: 7, 30 (default) or 90 days. A nightly job deletes older executions with their node data. An execution from the current month keeps its row (status, times and error message) until the month ends, because the monthly execution limit counts it; everything else about it is deleted. Running executions are not touched.
Don't save node input/output. In the editor, the gear button next to the Active switch has this workflow setting (off by default). When it is on, runs of that workflow store only their status, times and errors (redacted as above). Such runs cannot be retried, the execution page shows no data for them, and a webhook that answers "when the last node finishes" returns an empty object; use a Respond to Webhook node instead.
Everything a credential holds (an API key, a login, or the OAuth tokens of a connected Google or Salesforce account) is stored encrypted in Credential.value. It is decrypted only on the server, at the moment a node needs it. The browser never receives it, encrypted or not: the Credentials pages get the name, type and dates, and the edit form only says that a secret is saved. Leaving the secret empty when editing keeps the saved one.
Format
v1:<base64 of IV (12 bytes) | auth tag (16 bytes) | ciphertext>
ENCRYPTION_KEY with scrypt. Use a long random value, for example openssl rand -base64 32.v1: is the format version, so the format can change later without guessing.cryptr package: hex text, no prefix, AES-256-GCM with a 16-byte IV) are still read. The rotation script rewrites them as v1:; run it once after upgrading, without changing any key, to convert them.Changing the key
ENCRYPTION_KEY_PREVIOUS to the current key and ENCRYPTION_KEY to the new one, and deploy or restart. From now on new values are written with the new key, and old values are still read with the previous one, so nothing stops working.npx tsx scripts/rotate-encryption-key.ts --dry-runnpx tsx scripts/rotate-encryption-key.ts (or npm run rotate-encryption-key). It goes through the credentials 100 at a time and re-encrypts each with the new key. Values that are already current are skipped, so it can be stopped and run again. A credential a user saves while it runs is not overwritten.ENCRYPTION_KEY_PREVIOUS is set.ENCRYPTION_KEY_PREVIOUS and deploy or restart.If the script lists credentials that "could not be read", they were written with a key that is neither of the two: check ENCRYPTION_KEY_PREVIOUS, and keep it set until the list is empty. The script prints credential ids only, never values, and exits with code 1 in that case.
The script must run with the same ENCRYPTION_KEY, ENCRYPTION_KEY_PREVIOUS and DATABASE_URL as the app. It reads .env when there is one.
Create a key under Settings → API Keys (account menu at the bottom of the sidebar; Pro plan or free trial) and send it as Authorization: Bearer <key> or X-API-Key: <key>.
| Endpoint | Scope | Description |
|---|---|---|
GET /api/v1/workflows | workflows:read | List your workflows |
POST /api/v1/workflows/:id/execute | workflows:execute | Run a workflow from its manual trigger; the JSON body becomes the run's variables |
GET /api/v1/executions/:id | executions:read | Status and, once finished, the output |
GET /api/v1/executions?workflowId=... | executions:read | Recent executions |
POST /api/v1/executions/:id/retry | executions:retry | Run an execution again with the same starting data |
API keys
rxj_ab12cd…) are stored, so a key cannot be read back from the app or the database.403 with the name of the missing scope; an expired, revoked or unknown key gets 401.API_RATE_LIMIT_PER_MINUTE requests per minute (default 120); after that it gets 429 with a Retry-After header.curl -X POST "$APP_URL/api/v1/workflows/<workflowId>/execute" \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-d '{"customer":"Ali"}'
RXJ includes a complete SaaS monetization architecture powered by Polar.
RXJ tracks:
Users can view:
How the numbers are counted (src/lib/subscription/usage-counts.ts). Usage belongs to the workspace, so it is the workspace owner's.
execution_usage table and only goes up: deleting executions or workflows does not give any back. A run that would go over the limit is refused and not counted. Before the first run of a month the month's existing executions are the count, so the switch to this counter gave none back.user.workflowCount and user.executionsUsed columns are no longer read or written. They are dropped in a later change.scripts/reconcile-usage.ts recomputes the numbers from the rows: npx tsx scripts/reconcile-usage.ts --dry-run prints what it would change, without the flag it changes it. It never lowers an execution count.Deleting a workflow removes, besides the workflow (whose nodes, connections, executions, versions, pinned data and evaluation datasets go with it by ON DELETE CASCADE), its agent memory, and the webhook of an active Telegram Trigger. For workflows that were deleted straight in the database, scripts/cleanup-orphans.ts finds what was left behind (agent memory, chat widget messages, chatbot documents, duplicate-delivery keys) and Telegram bots whose webhook points at a workflow that no longer exists: npx tsx --conditions=react-server scripts/cleanup-orphans.ts --dry-run reports, without the flag it deletes and removes the webhooks. AI usage rows are kept on purpose: the month's AI spend stays complete.
Page speed. A request reads its session once (src/lib/request-session.ts, shared by the dashboard layout, the page and its tRPC prefetches), and a tRPC batch reads the session and looks up the workspace once for all its procedures. PERF_LOG=1 logs, with [perf] in front, how long each tRPC procedure, session read and database query takes; it is off unless set.
Every call to a language or embedding model is written down with the tokens the provider reported: input, output and cached input. This covers the AI Agent, the model nodes (OpenAI, Anthropic, Gemini, Chat Model, DeepSeek, Kimi, Qwen), the Text Classifier, the Information Extractor, the Vector Store's embeddings and chat-model reranking, and the Document Loader's embeddings, also when one of them runs as an AI Agent tool. Only counts are stored, never prompts or answers, and they are kept after an execution's data is deleted so the month stays complete.
openai, anthropic, gemini, the Chat Model preset (openrouter, deepseek, kimi...) or custom. The table starts empty; the card lists the models that ran without a price so they can be added with one click. Price changes are in the audit log.The amounts are estimates for keeping an eye on spend. You pay your providers directly and their invoice is what counts: tokens written to a provider's cache are priced as normal input, and a model without a price does not count towards a budget.
A user can run cheap models without an API key of their own. The platform's provider keys do the calling and the user pays with AI credits.
Credits. One credit is a tenth of a US cent of model cost: 1,000 credits are one dollar. A call's cost comes from the price table above ("AI token usage and cost"), at the price set when the call is made.
Monthly credits per plan (calendar month, UTC; they do not carry over; a free trial does not change the number):
| Free | Beginner | Intermediate | Pro |
|---|---|---|---|
| 100 | 1,000 | 5,000 | 20,000 |
The numbers are in one file, src/config/ai-credits.ts.
Setting it up (admins). Under Settings → RXJ AI (included) an admin with two-factor on adds a provider (one of the Chat Model presets that has its own address, for example Zhipu GLM or DeepSeek), its API key and the model IDs users may pick. The key is stored encrypted, is never shown again, and is covered by the key rotation script. A provider can be switched off without removing it. Every included model needs a row in the price table: without one it cannot be used, because it would cost no credits; the card says which models are missing a price. Saving and removing a provider is in the audit log. Until a provider is added, users do not see any of this.
Using it. On a Chat Model node the credential list has RXJ AI (included). With it, Provider and Model have to be one of the included ones (the dialog says which, and "Load models" lists them). It works wherever a Chat Model node works: as a step, as the model of an AI Agent, Text Classifier, Information Extractor, Analyze Image / Document or Guardrails, as a fallback model and behind a Model Router. Build with AI offers it in its credential list too. The platform's key is only ever sent to the provider's own address, never to a Base URL typed into the node.
What is charged. Each included call is written down like any other (the execution's AI usage table, the Billing page) and its cost is taken from the month's plan credits first, then from bought credits.
Hard stop. With no plan credits and no bought credits left, a node that uses RXJ AI (included) fails before it calls anything, with: "RXJ AI (included) credits are used up: the Free plan includes 100 credits a month and you have no bought credits left. Upgrade your plan or buy credits at <app>/billing, or pick your own API key credential on the node. The monthly credits start again on the 1st (UTC)." Build with AI shows the same message. The check is made before a node runs, not during it, so the last node (an agent with many steps, for example) can use more than was left; bought credits never go below zero.
The page at / is the public marketing page. It is built once and rebuilt at most every hour, and it does not load the workflow editor.
src/app/page.tsx; the sections, the navbar and the hero's workflow canvas are in src/features/marketing/components/, and the words (features, use cases, security, FAQ) in src/features/marketing/lib/content.ts. A claim there is changed together with the feature it describes.NodeType. The plans and prices are src/config/pricing-plans.ts, which the Pricing page uses too. The limits and AI credits in the FAQ come from src/config/plans.ts and src/config/ai-credits.ts.src/features/marketing/lib/integrations.ts, with glyphs from the simple-icons package. Add an integration only when its node exists.src/app/marketing.css: the colours are sampled from the logo (violet #9829FD, indigo #401EFB, blue #1568FD, sky #0099FE, ink #03031A), scoped to .mkt, and follow the system light/dark preference. The navbar, hero, security band, final call to action and footer are always dark, because the logo is drawn for a dark background. Headings are Bricolage Grotesque, body text Geist. The app itself keeps its own theme.prefers-reduced-motion; the hero canvas then shows the finished run.public/logos/RXJ.png and is never edited. node scripts/generate-brand-assets.mjs makes the cropped wordmark (public/brand/rxj-wordmark.png), src/app/favicon.ico, src/app/icon.png and src/app/apple-icon.png from it; run it again when the logo changes. RxjLogo (src/features/marketing/components/rxj-logo.tsx) shows it: plain on a dark surface, with tile on a light one (the sign-in pages).src/app/opengraph-image.tsx draws the 1200 × 630 image with the logo and the headline; uses the same one. Their addresses and the canonical URL are built from .RXJ includes onboarding UX to improve activation and retention.
The Templates page is a gallery of ready-made workflows. "Use template" copies one into the user's account as a new workflow, which opens with its nodes marked "Not Configured" wherever the user has to connect their own account.
Built-in templates are added by database migrations and can be edited or hidden by an admin like any other: Invoice to Google Sheets, CV screening: score and email, Shopify order paid: WhatsApp confirmation and Shopify abandoned cart: email reminder. The WhatsApp one sends an approved message template, because WhatsApp does not let a business start a conversation with free text; its description says how to set that up and how to switch it to WooCommerce. The abandoned cart one runs every hour and emails the shoppers whose checkout was last touched one to two hours ago.
Publishing: an admin builds a workflow, then picks it under New template and gives it a name, description, category and the plan it is available from. Editing a template can replace its content with the current version of a workflow; hidden templates are visible to admins only.
Admins are accounts whose email is in the ADMIN_EMAILS environment variable and is verified. Only they can publish, edit and delete templates. A Google or GitHub sign-in counts as verified; a password account has to confirm its email first, which needs RESEND_API_KEY. Without that key the server logs a warning at startup, and a password account using an admin address stays an ordinary user.
Admins need two-factor authentication. Publishing, editing and deleting templates is refused until the admin turns it on under Settings; until then the Templates page shows a note with a link there instead of the admin buttons.
What a template never contains: stored credentials, webhook and signing secrets, WhatsApp verify tokens and app secrets, Slack and Discord webhook URLs, and the workflow an Execute Workflow node pointed at. Anything typed into ordinary fields (a key pasted into a header, a prompt) is copied as it is.
Secret scan: before a template is published or updated, every field of every node is scanned for the secret shapes the log redaction knows (sk- keys, Bearer tokens, JWTs, AWS, Slack and GitHub tokens, passwords in URLs) and for hand-typed values in Authorization, Cookie and X-API-Key headers. Values made only of {{ }} expressions pass. If anything is found the template is not saved and the dialog lists the node and field (never the value). An admin can tick I checked this, publish anyway to save it regardless. Editing only a template's details scans its stored content again. The scan knows those shapes only, so a secret of another shape still has to be caught by reading the workflow.
Describe a workflow in words and get it on the canvas. The Build with AI button is on the Workflows page (makes a new workflow) and at the top right of the editor (builds on an empty canvas, or changes the workflow that is there).
src/config/node-components.ts), the node selector's descriptions and each node's settings, so a new node made with createIntegrationNode is in it without further work. Nodes with a hand-written dialog are described in src/features/ai-builder/lib/manual-specs.ts; a test fails when a registered node is described nowhere.name, summary, nodes, connections). The server checks it: known node types, the fields' kinds and options, required fields, that every connection uses ports the nodes really have (including Switch rules and Text Classifier categories), that a model, memory or parser node is on the port made for it, a trigger, one variable name per node, nothing unconnected, and no cycles. When something is wrong the answer goes back to the model once, with the list of problems; if the second answer fails too, the dialog shows the problems and nothing is changed.Chatbots in the sidebar opens the Create a chatbot page: a chatbot that answers from your own documents, without building the workflow yourself.
/chatbots/<workflow id>).chatbot-...), cut into chunks (by heading for web pages and Markdown) and embedded with the Vector Store node's own settings and credential. The first 400,000 characters of a document are read; a chatbot can have 50 documents and pages. The embedding calls are written down on the Billing page under "Chatbot knowledge" and count towards the account's AI budget, which is checked first. Deleting the workflow does not delete the collection: remove the documents on the chatbot's page first, or use a Vector Store node with Delete Collection.The Settings page (sidebar) holds two things.
Two-factor authentication. Turning it on shows a QR code for an authenticator app (TOTP) and ten backup codes, and asks for a first code before it takes effect. From then on a password sign-in also asks for a code on /two-factor; a backup code works once in its place, and "Do not ask on this device for 30 days" skips the question on that browser. New backup codes can be made, and it can be turned off, after confirming the password. Accounts that only sign in with Google or GitHub have no password to confirm with and are not asked for a code at sign-in: for them the provider's own two-factor is what protects the sign-in.
Active sessions. Every signed-in browser with its device, IP address and when it was last active, with Sign out for one and Sign out all other sessions. "Last active" is when Better Auth last refreshed the session, which it does about once a day, not the last click.
Sign-in lockout. Ten wrong passwords for one account within 15 minutes lock password sign-in for that account for 15 minutes, whatever addresses the attempts came from. The answer is the same for addresses that have no account. The lock ends by itself after the 15 minutes, and a correct password then starts the count again; resetting the password does not lift it early. Google and GitHub sign-in are not affected. Anyone who knows an address can lock its password sign-in this way, which is the price of the rule.
Sign-in rate limit. Password and two-factor code attempts are limited per IP address (SIGN_IN_RATE_LIMIT_PER_MINUTE, default 20). The address is read from the proxy headers Better Auth trusts (x-forwarded-for), so the app has to run behind a proxy that sets it, as it does on Vercel.
Passkeys are not included: in the installed Better Auth (1.6) they are a separate package, @better-auth/passkey, which is not installed.
Audit log. The Settings page lists what happened on the account, latest first, with the time and the IP address it came from:
A free trial unlocks the paid nodes, so these keep a trial or a cheap plan from being used to send in bulk or to sign up over and over.
Daily message caps. Each user may send this many messages per day (the day is UTC) through the nodes that reach people outside the app. The numbers are in one file, src/config/message-caps.ts.
| Plan | Email (SMTP, Gmail, Resend, SendGrid) | Twilio (SMS and WhatsApp) | Telegram | |
|---|---|---|---|---|
| Free (and the free trial) | 20 | 20 | 10 | 50 |
| Beginner | 100 | 50 | 25 | 200 |
| Intermediate | 1,000 | 500 | 250 | 2,000 |
| Pro | 10,000 | 5,000 | 2,500 | 20,000 |
Disposable email addresses (mailinator, 10minutemail and the like, and their subdomains) cannot be used to sign up, with a password or through Google or GitHub. The list comes from the disposable-email-domains-js package; npm update disposable-email-domains-js brings in new domains. Accounts that already exist are not affected.
Cloudflare Turnstile on the password sign-up form, when TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY are both set. Without them (or with only one, which logs a warning at startup) sign-up works as before. Google and GitHub sign-ups are not asked: the provider has checked them.
npm install
npx prisma migrate deploy # apply the database migrations
npm run dev:all # Next.js + Inngest dev server + ngrok
A database set up before 10 October 2026. The 38 migrations that existed until then were replaced by one, 20261010000000_baseline, because the old history could not be replayed on an empty database. A new database needs nothing special: migrate deploy applies the baseline and everything after it. A database that already has the old history must be told once that the baseline is what it holds, or migrate deploy will try to create tables that exist:
Make sure it was fully migrated with the old history, up to 20261009150000_execution_retention_and_save_setting, and matches the schema of that time. If it stopped at an older migration, bring it up to date from a checkout made before the change first.
Clear Prisma's migration history. This empties Prisma's own bookkeeping table and touches no application table:
echo 'DELETE FROM "_prisma_migrations";' | npx prisma db execute --stdin --schema prisma/schema.prisma
Mark the baseline as applied, then apply what came after it:
npx prisma migrate resolve --applied 20261010000000_baseline
npx prisma migrate deploy
npx prisma migrate status should then report "Database schema is up to date!".
Run the checks that need no database or external service:
npx tsx --conditions=react-server src/inngest/__test__/testCoreNodes.ts
NODE_OPTIONS=--max-old-space-size=8192 npx tsc --noEmit
Step-by-step workflows for testing every node by hand are in docs/TESTING.md.
.github/workflows/ci.yml runs on every pull request and on every push to main. It has five jobs, each of which fails the run on its own:
| Job | What it runs |
|---|---|
| Type check | npx tsc --noEmit (with the 8 GB heap the project needs) |
| Core node tests | npx tsx --conditions=react-server src/inngest/__test__/testCoreNodes.ts |
| Build | npx prisma generate then npx next build, with placeholder env values. Catches what tsc does not, such as a server package (sharp, fs) pulled into the browser bundle by a "use client" import. It runs next build directly, not npm run build, so scripts/migrate-on-deploy.mjs never runs |
| npm audit | npm audit --audit-level=high: fails on a known vulnerability of high or critical severity in any dependency |
| Secret scan | gitleaks over the commits of the push or pull request, with .gitleaks.toml |
DATABASE_URL in the workflow is a placeholder that prisma generate reads without connecting..gitleaks.toml uses gitleaks' own rules and skips two places that hold made-up secrets on purpose: src/inngest/__test__/ and docs/TESTING.md. A real secret must never go into those..github/dependabot.yml has Dependabot open pull requests once a week: one for all minor and patch updates of npm packages, separate ones for major updates, security updates and GitHub Actions. They run through the same CI and are merged by hand, because a merge to main deploys.The live app on Vercel and local development use the same database, so the schema only changes in one controlled way.
npm run build: prisma generate, then node scripts/migrate-on-deploy.mjs, then next build.VERCEL_ENV=production, which is what a merge to main produces), the script runs prisma migrate deploy before the app is built. Pending migrations are applied; with none pending it changes nothing.main, and no other way. A pull request that adds a folder under prisma/migrations changes the production database the moment it is merged, so read its SQL as part of the review.DATABASE_URL must be available to the build on Vercel (it already is for prisma generate), and should be a direct connection, not a pooled one: prisma migrate deploy takes a lock that connection poolers can break.DATABASE_POOLED_URL (optional) is what the app's own queries use when it is set: Neon's pooled address, the same as DATABASE_URL with -pooler after the endpoint id in the host, and &pgbouncer=true at the end. Serverless functions then share the pooler's connections instead of each opening its own. Migrations keep using DATABASE_URL.INNGEST_SIGNING_KEY has to be set in the production environment, or the server does not start (see "Security").| Variable | Purpose |
|---|---|
DATABASE_URL | PostgreSQL connection string (direct; migrations use it) |
DATABASE_POOLED_URL | Optional. The pooled connection string the app's queries use instead. See "Deploying (Vercel)" |
PERF_LOG | Optional. 1 logs timings of tRPC procedures, session reads and database queries, each line starting with [perf] |
ENCRYPTION_KEY | Encrypts stored credentials and OAuth tokens. A long random value |
ENCRYPTION_KEY_PREVIOUS | Only while changing ENCRYPTION_KEY: the old key, so values written with it can still be read. See "Credential encryption and key rotation" |
NEXT_PUBLIC_APP_URL | Public address of the app; used for webhook URLs, OAuth redirects and trusted origins |
TRUSTED_ORIGINS | Extra origins allowed to sign in, comma-separated. Also where the address of every SSO identity provider has to be listed before it can be registered |
EXTERNAL_SECRETS_CACHE_SECONDS | How long a secret read from Vault or AWS Secrets Manager is used before it is read again, default 300. 0: read every time |
ADMIN_EMAILS | Who may publish, edit and delete workflow templates, comma-separated. The account's email must also be verified |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | Google sign-in and the "Google account" credential. Add <app URL>/api/oauth/google/callback as an authorised redirect URI |
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET | GitHub sign-in |
SALESFORCE_CLIENT_ID, SALESFORCE_CLIENT_SECRET | The Salesforce connected app behind "Connect Salesforce". Its callback URL is <app URL>/api/oauth/salesforce/callback, with the api and refresh_token, offline_access scopes |
RESEND_API_KEY | Sends verification and password-reset emails. Email verification is only required when this is set |
EMAIL_FROM | Sender of those emails; must be on a domain verified in Resend |
Nodes that are not built yet. Each new integration is a config passed to createIntegrationNode plus an executor; see src/features/executions/components/apps for the pattern.
| Node | What it needs |
|---|---|
| Google Docs | Creating and editing documents (Drive can already download them as PDF) |
| More PDF scripts | Chinese, Japanese, Korean, Devanagari and emoji have no bundled font and are rejected; Urdu is drawn in Naskh style, not Nastaliq |
| Old Office files | Extract from File cannot read the old .xls and .doc formats. (It also cannot read scanned PDFs, but the Analyze Image / Document node now can, with a vision model.) |
| Rendering PDF pages | Analyze Image / Document takes a scanned page's picture out of the PDF instead of drawing the page, so a page made only of vector drawings without text cannot be read. Drawing pages needs a canvas library on the server |
| Browser Automation | Deliberately not built: it needs a headless browser service that cannot run on standard serverless functions, and it is easy to abuse. If added, the plan is a node that uses the user's own hosted-browser API key |
| MongoDB, Redis, Supabase (REST) | More data stores next to Postgres and MySQL |
| Slack / Discord bots | Bot-token nodes and triggers; today both send through webhook URLs |
Also not built: a true n8n items model for every node from the trigger onward (items start at a list node), a real tokenizer for the Vector Store's token splitting, and the stdio transport for MCP (it needs a process on the user's own machine).
Planned, not built. These are the pieces an agent needs to find link prospects, email them from real mailboxes, follow up in the same thread and keep track of who was contacted.
| Planned item | What it needs |
|---|---|
| Google Sheets: row operations | Get Rows (with filter), Lookup Row, Update Row, Append or Update (upsert by a key column) and Delete Row; rows come out as a list so Split Out can take them |
| IMAP Trigger and IMAP node | A trigger for new email in a folder (polling every 1, 5, 15 or 60 minutes, a search filter, duplicate check by Message-ID) and a node with Search, Get Message, Mark as Read and Move to Folder. Needs a new IMAP credential: host, port, TLS, username, app password |
| Email (SMTP): headers and replies | Custom Headers (for List-Unsubscribe and List-Unsubscribe-Post), a "Reply To Message" option that sets In-Reply-To and References and adds "Re:", and the sent Message-ID in the output. The Gmail node returns messageId and threadId |
| Per-item approval and "Approve with edits" | Send and Wait for Approval asks once per item, and each item continues through Approved or Rejected. "Approve with edits" makes the subject and body editable on the approval page and returns them as {{approval.edited.subject}} / {{approval.edited.body}}. Needs default values on Form fields |
| Delay: random delay and sending window | Random Delay (min–max) per item, and a Sending Window (days, start and end hour, time zone) |
| Data Table node | A built-in key/value table per workspace with Add, Check Exists, Remove and List, for suppression lists and "already contacted" checks |
| Mailbox rotation on Gmail / SMTP | Several credentials on one node, picked by Round Robin or Least Used Today, with a daily limit per mailbox that is separate from the plan cap |
| Text Formatter: more operations | Extract Domain, Extract Root URL, Find Emails in Text, Normalize Email |
| Integration nodes | Hunter.io (Domain Search, Email Finder, Email Verifier), DataForSEO (SERP, Backlinks, Domain metrics), ZeroBounce / NeverBounce (Verify Email) |
| Link Checker option | Search the raw HTML (including nav, footer and sidebar) for a domain; return found, anchorText, rel and href |
| Gallery template | "AI Backlink Outreach" in the template gallery, built from the items above |
Note on cold email: Resend and SendGrid are transactional services, and their policies generally forbid unsolicited cold email. Cold outreach should go through Gmail or SMTP mailboxes on a separate outreach domain, include an opt-out line, and follow CAN-SPAM / GDPR.
assets/fontsFrontend UI
↓
React Flow Workflow Builder
↓
Next.js App Router
↓
tRPC API Layer Public API (/api/v1) Webhook routes
↓ ↓ ↓
Prisma ORM ←──────────────────────────────────────────────┘
↓
Neon PostgreSQL
Workflow Execution Engine
↓
Inngest Background Jobs & Cron Heartbeats
↓
Node Executors
↓
External APIs / AI Providers / Messaging Services
TURNSTILE_SITE_KEYTURNSTILE_SECRET_KEYNot there yet. One workspace per account: a second workspace, moving a workflow between workspaces and handing a workspace to another owner are not possible. An API key works in its owner's own workspace. The pages do not hide what a role cannot do; the server refuses it with a message that says which role it needs.
Output: {{output}} and {{<variableName>.output}}, plus intermediateSteps when enabled.
Chat panel: workflows with a Chat Trigger get an "Open chat" button in the editor to talk to the agent.
| Kimi (Moonshot AI) |
https://api.moonshot.ai/v1 |
kimi-k2.6, kimi-k3, kimi-k2.7-code |
| Qwen (Alibaba Cloud) | https://dashscope-intl.aliyuncs.com/compatible-mode/v1 | qwen-plus, qwen-turbo |
| Ollama | http://localhost:11434/v1 | use Load models |
| Custom base URL | typed in | typed in |
| RSS Feed Trigger | Runs when a new item appears in an RSS or Atom feed; polls every 1, 5, 15, 60 minutes or daily. One poll starts at most 10 runs, oldest item first; the rest are picked up by the next polls |
| Google Form Trigger | Form submission automation |
| Stripe Trigger | Payment event automation |
| Error Trigger | Runs when a workflow fails (this workflow, or all of your workflows), for alerts |
| When Executed by Another Workflow | Start of a sub-workflow called by an Execute Workflow node or an AI Agent tool |
| Delay | Pause workflow |
| Wait | Pause for a time, until a date, or until the execution's resume URL is called; timeout that continues or fails; cancellable |
| Send and Wait for Approval | Ask a person to approve or reject (in the app, by email, Slack, Telegram or WhatsApp) and continue through the Approved or Rejected output |
| Form | The next page of a multi-step hosted form; the workflow waits until it is submitted |
| Date & Time | Get current time, format, manipulate, or compare dates |
| Text Formatter | Transform, clean, or extract text strings (Uppercase, Replace, Regex) |
| Calculator | Perform mathematical operations on numbers or variables |
pages_manage_postsinstagram_content_publishinstagram_business_content_publish| SALESFORCE |
| Salesforce node ("Connect Salesforce", production or sandbox) |
| SHOPIFY | Shopify node (a custom app's Admin API access token, shpat_...) |
| WOOCOMMERCE | WooCommerce node (consumer_key:consumer_secret) |
| POSTGRES | Postgres node (connection string) |
| MYSQL | MySQL node (mysql://user:password@host:3306/database) |
| SSH | SSH node (host, port, username, and a private key or password; optional host key fingerprint) |
| HTTP_HEADER_AUTH / HTTP_BEARER_AUTH / HTTP_BASIC_AUTH | HTTP Request and MCP Client nodes |
| HTTP_QUERY_AUTH | HTTP Request and MCP Client nodes: an API key sent as a URL parameter, written name: value (for example api_key: your-key). Added to the address only when the request is sent, and redacted from stored node data and errors like every credential value |
submitted_atsecret_token, and every update has to carry it in X-Telegram-Bot-Api-Secret-Token. The token is derived from ENCRYPTION_KEY; without that key no webhook is registered and no update is accepted.POLAR_WEBHOOK_SECRET: the Better Auth plugin's /api/auth/polar/webhooks and /api/webhooks/polar. Without the secret the second answers 503 and changes nothing.Inngest: in production (NODE_ENV=production) the server refuses to start without INNGEST_SIGNING_KEY, the key that proves a call to /api/inngest comes from Inngest. next build is not affected.
Security headers on every response (src/config/security-headers.ts, wired in next.config.ts):
| Header | Value | Where |
|---|---|---|
Strict-Transport-Security | max-age=63072000; includeSubDomains | everywhere, in production only |
X-Frame-Options | DENY | everywhere except the hosted chat page /chat/<id> |
X-Content-Type-Options | nosniff | everywhere |
Permissions-Policy | camera, microphone, geolocation, payment, USB and the motion sensors switched off | everywhere |
Referrer-Policy | strict-origin-when-cross-origin | pages (API routes that need one set their own) |
Content-Security-Policy-Report-Only | the policy described below | pages |
CSP_REPORT_URI if that is set. It allows the app's own origin, inline scripts and styles (Next.js and the UI libraries need them), images from any https address, connections to any https or wss address, and Cloudflare's Turnstile; it refuses plugins, foreign <base> and form targets, and being framed. The plan is to tighten it from the reports and then enforce it./chat/<id>, which the widget frames. /chat is the only entry of EMBEDDABLE_PATH_PREFIXES in src/config/security-headers.ts; only those paths lose X-Frame-Options and frame-ancestors 'none'. The chat page then sends its own enforcing Content-Security-Policy: frame-ancestors ... for each widget: the widget's allowed domains, any site when the list is empty, and 'none' when there is no such public chat. The widget's API (/api/chat/...), its script and every other page stay DENY.preload is not in the HSTS header: it is hard to undo. Add it there once every subdomain is https for good.Duplicate deliveries: an event a provider sends twice starts one run, not two (see below).
Webhook responses are served with a sandbox policy so a workflow cannot run scripts on the app's origin.
Sign-in: email verification for password sign-ups, password reset by email, Google and GitHub sign-in.
Sandboxed code: the Code node and {{ $json }} expressions run in QuickJS with no access to Node, the network or the filesystem. Every run gets an engine of its own with these limits; going over one fails that node with a message that says which:
SANDBOX_MAX_OUTPUT_KB). A Code node keeps at most 200 console.log lines of 10,000 characters each.Credentials are encrypted with AES-256-GCM and never sent to the browser; the key can be changed without losing them (see "Credential encryption and key rotation").
Files: download links are signed and expire after 15 minutes; files are always sent as attachments with nosniff; XLSX, DOCX, PDF and CSV files are checked against size, page, time and row limits before or while they are read (see "Files").
Tenant isolation: every workflow, execution, credential and file belongs to one workspace (kept under its owner's account, see Teams and roles), and every API key to one account. Anything that loads one of them by id goes through a single check, assertOwnership in src/lib/ownership.ts: a record that belongs to someone else gets the same 404 "not found" as one that does not exist, in the app, in /api/v1 and on file download links. scripts/test-tenant-isolation.ts tries every such route as a second user (see docs/TESTING.md, T11d).
Execution logs: credential values and common secret shapes are replaced with [REDACTED] before node input, output and errors are stored or sent to Sentry; execution data is deleted after 7, 30 or 90 days; a workflow can be set not to save node data at all (see "Execution logs: redaction and retention").
SQL injection: in the Postgres and MySQL nodes, values belong in Query Parameters (a JSON array, one value per $1 or ?), which accepts expressions such as ["{{webhook.body.email}}"] and never mixes them into the SQL. An "Execute Query" whose query text contains a {{ }} expression shows a warning in the editor and is refused when the workflow runs, unless the node's Allow expressions in query text (unsafe) option is on (off by default). Nodes that already had expressions in their query before this rule keep working: the migration switches the option on for them.
SSH: the server address is part of the credential, so a saved key can only be used against that server. Private and local addresses are refused, a command has a time limit (30 seconds, up to 120) and an output limit, and the node is not unlocked by the free trial. Use {{shellQuote value}} for anything that comes from outside the workflow, such as webhook data, so it cannot be read as extra commands.
Billing page. The AI credits card shows what is left of the month's credits and of bought credits, and has the buttons to buy more and the link to the plans.
Buying credits (Polar credit packs). A pack is a one-time product in Polar. POLAR_CREDIT_PACKS lists the packs on sale as <product id>:<credits>, comma-separated, for example prod_abc:5000,prod_def:25000; the price is whatever the product costs in Polar. "Buy 5,000 credits" opens Polar's checkout. When Polar reports the order as paid (order.paid on the Polar webhook), the credits are added. Each order is counted once, however often Polar sends the event. Bought credits do not expire and are used after the month's plan credits. Without POLAR_CREDIT_PACKS there are no buy buttons, only the link to the plans.
AI budgets still apply: an included call has a cost, so it counts towards the account's and the workflow's monthly AI budget and appears in "AI spend this month" (where the note about paying your provider does not apply to it).
twitter-image.tsxNEXT_PUBLIC_APP_URL/docs is a getting-started guide and /about says what RXJ is; both are static pages inside MarketingShell (src/features/marketing/components/marketing-shell.tsx: the navbar, a dark header, the footer). Their words are in src/features/marketing/lib/docs-content.ts and about-content.ts, and follow the same rule as content.ts. The navbar links to Docs.src/features/marketing/components/site-footer.tsx, shared by every public page: Product, Resources (with Docs), Company (About) and Account. It links only to pages that exist.COLUMNS in site-footer.tsx when they exist.The gallery. Compact cards of the same height: plan badge (each plan its own colour, with a lock when the user's plan is lower), language, name, category, a two-line summary, up to four node chips then "+N", and Use template (Upgrade on a locked one). Clicking a card opens a Details panel with the whole description, the setup steps as a numbered list, every node used, and the same button. Filters: search, category, plan and language; sort by most used or newest. 24 cards at first, then Load more. The grid has 4 columns on wide screens, 3 next to the sidebar at 1366 px and 1 on a phone; it follows the width the page gets, so collapsing the sidebar can add a column. The page does not poll and its links do not prefetch.
Summary and steps come from the one description field: the summary is the text before "Set up:" (or "To set it up:", "سیٹ اپ:", "الإعداد:"), and the steps are what follows, one per line, numbered, or one per sentence or semicolon. A description without that marker is all summary. There is no separate database field.
Plans: a template needs the cheapest plan that can run every one of its nodes (getMinPlanForNodes in src/config/plans.ts: WhatsApp and SSH are Pro; Gmail, Google Sheets, the Schedule Trigger and the AI Agent are Intermediate), or the plan its admin picked if that is higher. The gallery shows that plan, Use template checks it on the server (from the nodes that will be copied), and saving a template never stores a lower one. A template saved earlier as Free with a WhatsApp node shows and is locked as Pro without any change to its row. Locked templates stay in the gallery with a lock and Upgrade. An active free trial opens every template, the same way it opens plan-locked nodes, except one with an SSH node (the trial does not unlock SSH).
Using a template counts toward the plan's workflow limit, and workflows already made from a template do not change when the template is edited or deleted.
Languages. Every template has a language: English, Urdu or Arabic (templates made before the field existed are English). The gallery has a language filter next to the search; Urdu and Arabic names and descriptions are shown right to left. An admin picks the language under New template and in the edit dialog.
Starter templates ship with the app as JSON seed data in src/features/templates/seed/, each in English, Urdu and Arabic: 40 templates, 120 in all. They are not in the gallery until an admin adds them: Templates → Starter templates, tick the ones wanted, and add them hidden (to read them first) or published. Adding them again changes nothing: one that is already there, also one edited since, is left alone.
| Starter template | What it does | Plan |
|---|---|---|
| WhatsApp clinic appointment bot | Answers patients about doctors, hours and fees and books appointments into a Google Sheet. Gives no medical advice | Pro |
| School fee reminder on WhatsApp | On the 1st of the month reads the fee list from a Google Sheet and sends an approved WhatsApp template to parents who have not paid | Pro |
| WhatsApp shop order bot | Shows the menu, takes the order, adds up the total and saves it in a Google Sheet | Pro |
| WhatsApp real estate lead bot | Asks what the person wants (buy, rent or sell, type, area, budget, timeline) and saves the lead in a Google Sheet | Pro |
| Support FAQ bot | A website chat that answers from a list of questions and answers written into the agent's instructions | Intermediate |
| Cash-on-delivery order confirmation on WhatsApp | Each new cash-on-delivery order gets a WhatsApp message asking the customer to confirm. | Pro |
| Order status on WhatsApp | Customers send their order number on WhatsApp and get the status back in their own language. | Pro |
| WhatsApp helper that understands voice notes | Answers customers' WhatsApp questions about your business, including voice notes in Urdu, Arabic or English, which are turned into text first. | Pro |
| WhatsApp product catalogue assistant | Answers product questions on WhatsApp from your own catalogue files and hands the chat to your team on Telegram when a customer wants to order or needs a person. | Pro |
| Appointment reminders on WhatsApp | Every evening at 18:00 (Pakistan time), reads your bookings sheet and sends a WhatsApp reminder to everyone booked for tomorrow. | Pro |
| After-sale review request on WhatsApp | Three days after a Shopify order is fulfilled, asks the customer on WhatsApp to rate it from 1 to 5. | Pro |
| Expense logger on Telegram | Send your bot a message like "fuel 2500" or "bought stock for 12000" and it adds a dated row to a Google Sheet with the item, amount and category. | Intermediate |
| Overdue invoice reminder on Telegram | Every morning at 09:00 (Pakistan time), sends you one Telegram message for each unpaid invoice in your sheet that is past its due date. | Intermediate |
| Contact form to Google Sheets with confirmation email | A hosted contact form: every submission becomes a row in your Google Sheet and the person gets a confirmation email from your Gmail. | Intermediate |
| Marketplace order emails to Google Sheets | Reads the new-order emails your marketplace or website sends you (Daraz, Noon, Amazon or your own shop) and writes each order as a row: number, customer, items, total and city. | Intermediate |
| AI email replies you approve first | For each new customer email, Gemini drafts a reply from your business facts; you approve or reject it in RXJ (Approvals), and only approved replies are sent, in the same thread. | Intermediate |
| Daily Shopify sales email | Every morning at 08:00 (Pakistan time), emails you yesterday's paid Shopify orders: how many, the total and the list. | Intermediate |
| Weekly PDF report from a Google Sheet | Every Monday at 09:00 (Pakistan time), reads a Google Sheet (sales, expenses or donations), totals the amount per category and emails you a PDF report. | Intermediate |
| School admission enquiries | Parents fill in an admission form; the enquiry is saved to a Google Sheet and the parent gets an email straight away with the fee for that class and the next step. | Intermediate |
| Shopify paid orders ledger in Google Sheets | Every paid Shopify order becomes a row in a Google Sheet for your accounts: date, order, customer, phone, total, currency and payment method. | Intermediate |
| Website chat that answers order questions | A chat for your WooCommerce website: it looks up a visitor's orders by the email they ordered with and tells them the status, and answers delivery and return questions. | Intermediate |
| Website chat that answers from your documents | A chat widget for your website that answers only from your own documents: brochures, price lists, FAQs and policies in a Google Drive folder. | Intermediate |
| Staff check-in on Telegram | Staff send "in" or "out" (or a short note) to your Telegram bot and it is logged with the time and their name in a Google Sheet, so you have a simple attendance record. | Intermediate |
| New Shopify order alert on Telegram | Every new Shopify order posts a short summary to your team's Telegram group: order number, customer, phone, city and total, so packing can start at once. | Free |
| Simple AI bot on Telegram | A Telegram bot that answers any question with Gemini, in the language it is asked in. | Free |
| Staff knowledge bot on Telegram | Your staff ask the Telegram bot questions (prices, procedures, policies) and get answers from the documents in a Google Drive folder. | Free |
| Customer feedback form with unhappy-customer alert | A feedback form for your shop, clinic or restaurant. | Free |
| Facebook Page FAQ bot on Messenger | Answers the common questions people send to your Facebook Page (hours, delivery, payment) on Messenger, in their language, from facts you write. | Free |
| Telegram voice notes to Notion | Send a voice note to your Telegram bot (in Urdu, Arabic or English) and it is written out as text and saved as a page in a Notion database: ideas, to-dos and meeting notes on the go. | Free |
| School announcements with principal approval | Teachers submit an announcement on a form; the principal approves or rejects it on Telegram, and approved announcements are posted to the parents' Telegram channel. | Free |
| Leave and expense requests with Telegram approval | Staff request leave or an expense on a form; the manager approves or rejects it with one tap on Telegram, and the person gets the answer by email. | Free |
| Booking form to Google Calendar | Customers pick a date and a time slot on a booking form; a 30-minute event goes into your Google Calendar and they get a confirmation email. | Free |
| Event registration with a PDF ticket | People register for your event, class or seminar on a form and get their own PDF ticket by email at once. | Free |
| Quote requests drafted by AI, sent after your approval | Customers ask for a price on a form; Gemini drafts a quote from your price list, you check it in RXJ (Approvals), and only approved quotes are emailed to the customer. | Free |
| Low stock alert on Telegram | When a Shopify product is updated and one of its variants has fewer than 5 in stock, you get a Telegram message with the product, variant and quantity, so you can reorder in time. | Free |
| Shipping email with tracking link | When a Shopify order is fulfilled, the customer gets an email in your own words with the courier and the tracking link. | Free |
| Product descriptions in English, Urdu and Arabic | When you add a product to Shopify, Gemini writes a short description in English, Urdu and Arabic from its title, type and tags and saves it on the product. | Free |
| Cancelled order alert from WooCommerce | When an order in your WooCommerce store is cancelled, your team gets a Telegram message with the order, customer, phone and total, so someone can call and save the sale or stop the parcel. | Free |
| PDF summary by email | Upload a PDF (a contract, a report, a tender, a circular) on a form and get a short summary with the key points by email a minute later. | Free |
| Invoice generator: PDF by email and to Google Drive | Fill in a short form and get a numbered PDF invoice: it is emailed to your customer and a copy is saved to a Google Drive folder. | Free |
The plan is the cheapest one that can run every node of the template (WhatsApp is Pro, the AI Agent is Intermediate); an admin can raise it, not lower it. The agent's instructions and example details (clinic, menu, FAQ) are written in the template's language and are meant to be replaced. A seed file holds the workflow once and its texts per language; a setting written @text:<key> takes that text. To add a template or a translation, edit the JSON: the tests check that every language has every text.
More about the seeds:
Set up: (سیٹ اپ:, الإعداد:) and the steps separated by semicolons; the details panel shows them as a numbered list. At most 600 characters.{{form.<name>}}) is made from the Latin letters of its label, so Urdu and Arabic labels end with the English word in brackets, for example نام (Name). The tests check that a template's fields have the same names in all three languages and that every {{form.x}} it reads is one of its fields.0 13 * * * in the English and Urdu versions (Pakistan) and 0 15 * * * in the Arabic one (Saudi Arabia). Booking times carry the offset (+05:00 / +03:00).npx tsx scripts/check-starter-templates.ts runs every starter in every language through the same check "Build with AI" uses before a workflow goes into the editor (node types, operations, required settings, ports and connections) against the real node registry, builds the editor's nodes and edges, and checks the declared plan. No database or network.AI_BUILDER_RATE_LIMIT_PER_MINUTE), descriptions up to 2,000 characters, workflows up to 60 nodes, and about 55 seconds for both attempts together. Making a workflow counts toward the plan's workflow limit, which is checked before the model is called.CHATBOT_SOURCE_RATE_LIMIT_PER_MINUTE).Each line holds the action, a name and id of what it was done to, the address and the time; never a secret, a key or a password. Lines are only ever added: the app has no way to change or remove one, and they go when the account itself is deleted. Each user sees their own log. If a line cannot be written, the action it describes still succeeds and the failure is logged on the server.
REQUIRE_EMAIL_VERIFICATION | Set to false to switch verification off |
POLAR_* | Billing (access token, product IDs, webhook secret, success URL). POLAR_WEBHOOK_SECRET is what Polar's webhooks are verified with |
POLAR_CREDIT_PACKS | AI credit packs on sale: <Polar product id>:<credits>, comma-separated (one-time products). Not set: no packs are sold. See "Built-in AI credits" |
INNGEST_SIGNING_KEY | Proves that calls to /api/inngest come from Inngest. Required in production: the server does not start without it |
PRIVATE_NETWORK_ALLOWLIST | The private addresses workflows may reach, comma-separated: hosts, host:port or CIDR ranges, e.g. 127.0.0.1:11434,10.0.0.0/24 (self-hosting only) |
ALLOW_PRIVATE_NETWORK_REQUESTS | true lets workflows reach every private address. Still works, but logs a warning: prefer PRIVATE_NETWORK_ALLOWLIST |
WEBHOOK_RATE_LIMIT_PER_MINUTE, API_RATE_LIMIT_PER_MINUTE | Requests per minute for one workflow's webhook URL, and for one API key. Default 120 each |
TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY | Cloudflare Turnstile on the sign-up form. Both or neither: with neither the check is off |
CSP_REPORT_URI | Where browsers send Content-Security-Policy reports. Not set: the app's Sentry project |
SIGN_IN_RATE_LIMIT_PER_MINUTE | Password and two-factor code attempts per minute from one IP address, default 20 |
WEBHOOK_RESPONSE_TIMEOUT_MS | How long a webhook waits for the workflow's response, default 25000 |
SANDBOX_MAX_OUTPUT_KB | The most a Code node or one expression may return, in KB, default 1024 |
MAX_ITEMS_PER_NODE | Items one node may process in a list, default 100 |
MAX_PINNED_DATA_KB | The most data that can be pinned on one node, default 512 |
WORKFLOW_VERSION_LIMIT | Versions kept per workflow, default 50 |
WORKFLOW_VERSION_DAYS | Days a version is kept (the newest always stays), default 30 |
MCP_TOOL_TIMEOUT_MS | How long an MCP Server Trigger's tool call waits for the run's result, default 50000 |
VECTOR_STORE_ENGINE | Set to app to make the Vector Store compare embeddings in the app even when pgvector is installed |
WAIT_MAX_DAYS | The longest a Wait node may wait, in days, default 7 (the limit of Inngest's free plan) |
FORM_RATE_LIMIT_PER_MINUTE | Submissions per minute for one hosted form, default 30 |
AI_BUILDER_RATE_LIMIT_PER_MINUTE | "Build with AI" requests per minute for one user, default 10 |
CHATBOT_SOURCE_RATE_LIMIT_PER_MINUTE | Documents and web pages added to chatbots per minute by one user, default 30 |
CHAT_WIDGET_RATE_LIMIT_PER_MINUTE, CHAT_VISITOR_RATE_LIMIT_PER_MINUTE | Messages per minute for one public chat widget, and for one visitor of it. Defaults 60 and 10 |
VISION_MAX_PDF_PAGES | The most pages of a PDF one Analyze Image / Document node may read, default 20 |
MAX_FILE_SIZE_MB, MAX_USER_STORAGE_MB, FILE_RETENTION_DAYS | Workflow file limits, defaults 10, 200 and 7 |
MAX_UNCOMPRESSED_FILE_MB, MAX_ZIP_ENTRIES | What an XLSX or DOCX file may unpack to, defaults 50 and 2000 |
PDF_MAX_PAGES, PDF_EXTRACT_TIMEOUT_SECONDS | Pages read from a PDF and the time allowed, defaults 200 and 20 |
CSV_MAX_ROWS | Most rows read from a CSV file, default 10000 |
TELEGRAM_PROXY | Optional proxy for calls to the Telegram API |
NGROK_URL | Tunnel domain for local development |