Description
n8n-nodes-roistat
n8n community nodes for Roistat — end-to-end marketing analytics:
46 operations across 11 resources of its REST API, plus a webhook trigger for calls.
Roistat ties a website visit to the advertising channel that brought it, follows that visit
into a CRM deal, and reports what each channel earned. Most of what people build around it
in n8n is one of three things: pull a report on a schedule, feed it the numbers it cannot
collect by itself (spend for a channel with no integration, deals from a CRM it is not
connected to), or react to a call.
Written from scratch against Roistat’s own API documentation.
No code from any other package.
- Installation
- Credentials
- Roistat node
- Roistat Trigger
- Rate limits
- Quirks worth knowing
- What is not here
- Feedback and bugs
Installation
In n8n: Settings → Community nodes → Install, then enter n8n-nodes-roistat.
Self-hosted, from the command line:
npm install n8n-nodes-roistat
Requires n8n 2.x and Node 20.19 or newer.
Credentials
Both nodes share one credential, Roistat API, and it has two fields that matter.
API Key — from Профиль → Настройки → API key. One key per Roistat profile, not per
project: it opens every project that profile can see. There is no OAuth and no scoping, so
treat it as an account-wide secret.
Project ID — the digits in the project URL, 12345 in cloud.roistat.com/project/12345.
Every API call names a project, and since the key covers several, the number is a separate
field. Pasting the whole URL works; everything but the digits is dropped. Every operation has
its own Project ID field that overrides this one per item, which is what an agency with
one key and many clients needs.
The Test button calls /project/settings/counter/list, the cheapest call that needs both
halves. It reports a wrong key and a wrong project number separately, and it can only do that
because the node sends the Use-Http-Code: 1 header — see Quirks.
The credential deliberately has no authenticate block, so n8n never offers it inside an
HTTP Request node. Nothing is lost: Roistat serves one host, cloud.roistat.com, so there is
no address field to point anywhere, and both nodes attach the header themselves.
Roistat node
| Resource | Operations |
|—|—|
| Analytics | Get Report, Get Funnel Report, Get Metrics, Get Dimensions, Get Dimension Values, Get Attribution Models, Set Custom Metric Value |
| Deal | Get Many, Get, Upload, Update Status, Delete, Get External URL, Get Statuses, Set Statuses, Get Custom Fields |
| Lead | Get Many, Create, Update, Get Statuses |
| Site Lead | Get Many, Get |
| Client | Get Many, Get Feed, Import |
| Call | Get Many, Create, Get Recording, Get Dashboard, Get Phones |
| Visit | Get Many, Update Params |
| Advertising Cost | Get Many, Add, Update, Delete, Get Channels |
| Event | Send, Send Many, Create |
| Statistic | Get Daily |
| Project | Get Many, Create, Get Counter, Get Access, Set Access |
Deal, Lead and Site Lead are three different things
The split is Roistat’s own, not this node’s, and it decides which half of the node works at all.
A project integrated with a CRM receives deals — the Deal resource. A project without
one uses «Управление заявками», Roistat’s built-in lead board, and those are leads — the
Lead resource. A project uses one or the other, and the methods of the wrong half answer with
an error rather than an empty list.
Site Lead is neither. Roistat’s own widgets — the callback form, the lead catcher — record
the submission before anything else happens to it, together with the visit that produced it.
That record survives even when the CRM never got the deal, which is exactly when you want to
read it. Its order_id field is empty in that case.
Analytics: dimensions and metrics come from the project
Get Report is the same data the Аналитика screen draws. Pick what to group by (dimensions)
and what to count (metrics) — both dropdowns read the project’s own dictionaries, because the
vocabulary differs per project: no calltracking option means no call metrics.
Simplify Output is on by default and worth understanding. Roistat sends every metric as
{value, formatted, metricname, attributionmodel_id} and every dimension as
{value, title, icon}. With Simplify on, a row becomes
{ "markerlevel1": "Yandex.Direct", "visits": 1000, "roi": 50, "_row": "item" }
and the originals stay under dimensions and metrics. With it off, the row arrives
exactly as Roistat sent it. Note _row: each interval also produces a totals row, tagged
total instead of item, because an average of ROI is not the ROI of the average and
recomputing it downstream gives a different number.
Attribution Model only changes metrics whose dictionary entry has
ishasattribution_model set. Visits and costs are counted the same way whatever is picked.
The node reads the metric dictionary first and attaches the model only where it means
something — sending {metric: "visits", attribution: "first_click"} is a validation error,
and it names a metric the user never thought about.
The visit number is what makes any of this work
Upload a deal, Create a lead, Create a call: each has a field for the visit number —
the value of the roistat_visit cookie, called roistat on deals and source on leads.
Without it Roistat has nothing to attribute the row to, and it lands in «Прямые заходы». The
request still succeeds. Nothing warns you. The report is quietly wrong a month later.
Upload answers with a tally rather than the deal: {"uploaded": 0, "processed": 1,. That particular combination is normal and means
"skippedbywaitingvisitinfo": 1, …}
Roistat has not seen that visit yet — it will match the deal up when it does.
Get Recording gives you an MP3
Call → Get Recording puts the file in a binary field, data by default. A call with no
recording answers 200 with an empty body, and the node raises an error naming the call rather
than passing on a zero-byte file. If the recording is stored outside Roistat, the call’s own
link field may point at it instead.
Roistat Trigger
Roistat has no API for webhooks. No subscribe, no unsubscribe, no listing. So this
trigger cannot register itself, and the address has to be pasted into Roistat by hand:
звонка or Webhook после звонка**;
Three consequences follow, and all three are worth knowing before wiring anything up:
1. Each of those fields holds exactly one address. Pasting a second workflow into it
replaces the first, and nothing in Roistat says so.
2. Deactivating the workflow does not stop the deliveries. Roistat keeps posting to a URL
n8n is no longer listening on. Clear the field in Roistat to stop it.
3. “Listen for test event” gets you a different URL than the active workflow, so testing
means pasting the test URL in, and pasting the real one back afterwards.
The in-call and after-call bodies differ: the first has the caller, the number dialled, the
visit and the source, the second adds status, duration and link. Call Statuses filters
on the second — leave it empty to pass everything, or pick NOANSWER, BUSY and CANCEL for
a workflow that chases missed calls.
A calltracking webhook always posts. A scenario in Автоматизация маркетинга can be set to
call the URL with GET instead — for that, set Event to Any and HTTP Method to GET,
and the query parameters arrive as the item.
Deliveries carry no signature and no headers of yours, so the URL is the only thing keeping
them private. Shared Secret is a second lock: set one, append ?token= to the
address you paste into Roistat, and anything arriving without it gets a 403.
Rate limits
Roistat counts these per project, across every integration touching it — your workflow,
someone else’s, and the Roistat interface itself. Going over answers 429 for all of them.
| Scope | Limit |
|—|—|
| Everything | 10 requests per second, 5000 per hour |
| Deal → Get Many | 20 per minute |
| Deal → Get Many with Include Visit | 10 per minute; 5 above 100 rows a page, 1 above 1000 |
| Statistic → Get Daily | 5 per hour |
The node paces itself against all of them. The two project-wide budgets are queues: requests
wait their turn, and the credential’s Requests per Second (8 by default) and **Requests per
Hour** (4000) leave room for whatever else uses the project. The per-method budgets are not
queues — when one is spent and the wait would be over a minute, the node refuses with a message
naming the limit. Five calls an hour means the sixth would wait twelve minutes, and a workflow
parked that long reads as broken.
So: ask Get Daily for a whole month in one call. A loop over days runs out of budget on the
sixth day.
Quirks worth knowing
Everything below was checked against a live project on 11.09.2026, not taken from the
documentation. Several of these contradict it.
Roistat answers 200 to failures. By default every error, a rejected key included, comes
back as 200 OK with the real code inside the body. Every request this node makes carries
Use-Http-Code: 1, which switches that off. Worth knowing if you also call the API from an
HTTP Request node: without that header a credential test passes on any key at all.
A filter with exactly one condition crashes the server. {"and": [["date", ">=", …]]}
answers 500 internal_error on the visit list and the deal list alike; the same condition as
a bare list, [["date", ">=", …]], answers 200, and two conditions inside and answer 200.
The node sends one condition flat and several as a tree, so you will not meet this — unless
you write an and of one yourself in the Filters field.
Metrics in a report come back as an array, not an object. The documentation shows
{"visits": {...}, "roi": {...}}. A live project sends
[{value, formatted, metricname, attributionmodel_id}, …]. Simplify Output reads both, keyed
by metric_name; a metric requested under a non-default attribution model gets the model
appended to its key, so two attributions of one metric do not overwrite each other.
The funnel report requires next_dimensions, which the documentation calls optional.
Without it: 400 incorrectrequest — Required argument nextdimensions is missing. The node
always sends it, empty when you picked nothing.
The channel list is enormous and not shaped as documented. It returns every channel at
every nesting level in one answer, and on a project with a long advertising history that answer
is very large. Rows carry source, title, type and level — not the name and system_name the
docs describe. The Channel dropdowns therefore list level 1 only; for a deeper channel, switch
the field to an expression and enter its system name. Advertising Cost → Get Channels still
returns the whole tree.
Errors put their text in description, not message, and a 402 names the missing option
under details.option. A refusal looks like
{"status": "error", "error": "optionnotpaid", "description": "…", "details": {"option": "…"}}.
The node reads all three.
Two documented methods answer 404 resourcenotfound on a live project, with a correct
key and project, for both POST and GET: /project/settings/counter/list (Project → Get Counter)
and /project/access/get-authorized-users (Project → Get Access). They are still in the node
because they are documented; the credential test deliberately uses a different endpoint.
A feature that is off answers with an error, not an empty list. A feature not switched on
for the project answers optionnotenabled, one the plan does not include answers 402, and the lead board answers
optionnotpaid400 integration_error on a project that works
through a CRM instead. All three are reported as what they are rather than as a failure of the
request.
Two date formats, and the API is inconsistent about which. The analytics half takes ISO
instants with an offset. Get Daily and Site Lead → Get Many take a single dotted string,
2026-07-01-2026-07-31. Upload takes YYYY-MM-DD HH:MM. The node converts whatever the date
picker gives it, and a value with no offset is read by Roistat as UTC.
The analytics filters are objects, not the triples the lists take. Get Report and Get filter by
Funnel Report{field, operator, value}, with operators >, <, =, <>, >=,
<=, in and notin — in and notin want an array in value. Deals, calls and visits
filter by triples and a different operator list; the two are not interchangeable. Roistat's
documentation called the report's key operation until 21.09.2026, and a filter written that
way answers 422 requestdatavalidation_error.
Set Statuses replaces the dictionary, not adds to it. Send every status you use. A status
left out stops being recognised and its deals fall into «не учитывается».
Advertising Cost → Get Many reads the whole history. No period, no filter. Limit trims the
answer after it arrives.
A visit list answers at most 10 000 rows. Narrow the period rather than raising the limit.
What is not here
0.1.0 covers analytics, deals, leads, clients, calls, visits, costs, events, statistics and
projects. Roistat's API is larger than that. Not implemented:
answer with a file rather than data, and a workflow that wants the numbers wants the JSON.
Ask for any of it in an issue and
say what you are building; that is how the order gets decided.
What has been run, and what has not. On 11.09.2026 every reading operation was run
against a live project: all 11 resources, every dropdown, the report, the funnel, the deal
card, the daily summary. Four bugs came out of that run and are fixed in 0.1.0 — the crash on
a one-condition filter, the metrics array, the missing next_dimensions, and a channel
dropdown that read fields the API does not send.
The writing half has not been run yet, deliberately: a test deal uploaded into a working
project stays in its reports. Upload, Import, the cost writes, Send Event, Create Call, Update Params, the lead
writes and the access change are written from the documentation only. If one of them comes back
in a shape the node does not expect, that is where it will happen, and an issue with the
endpoint and the raw answer is the fastest way to fix it.
Feedback and bugs
Issues — a bug report that names
the operation, the raw answer and what you expected gets fixed; "does not work" does not.
Do not paste your API key into an issue. It is one key for every project in the profile.
Using the node with an AI agent
Both nodes are exposed as tools (usableAsTool), and the action node's own description tells
a model what the resources mean. Two warnings:
$fromAI — the model gets a free-form stringwith no enumeration and invents values;
Deal → Upload or Advertising Cost → Add is writing into analytics that someone reports on. Pin the project number on the credential rather than leaving it to the
model, and keep Delete away from it.