roistat 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +18 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +757 -0
  6. data/Rakefile +12 -0
  7. data/docs/ru/README.md +142 -0
  8. data/lefthook.yml +8 -0
  9. data/lib/generators/roistat/install/install_generator.rb +13 -0
  10. data/lib/generators/roistat/install/templates/roistat.rb +13 -0
  11. data/lib/roistat/client.rb +220 -0
  12. data/lib/roistat/configuration.rb +17 -0
  13. data/lib/roistat/errors.rb +18 -0
  14. data/lib/roistat/resources/access.rb +25 -0
  15. data/lib/roistat/resources/analytics.rb +68 -0
  16. data/lib/roistat/resources/base.rb +31 -0
  17. data/lib/roistat/resources/billing.rb +17 -0
  18. data/lib/roistat/resources/calltracking.rb +102 -0
  19. data/lib/roistat/resources/channels.rb +28 -0
  20. data/lib/roistat/resources/clients.rb +38 -0
  21. data/lib/roistat/resources/dashboards.rb +13 -0
  22. data/lib/roistat/resources/emailtracking.rb +16 -0
  23. data/lib/roistat/resources/events.rb +42 -0
  24. data/lib/roistat/resources/indicators.rb +13 -0
  25. data/lib/roistat/resources/lead_hunter.rb +8 -0
  26. data/lib/roistat/resources/leads.rb +33 -0
  27. data/lib/roistat/resources/managers.rb +33 -0
  28. data/lib/roistat/resources/mediaplan.rb +23 -0
  29. data/lib/roistat/resources/orders.rb +76 -0
  30. data/lib/roistat/resources/projects.rb +30 -0
  31. data/lib/roistat/resources/proxy_leads.rb +18 -0
  32. data/lib/roistat/resources/sms.rb +15 -0
  33. data/lib/roistat/resources/speech.rb +68 -0
  34. data/lib/roistat/resources/statistics.rb +8 -0
  35. data/lib/roistat/resources/visits.rb +23 -0
  36. data/lib/roistat/resources/vpbx.rb +100 -0
  37. data/lib/roistat/resources/widgets.rb +17 -0
  38. data/lib/roistat/response.rb +88 -0
  39. data/lib/roistat/version.rb +5 -0
  40. data/lib/roistat.rb +66 -0
  41. data/mise.toml +14 -0
  42. data/sig/roistat.rbs +251 -0
  43. metadata +187 -0
data/README.md ADDED
@@ -0,0 +1,757 @@
1
+ # Roistat
2
+
3
+ Ruby wrapper for the [Roistat REST API](https://help-ru.roistat.com/API/methods/about/).
4
+
5
+ **[Документация на русском](docs/ru/README.md)**
6
+
7
+ The gem sends every request with the `Api-key` header (never as a `key` query param). Project-scoped calls also send `project` in the query string.
8
+
9
+ Official docs differ by language. Prefer [help-ru](https://help-ru.roistat.com/API/methods/about/) for the method list. [help-en](https://help.roistat.com/API/methods/about/) documents additional endpoints (dashboards, widgets, and alternate access/counter paths); this gem wraps those as well.
10
+
11
+ ## Contents
12
+
13
+ - [Installation](#installation)
14
+ - [Configuration](#configuration)
15
+ - [Clients](#clients)
16
+ - [Low-level HTTP API](#low-level-http-api)
17
+ - [Resource APIs](#resource-apis)
18
+ - [Projects](#projects--clientprojects)
19
+ - [Access](#access--clientaccess)
20
+ - [Dashboards](#dashboards--clientdashboards)
21
+ - [Widgets](#widgets--clientwidgets)
22
+ - [Billing](#billing--clientbilling)
23
+ - [Calltracking](#calltracking--clientcalltracking)
24
+ - [Orders](#orders--clientorders)
25
+ - [Proxy leads](#proxy-leads--clientproxy_leads)
26
+ - [Leads](#leads--clientleads)
27
+ - [Managers](#managers--clientmanagers)
28
+ - [Clients](#clients--clientclients)
29
+ - [Visits](#visits--clientvisits)
30
+ - [Events](#events--clientevents)
31
+ - [Analytics](#analytics--clientanalytics)
32
+ - [Channels](#channels--clientchannels)
33
+ - [Statistics](#statistics--clientstatistics)
34
+ - [Indicators](#indicators--clientindicators)
35
+ - [Lead hunter](#lead-hunter--clientlead_hunter)
36
+ - [Email tracking](#email-tracking--clientemailtracking)
37
+ - [SMS](#sms--clientsms)
38
+ - [Mediaplan](#mediaplan--clientmediaplan)
39
+ - [Speech analytics](#speech-analytics--clientspeech)
40
+ - [Cloud PBX (VPBX)](#cloud-pbx-vpbx--clientvpbx)
41
+ - [Responses](#responses)
42
+ - [Errors](#errors)
43
+ - [Development](#development)
44
+ - [License](#license)
45
+
46
+ ## Installation
47
+
48
+ Add the gem to the Gemfile:
49
+
50
+ ```ruby
51
+ gem "roistat"
52
+ ```
53
+
54
+ Then run:
55
+
56
+ ```bash
57
+ bundle install
58
+ ```
59
+
60
+ Or install it directly:
61
+
62
+ ```bash
63
+ gem install roistat
64
+ ```
65
+
66
+ ### Rails
67
+
68
+ Generate the initializer:
69
+
70
+ ```bash
71
+ rails generate roistat:install
72
+ ```
73
+
74
+ This creates `config/initializers/roistat.rb` with all configuration options.
75
+
76
+ ## Configuration
77
+
78
+ ### Global config (Rails-friendly)
79
+
80
+ ```ruby
81
+ Roistat.configure do |config|
82
+ config.api_key = ENV.fetch("ROISTAT_API_KEY")
83
+ config.project = ENV.fetch("ROISTAT_PROJECT_ID")
84
+
85
+ # Optional:
86
+ # config.base_url = "https://cloud.roistat.com/api/v1"
87
+ # config.timeout = 30
88
+ # config.open_timeout = 10
89
+ # config.binary_tempfile_threshold = 1_048_576
90
+ end
91
+ ```
92
+
93
+ Then use the shared client:
94
+
95
+ ```ruby
96
+ Roistat.client.projects.list
97
+ ```
98
+
99
+ ### Configuration options
100
+
101
+ | Option | Required | Default | Description |
102
+ |--------|----------|---------|-------------|
103
+ | `api_key` | yes | — | Roistat API key (`Api-key` header) |
104
+ | `project` | yes for project-scoped calls | — | Project id (query `project`) |
105
+ | `base_url` | no | `https://cloud.roistat.com/api/v1` | API base URL |
106
+ | `timeout` | no | `30` | Request timeout in seconds |
107
+ | `open_timeout` | no | `10` | Connection open timeout in seconds |
108
+ | `binary_tempfile_threshold` | no | `1048576` (1 MiB) | Binary bodies larger than this become a `Tempfile` |
109
+
110
+ ### Environment variables (install generator)
111
+
112
+ | Variable | Maps to |
113
+ |----------|---------|
114
+ | `ROISTAT_API_KEY` | `config.api_key` |
115
+ | `ROISTAT_PROJECT_ID` | `config.project` |
116
+ | `ROISTAT_BASE_URL` | `config.base_url` (commented in template) |
117
+ | `ROISTAT_TIMEOUT` | `config.timeout` (commented) |
118
+ | `ROISTAT_OPEN_TIMEOUT` | `config.open_timeout` (commented) |
119
+ | `ROISTAT_BINARY_TEMPFILE_THRESHOLD` | `config.binary_tempfile_threshold` (commented) |
120
+
121
+ ## Clients
122
+
123
+ ### Shared client
124
+
125
+ `Roistat.client` builds a client from the global configuration.
126
+
127
+ ### Explicit client
128
+
129
+ Use a dedicated instance when you need another project or key:
130
+
131
+ ```ruby
132
+ client = Roistat::Client.new(
133
+ api_key: "…",
134
+ project: "12345",
135
+ timeout: 20
136
+ )
137
+
138
+ client.access.user_list
139
+ ```
140
+
141
+ ### API-key-only client
142
+
143
+ Some account endpoints do not need a project id:
144
+
145
+ ```ruby
146
+ client = Roistat::Client.new(api_key: "…", project_required: false)
147
+ client.get("user/projects")
148
+ ```
149
+
150
+ Resource helpers that are API-key-only (`projects.list`, `projects.create`) create that client internally.
151
+
152
+ Blank or whitespace-only credentials raise `Roistat::ConfigurationError` before any HTTP call.
153
+
154
+ ## Low-level HTTP API
155
+
156
+ Use these for any documented Roistat path that does not have a resource method yet:
157
+
158
+ ```ruby
159
+ client.get("project/calltracking/phone/list", params: {limit: 10})
160
+ client.post("project/events/send", body: {name: "purchase"})
161
+ client.request(:get, "project/permissions/user/list")
162
+ ```
163
+
164
+ ### Method signatures
165
+
166
+ | Method | Purpose |
167
+ |--------|---------|
168
+ | `get(path, params: {}, parse: :json)` | GET request |
169
+ | `post(path, params: {}, body: nil, parse: :json)` | POST request |
170
+ | `request(method, path, params: {}, body: nil, parse: :json)` | Arbitrary verb |
171
+
172
+ Notes:
173
+
174
+ - `path` is relative to `base_url` (leading slash is optional).
175
+ - `params` become query parameters. The client adds `project` automatically when the client has a project id.
176
+ - `body` is JSON-encoded. The gem sets `Content-Type: application/json`.
177
+ - `parse: :json` (default) returns a parsed Hash/Array.
178
+ - `parse: :binary` returns a `String` or `Tempfile` (see [Responses](#responses)).
179
+
180
+ ## Resource APIs
181
+
182
+ High-level helpers are available on every client:
183
+
184
+ ```ruby
185
+ client.projects
186
+ client.access
187
+ client.dashboards
188
+ client.widgets
189
+ client.billing
190
+ client.calltracking
191
+ client.orders
192
+ client.proxy_leads
193
+ client.leads
194
+ client.managers
195
+ client.clients
196
+ client.visits
197
+ client.events
198
+ client.analytics
199
+ client.channels
200
+ client.statistics
201
+ client.indicators
202
+ client.lead_hunter
203
+ client.emailtracking
204
+ client.sms
205
+ client.mediaplan
206
+ client.speech
207
+ client.vpbx
208
+ ```
209
+
210
+ Official parameter details live in the [Roistat API docs](https://help-ru.roistat.com/API/methods/about/).
211
+
212
+ ### Projects — `client.projects`
213
+
214
+ | Ruby method | HTTP | Path | Auth |
215
+ |-------------|------|------|------|
216
+ | `list` | GET | `/user/projects` | API key only |
217
+ | `create(name:, currency:)` | POST | `/account/project/create` | API key only |
218
+ | `modules_list(method: :get)` | GET or POST | `/project/settings/module/list` | API key + project |
219
+ | `counter_list` | POST | `/project/settings/counter/list` | API key + project (help-en) |
220
+
221
+ Examples:
222
+
223
+ ```ruby
224
+ Roistat.client.projects.list
225
+ Roistat.client.projects.create(name: "Demo", currency: "RUB")
226
+ Roistat.client.projects.modules_list
227
+ Roistat.client.projects.modules_list(method: :post)
228
+ Roistat.client.projects.counter_list
229
+ ```
230
+
231
+ ### Access — `client.access`
232
+
233
+ | Ruby method | HTTP | Path | Auth |
234
+ |-------------|------|------|------|
235
+ | `user_list` | GET | `/project/permissions/user/list` | API key + project (help-ru) |
236
+ | `authorized_users(method: :get)` | GET or POST | `/project/access/get-authorized-users` | API key + project (help-en) |
237
+ | `change(**body)` | POST | `/project/access/change` | API key + project (help-en) |
238
+
239
+ Examples:
240
+
241
+ ```ruby
242
+ Roistat.client.access.user_list
243
+ Roistat.client.access.authorized_users
244
+ Roistat.client.access.authorized_users(method: :post)
245
+ Roistat.client.access.change(email: "user@example.com", permissions: ["access_analytics_base_read"])
246
+ ```
247
+
248
+ ### Dashboards — `client.dashboards`
249
+
250
+ Documented on [help-en](https://help.roistat.com/API/methods/about/).
251
+
252
+ | Ruby method | HTTP | Path |
253
+ |-------------|------|------|
254
+ | `list` | GET | `/project/dashboards` |
255
+ | `widgets(dashboard_id:)` | GET | `/project/dashboards/{dashboardId}/widgets` |
256
+
257
+ Examples:
258
+
259
+ ```ruby
260
+ Roistat.client.dashboards.list
261
+ Roistat.client.dashboards.widgets(dashboard_id: 1)
262
+ ```
263
+
264
+ ### Widgets — `client.widgets`
265
+
266
+ Documented on [help-en](https://help.roistat.com/API/methods/about/). Default verb is POST (matches curl examples); pass `method: :get` when needed.
267
+
268
+ | Ruby method | HTTP | Path |
269
+ |-------------|------|------|
270
+ | `data(widget_id:, method: :post, **body)` | GET or POST | `/project/widget/{widgetId}/data` |
271
+
272
+ Examples:
273
+
274
+ ```ruby
275
+ Roistat.client.widgets.data(widget_id: 9, period: "2026-01-01-2026-01-31")
276
+ Roistat.client.widgets.data(widget_id: 9, method: :get)
277
+ ```
278
+
279
+ ### Billing — `client.billing`
280
+
281
+ | Ruby method | HTTP | Path | Auth | Response |
282
+ |-------------|------|------|------|----------|
283
+ | `transactions_list(period:)` | POST | `/user/billing/transactions/list` | API key + project | JSON |
284
+ | `transactions_export_excel(period:)` | POST | `/user/billing/transactions/list/export/excel` | API key + project | binary |
285
+
286
+ `period` must include `from` and `to` (Roistat date strings).
287
+
288
+ Examples:
289
+
290
+ ```ruby
291
+ period = {
292
+ from: "2026-01-01T00:00:00+0300",
293
+ to: "2026-07-22T23:59:59+0300"
294
+ }
295
+
296
+ Roistat.client.billing.transactions_list(period: period)
297
+
298
+ file = Roistat.client.billing.transactions_export_excel(period: period)
299
+ # String if ≤ 1 MiB, otherwise Tempfile
300
+ ```
301
+
302
+ ### Calltracking — `client.calltracking`
303
+
304
+ Official docs: [calltracking API](https://help-ru.roistat.com/API/methods/calltracking/). The gem uses POST for these endpoints.
305
+
306
+ | Ruby method | HTTP | Path | Notes |
307
+ |-------------|------|------|-------|
308
+ | `script_list(**body)` | POST | `/project/calltracking/script/list` | optional body (e.g. `is_deleted:`) |
309
+ | `script_create(**body)` | POST | `/project/calltracking/script/create` | |
310
+ | `script_update(**body)` | POST | `/project/calltracking/script/update` | |
311
+ | `script_delete(id:)` | POST | `/project/calltracking/script/delete` | |
312
+ | `phone_list(**body)` | POST | `/project/calltracking/phone/list` | |
313
+ | `phone_prefix_list(**body)` | POST | `/project/calltracking/phone/prefix/list` | |
314
+ | `phone_create(phones:)` | POST | `/project/calltracking/phone/create` | |
315
+ | `phone_buy(prefix:, count:)` | POST | `/project/calltracking/phone/buy` | |
316
+ | `phone_update(**body)` | POST | `/project/calltracking/phone/update` | |
317
+ | `phone_delete(phones:)` | POST | `/project/calltracking/phone/delete` | phone ids |
318
+ | `call_list(**body)` | POST | `/project/calltracking/call/list` | filters, extend, sort, limit, offset |
319
+ | `call_update(**body)` | POST | `/project/calltracking/call/update` | |
320
+ | `call_delete(ids:)` | POST | `/project/calltracking/call/delete` | |
321
+ | `call_file(call_id:)` | POST | `/project/calltracking/call/{id}/file` | binary (MP3) |
322
+ | `call_xls_export(period:)` | POST | `/project/calltracking/call/xls/export` | binary (XLS) |
323
+ | `data(period:)` | POST | `/project/calltracking/data` | dashboard analytics |
324
+ | `phone_call(**body)` | POST | `/project/phone-call` | create call history row |
325
+
326
+ Examples:
327
+
328
+ ```ruby
329
+ Roistat.client.calltracking.phone_list
330
+ Roistat.client.calltracking.script_list(is_deleted: 0)
331
+ Roistat.client.calltracking.call_list(
332
+ filters: {and: [["date", ">", "2026-01-01T00:00:00+0000"]]},
333
+ limit: 50
334
+ )
335
+ Roistat.client.calltracking.data(period: period)
336
+ audio = Roistat.client.calltracking.call_file(call_id: 1234)
337
+ ```
338
+
339
+ ### Orders — `client.orders`
340
+
341
+ Official docs: [orders API](https://help-ru.roistat.com/API/methods/orders/).
342
+
343
+ | Ruby method | HTTP | Path |
344
+ |-------------|------|------|
345
+ | `list(**body)` | POST | `/project/integration/order/list` |
346
+ | `add(orders)` | POST | `/project/add-orders` (JSON array body) |
347
+ | `info(order_id:)` | GET | `/project/orders/{id}/info` |
348
+ | `external_url(order_id:)` | GET | `/project/orders/{id}/external-url` |
349
+ | `status_list(**body)` | POST | `/project/integration/status/list` |
350
+ | `set_statuses(statuses)` | POST | `/project/set-statuses` (JSON array body) |
351
+ | `custom_fields(**body)` | POST | `/project/analytics/order-custom-fields` |
352
+ | `status_update(order_id:, status_id:)` | POST | `/project/integration/order/{id}/status/update` |
353
+ | `goal_update(order_id:, **body)` | POST | `/project/integration/order/{id}/goal/update` |
354
+ | `update(orders:)` | POST | `/project/integration/order/update` |
355
+ | `delete(order_id:)` | POST | `/project/integration/order/{id}/delete` |
356
+ | `delete_many(ids:)` | POST | `/project/integration/order/delete` |
357
+
358
+ Examples:
359
+
360
+ ```ruby
361
+ Roistat.client.orders.list(limit: 50)
362
+ Roistat.client.orders.status_list
363
+ Roistat.client.orders.info(order_id: "order_12345")
364
+ Roistat.client.orders.add([
365
+ {id: "1", name: "New order", date_create: "2022-06-19T00:32:12+0000"}
366
+ ])
367
+ ```
368
+
369
+ ### Proxy leads — `client.proxy_leads`
370
+
371
+ Official docs: [proxy leads API](https://help-ru.roistat.com/API/methods/proxy-leads/).
372
+ `period` is `YYYY-MM-DD-YYYY-MM-DD` in the query string.
373
+
374
+ | Ruby method | HTTP | Path |
375
+ |-------------|------|------|
376
+ | `list(period:)` | GET | `/project/proxy-leads` |
377
+ | `duplicates(period:)` | GET | `/project/proxy-leads/duplicates` |
378
+ | `get(id:)` | GET | `/project/proxy-leads/{id}` |
379
+
380
+ Examples:
381
+
382
+ ```ruby
383
+ Roistat.client.proxy_leads.list(period: "2026-01-01-2026-07-22")
384
+ Roistat.client.proxy_leads.get(id: "2")
385
+ ```
386
+
387
+ ### Leads — `client.leads`
388
+
389
+ Official docs: [lead management API](https://help-ru.roistat.com/API/methods/lead_management/).
390
+
391
+ | Ruby method | HTTP | Path |
392
+ |-------------|------|------|
393
+ | `list(**body)` | POST | `/project/leads/lead/list` |
394
+ | `status_list(**body)` | POST | `/project/leads/status/list` |
395
+ | `create(**body)` | POST | `/project/leads/lead/create` |
396
+ | `update(**body)` | POST | `/project/leads/lead/update` |
397
+
398
+ Examples:
399
+
400
+ ```ruby
401
+ Roistat.client.leads.status_list
402
+ Roistat.client.leads.list(
403
+ period: {from: "2023-10-17T21:00:00.000Z", to: "2023-11-30T20:59:59.999Z"},
404
+ sort_field: "creation_date",
405
+ limit: 20
406
+ )
407
+ ```
408
+
409
+ ### Managers — `client.managers`
410
+
411
+ Official docs: [managers API](https://help-ru.roistat.com/API/methods/managers/).
412
+
413
+ | Ruby method | HTTP | Path |
414
+ |-------------|------|------|
415
+ | `list(**body)` | POST | `/project/integration/manager/list` |
416
+ | `add(**body)` | POST | `/project/integration/manager/add` |
417
+ | `update(**body)` | POST | `/project/integration/manager/update` |
418
+ | `delete(id:)` | POST | `/project/integration/manager/delete` |
419
+
420
+ Examples:
421
+
422
+ ```ruby
423
+ Roistat.client.managers.list
424
+ Roistat.client.managers.add(id: "12345", name: "Petrov", email: "a@b.c")
425
+ ```
426
+
427
+ ### Clients — `client.clients`
428
+
429
+ Official docs: [clients API](https://help-ru.roistat.com/API/methods/clients/).
430
+
431
+ | Ruby method | HTTP | Path |
432
+ |-------------|------|------|
433
+ | `list(**body)` | POST | `/project/clients` |
434
+ | `import(clients)` | POST | `/project/clients/import` (JSON array body) |
435
+ | `detail_feed(client:)` | GET | `/project/clients/detail/feed` (`client` query) |
436
+ | `campaign_list(**body)` | POST | `/project/clients/campaign/list` |
437
+ | `campaign_contact_list(**body)` | POST | `/project/clients/campaign/contact/list` |
438
+
439
+ Examples:
440
+
441
+ ```ruby
442
+ Roistat.client.clients.list(limit: 100)
443
+ Roistat.client.clients.detail_feed(client: "123")
444
+ Roistat.client.clients.import([{id: "111", name: "Valera"}])
445
+ ```
446
+
447
+ ### Visits — `client.visits`
448
+
449
+ Official docs: [visits API](https://help-ru.roistat.com/API/methods/visit/).
450
+
451
+ | Ruby method | HTTP | Path |
452
+ |-------------|------|------|
453
+ | `list(**body)` | POST | `/project/site/visit/list` |
454
+ | `params_update(**body)` | POST | `/project/site/visit/params/update` |
455
+
456
+ Examples:
457
+
458
+ ```ruby
459
+ Roistat.client.visits.list(limit: 100)
460
+ Roistat.client.visits.params_update(visit: "123", roistat_param1: "onlineshop")
461
+ ```
462
+
463
+ ### Events — `client.events`
464
+
465
+ Official docs: [events API](https://help-ru.roistat.com/API/methods/events/).
466
+ `send_event` avoids shadowing Ruby `Kernel#send`.
467
+
468
+ | Ruby method | HTTP | Path |
469
+ |-------------|------|------|
470
+ | `send_event(**body)` | POST | `/project/events/send` |
471
+ | `bulk_send(events)` | POST | `/project/events/bulk/send` (JSON array) |
472
+ | `add(events)` | POST | `/project/events/add` (JSON array) |
473
+ | `log(**params)` | GET | `/project/events/log` (filters as query params) |
474
+ | `archive(event_id:, events:)` | POST | `/project/events/meta/{id}/archive` (JSON array) |
475
+
476
+ Examples:
477
+
478
+ ```ruby
479
+ Roistat.client.events.log(name: "Cart")
480
+ Roistat.client.events.send_event(name: "Purchase", visit: "100001")
481
+ ```
482
+
483
+ ### Analytics — `client.analytics`
484
+
485
+ Official docs: [analytics API](https://help-ru.roistat.com/API/methods/analytics/).
486
+
487
+ | Ruby method | HTTP | Path |
488
+ |-------------|------|------|
489
+ | `data(**body)` | POST | `/project/analytics/data` |
490
+ | `data_export_excel(**body)` | POST | `/project/analytics/data/export/excel` (binary) |
491
+ | `metrics_new(**body)` | POST | `/project/analytics/metrics-new` |
492
+ | `dimensions(**body)` | POST | `/project/analytics/dimensions` |
493
+ | `dimension_values(**body)` | POST | `/project/analytics/dimension-values` |
494
+ | `attribution_models(**body)` | POST | `/project/analytics/attribution-models` |
495
+ | `list_orders(**body)` | POST | `/project/analytics/list-orders` |
496
+ | `custom_metrics_list(**params)` | GET | `/project/analytics/metrics/custom/list` |
497
+ | `custom_manual_value_list(**body)` | POST | `/project/analytics/metrics/custom/manual/value/list` |
498
+ | `custom_manual_value_add(**body)` | POST | `/project/analytics/metrics/custom/manual/value/add` |
499
+ | `custom_manual_value_delete(**body)` | POST | `/project/analytics/metrics/custom/manual/value/delete` |
500
+ | `funnel_data(**body)` | POST | `/project/reports/funnel/data` |
501
+ | `event_add(**body)` | POST | `/project/analytics/event/add` |
502
+
503
+ Examples:
504
+
505
+ ```ruby
506
+ Roistat.client.analytics.dimensions
507
+ Roistat.client.analytics.metrics_new
508
+ Roistat.client.analytics.custom_metrics_list
509
+ Roistat.client.analytics.data(
510
+ period: {from: "2026-01-01T00:00:00+0300", to: "2026-07-31T23:59:59+0300"},
511
+ metrics: ["visits"],
512
+ dimensions: ["marker_level_1"]
513
+ )
514
+ ```
515
+
516
+ ### Channels — `client.channels`
517
+
518
+ Official docs: [channels / source costs API](https://help-ru.roistat.com/API/methods/channels/).
519
+
520
+ | Ruby method | HTTP | Path |
521
+ |-------------|------|------|
522
+ | `source_list(**body)` | POST | `/project/analytics/source/list` |
523
+ | `cost_list(**body)` | POST | `/project/analytics/source/cost/list` |
524
+ | `cost_add(**body)` | POST | `/project/analytics/source/cost/add` |
525
+ | `cost_update(**body)` | POST | `/project/analytics/source/cost/update` |
526
+ | `cost_delete(id:)` | POST | `/project/analytics/source/cost/delete` |
527
+
528
+ Examples:
529
+
530
+ ```ruby
531
+ Roistat.client.channels.source_list
532
+ Roistat.client.channels.cost_list
533
+ ```
534
+
535
+ ### Statistics — `client.statistics`
536
+
537
+ Official docs: [statistics API](https://help-ru.roistat.com/API/methods/statistics/).
538
+
539
+ | Ruby method | HTTP | Path |
540
+ |-------------|------|------|
541
+ | `get_daily(**body)` | POST | `/project/statistics/get-daily` (`period`: `YYYY-MM-DD-YYYY-MM-DD` in UTC; optional `time_zone`, `channel`) |
542
+
543
+ Examples:
544
+
545
+ ```ruby
546
+ Roistat.client.statistics.get_daily(period: "2026-01-01-2026-07-22")
547
+ ```
548
+
549
+ ### Indicators — `client.indicators`
550
+
551
+ Official docs: [health indicators API](https://help-ru.roistat.com/API/methods/indicators/).
552
+
553
+ | Ruby method | HTTP | Path |
554
+ |-------------|------|------|
555
+ | `list` | GET | `/project/health/indicator/list` |
556
+ | `run_script(indicator_id:)` | POST | `/project/health/indicator/{id}/run-script` |
557
+
558
+ Examples:
559
+
560
+ ```ruby
561
+ Roistat.client.indicators.list
562
+ Roistat.client.indicators.run_script(indicator_id: 7)
563
+ ```
564
+
565
+ ### Lead hunter — `client.lead_hunter`
566
+
567
+ Official docs: [lead hunter API](https://help-ru.roistat.com/API/methods/lead-hunter/).
568
+
569
+ | Ruby method | HTTP | Path |
570
+ |-------------|------|------|
571
+ | `list(**params)` | GET | `/project/lead-hunter/lead/list` (filters as query params) |
572
+
573
+ Examples:
574
+
575
+ ```ruby
576
+ Roistat.client.lead_hunter.list
577
+ ```
578
+
579
+ ### Email tracking — `client.emailtracking`
580
+
581
+ Official docs: [email tracking API](https://help-ru.roistat.com/API/methods/emailtracking/).
582
+
583
+ | Ruby method | HTTP | Path |
584
+ |-------------|------|------|
585
+ | `list(**body)` | POST | `/project/emailtracking/email/list` |
586
+ | `attachment(email_id:, attachment_id:)` | GET | `/project/emailtracking/email/{email_id}/attachment/{attachment_id}` (binary) |
587
+
588
+ Examples:
589
+
590
+ ```ruby
591
+ Roistat.client.emailtracking.list(limit: 10)
592
+ ```
593
+
594
+ ### SMS — `client.sms`
595
+
596
+ | Ruby method | HTTP | Path |
597
+ |-------------|------|------|
598
+ | `set_report_enabled(enabled:)` | POST | `/project/set-sms-report-enabled` (`enabled`: `"0"` or `"1"`) |
599
+
600
+ Examples:
601
+
602
+ ```ruby
603
+ Roistat.client.sms.set_report_enabled(enabled: true)
604
+ ```
605
+
606
+ ### Mediaplan — `client.mediaplan`
607
+
608
+ Official docs: [mediaplan API](https://help-ru.roistat.com/API/methods/mediaplan/).
609
+
610
+ | Ruby method | HTTP | Path |
611
+ |-------------|------|------|
612
+ | `target_list(**body)` | POST | `/project/mediaplan/target/list` |
613
+ | `target_create(**body)` | POST | `/project/mediaplan/target/create` |
614
+ | `target_update(**body)` | POST | `/project/mediaplan/target/update` |
615
+ | `target_delete(id:)` | POST | `/project/mediaplan/target/delete` |
616
+
617
+ Examples:
618
+
619
+ ```ruby
620
+ Roistat.client.mediaplan.target_list(
621
+ period: {from: "2026-07-01T00:00:00+0300", to: "2026-07-31T23:59:59+0300"}
622
+ )
623
+ ```
624
+
625
+ ### Speech analytics — `client.speech`
626
+
627
+ **RU help only.** Official docs: [speech analytics API](https://help-ru.roistat.com/API/methods/speech/).
628
+
629
+ | Ruby method | HTTP | Path |
630
+ |-------------|------|------|
631
+ | `call_list(**body)` | POST | `/project/speech/call/list` |
632
+ | `call_list_export_excel(**body)` | POST | `/project/speech/call/list/export/excel` (binary) |
633
+ | `call_add(**body)` | POST | `/project/speech/call/add` |
634
+ | `call_comment_update(**body)` | POST | `/project/speech/call/comment/update` |
635
+ | `call_operator_update(**body)` | POST | `/project/speech/call/operator/update` |
636
+ | `call_transcription_list(**body)` | POST | `/project/speech/call/transcription/list` |
637
+ | `dictionary_list(**body)` | POST | `/project/speech/dictionary/list` |
638
+ | `dictionary_custom_create(**body)` | POST | `/project/speech/dictionary/custom/create` |
639
+ | `dictionary_custom_update(**body)` | POST | `/project/speech/dictionary/custom/update` |
640
+ | `dictionary_custom_delete(**body)` | POST | `/project/speech/dictionary/custom/delete` |
641
+ | `dictionary_custom_phrase_list(**body)` | POST | `/project/speech/dictionary/custom/phrase/list` |
642
+ | `settings_list(**body)` | POST | `/project/speech/settings/list` |
643
+ | `settings_update(**body)` | POST | `/project/speech/settings/update` |
644
+
645
+ Examples:
646
+
647
+ ```ruby
648
+ Roistat.client.speech.dictionary_list
649
+ Roistat.client.speech.call_list
650
+ ```
651
+
652
+ ### Cloud PBX (VPBX) — `client.vpbx`
653
+
654
+ **RU help only.** Official docs: [VPBX API](https://help-ru.roistat.com/API/methods/vpbx/).
655
+
656
+ | Ruby method | HTTP | Path |
657
+ |-------------|------|------|
658
+ | `call_list(**body)` | POST | `/project/vpbx/call/list` |
659
+ | `operator_list(**body)` | POST | `/project/vpbx/operator/list` |
660
+ | `operator_create(**body)` | POST | `/project/vpbx/operator/create` |
661
+ | `operator_update(**body)` | POST | `/project/vpbx/operator/update` |
662
+ | `operator_deactivate(**body)` | POST | `/project/vpbx/operator/deactivate` |
663
+ | `operator_group_list(**body)` | POST | `/project/vpbx/operator/group/list` |
664
+ | `operator_group_create(**body)` | POST | `/project/vpbx/operator/group/create` |
665
+ | `operator_group_update(**body)` | POST | `/project/vpbx/operator/group/update` |
666
+ | `phone_list(**params)` | GET | `/project/vpbx/phone/list` |
667
+ | `phone_create(**body)` | POST | `/project/vpbx/phone/create` |
668
+ | `phone_update(**body)` | POST | `/project/vpbx/phone/update` |
669
+ | `phone_delete(**body)` | POST | `/project/vpbx/phone/delete` |
670
+ | `script_list(**params)` | GET | `/project/vpbx/script/list` |
671
+ | `script_create(**body)` | POST | `/project/vpbx/script/create` |
672
+ | `script_update(**body)` | POST | `/project/vpbx/script/update` |
673
+ | `script_delete(**body)` | POST | `/project/vpbx/script/delete` |
674
+ | `report_data(**body)` | POST | `/project/vpbx/report/data` |
675
+ | `settings_update(**body)` | POST | `/project/vpbx/settings/update` |
676
+ | `settings_file_audio_upload(file:, **fields)` | POST | `/project/vpbx/settings/file/audio/upload` (multipart) |
677
+
678
+ `settings_file_audio_upload` sends `multipart/form-data` via `Client#post_multipart`. Pass an IO or open file as `file:`; other keyword args become form fields.
679
+
680
+ Examples:
681
+
682
+ ```ruby
683
+ Roistat.client.vpbx.phone_list
684
+ Roistat.client.vpbx.script_list
685
+ ```
686
+
687
+ ## Responses
688
+
689
+ ### JSON
690
+
691
+ Successful JSON responses return the parsed body (usually a Hash with string keys).
692
+
693
+ ### Binary
694
+
695
+ With `parse: :binary` (billing Excel export, calltracking XLS export, call audio, email attachments, speech call export):
696
+
697
+ | Body size | Return type |
698
+ |-----------|-------------|
699
+ | ≤ `binary_tempfile_threshold` (default 1 MiB) | `String` |
700
+ | \> threshold | `Tempfile` (caller should close/unlink) |
701
+
702
+ ## Errors
703
+
704
+ Roistat API errors with `"status": "error"` map to typed exceptions:
705
+
706
+ | Roistat `error` code | Exception |
707
+ |----------------------|-----------|
708
+ | `authentication_failed` | `Roistat::AuthenticationError` |
709
+ | `authorization_failed` | `Roistat::AuthorizationError` |
710
+ | `access_denied` | `Roistat::AccessDeniedError` |
711
+ | `request_limit_error` | `Roistat::RateLimitError` |
712
+ | other / unknown | `Roistat::Error` |
713
+
714
+ Invalid local config raises `Roistat::ConfigurationError`.
715
+
716
+ Error instances may expose:
717
+
718
+ - `code` — Roistat error code
719
+ - `http_status` — HTTP status
720
+ - `response_body` — parsed body when available
721
+
722
+ ## Development
723
+
724
+ ```bash
725
+ bin/setup
726
+ bundle exec rake # RSpec + RuboCop
727
+ # or
728
+ mise test
729
+ mise lint
730
+ ```
731
+
732
+ Install Lefthook once for pre-commit hooks:
733
+
734
+ ```bash
735
+ bundle exec lefthook install
736
+ ```
737
+
738
+ Interactive console:
739
+
740
+ ```bash
741
+ bin/console
742
+ ```
743
+
744
+ Release:
745
+
746
+ ```bash
747
+ mise release
748
+ # or: bundle exec rake release
749
+ ```
750
+
751
+ ## Contributing
752
+
753
+ Bug reports and pull requests are welcome on GitHub at https://github.com/alec-c4/roistat.
754
+
755
+ ## License
756
+
757
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).