umami-ruby 0.1.3 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6864cffba61396afd181c29d69bf8f3676c4836b70acc06d309fb7d95c1e1e02
4
- data.tar.gz: 619f861f73eb0388813d6252cc5869f3c53246e799d90d912d9cde8e8118cb97
3
+ metadata.gz: 24515824a954d2c9b334b0445ef3a64f2faf79bb9fd0ec26657b702f0e170951
4
+ data.tar.gz: 2eec535b5aa02bf528b05a7d767d843b5781d8d970d03d7070953fc604582904
5
5
  SHA512:
6
- metadata.gz: 809be5c3afeacf57451b33218c9e7f4cf49045148ac41b1efd0970ddcf597dff99a7adf12c9f3ddb4beb47209d94e55da211857b3d7fd9901fbead106b454a1d
7
- data.tar.gz: 3abcca6c471a3353b8a7ca257f3583d6ede3cc5477b16f2d1abf546dd26b9a81a3720947cfbee0277e9ce7a7eb00005d084816d681ef0dbe6a1f4b53dd93e23a
6
+ metadata.gz: 53784a10bf0025c49791ace32354800ac39bdb7e662234a7f81f78f50538234aec5c32fda101bd7d844bd3df5eaf8a94e6002d85037729db637b8ff818e2d55f
7
+ data.tar.gz: 84cb4db13dd978dfb0ac65982081fe3a1a23b8e0b3f755c7cfc0c920e53c32d16861630b5a17c43059882c43222ce62f4e6aed1ff1b599d4da2e92bd56e56375
data/.env.test ADDED
@@ -0,0 +1,9 @@
1
+ # Test environment configuration (safe to commit)
2
+ # Real credentials should go in .env.test.local (gitignored)
3
+
4
+ UMAMI_ACCESS_TOKEN=test_token_placeholder
5
+ UMAMI_URI_BASE=https://api.umami.is
6
+
7
+ # Test website IDs (using placeholder UUIDs)
8
+ UMAMI_TEST_WEBSITE_ID=00000000-0000-0000-0000-000000000001
9
+ UMAMI_TEST_WEBSITE_ID_ALT=00000000-0000-0000-0000-000000000002
data/.simplecov ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SimpleCov configuration file (auto-loaded before test suite)
4
+ # This keeps test_helper.rb clean and follows best practices
5
+
6
+ SimpleCov.start do
7
+ # Use SimpleFormatter for terminal-only output (no HTML generation)
8
+ formatter SimpleCov::Formatter::SimpleFormatter
9
+
10
+ # Track coverage for the lib directory (gem source code)
11
+ add_filter "/test/"
12
+
13
+ # Track Ruby files in lib directory
14
+ track_files "lib/**/*.rb"
15
+
16
+ # Enable branch coverage for more detailed metrics
17
+ enable_coverage :branch
18
+
19
+ # Set minimum coverage threshold to prevent coverage regression
20
+ # Current coverage: Line ~73%, Branch ~73%
21
+ minimum_coverage line: 70, branch: 65
22
+
23
+ # Disambiguate parallel test runs
24
+ command_name "Job #{ENV['TEST_ENV_NUMBER']}" if ENV['TEST_ENV_NUMBER']
25
+ end
26
+
27
+ # Print coverage summary to terminal after tests complete
28
+ SimpleCov.at_exit do
29
+ SimpleCov.result.format!
30
+ puts "\n" + "=" * 60
31
+ puts "COVERAGE SUMMARY"
32
+ puts "=" * 60
33
+ puts "Line Coverage: #{SimpleCov.result.covered_percent.round(2)}%"
34
+ puts "Branch Coverage: #{SimpleCov.result.coverage_statistics[:branch]&.percent&.round(2) || 'N/A'}%"
35
+ puts "=" * 60
36
+ end
data/CHANGELOG.md CHANGED
@@ -1,4 +1,97 @@
1
- ## [Unreleased]
1
+ ## [0.3.0] - 2026-07-24
2
+
3
+ ### Fixed
4
+
5
+ - **Username/password authentication against self-hosted instances actually works now.** Since the connection memoization was introduced, `authenticate` built (and memoized) the Faraday connection in order to POST `/api/auth/login` — before `@access_token` existed. Faraday bakes headers in at connection build time, so every request after a successful login went out without an `Authorization` header and failed with 401. The token-based flow was unaffected, which is how this survived: docs and tests exercised logins, never "login **then** call an endpoint". `authenticate` now drops the memoized connection after storing the token so the next request rebuilds it with the Bearer header, and the new `test/integration/authentication_flow_test.rb` pins the full lifecycle (login → authorized request → connection reuse without re-login).
6
+ - `authenticate` raises `Umami::AuthenticationError` when the login response is a 200 without a `token` field, instead of leaving a client that 401s on every call with no hint why.
7
+
8
+ ### Changed
9
+
10
+ - HTTP 401 and 403 responses now raise `Umami::AuthenticationError` instead of the undifferentiated `Umami::ClientError`, giving long-lived clients a re-authentication signal (expired/revoked token, insufficient permissions). To keep existing `rescue Umami::APIError` handlers working — they used to catch these as `ClientError` — `AuthenticationError` is now a subclass of `Umami::APIError` (previously it subclassed `Umami::Error` directly). Code matching `Umami::ClientError` specifically for 401/403 must switch to `Umami::AuthenticationError`.
11
+
12
+ ## [0.2.0] - 2026-02-02
13
+
14
+ ### Added
15
+
16
+ **New Endpoint Categories:**
17
+ - **Me endpoints**: `me`, `my_teams`, `my_websites` - Get current user info and resources
18
+ - **Admin endpoints**: `admin_users`, `admin_websites`, `admin_teams` - Admin-only endpoints for self-hosted
19
+ - **Sessions endpoints** (8 new methods):
20
+ - `website_sessions` - Get sessions within a time range
21
+ - `website_sessions_stats` - Get summarized session statistics
22
+ - `website_sessions_weekly` - Get session counts by hour of weekday
23
+ - `website_session` - Get individual session details
24
+ - `website_session_activity` - Get session activity
25
+ - `website_session_properties` - Get session properties
26
+ - `website_session_data_properties` - Get session data property counts
27
+ - `website_session_data_values` - Get session data value counts
28
+ - **Realtime endpoint**: `realtime` - Get live stats for last 30 minutes
29
+ - **Links endpoints**: `links`, `link`, `update_link`, `delete_link`
30
+ - **Pixels endpoints**: `pixels`, `pixel`, `update_pixel`, `delete_pixel`
31
+ - **Reports endpoints** (13 new methods):
32
+ - CRUD: `reports`, `create_report`, `report`, `update_report`, `delete_report`
33
+ - Specialized: `report_attribution`, `report_breakdown`, `report_funnel`, `report_goals`, `report_journey`, `report_retention`, `report_revenue`, `report_utm`
34
+ - **Website stats endpoints**:
35
+ - `website_events_series` - Get events series with time bucketing
36
+ - `website_metrics_expanded` - Get detailed metrics with engagement data
37
+ - **Events endpoints**:
38
+ - `website_events_list` - Get paginated event details
39
+ - `website_event_data` - Get data for individual event
40
+ - `website_event_data_events` - Get event data names and counts
41
+ - `website_event_data_fields` - Get event data fields
42
+ - `website_event_data_properties` - Get event properties
43
+ - `website_event_data_values` - Get event data values
44
+ - `website_event_data_stats` - Get aggregated event stats
45
+
46
+ **Configuration:**
47
+ - `request_timeout=` setter - users can now customize timeout via `Umami.configure` block
48
+ - New `UMAMI_CLOUD_SEND_URL` constant for the Cloud send endpoint URL
49
+ - Optional `user_agent:` parameter to `send_event` method for custom User-Agent strings
50
+
51
+ **Documentation:**
52
+ - Configuration Options table in README with all available options and defaults
53
+ - Report Types reference table in README
54
+
55
+ **Test Suite:**
56
+ - Comprehensive Minitest test suite with 152 tests
57
+ - VCR cassettes for all read API endpoints (33 cassettes)
58
+ - CI workflow for Ruby 3.3, 3.4, 4.0
59
+ - ~72% line and branch coverage
60
+
61
+ ### Changed
62
+
63
+ - Updated `website_metrics` documentation with all current metric types
64
+ - Updated user role documentation to include `view-only` option
65
+ - Updated team role documentation with `team-manager`, `team-member`, `team-view-only` roles
66
+ - Updated `send_event` documentation with `tag` and `id` parameters
67
+ - Improved filter parameter documentation across all endpoints
68
+ - Added optional `id` parameter to `create_user` for UUID control
69
+ - Report endpoints use `startDate`/`endDate` (ISO 8601) instead of `startAt`/`endAt` (timestamps)
70
+ - Added `window` parameter to `report_funnel` for days between steps
71
+ - Added `timezone` parameter (required) to `report_retention`
72
+ - Updated `unit` parameter to include `minute` option
73
+ - Made `timezone` required for `website_sessions_weekly`
74
+ - Made `startAt`/`endAt` required for `website_session_activity`
75
+
76
+ ### Fixed
77
+
78
+ - **Critical**: Fixed `send_event` to use correct Umami Cloud URL (`https://cloud.umami.is/api/send`)
79
+ - **Critical**: Added mandatory `User-Agent` header to `send_event` requests (fixes [#2](https://github.com/rameerez/umami-ruby/issues/2))
80
+ - Fixed `send_event` to not send unnecessary `Authorization` header
81
+ - Fixed event data endpoint paths from `/api/event-data/...` to `/api/websites/:websiteId/event-data/...`
82
+ - Fixed `verify_token` to use POST method instead of GET
83
+ - Fixed `website_event_data_stats` return type documentation
84
+
85
+ ### Deprecated
86
+
87
+ - `event_data_events` - Use `website_event_data_events` instead
88
+ - `event_data_fields` - Use `website_event_data_fields` instead
89
+ - `event_data_stats` - Use `website_event_data_stats` instead
90
+ - `users` - Use `admin_users` instead
91
+
92
+ ## [0.1.3] - 2025-02-19
93
+
94
+ - Bug fixes and improvements
2
95
 
3
96
  ## [0.1.0] - 2024-07-22
4
97
 
data/CLAUDE.md ADDED
@@ -0,0 +1,45 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ `umami-ruby` is a Ruby wrapper for the Umami Analytics API. It works with both Umami Cloud and self-hosted instances.
8
+
9
+ ## Key Files
10
+
11
+ - `lib/umami/client.rb` - Main API client with all endpoint methods
12
+ - `lib/umami/configuration.rb` - Configuration management
13
+ - `lib/umami/errors.rb` - Custom error classes
14
+ - `test/` - Minitest test suite with VCR cassettes
15
+
16
+ ## Development Commands
17
+
18
+ ```bash
19
+ # Install dependencies
20
+ bundle install
21
+
22
+ # Run tests
23
+ bundle exec rake test
24
+
25
+ # Run tests with coverage
26
+ bundle exec rake test # SimpleCov runs automatically
27
+
28
+ # Generate documentation
29
+ yard doc
30
+ yard server
31
+ ```
32
+
33
+ ## Testing Guidelines
34
+
35
+ - All tests use VCR cassettes for HTTP interactions
36
+ - Cassettes are in `test/cassettes/`
37
+ - Use `VCR_RECORD_MODE=new_episodes bundle exec rake test` to record new cassettes
38
+ - Anonymize all sensitive data in cassettes before committing
39
+
40
+ ## Code Style
41
+
42
+ - Follow standard Ruby conventions
43
+ - Use YARD documentation for all public methods
44
+ - Keep methods focused and single-purpose
45
+ - Error handling uses custom error classes inheriting from `Umami::Error`
data/README.md CHANGED
@@ -1,8 +1,11 @@
1
- # Umami Ruby
1
+ # 📊 `umami-ruby` – Ruby client for the Umami Analytics API
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/umami-ruby.svg)](https://badge.fury.io/rb/umami-ruby)
3
+ [![Gem Version](https://badge.fury.io/rb/umami-ruby.svg)](https://badge.fury.io/rb/umami-ruby) [![Build Status](https://github.com/rameerez/umami-ruby/workflows/Tests/badge.svg)](https://github.com/rameerez/umami-ruby/actions)
4
4
 
5
- A Ruby wrapper for the Umami analytics API.
5
+ > [!TIP]
6
+ > **🚀 Ship your next Rails app 10x faster!** I've built **[RailsFast](https://railsfast.com/?ref=umami-ruby)**, a production-ready Rails boilerplate template that comes with everything you need to launch a software business in days, not weeks. Go [check it out](https://railsfast.com/?ref=umami-ruby)!
7
+
8
+ `umami-ruby` is a comprehensive Ruby wrapper for the [Umami Analytics API](https://umami.is/docs/api). Works with both Umami Cloud and self-hosted instances.
6
9
 
7
10
  ## Installation
8
11
 
@@ -33,79 +36,447 @@ yard doc
33
36
  yard server
34
37
  ```
35
38
 
36
- ## Usage
37
-
38
- ### Configuration
39
+ ## Configuration
39
40
 
40
41
  You can put this config in a handy location within your Rails project, like `config/initializers/umami.rb`
41
42
 
42
- #### For self-hosted Umami instances:
43
+ ### For Umami Cloud
43
44
 
44
45
  ```ruby
45
- # With username and password
46
46
  Umami.configure do |config|
47
- config.uri_base = "https://your-umami-instance.com"
48
- config.credentials = {
49
- username: "your_username",
50
- password: "your_password"
51
- }
47
+ config.access_token = "your_api_key"
48
+ # No need to specify uri_base - automatically uses https://api.umami.is
52
49
  end
50
+ ```
51
+
52
+ ### For Self-Hosted Umami Instances
53
53
 
54
- # Or with an access token
54
+ ```ruby
55
+ # With an access token
55
56
  Umami.configure do |config|
56
57
  config.uri_base = "https://your-umami-instance.com"
57
58
  config.access_token = "your_access_token"
58
59
  end
60
+
61
+ # Or with username and password
62
+ Umami.configure do |config|
63
+ config.uri_base = "https://your-umami-instance.com"
64
+ config.credentials = {
65
+ username: "your_username",
66
+ password: "your_password"
67
+ }
68
+ end
59
69
  ```
60
70
 
61
- #### For Umami Cloud:
71
+ ### Configuration Options
72
+
73
+ | Option | Description | Default |
74
+ |--------|-------------|---------|
75
+ | `access_token` | API key for Umami Cloud or access token for self-hosted | `nil` |
76
+ | `uri_base` | Base URL for self-hosted instances | Auto-detected |
77
+ | `credentials` | Hash with `:username` and `:password` for self-hosted | `nil` |
78
+ | `request_timeout` | Request timeout in seconds | `120` |
62
79
 
63
80
  ```ruby
81
+ # Example with custom timeout
64
82
  Umami.configure do |config|
65
83
  config.access_token = "your_api_key"
66
- # No need to specify uri_base for Umami Cloud
84
+ config.request_timeout = 60 # 60 seconds
67
85
  end
68
86
  ```
69
87
 
70
- ### Using the Client
88
+ ## Usage
71
89
 
72
- After configuration, you can use the client to interact with the Umami API:
90
+ ### Basic Usage
73
91
 
74
92
  ```ruby
75
93
  client = Umami::Client.new
76
94
 
95
+ # Get current user info
96
+ me = client.me
97
+
77
98
  # Get all websites
78
99
  websites = client.websites
79
100
 
80
101
  # Get a specific website
81
102
  website = client.website("website_id")
82
103
 
83
- # Get website stats
84
- stats = client.website_stats("website_id", { startAt: 1656679719687, endAt: 1656766119687 })
85
-
86
104
  # Verify token
87
105
  token_info = client.verify_token
88
106
  ```
89
107
 
108
+ ### Website Statistics
109
+
110
+ ```ruby
111
+ # Get website stats for a time range (timestamps in milliseconds)
112
+ stats = client.website_stats("website_id", {
113
+ startAt: 1656679719687,
114
+ endAt: 1656766119687
115
+ })
116
+
117
+ # Get active visitors (last 5 minutes)
118
+ active = client.website_active_visitors("website_id")
119
+
120
+ # Get pageviews with time grouping
121
+ pageviews = client.website_pageviews("website_id", {
122
+ startAt: 1656679719687,
123
+ endAt: 1656766119687,
124
+ unit: "day",
125
+ timezone: "America/Los_Angeles"
126
+ })
127
+
128
+ # Get metrics by type
129
+ metrics = client.website_metrics("website_id", {
130
+ startAt: 1656679719687,
131
+ endAt: 1656766119687,
132
+ type: "browser" # or: path, referrer, os, device, country, etc.
133
+ })
134
+
135
+ # Get realtime stats (last 30 minutes)
136
+ realtime = client.realtime("website_id")
137
+ ```
138
+
139
+ ### Sending Events
140
+
141
+ Send custom events to Umami for tracking. This is useful for server-side event tracking.
142
+
143
+ ```ruby
144
+ client = Umami::Client.new
145
+
146
+ # Send a basic event
147
+ client.send_event({
148
+ website: "your-website-id",
149
+ url: "/checkout",
150
+ hostname: "example.com",
151
+ name: "purchase_completed"
152
+ })
153
+
154
+ # Send an event with custom data
155
+ client.send_event({
156
+ website: "your-website-id",
157
+ url: "/checkout",
158
+ hostname: "example.com",
159
+ name: "purchase_completed",
160
+ language: "en-US",
161
+ screen: "1920x1080",
162
+ title: "Checkout - Example Store",
163
+ data: {
164
+ order_id: "12345",
165
+ total: 99.99,
166
+ currency: "USD"
167
+ }
168
+ })
169
+
170
+ # Send with a custom User-Agent (optional)
171
+ client.send_event(payload, user_agent: "MyApp/1.0")
172
+ ```
173
+
174
+ **Important notes about `send_event`:**
175
+ - No authentication token is sent (not required by the API)
176
+ - A `User-Agent` header is automatically included (required by Umami)
177
+ - For Umami Cloud, requests go to `https://cloud.umami.is/api/send` (different from other API calls)
178
+ - For self-hosted, requests go to your configured `uri_base`
179
+
180
+ ### Sessions
181
+
182
+ ```ruby
183
+ # Get sessions within a time range
184
+ sessions = client.website_sessions("website_id", {
185
+ startAt: 1656679719687,
186
+ endAt: 1656766119687,
187
+ page: 1,
188
+ pageSize: 20
189
+ })
190
+
191
+ # Get session statistics
192
+ stats = client.website_sessions_stats("website_id", {
193
+ startAt: 1656679719687,
194
+ endAt: 1656766119687
195
+ })
196
+
197
+ # Get individual session details
198
+ session = client.website_session("website_id", "session_id")
199
+
200
+ # Get session activity
201
+ activity = client.website_session_activity("website_id", "session_id", {
202
+ startAt: 1656679719687,
203
+ endAt: 1656766119687
204
+ })
205
+ ```
206
+
207
+ ### Events Data
208
+
209
+ ```ruby
210
+ # Get paginated event list with search
211
+ events = client.website_events_list("website_id", {
212
+ startAt: 1656679719687,
213
+ endAt: 1656766119687,
214
+ search: "click",
215
+ page: 1,
216
+ pageSize: 20
217
+ })
218
+
219
+ # Get event data aggregations
220
+ event_data = client.website_event_data_events("website_id", {
221
+ startAt: 1656679719687,
222
+ endAt: 1656766119687
223
+ })
224
+
225
+ # Get event statistics
226
+ stats = client.website_event_data_stats("website_id", {
227
+ startAt: 1656679719687,
228
+ endAt: 1656766119687
229
+ })
230
+ ```
231
+
232
+ ### Reports
233
+
234
+ ```ruby
235
+ # List reports (optionally filter by website and/or type)
236
+ all_reports = client.reports
237
+ website_reports = client.reports(websiteId: "website_id")
238
+ funnel_reports = client.reports(websiteId: "website_id", type: "funnel")
239
+
240
+ # Create a report
241
+ report = client.create_report({
242
+ websiteId: "website_id",
243
+ type: "funnel",
244
+ name: "Checkout Funnel",
245
+ parameters: {
246
+ startDate: "2024-01-01T00:00:00Z",
247
+ endDate: "2024-01-31T23:59:59Z"
248
+ }
249
+ })
250
+
251
+ # Run specialized reports
252
+ funnel = client.report_funnel({
253
+ websiteId: "website_id",
254
+ startDate: "2024-01-01T00:00:00Z",
255
+ endDate: "2024-01-31T23:59:59Z",
256
+ steps: [
257
+ { type: "path", value: "/" },
258
+ { type: "path", value: "/checkout" },
259
+ { type: "path", value: "/thank-you" }
260
+ ],
261
+ window: 7
262
+ })
263
+
264
+ retention = client.report_retention({
265
+ websiteId: "website_id",
266
+ startDate: "2024-01-01T00:00:00Z",
267
+ endDate: "2024-01-31T23:59:59Z",
268
+ timezone: "America/Los_Angeles"
269
+ })
270
+
271
+ # Available report types: attribution, breakdown, funnel, goal, journey, retention, revenue, utm
272
+ ```
273
+
274
+ **Report types:**
275
+ | Type | Description |
276
+ |------|-------------|
277
+ | `attribution` | Marketing channel attribution analysis |
278
+ | `breakdown` | Multi-dimensional data segmentation |
279
+ | `funnel` | Conversion funnel tracking |
280
+ | `goal` | Goal performance metrics |
281
+ | `journey` | User navigation path analysis |
282
+ | `retention` | Return visitor analysis |
283
+ | `revenue` | Revenue tracking and analysis |
284
+ | `utm` | UTM campaign parameter breakdown |
285
+
286
+ ### User & Team Management
287
+
288
+ ```ruby
289
+ # Get current user info
290
+ me = client.me
291
+ my_teams = client.my_teams(page: 1, pageSize: 10)
292
+ my_websites = client.my_websites(includeTeams: true)
293
+
294
+ # Team management
295
+ teams = client.teams
296
+ team = client.team("team_id")
297
+ client.create_team("My Team")
298
+ client.add_team_user("team_id", "user_id", "team-member")
299
+
300
+ # User management (admin only, self-hosted)
301
+ users = client.admin_users
302
+ client.create_user("username", "password", "user")
303
+ ```
304
+
305
+ ### Website Management
306
+
307
+ ```ruby
308
+ # Create a website
309
+ website = client.create_website({
310
+ name: "My Website",
311
+ domain: "example.com",
312
+ teamId: "optional-team-id"
313
+ })
314
+
315
+ # Update a website
316
+ client.update_website("website_id", {
317
+ name: "Updated Name",
318
+ shareId: "public-share-id"
319
+ })
320
+
321
+ # Reset website data
322
+ client.reset_website("website_id")
323
+
324
+ # Delete a website
325
+ client.delete_website("website_id")
326
+ ```
327
+
328
+ ## Available Methods
329
+
330
+ ### Authentication
331
+ - `verify_token` - Verify the current authentication token
332
+
333
+ ### Me (Current User)
334
+ - `me` - Get current user information
335
+ - `my_teams(params)` - Get user's teams (supports pagination)
336
+ - `my_websites(params)` - Get user's websites
337
+
338
+ ### Admin (Self-hosted only)
339
+ - `admin_users(params)` - List all users
340
+ - `admin_websites(params)` - List all websites
341
+ - `admin_teams(params)` - List all teams
342
+
343
+ ### Users
344
+ - `create_user(username, password, role, id:)` - Create a user
345
+ - `user(user_id)` - Get user by ID
346
+ - `update_user(user_id, params)` - Update user
347
+ - `delete_user(user_id)` - Delete user
348
+ - `user_websites(user_id, params)` - Get user's websites
349
+ - `user_teams(user_id, params)` - Get user's teams
350
+
351
+ ### Teams
352
+ - `teams(params)` - List all teams
353
+ - `create_team(name)` - Create a team
354
+ - `join_team(access_code)` - Join a team
355
+ - `team(team_id)` - Get team by ID
356
+ - `update_team(team_id, params)` - Update team
357
+ - `delete_team(team_id)` - Delete team
358
+ - `team_users(team_id, params)` - List team members
359
+ - `add_team_user(team_id, user_id, role)` - Add user to team
360
+ - `team_user(team_id, user_id)` - Get team member
361
+ - `update_team_user(team_id, user_id, role)` - Update member role
362
+ - `delete_team_user(team_id, user_id)` - Remove user from team
363
+ - `team_websites(team_id, params)` - List team websites
364
+
365
+ ### Websites
366
+ - `websites(params)` - List all websites
367
+ - `create_website(params)` - Create a website
368
+ - `website(website_id)` - Get website by ID
369
+ - `update_website(website_id, params)` - Update website
370
+ - `delete_website(website_id)` - Delete website
371
+ - `reset_website(website_id)` - Reset website data
372
+
373
+ ### Website Statistics
374
+ - `website_stats(website_id, params)` - Get statistics
375
+ - `website_active_visitors(website_id)` - Get active visitors
376
+ - `website_pageviews(website_id, params)` - Get pageviews
377
+ - `website_events(website_id, params)` - Get events (time-series)
378
+ - `website_events_series(website_id, params)` - Get events series
379
+ - `website_metrics(website_id, params)` - Get metrics
380
+ - `website_metrics_expanded(website_id, params)` - Get expanded metrics
381
+
382
+ ### Sessions
383
+ - `website_sessions(website_id, params)` - List sessions
384
+ - `website_sessions_stats(website_id, params)` - Get session stats
385
+ - `website_sessions_weekly(website_id, params)` - Get weekly breakdown
386
+ - `website_session(website_id, session_id)` - Get session details
387
+ - `website_session_activity(website_id, session_id, params)` - Get activity
388
+ - `website_session_properties(website_id, session_id)` - Get properties
389
+ - `website_session_data_properties(website_id, params)` - Get data properties
390
+ - `website_session_data_values(website_id, params)` - Get data values
391
+
392
+ ### Events
393
+ - `website_events_list(website_id, params)` - List events (paginated)
394
+ - `website_event_data(website_id, event_id)` - Get event data
395
+ - `website_event_data_events(website_id, params)` - Get event aggregations
396
+ - `website_event_data_fields(website_id, params)` - Get event fields
397
+ - `website_event_data_properties(website_id, params)` - Get event properties
398
+ - `website_event_data_values(website_id, params)` - Get event values
399
+ - `website_event_data_stats(website_id, params)` - Get event statistics
400
+
401
+ ### Realtime
402
+ - `realtime(website_id)` - Get realtime stats (last 30 minutes)
403
+
404
+ ### Links
405
+ - `links(params)` - List all links
406
+ - `link(link_id)` - Get link by ID
407
+ - `update_link(link_id, params)` - Update link
408
+ - `delete_link(link_id)` - Delete link
409
+
410
+ ### Pixels
411
+ - `pixels(params)` - List all pixels
412
+ - `pixel(pixel_id)` - Get pixel by ID
413
+ - `update_pixel(pixel_id, params)` - Update pixel
414
+ - `delete_pixel(pixel_id)` - Delete pixel
415
+
416
+ ### Reports
417
+ - `reports(params)` - List all reports (filter by `websiteId`, `type`)
418
+ - `create_report(params)` - Create a report
419
+ - `report(report_id)` - Get report by ID
420
+ - `update_report(report_id, params)` - Update report
421
+ - `delete_report(report_id)` - Delete report
422
+ - `report_attribution(params)` - Run attribution report
423
+ - `report_breakdown(params)` - Run breakdown report
424
+ - `report_funnel(params)` - Run funnel report
425
+ - `report_goals(params)` - Run goals report
426
+ - `report_journey(params)` - Run journey report
427
+ - `report_retention(params)` - Run retention report
428
+ - `report_revenue(params)` - Run revenue report
429
+ - `report_utm(params)` - Run UTM report
430
+
431
+ ### Sending Events
432
+ - `send_event(payload, user_agent:)` - Send tracking event
433
+
90
434
  ## Error Handling
91
435
 
92
436
  The gem defines several custom error classes:
93
437
 
94
- - `Umami::ConfigurationError`: Raised for configuration-related issues.
95
- - `Umami::AuthenticationError`: Raised for authentication-related issues.
96
- - `Umami::APIError`: Raised for API request failures.
438
+ ```ruby
439
+ begin
440
+ client.website("non-existent-id")
441
+ rescue Umami::NotFoundError => e
442
+ puts "Website not found: #{e.message}"
443
+ rescue Umami::AuthenticationError => e
444
+ puts "Authentication failed: #{e.message}" # rejected login, expired/revoked token (401), forbidden (403)
445
+ rescue Umami::ClientError => e
446
+ puts "Client error: #{e.message}"
447
+ rescue Umami::ServerError => e
448
+ puts "Server error: #{e.message}"
449
+ rescue Umami::ConfigurationError => e
450
+ puts "Configuration error: #{e.message}"
451
+ rescue Umami::APIError => e
452
+ puts "API error: #{e.message}"
453
+ end
454
+ ```
455
+
456
+ **Error class hierarchy:**
457
+ - `Umami::Error` - Base error class
458
+ - `Umami::ConfigurationError` - Configuration issues
459
+ - `Umami::APIError` - API request failures
460
+ - `Umami::AuthenticationError` - Authentication failures: rejected logins, and HTTP 401/403 API responses (since 0.3.0 — an expired token on a long-lived client surfaces here so you can re-authenticate)
461
+ - `Umami::NotFoundError` - Resource not found (404)
462
+ - `Umami::ClientError` - Other client errors (4xx)
463
+ - `Umami::ServerError` - Server errors (5xx)
97
464
 
98
465
  ## Logging
99
466
 
100
467
  The gem uses a logger that can be configured:
101
468
 
102
469
  ```ruby
470
+ # Set log level
103
471
  Umami.logger.level = Logger::DEBUG
472
+
473
+ # Use a custom logger
474
+ Umami.logger = Rails.logger
104
475
  ```
105
476
 
106
477
  ## Development
107
478
 
108
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
479
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
109
480
 
110
481
  To install this gem onto your local machine, run `bundle exec rake install`.
111
482