{"_id":"@empirio-ai/n8n-nodes-empirio-ai","_rev":"7-885422bba1e3d98c83b9e33e5359677d","name":"@empirio-ai/n8n-nodes-empirio-ai","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@empirio-ai/n8n-nodes-empirio-ai","version":"0.1.0","keywords":["n8n-community-node-package","n8n","n8n-node","empirio","survey","surveys","questionnaire","feedback","market-research"],"author":{"url":"https://www.empirio.ai","name":"empirio","email":"support@empirio.de"},"license":"MIT","_id":"@empirio-ai/n8n-nodes-empirio-ai@0.1.0","maintainers":[{"name":"marvinempirio","email":"marvin@empirio.de"}],"homepage":"https://www.empirio.ai","bugs":{"url":"https://github.com/empirio-de/empirio-ai-n8n/issues"},"n8n":{"nodes":["dist/nodes/EmpirioAi/EmpirioAi.node.js","dist/nodes/EmpirioAiTrigger/EmpirioAiTrigger.node.js"],"strict":true,"credentials":["dist/credentials/EmpirioAiApi.credentials.js","dist/credentials/EmpirioAiOAuth2Api.credentials.js"],"n8nNodesApiVersion":1},"dist":{"shasum":"8c1ecdce30e010c94b8195aa4b8116fc59ccb06d","tarball":"https://registry.npmjs.org/@empirio-ai/n8n-nodes-empirio-ai/-/n8n-nodes-empirio-ai-0.1.0.tgz","fileCount":46,"integrity":"sha512-/sc8Lxg4nDsLyZjPgb72cusijHWn6uYabb39vBIaZ17tBXGcaQ0gm9H29szg9SUG28OvVzQ475rDLYe9m7Bc9Q==","signatures":[{"sig":"MEUCIQC08dyJiwpMjrw5YRSBzqnCOsGRVTNtWPUgYLpnTzDBtQIgZaFJEWArfG+Z6cs9UzGUPUEJER/7OFWuW+KVpWfjg+U=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":140640},"gitHead":"d331043b695cd8313f121802f32c7d7a731ce7ee","scripts":{"dev":"n8n-node dev","lint":"n8n-node lint","test":"vitest run","build":"n8n-node build","release":"n8n-node release","lint:fix":"n8n-node lint --fix","typecheck":"tsc -p tsconfig.json --noEmit","test:watch":"vitest","prepublishOnly":"n8n-node prerelease"},"_npmUser":{"name":"marvinempirio","email":"marvin@empirio.de"},"repository":{"url":"git+https://github.com/empirio-de/empirio-ai-n8n.git","type":"git"},"_npmVersion":"11.6.0","description":"n8n community nodes for empirio.ai: build, publish and analyse surveys, and trigger workflows on every new survey response.","directories":{},"_nodeVersion":"24.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"9.39.4","vitest":"3.2.7","prettier":"3.9.6","release-it":"21.0.2","typescript":"5.9.3","@types/node":"22.19.17","@n8n/node-cli":"0.45.3"},"peerDependencies":{"n8n-workflow":"*"},"_npmOperationalInternal":{"tmp":"tmp/n8n-nodes-empirio-ai_0.1.0_1787326634598_0.5991745822784673","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@empirio-ai/n8n-nodes-empirio-ai","version":"0.1.1","keywords":["n8n-community-node-package","n8n","n8n-node","empirio","survey","surveys","questionnaire","feedback","market-research"],"author":{"url":"https://www.empirio.ai","name":"empirio","email":"support@empirio.de"},"license":"MIT","_id":"@empirio-ai/n8n-nodes-empirio-ai@0.1.1","maintainers":[{"name":"marvinempirio","email":"marvin@empirio.de"}],"homepage":"https://www.empirio.ai","bugs":{"url":"https://github.com/empirio-de/empirio-ai-n8n/issues"},"n8n":{"nodes":["dist/nodes/EmpirioAi/EmpirioAi.node.js","dist/nodes/EmpirioAiTrigger/EmpirioAiTrigger.node.js"],"strict":true,"credentials":["dist/credentials/EmpirioAiApi.credentials.js","dist/credentials/EmpirioAiOAuth2Api.credentials.js"],"n8nNodesApiVersion":1},"dist":{"shasum":"c200df53030e94b1dd32d81db66456efa8f00ede","tarball":"https://registry.npmjs.org/@empirio-ai/n8n-nodes-empirio-ai/-/n8n-nodes-empirio-ai-0.1.1.tgz","fileCount":46,"integrity":"sha512-mf2fOKiZL1LBhL+KN/qJP6CfGuX/FdnW1bfj/mU1f84vV7Bs7huEELWC3MCRltXSa3Y98SK5p/eqS99kK0MnLQ==","signatures":[{"sig":"MEYCIQDP7TMj3tVPLuVSUVaEavkbfZ5G310pfWwwSt9ZuS13xQIhAOBVR42A6nfjJ7tv7t+dRbnxyz7yvBwUyNanmrthGpvY","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@empirio-ai%2fn8n-nodes-empirio-ai@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":140640},"gitHead":"ec8f292f08732b05c85919fe2b6a0f4bfa41c4aa","scripts":{"dev":"n8n-node dev","lint":"n8n-node lint","test":"vitest run","build":"n8n-node build","release":"n8n-node release","lint:fix":"n8n-node lint --fix","typecheck":"tsc -p tsconfig.json --noEmit","test:watch":"vitest","prepublishOnly":"n8n-node prerelease"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:67ec0c00-330e-49fd-90bc-759bdf27b120"}},"repository":{"url":"git+https://github.com/empirio-de/empirio-ai-n8n.git","type":"git"},"_npmVersion":"11.5.1","description":"n8n community nodes for empirio.ai: build, publish and analyse surveys, and trigger workflows on every new survey response.","directories":{},"_nodeVersion":"24.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"9.39.4","vitest":"3.2.7","prettier":"3.9.6","release-it":"21.0.2","typescript":"5.9.3","@types/node":"22.19.17","@n8n/node-cli":"0.45.3"},"peerDependencies":{"n8n-workflow":"*"},"_npmOperationalInternal":{"tmp":"tmp/n8n-nodes-empirio-ai_0.1.1_1787327282403_0.17607176103705058","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@empirio-ai/n8n-nodes-empirio-ai","version":"0.1.2","keywords":["n8n-community-node-package","n8n","n8n-node","empirio","survey","surveys","questionnaire","feedback","market-research"],"author":{"url":"https://www.empirio.ai","name":"empirio.ai","email":"support@empirio.de"},"license":"MIT","_id":"@empirio-ai/n8n-nodes-empirio-ai@0.1.2","maintainers":[{"name":"empirio-ai-org","email":"info@empirio.ai"}],"homepage":"https://www.empirio.ai","bugs":{"url":"https://github.com/empirio-de/empirio-ai-n8n/issues"},"n8n":{"nodes":["dist/nodes/EmpirioAi/EmpirioAi.node.js","dist/nodes/EmpirioAiTrigger/EmpirioAiTrigger.node.js"],"strict":true,"credentials":["dist/credentials/EmpirioAiApi.credentials.js","dist/credentials/EmpirioAiOAuth2Api.credentials.js"],"n8nNodesApiVersion":1},"dist":{"shasum":"40ed490fa0d3d60f686e791ee9651fa7d2110c84","tarball":"https://registry.npmjs.org/@empirio-ai/n8n-nodes-empirio-ai/-/n8n-nodes-empirio-ai-0.1.2.tgz","fileCount":52,"integrity":"sha512-yP0vHPMkG8y+ZaBdOYwwdMtj/C6rOo5b5dlCskVlGQt6esBCE4A3/kR7J9vK3raZh246nf+dJgmgm4BWhLVZaA==","signatures":[{"sig":"MEUCIQCpPFcIJbiRCcHTj/rNHJ08xE6QeF0ODVXP1uqUam+nqQIgQEQZFqRy0y0bf2arviVuudrAiJxH/wIShszLoL3+CtU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@empirio-ai%2fn8n-nodes-empirio-ai@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":177748},"gitHead":"242965c050638735a3ba85ba3bab480c30ea4798","scripts":{"dev":"n8n-node dev","lint":"n8n-node lint","test":"vitest run","build":"n8n-node build","release":"n8n-node release","lint:fix":"n8n-node lint --fix","typecheck":"tsc -p tsconfig.json --noEmit","test:watch":"vitest","prepublishOnly":"n8n-node prerelease"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:67ec0c00-330e-49fd-90bc-759bdf27b120"}},"repository":{"url":"git+https://github.com/empirio-de/empirio-ai-n8n.git","type":"git"},"_npmVersion":"11.5.1","description":"n8n community nodes for empirio.ai: build, publish and analyse surveys, and trigger workflows on every new survey response.","directories":{},"_nodeVersion":"24.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"9.39.4","vitest":"3.2.7","prettier":"3.9.6","release-it":"21.0.2","typescript":"5.9.3","@types/node":"22.19.17","@n8n/node-cli":"0.45.3"},"peerDependencies":{"n8n-workflow":"*"},"_npmOperationalInternal":{"tmp":"tmp/n8n-nodes-empirio-ai_0.1.2_1787603984225_0.6855197654832912","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@empirio-ai/n8n-nodes-empirio-ai","version":"0.2.0","keywords":["n8n-community-node-package","n8n","n8n-node","empirio","survey","surveys","questionnaire","feedback","market-research"],"author":{"url":"https://www.empirio.ai","name":"empirio.ai","email":"support@empirio.de"},"license":"MIT","_id":"@empirio-ai/n8n-nodes-empirio-ai@0.2.0","maintainers":[{"name":"empirio-ai-org","email":"info@empirio.ai"}],"homepage":"https://www.empirio.ai","bugs":{"url":"https://github.com/empirio-de/empirio-ai-n8n/issues"},"n8n":{"nodes":["dist/nodes/EmpirioAi/EmpirioAi.node.js","dist/nodes/EmpirioAiTrigger/EmpirioAiTrigger.node.js"],"strict":true,"credentials":["dist/credentials/EmpirioAiApi.credentials.js","dist/credentials/EmpirioAiOAuth2Api.credentials.js"],"n8nNodesApiVersion":1},"dist":{"shasum":"5f75a18d348c414d0e555c0cce635edbfda80aff","tarball":"https://registry.npmjs.org/@empirio-ai/n8n-nodes-empirio-ai/-/n8n-nodes-empirio-ai-0.2.0.tgz","fileCount":58,"integrity":"sha512-zVrPygY/mmPmxtqV5ercr7s+mvyiS5WmCkB3wAnK6QU5ljbwrqOJLdwU0s/fJ461TzNuJUQTguxMWYsupugjzg==","signatures":[{"sig":"MEYCIQD7QF36Jh3PbF9/P5/ODrHo9tLagy+xlylius4XYMrn8wIhAPgeJ4EXi6Dvb6zzrZEkC7tHGQykwEJGJ4HhKmgXh9kg","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@empirio-ai%2fn8n-nodes-empirio-ai@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":298302},"engines":{"node":">=20"},"gitHead":"29b0dda06d4cf66eaa65d5ba9ce3068e1669eec9","scripts":{"dev":"n8n-node dev","lint":"n8n-node lint","test":"vitest run","build":"n8n-node build","release":"n8n-node release","lint:fix":"n8n-node lint --fix","typecheck":"tsc -p tsconfig.json --noEmit","test:watch":"vitest","prepublishOnly":"n8n-node prerelease"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:67ec0c00-330e-49fd-90bc-759bdf27b120"}},"repository":{"url":"git+https://github.com/empirio-de/empirio-ai-n8n.git","type":"git"},"_npmVersion":"11.5.1","description":"n8n community nodes for empirio.ai: build, publish and analyse surveys, and trigger workflows on every new survey response.","directories":{},"_nodeVersion":"24.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"9.39.4","vitest":"3.2.7","prettier":"3.9.6","release-it":"21.0.2","typescript":"5.9.3","@types/node":"22.19.17","@n8n/node-cli":"0.45.3"},"peerDependencies":{"n8n-workflow":"*"},"_npmOperationalInternal":{"tmp":"tmp/n8n-nodes-empirio-ai_0.2.0_1788527313871_0.8017080366145655","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@empirio-ai/n8n-nodes-empirio-ai","version":"0.3.0","description":"n8n community nodes for empirio.ai: build, publish and analyse surveys, and trigger workflows on every new survey response.","license":"MIT","publishConfig":{"access":"public"},"homepage":"https://www.empirio.ai","repository":{"type":"git","url":"git+https://github.com/empirio-de/empirio-ai-n8n.git"},"bugs":{"url":"https://github.com/empirio-de/empirio-ai-n8n/issues"},"author":{"name":"empirio.ai","email":"info@empirio.ai","url":"https://www.empirio.ai"},"keywords":["n8n-community-node-package","n8n","n8n-node","empirio","survey","surveys","questionnaire","feedback","market-research"],"engines":{"node":">=20"},"scripts":{"build":"n8n-node build","dev":"n8n-node dev","lint":"n8n-node lint","lint:fix":"n8n-node lint --fix","typecheck":"tsc -p tsconfig.json --noEmit","test":"vitest run","test:watch":"vitest","release":"n8n-node release","prepublishOnly":"n8n-node prerelease"},"n8n":{"n8nNodesApiVersion":1,"strict":true,"credentials":["dist/credentials/EmpirioAiApi.credentials.js","dist/credentials/EmpirioAiOAuth2Api.credentials.js","dist/credentials/EmpirioAiOAuth2AutoApi.credentials.js"],"nodes":["dist/nodes/EmpirioAi/EmpirioAi.node.js","dist/nodes/EmpirioAiTrigger/EmpirioAiTrigger.node.js"]},"peerDependencies":{"n8n-workflow":"*"},"devDependencies":{"@n8n/node-cli":"0.45.3","@types/node":"22.19.17","eslint":"9.39.4","prettier":"3.9.6","release-it":"21.0.2","typescript":"5.9.3","vitest":"3.2.7"},"_id":"@empirio-ai/n8n-nodes-empirio-ai@0.3.0","gitHead":"ed7ff00e47130b6eaa3410185b023e066df66a80","_nodeVersion":"24.9.0","_npmVersion":"11.5.1","dist":{"integrity":"sha512-5gnEaldK3Tt927ZWB1yhU2rVwUx+yapCFgzYYkG0orYEwm2FIQQ2movQIV8pHCyL2b/WTNRkFSTsX9ZE/Shx3w==","shasum":"14c2e3485358cd9c0c8e995fee4bba0b20bbc4e9","tarball":"https://registry.npmjs.org/@empirio-ai/n8n-nodes-empirio-ai/-/n8n-nodes-empirio-ai-0.3.0.tgz","fileCount":82,"unpackedSize":444501,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@empirio-ai%2fn8n-nodes-empirio-ai@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD0Voy2eUdH9p5cYNn2aKolXMLLducHyxxEhTorWRng4AIgAhdQCbjIwPKbldXObmDA9CgI/lfhpYz3YouA9U5z0p0="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:67ec0c00-330e-49fd-90bc-759bdf27b120"}},"directories":{},"maintainers":[{"name":"empirio-ai-org","email":"info@empirio.ai"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/n8n-nodes-empirio-ai_0.3.0_1789163067265_0.6482270930291498"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-21T15:37:14.284Z","modified":"2026-09-11T21:44:27.699Z","0.1.0":"2026-08-21T15:37:14.783Z","0.1.1":"2026-08-21T15:48:02.535Z","0.1.2":"2026-08-24T20:39:44.382Z","0.2.0":"2026-09-04T13:08:33.981Z","0.3.0":"2026-09-11T21:44:27.399Z"},"bugs":{"url":"https://github.com/empirio-de/empirio-ai-n8n/issues"},"author":{"name":"empirio.ai","email":"info@empirio.ai","url":"https://www.empirio.ai"},"license":"MIT","homepage":"https://www.empirio.ai","keywords":["n8n-community-node-package","n8n","n8n-node","empirio","survey","surveys","questionnaire","feedback","market-research"],"repository":{"type":"git","url":"git+https://github.com/empirio-de/empirio-ai-n8n.git"},"description":"n8n community nodes for empirio.ai: build, publish and analyse surveys, and trigger workflows on every new survey response.","maintainers":[{"name":"empirio-ai-org","email":"info@empirio.ai"}],"readme":"# @empirio-ai/n8n-nodes-empirio-ai\n\nn8n community nodes for [empirio.ai](https://www.empirio.ai) — build, publish and analyse online\nsurveys, and start a workflow the moment a respondent submits an answer.\n\nThe package contains two nodes:\n\n| Node                   | Type    | What it does                                                                                                                                                              |\n| ---------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **empirio.ai**         | Action  | Create, update, publish, duplicate and delete surveys; add, move, change and remove questions, answer options, logic rules and translations; read full draft/published definitions, responses, aggregates, non-matrix cross-tabulations, statistics and exports |\n| **empirio.ai Trigger** | Trigger | Runs the workflow whenever a response is submitted, delivered over a signed webhook                                                                                       |\n\n[Installation](#installation) · [Credentials](#credentials) · [empirio.ai node](#empirioai-node) ·\n[empirio.ai Trigger](#empirioai-trigger) · [Use as an AI tool](#use-as-an-ai-tool) ·\n[Self-hosted and Cloud](#self-hosted-and-cloud) · [Rate limits](#rate-limits-and-idempotency) ·\n[Compatibility](#compatibility)\n\n## Installation\n\n### n8n Cloud\n\nOpen the nodes panel on the canvas, search for **empirio.ai** under **More from the community**, select\nthe node and click **Install**. Only instance owners and admins can install verified community nodes.\n\n### Self-hosted n8n\n\nOpen **Settings → Community nodes → Install a community node**, enter `@empirio-ai/n8n-nodes-empirio-ai` and\nconfirm.\n\nTo install manually instead, run this in your n8n data folder (`~/.n8n` by default):\n\n```bash\nnpm install @empirio-ai/n8n-nodes-empirio-ai\n```\n\nRestart n8n afterwards.\n\n## Requirements\n\n- An empirio.ai account on any plan, including Free. The platform API and webhooks are not\n  gated on a plan. Some individual survey features still are, refusing with\n  `plan_features_exceeded` and naming the plan each one needs, and the two export\n  operations need a paid plan of their own (responses from Lite, charts from Plus),\n  refusing with `plan_feature_required`.\n- n8n 2.x. Development and the test suite run against 2.35.7.\n\n## Credentials\n\nBoth nodes offer API key, automatic OAuth2 and own-client OAuth2 credentials. Pick\nwhichever suits the deployment — an API key is the fastest to set up, OAuth2 avoids storing a\nlong-lived key.\n\n### empirio.ai API (API key)\n\n1. Sign in at [empirio.ai](https://www.empirio.ai) and open **Integrations**.\n2. Under **REST API**, create a key. The key is shown once — copy it right away.\n3. In n8n, create an **empirio.ai API** credential and paste the key into **API Key**.\n\n### OAuth2 (Automatic)\n\nFor **OAuth2 (Automatic)**, create an **empirio.ai OAuth2** credential and click **Connect my account**.\nn8n registers a public client through Dynamic Client Registration and uses PKCE S256.\nApprove surveys, responses and webhooks together. This connection serves both nodes and refreshes\naccess tokens automatically. It works on n8n versions with OAuth2 Dynamic Client Registration,\nincluding Cloud and self-hosted instances. Client ID and secret entry is unnecessary.\n\n### OAuth2 (Own Client)\n\nFor **OAuth2 (Own Client)**, register a confidential client with the redirect URL of your n8n host.\n\n1. In n8n, create an **empirio.ai OAuth2 API** credential. n8n shows an **OAuth Redirect URL** at the\n   top of the credential — copy it. It looks like\n   `https://<your-n8n-host>/rest/oauth2-credential/callback`.\n2. Sign in at [empirio.ai](https://www.empirio.ai), open **Integrations** and go to **OAuth\n   clients**.\n3. Create a client and paste the copied redirect URL into **Redirect URIs**. empirio.ai shows the\n   **Client ID** and **Client secret** once — copy both.\n4. Back in n8n, paste them into **Client ID** and **Client Secret**, then click **Connect my\n   account** and approve the consent screen.\n\nThe credential uses the authorization code flow with PKCE (S256) and requests the `surveys`,\n`responses` and `webhooks` scopes. Access tokens are refreshed automatically.\n\n`webhooks` is required by the trigger node, which registers a delivery endpoint on the survey when\na workflow is activated and removes it on deactivation. If trigger activation reports an\ninsufficient scope, reconnect the credential and approve the scopes shown above.\n\n> The redirect URL must match exactly, including the scheme and any port. If your n8n host name\n> changes, register the new redirect URL with the OAuth client as well.\n\n### Testing a credential\n\nAll credential types have a built-in test that calls `GET /me`. Click **Save** and n8n reports\nwhether the connection works. It reports nothing else — n8n shows the outcome of a credential test\nas a pass/fail message and does not surface the response body, so the plan and the granted scopes\nthat `/me` returns are not displayed. A passing test therefore confirms the credential and the host,\nnot the scopes — a grant missing `webhooks` still passes here and fails when the trigger registers.\n\nNeither credential has a host or environment field: `baseUrl` is a hidden, constant property fixed\nat `https://platform.empirio.ai/rest/v1`.\n\n## empirio.ai node\n\nChoose a **Resource**, then an **Operation**. Surveys are picked from a searchable dropdown, or\naddressed by survey URL or ID.\n\n### Survey\n\n| Operation    | Description                                                                                                                                                                                                                   |\n| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Create       | Create a draft, either from a prompt (`AI` mode) or from explicit questions (`Manual` mode)                                                                                                                                   |\n| Delete       | Delete a survey and its responses; takes effect immediately and cannot be undone                                                                                                                                              |\n| Duplicate    | Copy a survey into a new unpublished draft                                                                                                                                                                                    |\n| Get          | Read the complete working draft and latest published definition, including `plan_gate.status` (`clear`, `blocked`, or `unresolved`) and blocking feature details                                                              |\n| Get Many     | List surveys owned by or shared with the account                                                                                                                                                                              |\n| Get Stats    | View count, started and completed responses, completion rate and completion time                                                                                                                                              |\n| Publish      | Publish the working draft and return the public survey URL; a blocked owner-plan gate returns `plan_features_exceeded`                                                                                                        |\n| Revert Draft | Throw the working draft away and put the published version back in it. Every unpublished change goes, and nothing you can call brings it back. See [Use as an AI tool](#use-as-an-ai-tool) before attaching this to an agent. |\n| Unpublish    | Take a survey offline and move it back to draft                                                                                                                                                                               |\n| Update       | Apply changes to the working draft without publishing                                                                                                                                                                         |\n\n`Survey → Update` offers **Input → Builder / JSON** in Manual mode. Builder is the default. It has\nfour repeatable groups: Questions (add, update, delete, reorder), Answer Options\n(add, rename, delete, reorder), Logic Rules (add, update, delete), and Translations (survey text,\nquestion text, answer labels). Each row shows the fields for its selected change. Adding questions\nreuses Create's type-specific fields. Multiple groups and general settings become one PATCH.\n\nQuestion and answer reordering takes the complete ordered list of current IDs. Option changes and\nlabel translations support answer options, matrix rows and matrix columns. Dropdowns read the\nworking draft and resolve the question and list from the current row. Translation rows can cover\nmultiple questions and languages; conflicting values for the same translation are rejected.\n\nAn update writes only the fields explicitly added to its collection. `false` is a value, and\n**Remove Subtitle** explicitly clears and hides the subtitle. JSON mode accepts the four API sections\nfor advanced edits. Hidden fields from the other input mode are ignored, so a workflow that sends its\nchanges as JSON has to set **Input** to _JSON_.\n\n### Answer options\n\nRead answer options through **Survey → Get**, inside each question in `draft.questions`.\n**Survey → Update** adds, renames, removes and reorders them. Rename preserves option IDs and\nattached responses, rules and translations. The node has only the Survey and Response resources;\ndropdowns inside the Update builder resolve option IDs from the selected question.\n\nThe language dropdown lists the survey's additional languages. Translate the survey text, questions\nand answer labels to provide a complete language. Inspect `warnings`: text for an unconfigured\nlanguage is ignored by the API. The result retains `applied_changes` and `warnings`.\n\n`Get` returns the REST payload unchanged. `draft` is the complete working definition and\n`published` is the latest published definition or `null` before the first publish. Analytics\nquestion dropdowns read `published.questions`, so draft-only questions are never offered for\nresponse filters. Translation entries have to be read together with their `status`: `pending`\ncontent may be master-locale text copied in as an editable placeholder, not a completed\ntarget-locale translation. `Get Stats` remains on the Survey resource because it describes the survey as a\nwhole even though its REST endpoint sits under `/responses/`.\n\n`Create` and `Update` both accept an `AI` mode, where empirio.ai generates the change from a prompt,\nand a `Manual` mode with Builder and JSON inputs. JSON accepts question definitions on Create and\nquestion, option, logic-rule operations and translations on Update. General settings remain field\ncollections in both input modes. Logic Rule Operations accepts `add`, `update` and `delete` entries;\nthe `logic_rules` list returned by Get is the stored state, not an edit script.\n\nAll twenty-one question types are available, the two image ones included. An option of an\n**Image Single Choice** or **Image Multiple Choice** question carries an **Image URL**: give an\n`https` address and empirio.ai fetches the picture, checks it is a PNG, JPEG or WebP within 5 MB,\n6000 px per side and 16 megapixels, and stores it. The storage path `Get` reports is accepted\nthere too, which is what lets a survey be read and written back unchanged, and a URL that cannot\nbe fetched refuses the whole call rather than creating a picture round with a gap in it. The same\nfield sets the survey logo, under **Additional Fields → Logo URL** on `Create`.\n\nBoth `Create` and `Update` carry **Run Start** and **Run End** — the survey's run window. It\nactivates and deactivates itself at those instants without anybody publishing again, and unlike\nevery other field it takes effect immediately. Leave one empty to keep what is stored; send the\nliteral `null` to remove it.\n\n`Create` reaches every settings key the API takes — **Additional Locales**, **Hide Branding**,\n**Master Locale** and **Multi Language Enabled** alongside the three toggles.\n\n**Logic Rules** and **Translations** appear only when **Questions** is set to _JSON_. Both name the\nquestions _and the answer options_ of the same request by the `ref` given alongside them, and the\nQuestions builder offers no refs — a form built for people who do not write JSON should not ask\nthem to invent identifiers.\n\nWith the builder, logic and translations are a second node: `Create` reports every identifier it\nassigned under `created` — each question and each answer option — and `Update` takes those ids\ndirectly. In JSON mode the whole survey, its logic and a second language fit in one node, and the\nname is stored: `Get` returns it, and every later call may write it wherever a `question_id` or an\n`option_id` is expected, including an option added by that same `Update`. `Update` additionally takes **Logo Path**, the storage asset path `Get` returns\nunder `design` — the literal `null` removes the logo. There is no logo on `Create`: the path\nhas to contain the survey ID, which the create is what mints.\n\nEvery one of those sections has to carry something. An empty `[]` or `{}` is the right shape and\nstill asks for no change, and the API refuses an update that requests nothing — so the node refuses\nit first, naming the field you filled in. **Leave a section out entirely to mean \"do not touch this\none.\"** The two are not the same thing and the node keeps them apart: a field you never filled in is\nomitted in silence, while an expression that resolved to no entries fails the item, because that is\nusually a workflow that meant to send something and found nothing. Gate the update on an `If` node\nwhen a section may legitimately turn out empty.\n\n`Revert Draft` is the way back out of an `Update` that went wrong, and it is not an undo of the last\none: it replaces the whole working draft with whatever is currently published, so **every**\nunpublished change is discarded at once, and no call you can make brings it back. What it\nleaves alone is the published survey — same public link, same responses, still live — which is also\nwhy a revert leaves no visible mark: nothing on any read surface shows that the work existed. The\nitem the operation emits is the only account of it you get, under `discarded_changes`: which sections\ndiffered, the question count before and after, and the wording of every question the draft had\nadded, removed or changed. The two counts are snapshot sizes rather than a change indicator, so\nequal counts can still accompany additions, removals or modifications. Keep that item if the\nquestions may have to be re-entered.\n\nempirio.ai does keep a short revision history of working drafts on the server side, but it is not an\nundo you can build a workflow on: it is unreachable from the API, and it captures at most one\nrevision per survey every 30 seconds, so an `Update` followed straight by a `Revert Draft` usually\nrecords nothing. It is something support may be able to look at after the fact, and nothing more.\n\nTwo answers are worth knowing before you build on it. A survey that has **never been published** has\nnothing to fall back on and is refused with a `400` naming both ways on — publish the draft first, or\ndelete the survey; the draft is not silently emptied. And a draft that already matches the published\nversion is a **success**, not a failure: the item comes back with `discarded: false` and an empty\nsummary, so a workflow that reverts unconditionally does not need to branch on whether there was\nanything to revert.\n\n### Response\n\n| Operation        | Description                                                                                                                                                                                                                                                                                                                                                                       |\n| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Delete           | Delete responses named by `response_id`, by row number, or every response of a survey once **Confirm Delete All** is on. The responses leave every read surface immediately and no API call brings them back. See [Use as an AI tool](#use-as-an-ai-tool) before attaching this to an agent.                                                                                      |\n| Export Charts    | Render the results as charts: PowerPoint, PowerPoint with raster slides, PDF, DOCX, ZIP of images, or chart JSON. Every format answers with the finished file in one call — the four image-based ones are rendered in the background and the request waits for them. Chart JSON keeps complete configured option and bounded-scale axes, including unused values with `count: 0`. |\n| Export Responses | Export the raw participation data as CSV, XLSX, JSON or SPSS.                                                                                                                                                                                                                                                                                                                     |\n| Get Aggregates   | Sparse per-question answer buckets that omit zero-count values, with respondent-based percentages and row/column counts for matrix questions                                                                                                                                                                                                                                      |\n| Get Crosstab     | Cross-tabulate the answers of two questions                                                                                                                                                                                                                                                                                                                                       |\n| Get Many         | List participations with their answers                                                                                                                                                                                                                                                                                                                                            |\n\nBoth exports take **Answer Filters**, which narrows the export to the responses that answered\nparticular questions particular ways — the same filters the results view offers, and the only way\nto export a segment. Both operations expose their file type through **Format**, matching the REST\nAPI, MCP and Make field name `format`.\n\n`Get Many` pages through the API automatically when **Return All** is on. Its `Since` option takes\nan ISO timestamp and returns only responses submitted after it, which makes a scheduled polling\nworkflow straightforward when a webhook is not an option.\n\nTwo of its options cannot be combined: **Order** set to `Ascending` together with **Include\nIncomplete Participations**. The node refuses that pair itself and sends no request at all, so the\nmessage names the two fields you filled in rather than arriving as an API error. The reason is not\nsorting — `Descending` with **Include Incomplete Participations** works fine: an ascending page is a\ndescending window mirrored against the total number of responses, and that total counts only\ncomplete participations, so including the incomplete ones would make the mirrored window slide off\nthe end and drop the oldest rows. Leave the order at its descending default, or exclude incomplete\nparticipations; `row_no` is the newest-first rank under either order, so a descending page addresses\nexactly the same responses.\n\n`Delete` offers three ways to say which responses to remove, and the choice matters. **Response\nIDs** is where a new node starts. It names the responses by the `response_id` a read surface\nreturned — the trigger emits one on every delivery, and every row of `Get Many` carries one — and an\nID means the same participation under every filter and at every moment. **Selected Rows** names them\nby `row_no` instead, and is a deliberate switch away from that: a `row_no` is a _rank_ over the\nresponses that exist at the moment of the delete, not a handle on a response, so a participation\nsubmitted between your `Get Many` and your delete shifts every rank by one and `row_numbers: [1]`\nthen removes the arrival you never saw. Two deletes in one workflow both saying \"row 1\" remove two\ndifferent responses for the same reason.\n\nA rank is also a rank _inside a filtered set_, which is the half that gets data destroyed. `Get Many` narrows its list with **Date From**, **Date To** and **Include Incomplete Participations**,\nand `row_no` counts within whatever those leave — so \"row 1\" of a filtered list and \"row 1\" of the\nsurvey can be two different participations. **Selected Rows** therefore offers the same three under\n**Filters**, and they have to be set to the same values the `Get Many` that produced the numbers\nused. Set them on one side only and the delete still answers `200 deleted_count: 1` while removing a\nresponse that was never in your list. Nothing in the node can catch that: from inside it, a filtered\n`Get Many` feeding an unfiltered delete looks exactly like an unfiltered one feeding an unfiltered\ndelete, which is correct. That is why the mode is an opt-in rather than a warning. Prefer\n**Response IDs** unless all three filters are deliberately repeated.\n\nAll three can change what a rank names, and none of them is the safe one to leave off:\n\n| filter                                | what it does to the ranks                                                                                                                   | a mismatch                                               |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |\n| **Date To**                           | trims the newest end, so every row moves up                                                                                                 | picks a **different participation**, reported as success |\n| **Date From**                         | trims the oldest end; incomplete participations rank after every response, so shortening the response section moves each of them up         | picks a **different participation**, reported as success |\n| **Include Incomplete Participations** | appends the started-but-unsubmitted participations after every response, adding ranks that a delete resolves to submitted responses instead | picks a **different participation**, reported as success |\n| **Order**                             | nothing — `row_no` is newest-first either way, and there is no Order filter on the delete                                                   | harmless                                                 |\n\n**Date From** looks like the exception and is not, which is worth one worked example. On a survey of\nsix responses and two incomplete participations, with **Include Incomplete Participations** on,\nadding a **Date From** moved the two incomplete rows from rows 7 and 8 to rows 3 and 4 — the\nresponses kept their numbers, the appended rows did not. A delete that repeated the **Include\nIncomplete Participations** and left the **Date From** off then answered\n`200 {\"deleted_count\": 1, \"unresolved_row_numbers\": []}`, destroying the response that was row 3 of\nthe _unfiltered_ list — one the workflow had never listed — while the row it had actually selected\nsurvived. Repeat all three, or use **Response IDs**.\n\nIncomplete participations are also the one kind of row a `row_no` cannot address: a rank always\nresolves to a submitted response, so a `part_…` row you want gone has to be named in **Response\nIDs** (`400` by rank, `200` by ID). That is a second reason not to read a rank off a list you\nproduced with **Include Incomplete Participations** on.\n\nNothing checks that for you. The two selectors live on two nodes, and no n8n node can read another\nnode's parameters, so the node states the requirement in the editor and cannot enforce it. That is\nthe practical reason to prefer **Response IDs** whenever the numbers come from a previous node:\n`Get Many` returns a `response_id` on the same row as the `row_no`, and an ID means the same\nparticipation under every filter and at every moment.\n\n**Response IDs** has neither problem, and it is also the only selector that is safe to retry. An ID\nthat names nothing in this survey comes back under `unresolved_response_ids` and the rest are still\ndeleted; an ID whose response was already deleted comes back under `already_deleted_response_ids`,\nand when that is true of every ID you named the call answers `deleted_count: 0` — so re-running a\ndelete that already succeeded reports what happened instead of deleting something else. Both fields arrive on the item next to `deleted_count`. Use row numbers\nonly for a one-off clean-up you are watching; use IDs for anything a workflow runs on its own.\n\n**All Responses** takes no filters at all. It has no selection to narrow, and the API refuses every\nnarrowing field on that mode by name — including **Include Incomplete Participations** switched\n_off_, because n8n sends a filter you added even at its own default. So the **Filters** control is\nshown for **Selected Rows** only; switch to **All Responses** and it disappears, and a value left\nbehind in it from an earlier edit is not sent.\n`Get Aggregates` likewise reports `unresolved_question_ids` next to its counts — question IDs that\nmatch nothing in the _published_ survey. Check it before reading an absent question as \"no answers\":\na mistyped ID, or one that exists only in the working draft, lands there rather than in the results.\nIts `buckets` are sparse and omit configured values with no responses. Percentages use respondents\nwho answered the question as their denominator, so a multi-select question can sum above 100%; its\n`totalSelections` field reports the number of selections instead.\n\nThe question dropdowns of `Get Aggregates` and `Get Crosstab` are filled from the survey you\nselected, so you never have to look up a question ID by hand. Questions that cannot answer the\noperation are left out: display-only `content` blocks everywhere, because they hold no answers, and\nmatrix questions in `Get Crosstab`, which cannot cross-tabulate them.\n\n`Get Aggregates` reports each bucket by its **label**, not by an option ID. Two values escape that\nresolution and arrive raw: a free-text \"other\" answer, which is the text the respondent typed, and\nan option deleted after the response was submitted, which has no label left and stays an `opt_…`\nid. Current option IDs and labels come from `Survey → Get` under `published.questions`; an ID absent\nthere identifies historical data whose current published definition no longer has that option.\n\nChart exports answer with the finished file in one call, in every format. The four image-based\nformats — **DOCX**, **PDF**, **PowerPoint (Images)** and **ZIP of Images** — render in a background\njob on the API side, but the request waits for it, so the node sees the same shape as for\n**PowerPoint (Editable)** and **Chart JSON**. A render takes seconds on a typical survey and about a\nminute on the largest ones, well inside n8n's 300-second request timeout.\n\nChart JSON intentionally uses a denser representation than Get Aggregates for question types with\nconfigured options or a bounded scale: the complete configured axis is present, including unused\nvalues with `count: 0`, and historical answered values remain visible after the configured domain\nchanges. Its `totalAnswers` counts plotted bucket mentions, which means selections rather than\nrespondents for multi-select questions.\n\n## empirio.ai Trigger\n\nThe trigger registers a webhook on the selected survey when the workflow is activated, and removes\nit again when the workflow is deactivated. Every delivery is signed with a per-webhook secret, and\nthe trigger verifies the `X-EmpirioAi-Signature` HMAC before it emits anything. A delivery with a\nmissing or wrong signature is answered with **HTTP 401** and never starts the workflow. empirio.ai\ntreats any 2xx as delivered, so answering 401 is what makes it retry and then surface a failed\ndelivery — a silent 200 would discard the response and leave both sides reporting success.\n\n**Event** is _Response Submitted_, the one event empirio.ai delivers.\n\n**Output** controls the shape of the emitted item:\n\n- **Flat Answers** (default) — one item per response with the answers keyed by **question title**,\n  next to the response metadata (`response_id`, `responded_at`, `survey_id`, `survey_version`,\n  `duration_seconds`, …). This is what most workflows want: the keys read like the survey, so you\n  can map them straight into a spreadsheet row, a CRM record or a database column set. An\n  `allow_other` question contributes a second key, `<title> (Other)`. Two questions sharing a title,\n  or a title that collides with one of the metadata keys, are disambiguated with a ` (2)`, ` (3)` …\n  suffix, and the companion key follows the key its question actually got — the second _Comments?_\n  arrives as `Comments? (2)` and `Comments? (2) (Other)`. Three kinds of question have no title to\n  key by and fall back to their raw `question_id`, which is where a `q_…` key can still turn up: one\n  that was deleted from the survey after this response was stored, one whose title is empty, and one\n  whose title is `null`.\n- **Full Payload** — the complete delivery, including the per-cell `answers` array that mirrors the\n  CSV export layout (one entry per scalar answer, multiple-choice option, matrix row, rank position\n  and free-text companion). Every entry carries `question_id`, `question_title` and `question_type` —\n  so a `Switch` node can branch on the type without looking anything up, and a report can print the\n  title without a second call. Beyond those three an entry carries only the identifiers its own kind\n  has. A multiple-choice entry has `option_id` next to `option_label`, a matrix row has `row_id` and\n  `selected_column_id`, a single-select or rank entry has `selected_option_id` — and a text, number\n  or date entry has **none of them**: it is `question_id` + `value`, with `selected_option_id`\n  absent rather than `null`. Entries are told apart by which of those identifier fields is present,\n  not by a discriminator field.\n\nPick by what consumes the item. **Flat Answers** is readable but keyed on text a survey editor can\nchange: rename a question and its key changes with it, and a workflow mapped to the old key stops\nfinding its value. **Full Payload** keys on identifiers that a rename does not touch, which is what\nyou want once something downstream depends on the mapping staying put — with the caveat that a\nmatrix or ranking entry reports the identifier as it was stored, so it can still name an option the\nsurvey no longer defines.\n\n> **A key that disappears fails silently.** n8n's expression evaluator answers a missing key with\n> `null` instead of raising — so a node reading `{{ $json['Old title'] }}` after a rename stays\n> **green** and writes an empty value downstream. It does not error, it does not warn, and typing\n> the field as a number or boolean does not catch it either. Nothing but the data will tell you.\n> If that matters, use **Full Payload** or add an `If` node that asserts the key is present.\n\n**Options → Include Unanswered Questions** (Flat Answers only) emits `null` for questions the\nrespondent skipped or logic hid. Without it an item only carries the questions that were actually\nanswered, so the key set differs from response to response and the columns of a downstream\nspreadsheet or database node shift. Turn it on when something downstream expects a fixed shape.\nDisplay-only `content` blocks stay absent either way — there is no answer to report a `null` for.\n\n**Options → Label** sets the name shown for the webhook in the empirio.ai dashboard. It defaults to the\nworkflow name. It applies when the trigger registers a **new** webhook; a registration the trigger\nadopts (see below) keeps the name and owner it already had.\n\nQuestion IDs are stable for the lifetime of a survey. Inserting, removing or reordering questions\nnever shifts an existing ID, so a mapping built on **Full Payload** keeps working, and the same ID\nalso identifies the question in the empirio.ai node's analytics operations.\n\n### Recovering a lost registration\n\nIf a workflow is imported, duplicated or restored, n8n may no longer hold the webhook state. On the\nnext activation the trigger looks up the survey's webhooks, matches its own callback URL and adopts\nthe existing registration. Because empirio.ai hands out a webhook's signing secret only once, the\ntrigger rotates the secret in that case so it can verify deliveries again. Rotating affects only\nthis registration — other webhooks on the survey are untouched.\n\n## Use as an AI tool\n\nThe empirio.ai node is `usableAsTool`, so it can be attached to an **AI Agent** or exposed through an\n**MCP Server Trigger**. Read this before you attach one that can write.\n\n**What the model can drive.** n8n publishes a tool schema built from the node's parameters. It builds\nit by walking the _stored_ parameter values and collecting every `$fromAI(...)` call it finds, whatever\nthe field is — so each parameter the workflow author filled in with such an expression becomes an\ninput the model supplies. Anything the author typed in as a literal is fixed and the model cannot\nchange it. A `tools/call` supplying `confirmDeleteAll: true` performs a real delete of every response\nin the survey; one supplying `deleteMode: \"ids\"` and a list of `responseIds` deletes exactly the\nparticipations it names.\n\n**Response → Delete** has **nine** such inputs, not the four or five you might count off the fields\nin front of you: `deleteMode`, `responseIds`, `rowNumbers`, `confirmDeleteAll`, the three **Filters**\nentries `dateFrom` / `dateTo` / `includeIncompleteParticipations`, `idempotencyKey`, and the\n**Survey** field. Nine is the ceiling, and reaching it takes a node whose `Delete Mode` is itself\nmodel-filled. The walk runs over the parameters _as the mode displays them_, so pinning `Delete Mode`\nto a literal removes the **other** modes' fields from the published schema — measured on five nodes of\none MCP Server Trigger whose stored parameters were identical except for that one value:\n\n| stored `Delete Mode`                     | published tool inputs                                                                                   |\n| ---------------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| `$fromAI(…)`                             | 9 — every field above                                                                                   |\n| `\"ids\"`                                  | 3 — `surveyId`, `responseIds`, `idempotencyKey`                                                         |\n| `\"rows\"`                                 | 6 — `surveyId`, `rowNumbers`, `dateFrom`, `dateTo`, `includeIncompleteParticipations`, `idempotencyKey` |\n| `\"all\"`                                  | 3 — `surveyId`, **`confirmDeleteAll`**, `idempotencyKey`                                                |\n| _not set_ — the node left on its default | 3 — the `\"ids\"` row: `surveyId`, `responseIds`, `idempotencyKey`                                        |\n\nPinning the mode is the first item in the list below for a second reason, then: it is the one edit\nthat shrinks what the model is offered at all. Note what it does **not** do. `confirmDeleteAll` is\ndisplayed by `deleteMode: \"all\"`, so pinning the mode to `\"all\"` keeps that field in the schema —\nas a _required_ input, measured driving `{\"confirmDeleteAll\": true}` to `deleted_count: 8` on a\nmode-pinned tool. Pinning the mode narrows which fields a model can reach; only pinning\n`Confirm Delete All` itself takes the delete-everything switch out of its hands, which is why the\nlist below names both.\n\n`Confirm Delete All` and `Delete Mode` carry `noDataExpression`, which removes the expression editor\nand the editor's \"let the model define this parameter\" button from those two fields. Six visible fields of\nthis operation do not carry it — `Survey`, `Response IDs`, `Row Numbers` and the three **Filters**\nentries — so the editor offers the button on those six. `idempotencyKey` is hidden; a stored\n`$fromAI` expression on it still participates in the measured schema above. And on the two that do\ncarry it, it is a guard-rail in the editor, **not** an access control: `noDataExpression` is a\nproperty of the field _definition_, while the schema is built from the stored _values_, so a workflow\nwhose stored parameters already hold `$fromAI` expressions — hand-written, imported, or built by the\nAPI — publishes them to the model regardless. `displayOptions` is the only thing that keeps a field\nout, and only while the parameter it depends on holds a literal.\n\nSo the safe shape for an agent-facing delete is:\n\n- Pin **Delete Mode** and **Confirm Delete All** to literal values in the node. Leaving **Delete\n  Mode** alone gets you the `ids` schema, which is the right one; pinning it says so explicitly and\n  survives a future change of default.\n- If the model must choose _what_ to delete, let it fill **Response IDs**, not **Row Numbers**. An ID\n  names one participation for its whole life, so the model deletes what it named and nothing else,\n  and a retry repeats that same delete. A row number is a rank resolved when the call runs, inside\n  whatever the **Filters** select: a response arriving in between shifts every rank, and a filter the\n  model chooses differently from the list it read renumbers the whole set — the model's \"row 1\" is\n  then somebody else, reported as a success. Pinning the mode to `ids` also takes `rowNumbers` and\n  all three filters out of the schema, so there is nothing left for the model to get wrong.\n- Leave **On Error** at its default on a node attached as a tool. The default reports a refusal to\n  the model as `isError: true` with the API's own sentence; `Continue (using regular output)` hands\n  it `[{}]`, which is indistinguishable from a successful delete. See\n  [Errors reaching a model](#errors-reaching-a-model).\n- Pin the **Survey** field. This is the one control that bounds the damage — see below — so an agent\n  should get a node scoped to one survey rather than one whose survey ID it chooses.\n- If an agent needs no destructive power at all, do not attach a node with `Delete` selected. The\n  operation is part of the tool the model sees, not something it asks permission for. The same goes\n  for `Survey → Delete` and `Survey → Revert Draft` — see\n  [Survey → Revert Draft](#survey--revert-draft-and-the-other-whole-survey-writes).\n\n**What the node refuses regardless.** Every delete guard runs on every call, whoever made it. A\n`mode: \"all\"` request is refused unless `confirmDeleteAll` is a real `true`; a value that merely\nlooks set — `\"false\"`, `\"no\"`, `\"0\"`, a stringified expression — is refused by name rather than read\nfor truthiness. A `Delete Mode` that is none of `ids`, `rows` and `all` is refused rather than\nfalling through all three guards. Each guard is a no-op outside its own mode, so row numbers left in\nthe node are not sent with an `ids` delete and vice versa. Every value in **Response IDs** must look\nlike a `response_id` — a `resp_` or `part_` prefix and 22 more characters — so a row number, a survey\nUUID or a truncated ID fails in the node, by name, before anything is sent; an empty list and a list\nlonger than 1000 are refused the same way. The same discipline applies to every other guarded\nparameter in the node.\n\n**What that does and does not bound.** With **Response IDs** model-fillable, three things limit the\ndamage. The shape check above means the model cannot turn a number it saw somewhere into a delete.\nThe API resolves IDs **within the selected survey only**: an ID belonging to a different survey is\nreported back under `unresolved_response_ids` and deletes nothing there, and a call in which no ID\nnames a participation of this survey at all — live or already deleted — is refused outright with\n_\"No participation of this survey matches\"_ — so a pinned **Survey** field is a hard ceiling on what\nany tool call can reach. And an ID is a 128-bit digest of a random\ninternal key, so it cannot be guessed or enumerated: the only IDs a model has are the ones a read\nsurface handed it.\n\nWhat none of that bounds is the survey itself. There is no confirmation toggle on `ids` the way\n`confirmDeleteAll` gates `all`, and an agent that has just run `Response → Get Many` is holding every\n`response_id` in that survey. Within one survey, a model that can call this tool can delete any\nparticipation it chooses, in batches of up to 1000, with no undo. `ids` is the _safer_ selector —\nit deletes only what it names and it is safe to retry — but it is not the _weaker_ one. Treat the\nsurvey pin, and the decision to attach `Delete` at all, as the access control; the guards above are\nonly there to stop a malformed call.\n\n### Survey → Revert Draft, and the other whole-survey writes\n\n`Response → Delete` is the operation with the most model-reachable surface. It is not the only one\nthat destroys work, and the operations that do are the ones with the _smallest_ surface — which is\nwhy they are easy to miss on a node built for something else.\n\n| operation                 | published tool inputs            | what a call costs                                                           |\n| ------------------------- | -------------------------------- | --------------------------------------------------------------------------- |\n| **Survey → Revert Draft** | 2 — `surveyId`, `idempotencyKey` | every unpublished change in the draft, unrecoverable; live survey untouched |\n| **Survey → Delete**       | 2 — `surveyId`, `idempotencyKey` | the survey and all its responses                                            |\n| Survey → Publish          | 2 — `surveyId`, `idempotencyKey` | pushes the current draft live                                               |\n| Survey → Unpublish        | 2 — `surveyId`, `idempotencyKey` | takes the survey offline                                                    |\n\nTwo is the floor for a write in this node, and for **Revert Draft** it is also the ceiling: the\noperation has no selector, no filters and no confirmation toggle, because there is nothing to select\n— it always takes the whole draft of the one survey the node addresses. So on a node whose\n**Operation** holds a literal, the only thing a model chooses is _which survey_, and **pinning the\nSurvey field is the whole of the remaining access control**, exactly as it is for `Delete`. An agent\nholding an unpinned Revert Draft can empty the draft of every survey in the account, one call each,\nand every one of those calls answers `200`.\n\n\"Whose **Operation** holds a literal\" is a real condition, not a formality. Every number in the table\nis measured with **Resource** and **Operation** pinned, which is what a node built in the editor\nstores — and the schema is built from stored _values_, the same door the paragraph above the delete\nlist describes. A node whose stored **Operation** holds a `$fromAI` expression publishes `operation`\nas a model input as well, next to `surveyId` and `idempotencyKey`. Leaving selectors unpinned exposes\nthe fields of the operations they can select, including the survey builder. On such a node a model\ngiven the tool for `Survey → Get`\ncan select `revertDraft` and name the survey in the same call, and the operation the author chose\nbounds nothing. `Operation` and `Resource` both carry `noDataExpression`, so the editor never offers\nthe \"let the model define this parameter\" button on them — but a workflow imported or built through\nthe API does not go through the editor. Pin **Operation** as well as **Survey**, and prefer attaching\na node whose selected operation is one you would let the model run unattended.\n\nA confirmation toggle would not change that. `confirmDeleteAll` earns its place on `Response →\nDelete` because that operation _has_ a selector: a model-filled `Delete Mode` can escalate a named\nlist of IDs into \"every response in the survey\", and the toggle is the second lock on the escalation.\nRevert Draft has no such step to lock, so a toggle would only make every legitimate use refuse until\nsomeone switched it on — and it would not be an access control in any case, for the reason the\nparagraph above the delete list already gives: the schema is built from _stored values_, so\n`noDataExpression` keeps a field out of the editor's expression menu and not out of the model's\nschema.\n\nNothing on a read surface shows that a revert happened. The published survey, its public link and its\nresponses are exactly as they were, and `has_pending_draft_changes` simply reads `false` — which is\nalso what an untouched survey reads. The emitted item's `discarded_changes` is the only account you\nget, so\nleave **On Error** at its default (see [Errors reaching a model](#errors-reaching-a-model)) and keep\nthe item if the questions may have to be re-entered.\n\n**Retries.** See [Idempotency](#idempotency): under an AI Agent, the derived key comes from the\ncall's parameters, and under an MCP Server Trigger the node cannot derive a retry-safe key at all.\nRevert Draft is idempotent at the API as well — a second call finds the draft already equal to the\npublished version and answers `200` with `discarded: false`, having written nothing.\n\n### Errors reaching a model\n\nAn API refusal is legible on the two paths you get by default, and illegible on the one you have to\nchoose. The same refusal, three settings:\n\n| setting                                          | what arrives                                                                                    |\n| ------------------------------------------------ | ----------------------------------------------------------------------------------------------- |\n| default (fail the item)                          | `NodeApiError` whose `description` is the API's own sentence                                    |\n| tool call, default error handling                | `isError: true`, text ending `Details: <the API's sentence>`                                    |\n| **On Error** = `Continue (using regular output)` | workflow item `{\"error\": \"Bad request - please check your parameters\"}`; **tool result `[{}]`** |\n\nBoth losses are n8n's, not this node's, and neither has a hook the node could use. A declarative node\nnever sees the failure: n8n's `RoutingNode` catches it and pushes `{ json: {}, error }` — the\nexplanation lives in the sibling `error`, not in `json`. The workflow engine then rewrites `json` from\n`error.message`, the generic HTTP summary, dropping `error.description`, which is where the API's\nsentence is. The tool wrapper never runs that rewrite at all: it maps a result with\n`items.flatMap(item => item.json)` and `JSON.stringify`s it, so the sibling is discarded and an empty\nobject is all that is left. It carries no `isError`, so a model cannot tell it from a successful write.\n\nThe one thing to do about it is the setting itself: **leave On Error at its default on any node a\nmodel can call.** If a workflow must continue past a failed delete anyway, note that `$json.error`\ndownstream holds only that generic summary — the API's sentence survives in the run's error record,\nwhich is what the editor shows on the node, not in anything a later node can branch on.\n\n## Self-hosted and Cloud\n\nempirio.ai only delivers to endpoints it can reach:\n\n- The endpoint must be **HTTPS**. Plain `http://` URLs are rejected.\n- Endpoints resolving to **private or loopback addresses are rejected**, so `localhost` and\n  `192.168.x.x` do not work.\n\n**n8n Cloud** satisfies both conditions out of the box — the empirio.ai Trigger works with no extra\nsetup.\n\n**Self-hosted n8n** needs a publicly reachable HTTPS host name. Set `WEBHOOK_URL` to that public URL\nso n8n hands the correct address to empirio.ai, and put a TLS-terminating reverse proxy or tunnel in\nfront of the instance. During local development, `n8n` started through a tunnel works as well.\n\nThe empirio.ai node itself has no such requirement — it makes outbound calls only and works on any n8n\ninstance with internet access.\n\n## Example workflow\n\nA weekly CSV of a survey's responses. **Export Responses** answers with a signed `download_url`\nrather than the bytes, so a second step fetches it — that keeps a large export out of the workflow's\nmemory and lets the URL be passed to storage or email instead.\n\nReplace `YOUR_SURVEY_ID` with the ID from **Survey → Get Many**, or pick the survey from the list in\nthe node. Paste this into a new workflow with **Ctrl/Cmd+V**:\n\n```json\n{\n\t\"name\": \"empirio.ai \\u00b7 weekly CSV of new responses\",\n\t\"nodes\": [\n\t\t{\n\t\t\t\"parameters\": {\n\t\t\t\t\"rule\": {\n\t\t\t\t\t\"interval\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"field\": \"weeks\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"id\": \"a1\",\n\t\t\t\"name\": \"Every Monday\",\n\t\t\t\"type\": \"n8n-nodes-base.scheduleTrigger\",\n\t\t\t\"typeVersion\": 1.2,\n\t\t\t\"position\": [0, 0]\n\t\t},\n\t\t{\n\t\t\t\"parameters\": {\n\t\t\t\t\"resource\": \"response\",\n\t\t\t\t\"operation\": \"exportResponses\",\n\t\t\t\t\"surveyId\": {\n\t\t\t\t\t\"__rl\": true,\n\t\t\t\t\t\"mode\": \"list\",\n\t\t\t\t\t\"value\": \"YOUR_SURVEY_ID\"\n\t\t\t\t},\n\t\t\t\t\"format\": \"csv\",\n\t\t\t\t\"options\": {\n\t\t\t\t\t\"includeIncompleteParticipations\": false\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"id\": \"a2\",\n\t\t\t\"name\": \"Export responses\",\n\t\t\t\"type\": \"CUSTOM.empirioAi\",\n\t\t\t\"typeVersion\": 1,\n\t\t\t\"position\": [220, 0],\n\t\t\t\"credentials\": {\n\t\t\t\t\"empirioAiApi\": {\n\t\t\t\t\t\"id\": \"1\",\n\t\t\t\t\t\"name\": \"empirio.ai account\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"parameters\": {\n\t\t\t\t\"url\": \"={{ $json.download_url }}\",\n\t\t\t\t\"options\": {\n\t\t\t\t\t\"response\": {\n\t\t\t\t\t\t\"response\": {\n\t\t\t\t\t\t\t\"responseFormat\": \"file\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"id\": \"a3\",\n\t\t\t\"name\": \"Download the file\",\n\t\t\t\"type\": \"n8n-nodes-base.httpRequest\",\n\t\t\t\"typeVersion\": 4.2,\n\t\t\t\"position\": [440, 0]\n\t\t}\n\t],\n\t\"connections\": {\n\t\t\"Every Monday\": {\n\t\t\t\"main\": [\n\t\t\t\t[\n\t\t\t\t\t{\n\t\t\t\t\t\t\"node\": \"Export responses\",\n\t\t\t\t\t\t\"type\": \"main\",\n\t\t\t\t\t\t\"index\": 0\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t]\n\t\t},\n\t\t\"Export responses\": {\n\t\t\t\"main\": [\n\t\t\t\t[\n\t\t\t\t\t{\n\t\t\t\t\t\t\"node\": \"Download the file\",\n\t\t\t\t\t\t\"type\": \"main\",\n\t\t\t\t\t\t\"index\": 0\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t]\n\t\t}\n\t}\n}\n```\n\nFor charts instead of raw data, swap the operation for **Export Charts** and pick one of its six\nformats; everything else stays as it is.\n\n## Rate limits and idempotency\n\nThe empirio.ai API allows 60 read and 20 write requests per minute **per user account**, not per\ntoken — the budget is keyed on the account, so two n8n instances, or an n8n workflow and a Zap,\nrunning under the same account share one window. When a limit is hit the API answers `429` with a\n`Retry-After` header.\n\n**`Retry On Fail` cannot recover from a `429`.** n8n caps the node at 5 tries with at most 5 seconds\nbetween them, and it does not read `Retry-After`, so the longest backoff it can produce is about\n20 seconds against a window that lasts 60. A saturated window outlives every retry, and the node\nfails anyway. Space the calls out instead: a `Wait` node, a smaller batch size, or a schedule that\ndoes not fire every workflow at once. Retry On Fail is still worth enabling for transient network\nand `5xx` failures, which it does fix.\n\n### Idempotency\n\nEvery write automatically carries an `Idempotency-Key`, and the API applies a repeated key at most\nonce within 24 hours. The editor contains no idempotency input. Saved `idempotencyKey` values remain\navailable to the runtime as hidden parameters.\n\nBy default the node derives that key from the execution itself, as\n`n8n-<execution>-<node>-<run>-<item>` — the execution ID, the node, the pass over its input and the\nitem index. Two consequences worth knowing:\n\n- **A retry of one write reuses its key.** All of `Retry On Fail`'s attempts belong to the same\n  execution, node, pass and item, so the second attempt of a failed write is recognised as the same\n  write and applied once. This matters most for **Response → Delete** in _Selected Rows_ mode: row\n  numbers are a rank that shifts the moment a delete lands, so a retry that counted as a new request\n  would remove different rows.\n- **Re-running the workflow is a new write.** A manual re-run, a scheduled run and a second webhook\n  delivery are new executions with new keys, and each is applied on its own.\n\n#### As an AI tool\n\nn8n runs a node attached to an **AI Agent** through a different loop than a workflow step, and that\nloop advances the run index on every retry attempt rather than fixing it for the write. The pass\nindex therefore cannot tell \"attempt 2 of one call\" from \"call 2\", so on this path the key is derived\nfrom the call's own resolved parameters instead, as `n8n-<execution>-<node>-t<fingerprint>`. Retries\nstay safe, which is the property that matters for the destructive operations. The trade-off: **two\ntool calls that ask for byte-identical writes inside one execution count as one write.** If an agent\nshould be able to create two identical drafts in a single run, give it something to vary.\n\nUnder an **MCP Server Trigger** there is no execution to derive from at all: n8n does not give the\nnode an execution ID on that host, and there is nothing else that separates one MCP session from the\nnext. Rather than mint a key that would look stable and in fact be _the same key for unrelated\nwrites days apart_, the node falls back to a random key there. That means:\n\n> **Writes made through an MCP Server Trigger are not retry-protected.** Do not enable\n> `Retry On Fail` on **Response → Delete** in _Selected Rows_ mode on that host — a retried attempt\n> is a second, independent delete against re-ranked rows.\n\nA saved key is bound to its request data for 24 hours: reusing it after the data changes fails\nwith `idempotency_key_conflict`. A saved expression that resolves to `null`, `undefined` or a blank\nstill fails the item, so it cannot silently lose its retry protection.\n\n### Failed authentications\n\nRefused authentications are counted separately from the request budget, against **the credential that\nwas presented**, at 120 a minute. A workflow whose API key has been revoked or mistyped exhausts that\nbudget on its own and is answered `429` until the window rolls over; nothing else is affected.\n\nThe bucket is the credential rather than the caller's address, and that is the part that matters on\n**n8n Cloud**: every workflow on the platform leaves from a small pool of shared addresses, so a\nbudget keyed on the address would let one tenant's stale key lock out everyone else's working ones.\nYours is charged to your credential and to nothing else.\n\n## Compatibility\n\nDeveloped and tested against n8n 2.35.7. The package declares `engines.node >= 20`, which is what\nits own code needs; n8n's minimum is higher (2.x asks for Node 22) and is what actually applies at\nruntime.\n\n## License\n\n[MIT](LICENSE.md)\n\n\n### Partial logic-rule updates\n\nIn Update Survey, a logic-rule update sends only the properties selected under **What to Change**.\nChanging just the action preserves the rule's question, condition, compared values and identity.\n**End the Survey** removes the target reference. A targeted action requires a target question.\nCompared-value dropdowns use the rule's current question unless a new question is explicitly selected.\nThe forms do not copy an editor-time snapshot into the update.\n\nn8n and Zapier allow several properties to be selected together. Make selects a property group per\nrow; **Question, condition and values** updates a complete comparison together. Other properties\nremain unchanged. An incompatible comparison is rejected by the API.\n\nThe n8n examples `update-survey-builder.json` and `update-survey-edge-cases.json` in\n`integrations/n8n/examples/` create fresh draft surveys and check persisted results through Get\nsteps and assertions. They cover question and option CRUD, reordering, matrix columns,\ntranslations, partial rule edits, invalid orders, atomic rejection and idempotency. Requests are\npaced to respect the API budget; run the workflows sequentially. They leave drafts for inspection\nand never publish them. The examples use the local development node type `CUSTOM.empirioAi`;\nselect a credential pointing to the local API before running them.\n","readmeFilename":"README.md"}