Vrymdor™Guidesvrymdor.com

Connect your AI Node to every platform

The Vrymdor™ AI Node is a checkpoint that runs inside your own network, in front of your AI agents and the tools they use. n8n, Make, Zapier, LangChain, LlamaIndex, Claude Desktop, IDEs (integrated development environments) and your own code all call the node the same way, and every call gets the same protection:

  1. The agent's identity and status are checked (a stopped or expired agent is refused).
  2. Your policy decides whether the action is allowed for the agent's role.
  3. PII (personally identifiable information, such as email addresses and card numbers) and secrets (such as API keys, where API means application programming interface) are replaced with [REDACTED:<TYPE>] before anything leaves, and again on the way back.
  4. The request is signed and a metadata-only audit record is written. Prompts and payloads stay in your network.

The node never runs code it is sent. It runs one allowed action (remove PII, run a tool, call a model or send a webhook) against a destination you configured.

This guide takes you from a new account to a first governed call on each platform. Every command and response below matches the current product.

Before you start

You need an AI Node account. Create one at https://app.vrymdor.com/node/start. Community is free; Pro and Business start with a 14 day free trial (your card is saved at checkout), then are paid monthly; Enterprise is a custom contract. After you sign up you get an activation email: open the link, sign the service agreements, and set your password. Your workspace is then at https://app.vrymdor.com/app.

The whole setup at a glance

  1. Sign up and activate. Choose a plan at https://app.vrymdor.com/node/start, then open the link in your activation email, accept the agreements and set your password.
  2. Sign in at https://app.vrymdor.com/app. You land on Home base.
  3. Get your node address. Either turn on Hosted node on Home base (Pro, Business and Enterprise; the card then shows Your node address), or run the node yourself and use the address of the machine it runs on, port 8080 (Step 2).
  4. Only if you run the node yourself: get your node token. On Home base, in Connect your node, select Show my node token. It starts with vrym_live_.
  5. Get an agent ID and agent secret. Open the AI agents tab, fill in Register an agent and select Register. Copy the Agent ID (agt_...) and Agent secret (vrym_agent_...) right away: the secret is shown only once.
  6. Connect your platform (Step 4) with three values: the node address, the agent ID and the agent secret.
  7. Check it works. For a node you run, Connect your node reads Connected and active; for a hosted node, Hosted node reads Running and your first call shows on the Activity tab.
Community Pro Business Enterprise
Price $0 $29 a month $99 a month Custom annual contract
Active agents up to 2 up to 15 up to 50 Custom
Requests included each month 10,000 100,000 500,000 Custom
Past the included requests Blocked until the next month $0.001 each $0.0005 each Custom
Custom redaction patterns, response filtering, audit export, priority routing Not included Included Included Included
Custom policy sections, admin roles, advanced telemetry Not included Not included Included Included

Community is a hard cap: at 10,000 requests in a calendar month (UTC, Coordinated Universal Time) the node answers with HTTP (Hypertext Transfer Protocol) status 402 and the code quota_exhausted until the month resets or you upgrade. Paid plans are never blocked for volume. One request is one call the node evaluated, whatever the decision; calls refused for a bad credential, a plan limit or a rate limit do not count.

You also need Docker (for the Docker and Docker Compose options), or a Kubernetes cluster with Helm 3 or Helm 4 (for the Helm option), on a machine inside your network.

Step 1: get your credentials

There are two kinds of credential. Keep both like passwords.

Credential Looks like What uses it Where you get it
Node token vrym_live_ and 48 characters The node itself, so it can register with your workspace and sync your agents and policy Home base, Connect your node, Show my node token
Agent ID and Agent Secret agt_... and vrym_agent_... Each platform or program that calls the node (n8n, Make, Zapier, your code, and MCP clients, where MCP means Model Context Protocol) AI agents, Register an agent

The gateway URL (uniform resource locator, the web address) that platforms use is your node's own address as that platform can reach it, for example http://vrymdor-node:8080 inside Docker or https://ai-gateway.example.com behind your reverse proxy. The node listens on port 8080.

Get your node token

  1. Sign in at https://app.vrymdor.com/app. AI Node accounts open on the Home base tab.
  2. Find the Connect your node card. Its badge reads Waiting for node until a node reports in.
  3. Select Show my node token. A workspace admin can do this. The token is filled into the Docker, Docker Compose and Helm commands on the card, each with a Copy button (copy buttons work in the web app; in the phone app, select the text). The card's MCP entry and its n8n, Make and Zapier entry never use the node token: they take an agent's credentials (next section).

The card also shows your Workspace id. Only an air-gapped node needs it (Option D); a connected node looks it up by itself.

Register an agent for each platform

  1. Open the AI agents tab.
  2. In Register an agent, fill in Name (for example n8n production), Role (lowercase, as in your policy) (for example devops), Owner email (the person accountable for the agent) and Valid for (days) (90 or less).
  3. Select Register. The card shows "Copy these now. The agent secret is shown only this once; Vrymdor™ keeps only a hash of it." under it, with Agent ID (agt_...) and Agent secret (vrym_agent_...), each with a Copy button. Copy both now and keep them where the platform will use them. The secret cannot be shown again; if you lose it, register a new agent.

Use one agent per platform connection. Stopping an agent on the AI agents tab ends every token it holds at once, without touching your other agents.

Step 2: run your node

Hosted node: we run it, you paste one address

On AI Node Pro, Business and Enterprise, and on every Vrymdor™ Business Security plan, Vrymdor can run your node for you. Nothing to install, and it is the easiest way to use cloud platforms that cannot reach a computer on your own network: n8n Cloud, Make, Zapier and GitHub Actions on GitHub's own runners. (Community runs the node on your own computer with one of the options below.)

  1. In the app, open Home base, Hosted node, and press Turn on. Within about a minute the badge reads Running and the card shows your node address, for example https://node.vrymdor.com/t/k3v9q2m7x4ab. The last part is random and is not your company name.
  2. Under Add a model or tool provider, add each provider your agents call: a name (for example openai), its address (https://api.openai.com), the header that carries the key (Authorization or x-api-key) and the key itself (for Authorization, write Bearer and then the key). The key is locked the moment it arrives so that only your hosted node can read it, and it is never shown again. To change a key, save the provider again with the new one.
  3. Register an agent for each platform (Step 1), then use your node address wherever this guide says Gateway URL or VRYMDOR_NODE_URL: - n8n Cloud: import the n8n Cloud workflow (Step 4, n8n), put your node address in the URL of its Vrymdor token step and the agent's agent_id and agent_secret in that step's Custom Auth credential. - Make and Zapier: the HTTP module or Webhooks by Zapier step posts to your node address followed by the path in Step 4, for example https://node.vrymdor.com/t/k3v9q2m7x4ab/v1/token. - GitHub Actions: store your node address as the VRYMDOR_NODE_URL repository secret. GitHub's own runners reach it, so no self-hosted runner is needed.

Your prompts and responses pass through your own node and are not stored by Vrymdor. The Activity tab shows what the node did (time, agent, platform, target, decision, rule and how many values were masked), kept for your plan's retention, 30 days by default. The Holds tab lists requests a policy held for a person: open one to see the request (read live from your node, never stored), then approve or reject it.

Turn off stops the node and keeps its data for 30 days, then it is deleted. If your plan no longer includes hosting, the node is turned off the same way. The hosted address is covered by the uptime commitment in the SLA exhibit for paid plans.

Run it yourself

Pick one option. In every option the node registers with your workspace on start, and the Connect your node badge changes from Waiting for node to Connected and active within about a minute (the node reports every 60 seconds). If reports stop for three minutes the badge reads Node offline.

Get the node. In the app, open Home base, Connect your node, and press Download the node. Choose amd64 for most servers and PCs, or arm64 for ARM servers and Apple silicon, and download the file and its checksum (the links work for 15 minutes). The node is Self-Hosted Software licensed to your workspace under the MSA; keep the file to your own machines. On the machine that runs the node, check and load it (on macOS, check it with shasum -a 256 -c instead). The file carries the image under two tags, vrymdor/node-proxy:1.0.0 (what Docker Compose and the Helm chart run) and vrymdor/node-proxy:latest (what the docker run commands below run), so every option finds it.

sha256sum -c vrymdor-node-proxy-amd64.tar.sha256     # prints: vrymdor-node-proxy-amd64.tar: OK
docker load -i vrymdor-node-proxy-amd64.tar

Option A: Docker

  1. Start the node with your node token:
docker run -d --name vrymdor-node -p 127.0.0.1:8080:8080 \
  -v vrymdor-node-data:/data \
  -e VRYMDOR_NODE_TOKEN=vrym_live_... \
  vrymdor/node-proxy:latest

VRYMDOR_NODE_TOKEN is the only required setting. The node then contacts https://api.vrymdor.com (outbound only, over HTTPS, the encrypted form of HTTP; nothing needs to reach the node from the internet). The vrymdor-node-data volume keeps the node's signing key, admin token and audit across restarts.

-p 127.0.0.1:8080:8080 makes the node reachable only from this machine, which is the safe choice for a first test. When other machines, or other containers such as n8n or the MCP bridge on Linux, must reach it, use -p 8080:8080 instead, and limit who can reach port 8080 with your firewall.

Because of -d the container runs in the background, so its messages, including the Activate License message described below, are in docker logs vrymdor-node.

  1. Check it is healthy:
curl -s http://127.0.0.1:8080/healthz

A ready node answers {"status":"ready","checks":{"redaction_engine":true,"token_key":true,"signing_key":true,"state_store":true,"audit_writable":true},...,"fail_mode":"FAIL_CLOSED"}. docker ps also shows the container as healthy after its first health check.

  1. Watch the Connect your node card switch to Connected and active.

  2. Optional: tell the node where agents may go. Without this, only Remove PII and secrets and the policy checks work. Add the destinations as JSON (JavaScript Object Notation) in VRYMNOX_UPSTREAMS, with the API keys in their own variables (the node holds the keys; platforms and agents never see them). Remove the first container first (the vrymdor-node-data volume keeps the keys), then start it again with the new settings:

docker rm -f vrymdor-node
docker run -d --name vrymdor-node -p 127.0.0.1:8080:8080 \
  -v vrymdor-node-data:/data \
  -e VRYMDOR_NODE_TOKEN=vrym_live_... \
  -e VRYMNOX_UPSTREAMS='{"anthropic":{"url":"https://api.anthropic.com","kind":"llm","auth_header":"x-api-key","auth_env":"ANTHROPIC_API_KEY"},"github":{"url":"https://mcp.example.com/github","kind":"mcp"},"hooks":{"url":"https://hooks.example.com/vrymdor","kind":"webhook"}}' \
  -e ANTHROPIC_API_KEY=sk-ant-... \
  vrymdor/node-proxy:latest

kind is llm (a model API), mcp (an MCP server), webhook, websocket or grpc. The name before each object (anthropic, github, hooks) is the Upstream you enter in each platform.

  1. Find the admin token for the node's local admin API (used in Step 3):
docker exec vrymdor-node cat /data/admin.token

What the Activate License message means. If the container starts without a node token, it prints the message below and stops with exit code 2 (with -d, read it with docker logs vrymdor-node). Follow the link to create a free Community account, then copy your token from Connect your node, remove the stopped container with docker rm vrymdor-node, and run the command again.

Vrymdor AI Node: no node token yet.

  Activate License: https://app.vrymdor.com/node/start?plan=node_community

Option B: Docker Compose

Use this to run the node with its own Redis (needed for more than one node replica) on one host. The package folder holds docker-compose.yml and .env.example.

Load the image first. Support provides the package folder; load the image file from Download the node first (see the note at the start of Step 2). The compose file runs vrymdor/node-proxy:1.0.0 unless .env sets VRYMNOX_GATEWAY_VERSION; without that image on the machine, docker compose up -d stops with "failed to read dockerfile" (the package folder has no Dockerfile to build from). If your image file has only the latest tag, either tag it with docker tag vrymdor/node-proxy:latest vrymdor/node-proxy:1.0.0, or put VRYMNOX_GATEWAY_VERSION=latest in .env.

  1. In the package folder, copy the settings file and open it:
cp .env.example .env
nano .env
  1. Set your token. This is the only line you must fill in:
VRYMDOR_NODE_TOKEN=vrym_live_...

Optional lines in the same file: VRYMNOX_CONTROL_PLANE_URL (default https://api.vrymdor.com), VRYMNOX_GATEWAY_VERSION (the image tag, default 1.0.0), VRYMNOX_UPSTREAMS and the API keys it names (see Option A, step 4), and VRYMNOX_AUDIT_RETENTION_DAYS (183 in the example, which meets the EU (European Union) AI Act six-month log duty for high-risk deployers). Leave VRYMNOX_SIGNING_KEY and VRYMNOX_ADMIN_TOKEN unset and the node generates them into its data volume.

  1. Start it:
docker compose up -d
  1. Check it is ready:
docker compose exec gateway python -c "import urllib.request;print(urllib.request.urlopen('http://127.0.0.1:8080/healthz').read().decode())"
  1. Find the admin token for the node's local admin API (used in Step 3):
docker compose exec gateway cat /data/admin.token

The compose file keeps the node on an internal Docker network named vrymnox-gateway_agents and does not publish a port, so only containers on that network can call it, at http://gateway:8080. To reach it from the host or other machines, add a docker-compose.override.yml beside it (127.0.0.1:8080:8080 keeps it to the host; 8080:8080 opens it to other machines, as in Option A):

services:
  gateway:
    ports: ["127.0.0.1:8080:8080"]

Option C: Helm, Kubernetes or OpenShift

The chart is vrymdor-node. It runs the node as a non-root user (user id 10002) with a read-only root filesystem, every Linux capability dropped, liveness and readiness probes on /healthz, and a Secret that holds your node token.

The chart is not public. Support provides the chart as a folder (vrymdor-node) or a file (vrymdor-node-1.0.0.tgz); use that path in place of ./vrymdor-node below. The chart runs the image vrymdor/node-proxy:1.0.0, which is not in a public registry, so give your cluster the image from Download the node (see the note at the start of Step 2) in one of two ways:

  • Push it to a registry your cluster can pull from and point the chart at it: docker tag vrymdor/node-proxy:1.0.0 registry.example.com/vrymdor/node-proxy:1.0.0, docker push registry.example.com/vrymdor/node-proxy:1.0.0, then add --set image.repository=registry.example.com/vrymdor/node-proxy to the helm install command.
  • Or import it on every Kubernetes node that may run the pod: sudo ctr -n k8s.io images import vrymdor-node-proxy.tar (containerd; on k3s, sudo k3s ctr images import vrymdor-node-proxy.tar), or, on a kind test cluster, after docker load, kind load docker-image vrymdor/node-proxy:1.0.0.
  1. Install with your node token:
helm install vrymdor-node ./vrymdor-node --set nodeKey=vrym_live_...

To keep the token out of your shell history, create the Secret yourself and point the chart at it:

kubectl create secret generic vrymdor-node-token --from-literal=VRYMDOR_NODE_TOKEN=vrym_live_...
helm install vrymdor-node ./vrymdor-node --set existingSecret=vrymdor-node-token
  1. Verify. The port-forward command keeps running and holds the terminal, so run curl in a second terminal:
kubectl get pods -l app.kubernetes.io/name=vrymdor-node
kubectl port-forward svc/vrymdor-node 8080:8080
curl -s http://127.0.0.1:8080/healthz          # in a second terminal

Stop the port forward with Ctrl+C when you are done. Inside the cluster, platforms call http://vrymdor-node.<namespace>.svc:8080. The admin token for Step 3 is in the pod: kubectl exec deploy/vrymdor-node -- cat /data/admin.token.

The values you can set:

Value Default What it does
nodeKey empty Your node token. Required unless existingSecret or airGapped is set
existingSecret, existingSecretKey empty, VRYMDOR_NODE_TOKEN Use a Secret you manage
image.repository, image.tag vrymdor/node-proxy, the chart's app version (1.0.0) Where the image comes from
controlPlaneUrl https://api.vrymdor.com Where the node syncs agents and policy
upstreams empty The VRYMNOX_UPSTREAMS JSON (Option A, step 4)
extraEnvFrom None For example a secretRef to a Secret holding the upstream API keys
policy empty A vrymnox-policy.yaml, mounted read-only and loaded at start
replicaCount, redisUrl 1, empty More than one replica needs a shared Redis at redisUrl
persistence.enabled false true keeps /data on a PersistentVolumeClaim instead of an emptyDir
ingress.enabled false An optional Ingress; the node needs no inbound route from the internet
openshift false true leaves the user and group ids to OpenShift's restricted SCC (Security Context Constraints)
airGapped, tenantId, airGappedAgents, airGappedAgentsSecret Off Air-gapped mode (Option D)
resources 100m CPU (central processing unit) and 256Mi requested, 1 CPU and 512Mi limit Container resources

An install that cannot start is refused with the fix in the message, for example "set nodeKey (your node token from Connect your node in the Vrymdor app) or existingSecret".

Option D: air-gapped (Enterprise)

Air-gapped mode is for a network with no route to the internet. Set VRYMDOR_AIR_GAPPED=1 and the node never contacts Vrymdor: no registry sync, no reports, no telemetry. Because it cannot fetch your agents, it reads them from a file. The Connect your node card stays at Waiting for node for an air-gapped node, because it never reports.

An Enterprise air-gapped install is arranged with Vrymnox. Vrymnox support provides the node image as the file vrymdor-node-proxy.tar with its checksum file vrymdor-node-proxy.tar.sha256, on a USB (Universal Serial Bus) drive, and prepares the agents file with you; the agent secrets themselves are never on the drive. On the server:

  1. Check and load the image from the drive:
sha256sum -c vrymdor-node-proxy.tar.sha256     # prints: vrymdor-node-proxy.tar: OK
docker load -i vrymdor-node-proxy.tar          # loads vrymdor/node-proxy:1.0.0 and :latest
  1. Put the agents file and your policy in a folder, for example /opt/vrymdor/node. The agents file is a JSON list; each agent has agent_id, name, role, owner_email, expires_at (a date and time in ISO 8601, the international standard format, for example 2027-01-31T00:00:00Z) and secret_sha256 (the hex digest of the agent secret under SHA-256, the Secure Hash Algorithm with a 256-bit digest).
  2. Start the node. VRYMNOX_TENANT_ID is your Workspace id, shown on the Connect your node card (Vrymnox support also has it):
docker run -d --name vrymdor-node -p 127.0.0.1:8080:8080 -v vrymdor-node-data:/data \
  -v /opt/vrymdor/node:/etc/vrymdor:ro \
  -e VRYMDOR_AIR_GAPPED=1 \
  -e VRYMNOX_TENANT_ID=<your workspace id> \
  -e VRYMNOX_AGENTS_FILE=/etc/vrymdor/agents.json \
  -e VRYMNOX_POLICY_FILE=/etc/vrymdor/vrymnox-policy.yaml \
  vrymdor/node-proxy:latest
  1. Check curl -s http://127.0.0.1:8080/healthz answers "status":"ready". As in Option A, use -p 8080:8080 when other machines must reach the node.

Starting air-gapped without a tenant id is refused. With a node token set, docker logs vrymdor-node shows "Vrymnox gateway cannot start: air-gapped mode (VRYMDOR_AIR_GAPPED) needs VRYMNOX_TENANT_ID, and the agents in VRYMNOX_AGENTS_FILE, because the gateway never talks to the control plane". With neither a token nor a tenant id, the node shows the Activate License message (Option A) instead, because it checks for those first.

With Helm, set airGapped=true, tenantId and the agents. A JSON list cannot go through --set (Helm splits the value at its commas), so pass the agents file with --set-file, or name a Secret you manage that holds it under the key agents.json:

helm install vrymdor-node ./vrymdor-node --set airGapped=true --set tenantId=<your workspace id> \
  --set-file airGappedAgents=agents.json
# or keep the agents in a Secret you manage:
kubectl create secret generic vrymdor-node-agents --from-file=agents.json
helm install vrymdor-node ./vrymdor-node --set airGapped=true --set tenantId=<your workspace id> \
  --set airGappedAgentsSecret=vrymdor-node-agents

The chart refuses an air-gapped install without a tenant id ("airGapped needs tenantId (the node never asks the cloud which tenant it serves)") or without agents ("airGapped needs airGappedAgents (a JSON list) or airGappedAgentsSecret").

The separate offline USB kit for Vrymdor™ Business Security servers is described in the onboarding guide; it installs the security connector, not the AI Node.

Step 3: set your policy

The policy decides which roles may use which upstreams and tools, and which tools wait for a person. Set it before you connect platforms that run tools: a tool call is refused with tool_not_allowed until the policy lists the tool for the agent's role. Removing PII works without a policy, and so do model calls and webhooks until a role limits its upstreams (a role with an upstreams list reaches only those).

  1. Start from the example in the package, vrymnox-policy.example.yaml, and keep your copy as vrymnox-policy.yaml in your own repository. A small policy:
version: 1
roles:
  devops:
    upstreams: [github, hooks, anthropic]   # the upstreams this role may reach
mcp:
  tools:
    - name: create_issue
      upstream: github
      roles: [devops]
      input_schema:                  # the tool's arguments are checked against this
        type: object
        required: [title]
        properties:
          title: {type: string, maxLength: 200}
    - name: delete_branch
      upstream: github
      roles: [devops]
      require_approval: true         # every call waits for a person

The example file also shows redaction (your own patterns and literal terms), injection (flag or block prompt injection markers) and defaults (rate limits and token lifetime). 2. Check it without starting anything:

docker run --rm -v "$PWD":/w:ro vrymdor/node-proxy:latest policy-check /w/vrymnox-policy.yaml

A valid file prints Policy valid. sha256 <hex>, 2 tool(s), 14 detector(s).; an invalid one prints Policy rejected: <reason> and exits 1, so you can run it in CI (continuous integration). 3. Apply it to the node with the admin token from Step 2. On the machine that runs the node with Option A:

GW=http://127.0.0.1:8080
ADMIN_TOKEN=$(docker exec vrymdor-node cat /data/admin.token)
curl -X POST --data-binary @vrymnox-policy.yaml -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H 'Content-Type: application/yaml' "$GW/api/v1/policies/import"

With Option B or C, get the admin token as shown there, and set GW to an address where you reach the node. The answer is {"status":"applied","policy_sha256":"<hex>","tools":2}. The node saves the policy in its data volume, so it also survives a restart.

If your workspace also stores a policy, the workspace policy wins at the next full sync. The node asks for your registry every 5 seconds, and while nothing changed the answer is a short "not modified" (HTTP 304), so a policy you imported locally stays in force until your workspace registry changes (an agent is registered, stopped, released or extended, the plan changes, or the workspace policy changes) or the node restarts. Then the workspace policy replaces it. Use one place for your policy.

Holds and approvals. A tool with require_approval: true answers HTTP 202 with "decision":"hold" and a hold_id. The person approving sees the agent's stated intent beside the exact request. The agent states it with the X-Vrymnox-Intent header (up to 500 characters) or, in a node request, as "context": {"intent": "..."} (up to 200 characters; the header wins when both are sent). From the Python client: node.tool("github", "delete_branch", {...}, context={"intent": "..."}). Approve or reject on the node, inside your network, so the request never leaves it:

curl -s -H "Authorization: Bearer $ADMIN_TOKEN" "$GW/api/v1/holds"
curl -s -X POST -H "Authorization: Bearer $ADMIN_TOKEN" "$GW/api/v1/holds/<hold_id>/approve"   # or /reject

The caller then gets the result from GET $GW/v1/node/holds/<hold_id> (202 while waiting; the final answer once, then it is forgotten). A rejected hold answers with the code rejected_by_reviewer; a hold nobody decides is rejected after hold_timeout_seconds (900 by default) with approval_expired. The AI agents tab shows how many requests are waiting for approval in total across your nodes.

Where decisions show up. Every decision is in the node's hash-chained audit (in /data/audit). The AI agents tab shows each node's report (counts and readiness, never payloads) and every agent's state, and Home base shows this month's requests against your plan.

Step 4: connect your platform

Every platform below needs three values from Step 1: the gateway URL (your node), an Agent ID and an Agent Secret. The examples use an agent with the role devops, an upstream named github that offers the tool create_issue, an upstream named anthropic and a webhook upstream named hooks, as set in Step 2 (Option A, step 4) and Step 3. Replace them with the names in your node's settings and policy. Every tool example needs the tool in your policy (Step 3); without it the call is refused with tool_not_allowed.

n8n

The community node is n8n-nodes-vrymnox. It adds the credential Vrymnox Agent API and the node Vrymnox, which n8n also offers as an AI agent tool. For the AI agent tool, start the n8n instance with N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true; n8n offers community nodes as tools only with that setting.

The fastest start: a ready workflow. Download one, then in n8n create a workflow, open the menu at the top right, choose Import from File and pick the file: the workflow with the Vrymnox node for self-hosted n8n, or the n8n Cloud workflow, a Vrymdor token step that keeps your agent in an n8n credential and a Vrymdor AI Node step that cleans the text (the steps are under "n8n Cloud" below). Each has a note on the canvas with its steps, and its run shows [REDACTED:EMAIL] and [REDACTED:CREDIT_CARD] where the sample text had an email address and a card number. The same files are in the examples folder of the n8n-nodes-vrymnox repository on GitHub.

  1. In n8n, open Settings, Community Nodes, Install, enter n8n-nodes-vrymnox, accept the risk notice and install.
  2. Create the credential: Credentials, Add credential, search Vrymnox Agent API, and fill in: - Gateway URL: your node as n8n reaches it, for example http://vrymdor-node:8080 (default http://vrymnox-gateway:8080). - Agent ID: agt_... - Agent Secret: vrym_agent_...

Save. n8n tests the credential by exchanging it at your node's /v1/token. n8n then keeps the short-lived access token from /v1/token in the credential and exchanges the secret again whenever the node answers that the token expired, so you never handle tokens yourself. 3. Add the Vrymnox node to a workflow and choose its credential.

  1. Choose an Operation. The fields shown depend on it:
Operation Fields
Remove PII and Secrets Input
Run a Tool Upstream, Tool, Input (the tool's arguments), Wait for Approval, Wait Up To (Minutes)
Call a Model Upstream, API Path (default v1/messages), Input (the model request body), Wait for Approval, Wait Up To (Minutes)
Send a Webhook Upstream, Input (the payload), Wait for Approval, Wait Up To (Minutes)
Check Only (Dry Run) Action Type (Tool, Model or Webhook), Upstream, Tool (for Tool), Input

Input defaults to the incoming item ({{ $json }}); it also accepts JSON text. 5. First test: set Operation to Remove PII and Secrets and Input to {"note": "mail [email protected], card 4111 1111 1111 1111"}, then select Execute step. The output item has "ok": true, "decision": "allow" and "output": {"note": "mail [REDACTED:EMAIL], card [REDACTED:CREDIT_CARD]"}.

How it behaves:

n8n Cloud, or any n8n without the Vrymnox node

n8n Cloud only offers community nodes that n8n has verified, and the Vrymnox node is waiting for that. Until then, n8n's own steps do the same job. Your node must have an address n8n Cloud can reach: the hosted node (https://node.vrymdor.com/t/..., Home base, Hosted node) is the simplest.

Your agent secret always goes in an n8n credential, never in a step. n8n keeps credentials encrypted, does not show a saved secret again, and leaves credentials out when a workflow is shared or exported.

With the ready workflow (the n8n Cloud workflow, imported with Import from File):

  1. Your node address. Open the Vrymdor token step. In URL, replace PASTE_YOUR_NODE_ID so the URL is your node address from the Vrymdor app (Home base, Hosted node) followed by /v1/token, for example https://node.vrymdor.com/t/k3v9q2m7x4ab/v1/token.
  2. Your agent, in a credential. In the same step, under Custom Auth, choose Create new credential. In its JSON field paste your own agent from the Vrymdor app (AI agents), then Save:

json {"body": {"agent_id": "agt_...", "agent_secret": "vrym_agent_..."}}

  1. Check that it connects. Select Execute step on Vrymdor token. An access_token in the output means your node accepted the agent. Authorization failed means the agent id or secret is wrong, or the agent is not registered for this node: copy them again from AI agents, or register a new agent.
  2. Run it. Select Execute workflow. The Vrymdor AI Node step shows [REDACTED:EMAIL] and [REDACTED:CREDIT_CARD] where the sample text had an email address and a card number, and vrymdor.redactions counts them. The step reads your node address from the Vrymdor token URL, so you paste it only once.

To use your own data, put your trigger in place of Sample text and set items_from at the top of the Vrymdor AI Node step to your trigger's name. field names the field to clean (text by default).

Building it by hand with two HTTP Request nodes:

  1. Keep the secret in a credential. In the Vrymdor token step of step 2 (or Credentials, Add credential), Credential for Custom Auth, Create new credential. n8n names it Custom Auth account. In its JSON field paste, with your own agent:

json {"body": {"agent_id": "agt_...", "agent_secret": "vrym_agent_..."}}

  1. Get a token. Add an HTTP Request node named Vrymdor token: Method POST, URL https://node.vrymdor.com/t/<your node>/v1/token, Authentication Generic Credential Type, Generic Auth Type Custom Auth, Custom Auth Custom Auth account, Send Body on, Body Content Type JSON, Specify Body Using JSON, JSON {}. Run it: the answer has access_token, which lasts 10 minutes.
  2. Remove PII and secrets. Add a second HTTP Request node: Method POST, URL the same address ending in /v1/node/run, Send Headers on, header Authorization with the value Bearer {{ $('Vrymdor token').item.json.access_token }}, Send Body on, Body Content Type JSON, Specify Body Using JSON, and the JSON (switch the field to Expression):

{{ JSON.stringify({ action: { type: "sanitize" }, input: $json.text }) }}

Run it on an item with a text field such as mail [email protected]. The answer's output is mail [REDACTED:EMAIL], and redactions counts what was removed. Map output into the next step, and stop the workflow (an If node on redactions) wherever text with personal data must not go on. 4. Call a model through the node the same way: URL /v1/node/run and the JSON {"action":{"type":"llm","upstream":"anthropic","path":"v1/messages"},"input":{...}}, the model request as the provider documents it. The node removes PII before the model sees the prompt.

Get a new token for each run (step 2 first in every workflow), since a token lasts 10 minutes. When the Vrymnox node is verified on n8n Cloud, it replaces both requests and renews the token by itself.

Make

The Make custom app has one connection and one module, Run a governed action.

Not yet public in Make. Until Make approves the listing, Vrymnox can invite your Make organization to the app.

  1. In a scenario, add the Vrymnox AI Node app and the module Run a governed action.
  2. Create a connection with: - Gateway URL: your node as Make's cloud can reach it, with no trailing slash, for example https://ai-gateway.example.com. Make runs in the cloud, so the node must be reachable over HTTPS from the internet (for example behind your reverse proxy). - Agent ID: agt_... - Agent secret: vrym_agent_...

A wrong credential shows "The gateway refused this agent's credential." 3. Fill in the module:

Field Value
Action Remove PII and secrets, Run a tool, Call a model, or Send a webhook
Upstream A name configured on your node, for example github. Not needed to remove PII
Tool For Run a tool only, for example create_issue (it must be in your policy, Step 3)
API path (models) For Call a model only, default v1/messages
Input The data to clean, the tool arguments, the model request body or the webhook payload
Check only (dry run) Yes runs every check and forwards nothing
  1. First test: Action Remove PII and secrets, Input mail [email protected], then Run once. The output shows decision allow and output mail [REDACTED:EMAIL].

The module's outputs are ok, decision, output, redactions, rules, hold_id, error and audit. A refusal shows as an error such as Vrymnox tool_not_allowed: .... A held action returns decision hold and a hold_id; the module does not wait.

Zapier (Webhooks by Zapier)

Zapier uses two Webhooks by Zapier steps: one exchanges the agent secret for a short-lived access token, the next sends the action. The node must be reachable over HTTPS from the internet.

  1. Add a step: Webhooks by Zapier, event POST. - URL: https://ai-gateway.example.com/v1/token - Payload Type: json - Data: agent_id = agt_... and agent_secret = vrym_agent_...

Test it. The result has access_token, token_type (Bearer) and expires_in (600 seconds by default). 2. Add a second step: Webhooks by Zapier, event Custom Request. - Method: POST - URL: https://ai-gateway.example.com/v1/node/run - Headers: Authorization = Bearer followed by the Access Token from step 1, and Content-Type = application/json - Data:

{"action": {"type": "sanitize"}, "input": "Customer [email protected] paid with 4111 1111 1111 1111",
 "context": {"platform": "zapier", "workflow": "zap-42"}}

Map fields from earlier steps into input. A mapped value inside quotes must not contain an unescaped double quote; for free text, a Code by Zapier step that builds the JSON is safer. 3. Test it. The answer has "decision": "allow" and "output": "Customer [REDACTED:EMAIL] paid with [REDACTED:CREDIT_CARD]". Map output into the next step.

For a tool that your policy lists (Step 3), change Data to {"action": {"type": "tool", "upstream": "github", "tool": "create_issue"}, "input": {"title": "..."}}.

LangChain, LlamaIndex and Python

The Python client is the package vrymdor-node (module vrymnox_node, one dependency: httpx). The LangChain extra needs langchain-core 0.3.49 or newer, the first version that accepts the tools' JSON Schemas as they are.

Not yet on PyPI. Until it is, install the wheel file from support: pip install vrymdor_node-1.0.0-py3-none-any.whl.

  1. Install:
pip install vrymdor-node                  # the client
pip install "vrymdor-node[langchain]"     # plus LangChain
pip install "vrymdor-node[llamaindex]"    # plus LlamaIndex
  1. Call the node. sanitize, llm and webhook work before you set a policy; tool needs the tool in your policy (Step 3):
from vrymnox_node import VrymnoxError, VrymnoxHeld, VrymnoxNode

node = VrymnoxNode("http://vrymdor-node:8080", agent_id="agt_...", agent_secret="vrym_agent_...")

print(node.sanitize("Customer [email protected] called"))       # Customer [REDACTED:EMAIL] called
node.tool("github", "create_issue", {"title": "Login fails"})
node.llm("anthropic", "v1/messages", {"model": "claude-...", "max_tokens": 200,
                                       "messages": [{"role": "user", "content": "Hello"}]})
node.webhook("hooks", {"customer": "[email protected]"})

try:
    node.tool("github", "drop_db", {})
except VrymnoxHeld as held:
    print(node.wait_for(held.hold_id))      # waits until a person decides (default up to 15 minutes)
except VrymnoxError as err:
    print(err.code, err.retryable)          # tool_not_allowed False
  1. Give an agent framework only the tools your policy allows:
tools = node.langchain_tools()      # one LangChain StructuredTool per allowed tool, named github__create_issue
tools = node.llamaindex_tools()     # the same, as LlamaIndex FunctionTools with each tool's input schema

The client exchanges the secret for a token and renews it a minute before it expires. Every call is governed like any other: a tool the role may not use is not in the list at all.

Claude Desktop, Cursor, VS Code and other MCP clients

The node is an MCP server. Its tool list shows vrymnox_sanitize, vrymnox_check (dry run), vrymnox_hold_status, vrymnox_llm (when a model upstream is configured) and every tool your policy allows for the agent's role, named <upstream>__<tool>, for example github__create_issue.

What the node protects here. Text you type into Claude Desktop or an IDE chat goes to that client's model provider before any tool runs, so the node cannot remove PII from your prompt. It protects what the tools send and receive: the arguments a tool call carries and the answer that comes back. Try it with test data, for example "use vrymnox_sanitize on: mail [email protected]", never with real personal data.

Desktop clients and IDEs start a local command, so they use the stdio (standard input and output) bridge, which runs from the node image and renews the agent's token by itself. It needs Docker on the computer running the client. Use http://host.docker.internal:8080 when the node runs on the same computer (Docker Desktop on macOS and Windows), or your node's address otherwise. With Docker Engine on Linux, host.docker.internal is not defined: add "--add-host=host.docker.internal:host-gateway" to args before the image name, and publish the node's port on an address the containers can reach (-p 8080:8080 does; -p 127.0.0.1:8080:8080 does not).

Claude Desktop. Open Settings, Developer, Edit Config, or edit the file directly: ~/Library/Application Support/Claude/claude_desktop_config.json on macOS, and %APPDATA%\Claude\claude_desktop_config.json on Windows. Add:

{
  "mcpServers": {
    "vrymdor": {
      "command": "docker",
      "args": ["run", "-i", "--rm",
               "-e", "VRYMNOX_NODE_URL", "-e", "VRYMNOX_AGENT_ID", "-e", "VRYMNOX_AGENT_SECRET",
               "vrymdor/node-proxy:latest", "mcp-stdio"],
      "env": {
        "VRYMNOX_NODE_URL": "http://host.docker.internal:8080",
        "VRYMNOX_AGENT_ID": "agt_...",
        "VRYMNOX_AGENT_SECRET": "vrym_agent_..."
      }
    }
  }
}

Restart Claude Desktop. The Vrymdor tools appear in the tools menu. Ask it to "use vrymnox_sanitize on: mail [email protected]" and the tool answers "mail [REDACTED:EMAIL]" (the output as JSON, so a text answer comes back in quotes).

Cursor. Put the same mcpServers block in ~/.cursor/mcp.json (all projects) or .cursor/mcp.json in a project.

VS Code. In .vscode/mcp.json, the key is servers and each server names its type:

{
  "servers": {
    "vrymdor": {
      "type": "stdio",
      "command": "docker",
      "args": ["run", "-i", "--rm", "-e", "VRYMNOX_NODE_URL", "-e", "VRYMNOX_AGENT_ID", "-e", "VRYMNOX_AGENT_SECRET",
               "vrymdor/node-proxy:latest", "mcp-stdio"],
      "env": {"VRYMNOX_NODE_URL": "http://host.docker.internal:8080", "VRYMNOX_AGENT_ID": "agt_...",
              "VRYMNOX_AGENT_SECRET": "vrym_agent_..."}
    }
  }
}

If Python can import the node's package on that computer (for example on the node's own server), the command can be python with the arguments -m vrymnox_gateway mcp-stdio and the same three variables.

MCP over HTTP. POST /mcp speaks MCP over streamable HTTP (JSON responses; protocol versions 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05). Every request needs Authorization: Bearer <token> from /v1/token, and that token lasts 600 seconds by default, so use /mcp directly from code that can fetch a fresh token, and the stdio bridge for desktop clients:

curl -s -X POST "$GW/mcp" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

GitHub Actions

This step removes PII and secrets from a pull request description. The node must be reachable from the runner: a public HTTPS address, or a self-hosted runner inside your network. Add three repository secrets (Settings, Secrets and variables, Actions): VRYMDOR_NODE_URL, VRYMDOR_AGENT_ID and VRYMDOR_AGENT_SECRET.

GitHub gives no repository secrets to a pull_request run from a fork, so this workflow works for pull requests from branches of the same repository; on a pull request from a fork the token request fails, and so does the step. Do not switch to pull_request_target to get the secrets for forks if the job checks out or runs the pull request's code: that hands your agent secret to untrusted code. (The step below reads only the pull request body, never its code.)

name: Clean pull request text
on:
  pull_request:
    types: [opened, edited, synchronize, reopened]   # edited: fixing the description runs the check again
jobs:
  sanitize:
    runs-on: ubuntu-latest
    steps:
      - name: Remove PII and secrets from the pull request body
        shell: bash                  # bash -eo pipefail: a refused or failed call fails the step
        env:
          GW: ${{ secrets.VRYMDOR_NODE_URL }}
          AGENT_ID: ${{ secrets.VRYMDOR_AGENT_ID }}
          AGENT_SECRET: ${{ secrets.VRYMDOR_AGENT_SECRET }}
          PR_BODY: ${{ github.event.pull_request.body }}
        run: |
          TOKEN=$(jq -n --arg id "$AGENT_ID" --arg s "$AGENT_SECRET" '{agent_id:$id,agent_secret:$s}' \
            | curl -sf -X POST "$GW/v1/token" -H 'Content-Type: application/json' -d @- | jq -r .access_token)
          echo "::add-mask::$TOKEN"
          RESULT=$(jq -n --arg body "$PR_BODY" '{action:{type:"sanitize"},input:$body,context:{platform:"github-actions"}}' \
            | curl -sf -X POST "$GW/v1/node/run" -H "Authorization: Bearer $TOKEN" \
                -H 'Content-Type: application/json' -d @-)
          jq -r .output <<<"$RESULT" > sanitized-body.txt
          cat sanitized-body.txt
          # The node flags a likely prompt injection (rule injection_marker, ASI01) and still returns the
          # cleaned text, so live traffic is never refused on a guess. A pull request is different: this fails
          # the check so a person reviews the text first. Make the check required to stop the merge.
          if jq -e '.rules | index("injection_marker")' <<<"$RESULT" >/dev/null; then
            # The node names each phrase it matched (its fixed list, never other text); point at the line.
            jq -r '.markers // [] | .[]' <<<"$RESULT" | while IFS= read -r phrase; do
              line=$(grep -n -i -F -m1 -- "$phrase" <<<"$PR_BODY" | cut -d: -f1)
              echo "::error title=Possible prompt injection::Found \"$phrase\" in the pull request description${line:+ on line $line}. Edit the description and this check runs again."
            done
            echo "::error title=Possible prompt injection::The pull request description contains a known prompt injection phrase (ASI01). Review it before merging."
            exit 1
          fi

The pull request body is passed through an environment variable and jq, never pasted into the script, so text in the pull request cannot change the command. For a body with [email protected] and an Anthropic key, the file holds [REDACTED:EMAIL] and [REDACTED:ANTHROPIC_KEY] in their place.

Stopping a prompt injection before it merges. The node checks text for phrases attackers use to take over an AI agent, such as "ignore previous instructions" or "print your system prompt" (OWASP ASI01, goal hijack). By default it flags them (the rule injection_marker in the result) and still passes the cleaned text on, so your live traffic is never refused on a guess. In a pull request you want a person to look first, so the last lines of the step fail the check when the node flags the text, naming each phrase it found and the line of the description it is on. Editing the description runs the check again, so fixing the text is enough; no new commit is needed. To make that stop the merge, turn the check into a required one: Settings, Branches, add a branch protection rule for your main branch, check Require status checks to pass before merging, and choose this check. GitHub then keeps the Merge button off until the text is fixed or a maintainer reviews it. The phrase check catches the common, copied attacks, not every rewording, so treat it as one signal alongside your review.

Any HTTP client (curl)

Set GW to your node, exchange the agent secret, then call the node. The token request also accepts the OAuth 2.1 (Open Authorization) client credentials form (grant_type=client_credentials, client_id, client_secret).

GW=http://vrymdor-node:8080
TOKEN=$(curl -s -X POST "$GW/v1/token" -H 'Content-Type: application/json' \
  -d '{"agent_id":"agt_...","agent_secret":"vrym_agent_..."}' | jq -r .access_token)

Remove PII and secrets (nothing is sent anywhere):

curl -s -X POST "$GW/v1/node/run" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"action":{"type":"sanitize"},"input":"Card 4111 1111 1111 1111, mail [email protected]"}'
{"node_version":"1","ok":true,"decision":"allow","output":"Card [REDACTED:CREDIT_CARD], mail [REDACTED:EMAIL]",
 "redactions":{"CREDIT_CARD":1,"EMAIL":1},"asi":[],"rules":["redacted"],"hold_id":null,"error":null,
 "attestation":{"payload_sha256":"<hex>","signature":"t=<unix>,v1=<hex>"},
 "audit":{"hash":"<hex>","trace_id":"<hex>"},"latency_ms":3.8}

Run a tool (an MCP tool your policy allows, Step 3):

curl -s -X POST "$GW/v1/node/run" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"action":{"type":"tool","upstream":"github","tool":"create_issue"},"input":{"title":"Login fails for [email protected]"},"idempotency_key":"run-881:item-0"}'

The tool receives Login fails for [REDACTED:EMAIL] and its answer comes back in output. A repeated idempotency_key from the same agent gets the first answer back for ten minutes instead of running twice.

Call a model (the request body goes to action.path on the model upstream):

curl -s -X POST "$GW/v1/node/run" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"action":{"type":"llm","upstream":"anthropic","path":"v1/messages"},"input":{"model":"claude-...","max_tokens":200,"messages":[{"role":"user","content":"Summarize the ticket from [email protected]"}]}}'

Send a webhook (to an upstream of kind webhook):

curl -s -X POST "$GW/v1/node/run" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"action":{"type":"webhook","upstream":"hooks"},"input":{"customer":"[email protected]"}}'

Check only (dry run): add "params":{"dry_run":true} to any tool, model or webhook request. Every check runs, nothing is forwarded, and rules includes dry_run.

Inspect an envelope (webhook mode: the sanitized payload comes back, nothing is forwarded):

curl -s -X POST "$GW/v1/inspect" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"vrymnox_version":"1","source":{"platform":"zapier","workflow_id":"zap-42","step":"2"},"action":{"kind":"webhook"},"payload":{"customer":"[email protected]","note":"Card 4111 1111 1111 1111 on file"}}'
{"vrymnox_version":"1","decision":"allow","redactions":{"EMAIL":1,"CREDIT_CARD":1},"asi":[],"rules":["redacted"],
 "payload":{"customer":"[REDACTED:EMAIL]","note":"Card [REDACTED:CREDIT_CARD] on file"},
 "attestation":{"payload_sha256":"<hex>","signature":"t=<unix>,v1=<hex>"},"audit":{"hash":"<hex>","trace_id":"<hex>"}}

source.platform is zapier, n8n, github or custom. GET $GW/v1/node/schema (no token needed) returns the request and response JSON Schemas and every error code.

Troubleshooting

Refusals carry a stable code and no internal detail, in the shape of the route that answered:

Route What a refusal looks like
POST /v1/node/run and GET /v1/node/holds/<hold_id> (used by n8n, Make, Zapier and the Python client) {"ok": false, "decision": "block", "error": {"code": "...", "message": "...", "retryable": false}, ...} with the HTTP status in the next table. Plan refusals add error.upgrade_url
POST /v1/token, POST /v1/inspect and the admin API (/api/v1/...) {"detail": "...", "rules": [...], "asi": [...]} with the HTTP status (plan refusals add code and upgrade_url). A policy refusal at /v1/inspect instead answers 200 with "decision": "block" and a message
POST /mcp (MCP clients) A JSON-RPC (JSON remote procedure call) error such as {"jsonrpc": "2.0", "id": 1, "error": {"code": -32001, "message": "..."}} for a bad token (-32002 for a plan limit). A refused tool call is a tool result with "isError": true and the text <code>: <message>
Any route, on an unexpected fault HTTP 500 with {"error": {"code": "internal_error", "message": "...", "retryable": true}, "trace_id": "..."}
Code (HTTP status) What it means What to do
unauthenticated (401) The token is missing, malformed or expired, or /v1/token got a wrong secret ("Unknown agent or wrong secret."). Tokens of an agent that was killed or quarantined get "This token was revoked." Copy the Agent ID and Agent Secret again and get a new token; n8n, the Python client and the stdio bridge renew tokens by themselves. For "This token was revoked.", see agent_stopped: a new token is refused until the agent is released
agent_stopped (403) The agent was killed or quarantined on the AI agents tab, for example while its action waited for approval ("The agent was stopped while the action waited."). /v1/token refuses it a new token with 403 "This agent may not receive tokens." A quarantined agent: select Release on its card on the AI agents tab. A killed agent cannot be revived (the tab says "A killed identity cannot be revived"): register a new agent. An agent in drain and hold is not refused: its requests wait for approval (202, "decision": "hold") until you release it
agent_expired (403) "This agent's registration has expired." Select Extend 30 days on the agent's card
unregistered_agent (403) "This agent is not registered." The agent is not in your registry on this node Register it; a synced node picks it up within about 5 seconds
tool_not_allowed (403) "Tool drop_db is not on the allowlist." The tool is not in the policy for this role and upstream Add it under mcp.tools with the role
upstream_not_allowed (403) "Role devops may not use hooks." Add the upstream to the role's upstreams
forbidden (403) A refusal with no more specific code Read error.message and rules
unknown_upstream (404) "No mcp upstream named nope is configured." The upstream name is not in the node's VRYMNOX_UPSTREAMS Check the spelling, or add it
not_found (404) "No such hold for this agent." The hold_id is unknown, belongs to another agent, or its final answer was already collected Use the hold_id from the 202 answer, with the same agent
invalid_arguments (400) "Tool arguments do not match the schema: arguments.title is required" Send the arguments the tool's input_schema asks for
invalid_request (422 or 400) 422: the request does not match the NodeRequest schema, and the message names the field. 400: a tool's input is not a JSON object ("tools/call arguments must be an object") Compare the request with GET /v1/node/schema; send a tool's arguments as a JSON object
payload_too_large (413) "Payload is larger than 1048576 bytes." (1 MiB by default) Send less, or raise defaults.max_body_bytes in the policy (up to 16 MiB)
tool_changed (403) The tool's description or schema changed since it was approved An admin approves it again on the node
injection_blocked (403) A prompt injection marker was found and the policy blocks them Review the input; or set injection.action to flag
rate_limited (429) "Rate limit of 120 requests per minute reached." (default) Retry later; raise the limit in defaults or the role
budget_exhausted (429) The agent's daily budget is used Wait for the next day, or raise the agent's budget
loop_detected (403) Agents called each other too often or too deep Fix the loop; the agent may be quarantined
quota_exhausted (402) Community only: the month's 10,000 included requests are used. error.upgrade_url links to your plan Wait for the next month (UTC) or upgrade
agent_limit (402) More active agents than the plan allows Stop an agent, or upgrade
feature_not_in_plan (402) An extra capability outside your plan (standard protection is never switched off) Upgrade to the tier in the table above
rejected_by_reviewer (200, from the holds route) A person rejected the held action Nothing ran; adjust and try again
approval_expired (200, from the holds route) Nobody decided the hold in time Ask again, or raise hold_timeout_seconds
tool_error (200, with "ok": false) The tool ran and reported its own error; error.message is the tool's message. The node allowed the call ("decision": "allow") Check the tool's arguments and the destination
upstream_error (502) The destination failed, for example "github answered HTTP 500." (its own text is not passed on) Check the destination; retryable says whether to retry
timeout (504) The destination did not answer in time Retry, or raise params.timeout_ms
unavailable (503) The node could not complete a check and refused (fail closed), or it is busy Retry shortly; check /healthz
internal_error (500) An unexpected fault; the trace_id matches the node's log Send the trace_id to support

Plan headers: once the node has received your plan in its first sync, answers on the metered agent routes (/v1/node/run, /mcp, /v1/inspect, /v1/llm/..., /v1/mcp/... and /v1/webhooks/...) carry X-Vrymdor-Requests-Used and X-Vrymdor-Requests-Included, and from 80 percent of the allowance also X-Vrymdor-Quota-Warning. Other routes (/v1/token, /healthz, /v1/node/schema, the admin API) carry none, and an air-gapped node sends none, because it never receives a plan.

The node itself:

What you see Cause and fix
The container stops with "Activate License" No node token. Set VRYMDOR_NODE_TOKEN (Step 2, Option A); with -d, the message is in docker logs vrymdor-node
Conflict. The container name "/vrymdor-node" is already in use A container with that name exists, running or stopped. Remove it with docker rm -f vrymdor-node (the vrymdor-node-data volume keeps the keys) and run the command again
docker compose up -d stops with "failed to read dockerfile" The image vrymdor/node-proxy:1.0.0 (or the tag in VRYMNOX_GATEWAY_VERSION) is not on the machine. Load the image file from support (Step 2)
"the control plane refused AWARENESS_TOKEN" The token is wrong or was rotated. Copy it again from Connect your node
"could not reach https://api.vrymdor.com to look up the tenant" The node has no outbound HTTPS. Allow it, or use air-gapped mode
Connect your node stays at Waiting for node The node is not running, cannot reach https://api.vrymdor.com, or is air-gapped (it never reports)
Node offline No report for three minutes. Check docker ps and the node's log
/healthz answers 503 A readiness check failed; the checks object names it, and the node blocks traffic until it passes
docker ps shows unhealthy on a slow or emulated machine The health check gives the node 10 seconds to answer. If curl -s http://127.0.0.1:8080/healthz answers ready, the node works and the machine is only slow