kit-rb 0.2.0 → 0.3.1

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.githooks/pre-commit +21 -0
  3. data/.githooks/pre-push +6 -0
  4. data/CHANGELOG.md +84 -0
  5. data/README.md +66 -16
  6. data/docs/DESIGN.md +38 -14
  7. data/docs/TASKS.md +47 -0
  8. data/lib/kit/auth/api_key.rb +7 -0
  9. data/lib/kit/auth/credential.rb +22 -0
  10. data/lib/kit/auth/oauth.rb +7 -8
  11. data/lib/kit/configuration.rb +3 -2
  12. data/lib/kit/connection.rb +53 -20
  13. data/lib/kit/errors.rb +52 -15
  14. data/lib/kit/oauth/token.rb +9 -0
  15. data/lib/kit/objects/account.rb +44 -3
  16. data/lib/kit/objects/bulk_result.rb +39 -0
  17. data/lib/kit/objects/custom_field.rb +4 -2
  18. data/lib/kit/objects/post.rb +4 -2
  19. data/lib/kit/objects/sequence_email.rb +5 -2
  20. data/lib/kit/objects/subscriber.rb +16 -9
  21. data/lib/kit/objects/tag.rb +5 -3
  22. data/lib/kit/objects/webhook_endpoint.rb +7 -2
  23. data/lib/kit/pagination.rb +47 -3
  24. data/lib/kit/resources/account.rb +4 -4
  25. data/lib/kit/resources/base.rb +46 -4
  26. data/lib/kit/resources/broadcasts.rb +32 -13
  27. data/lib/kit/resources/bulk.rb +18 -20
  28. data/lib/kit/resources/custom_fields.rb +2 -2
  29. data/lib/kit/resources/forms.rb +3 -3
  30. data/lib/kit/resources/posts.rb +1 -1
  31. data/lib/kit/resources/purchases.rb +10 -6
  32. data/lib/kit/resources/sequences.rb +49 -25
  33. data/lib/kit/resources/snippets.rb +13 -8
  34. data/lib/kit/resources/subscribers.rb +23 -9
  35. data/lib/kit/resources/tags.rb +6 -6
  36. data/lib/kit/resources/webhook_endpoints.rb +12 -4
  37. data/lib/kit/resources/webhooks.rb +1 -1
  38. data/lib/kit/version.rb +1 -1
  39. data/lib/kit/webhooks/delivery.rb +61 -0
  40. data/lib/kit/webhooks/events.rb +113 -0
  41. data/lib/kit/webhooks/signature.rb +100 -0
  42. data/lib/kit-rb.rb +5 -0
  43. data/sig/kit-rb.rbs +172 -30
  44. metadata +19 -3
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kit
4
+ module Webhooks
5
+ # Event types a webhook endpoint (/v4/webhook_endpoints) can subscribe to,
6
+ # as listed at developers.kit.com/webhooks/event-types (verified
7
+ # 2026-09-06). Use these instead of hand-typed strings:
8
+ #
9
+ # client.webhook_endpoints.create(url: url, events: [Events::SUBSCRIBER_CREATED, Events::TAG_CREATED])
10
+ #
11
+ # DATA_KEYS names the keys each event's `data` carries. PLANNED events are
12
+ # documented but not yet delivered.
13
+ module Events
14
+ SUBSCRIBER_CREATED = "subscriber.created"
15
+ SUBSCRIBER_ACTIVATED = "subscriber.activated"
16
+ SUBSCRIBER_UNSUBSCRIBED = "subscriber.unsubscribed"
17
+ SUBSCRIBER_BOUNCED = "subscriber.bounced"
18
+ SUBSCRIBER_COMPLAINED = "subscriber.complained"
19
+ SUBSCRIBER_SUBSCRIBED_TO_FORM = "subscriber.subscribed_to_form"
20
+ SUBSCRIBER_ADDED_TO_SEQUENCE = "subscriber.added_to_sequence"
21
+ SUBSCRIBER_SEQUENCE_COMPLETED = "subscriber.sequence_completed"
22
+ SUBSCRIBER_TAG_ADDED = "subscriber.tag_added"
23
+ SUBSCRIBER_TAG_REMOVED = "subscriber.tag_removed"
24
+ SUBSCRIBER_CUSTOM_FIELD_VALUE_UPDATED = "subscriber.custom_field_value_updated"
25
+ SUBSCRIBER_PRODUCT_PURCHASED = "subscriber.product_purchased" # planned
26
+ SUBSCRIBER_LINK_CLICKED = "subscriber.link_clicked" # planned
27
+ SUBSCRIBER_EMAIL_OPENED = "subscriber.email_opened" # planned
28
+ TAG_CREATED = "tag.created"
29
+ TAG_DELETED = "tag.deleted"
30
+ CUSTOM_FIELD_CREATED = "custom_field.created"
31
+ CUSTOM_FIELD_DELETED = "custom_field.deleted"
32
+ SEQUENCE_CREATED = "sequence.created"
33
+ SEQUENCE_DELETED = "sequence.deleted"
34
+ SEQUENCE_PUBLISHED = "sequence.published"
35
+ SEQUENCE_DISABLED = "sequence.disabled"
36
+ BROADCAST_CREATED = "broadcast.created"
37
+ BROADCAST_SENT = "broadcast.sent"
38
+ BROADCAST_DELETED = "broadcast.deleted"
39
+ POST_PUBLISHED = "post.published"
40
+ LANDING_PAGE_CREATED = "landing_page.created" # planned
41
+ LANDING_PAGE_DELETED = "landing_page.deleted" # planned
42
+
43
+ DATA_KEYS = {
44
+ SUBSCRIBER_CREATED => %w[subscriber], SUBSCRIBER_ACTIVATED => %w[subscriber],
45
+ SUBSCRIBER_UNSUBSCRIBED => %w[subscriber], SUBSCRIBER_BOUNCED => %w[subscriber],
46
+ SUBSCRIBER_COMPLAINED => %w[subscriber], SUBSCRIBER_SUBSCRIBED_TO_FORM => %w[subscriber form],
47
+ SUBSCRIBER_ADDED_TO_SEQUENCE => %w[subscriber sequence],
48
+ SUBSCRIBER_SEQUENCE_COMPLETED => %w[subscriber sequence],
49
+ SUBSCRIBER_TAG_ADDED => %w[subscriber tag], SUBSCRIBER_TAG_REMOVED => %w[subscriber tag],
50
+ SUBSCRIBER_CUSTOM_FIELD_VALUE_UPDATED => %w[subscriber custom_field],
51
+ SUBSCRIBER_PRODUCT_PURCHASED => %w[subscriber], SUBSCRIBER_LINK_CLICKED => %w[subscriber],
52
+ SUBSCRIBER_EMAIL_OPENED => %w[subscriber],
53
+ TAG_CREATED => %w[tag], TAG_DELETED => %w[tag],
54
+ CUSTOM_FIELD_CREATED => %w[custom_field], CUSTOM_FIELD_DELETED => %w[custom_field],
55
+ SEQUENCE_CREATED => %w[sequence], SEQUENCE_DELETED => %w[sequence],
56
+ SEQUENCE_PUBLISHED => %w[sequence], SEQUENCE_DISABLED => %w[sequence],
57
+ BROADCAST_CREATED => %w[broadcast], BROADCAST_SENT => %w[broadcast], BROADCAST_DELETED => %w[broadcast],
58
+ POST_PUBLISHED => %w[post],
59
+ LANDING_PAGE_CREATED => [], LANDING_PAGE_DELETED => []
60
+ }.freeze
61
+
62
+ PLANNED = [
63
+ SUBSCRIBER_PRODUCT_PURCHASED, SUBSCRIBER_LINK_CLICKED, SUBSCRIBER_EMAIL_OPENED,
64
+ LANDING_PAGE_CREATED, LANDING_PAGE_DELETED
65
+ ].freeze
66
+
67
+ ALL = DATA_KEYS.keys.freeze
68
+ AVAILABLE = (ALL - PLANNED).freeze
69
+ end
70
+
71
+ # Event names for the previous-generation /v4/webhooks (one event per
72
+ # webhook, unsigned). Kept for existing integrations; new ones should use
73
+ # webhook endpoints. REQUIRED_PARAM names the extra key the `event` hash
74
+ # must carry for the events that are scoped to a resource:
75
+ #
76
+ # client.webhooks.create(target_url: url,
77
+ # event: { name: LegacyEvents::TAG_ADD, tag_id: 12 })
78
+ module LegacyEvents
79
+ SUBSCRIBER_ACTIVATE = "subscriber.subscriber_activate"
80
+ SUBSCRIBER_UNSUBSCRIBE = "subscriber.subscriber_unsubscribe"
81
+ SUBSCRIBER_BOUNCE = "subscriber.subscriber_bounce"
82
+ SUBSCRIBER_COMPLAIN = "subscriber.subscriber_complain"
83
+ FORM_SUBSCRIBE = "subscriber.form_subscribe"
84
+ COURSE_SUBSCRIBE = "subscriber.course_subscribe"
85
+ COURSE_COMPLETE = "subscriber.course_complete"
86
+ LINK_CLICK = "subscriber.link_click"
87
+ PRODUCT_PURCHASE = "subscriber.product_purchase"
88
+ TAG_ADD = "subscriber.tag_add"
89
+ TAG_REMOVE = "subscriber.tag_remove"
90
+ PURCHASE_CREATE = "purchase.purchase_create"
91
+ FIELD_CREATED = "custom_field.field_created"
92
+ FIELD_DELETED = "custom_field.field_deleted"
93
+ FIELD_VALUE_UPDATED = "custom_field.field_value_updated"
94
+
95
+ REQUIRED_PARAM = {
96
+ FORM_SUBSCRIBE => :form_id,
97
+ COURSE_SUBSCRIBE => :sequence_id,
98
+ COURSE_COMPLETE => :sequence_id,
99
+ LINK_CLICK => :initiator_value,
100
+ PRODUCT_PURCHASE => :product_id,
101
+ TAG_ADD => :tag_id,
102
+ TAG_REMOVE => :tag_id,
103
+ FIELD_VALUE_UPDATED => :custom_field_id
104
+ }.freeze
105
+
106
+ ALL = [
107
+ SUBSCRIBER_ACTIVATE, SUBSCRIBER_UNSUBSCRIBE, SUBSCRIBER_BOUNCE, SUBSCRIBER_COMPLAIN,
108
+ FORM_SUBSCRIBE, COURSE_SUBSCRIBE, COURSE_COMPLETE, LINK_CLICK, PRODUCT_PURCHASE,
109
+ TAG_ADD, TAG_REMOVE, PURCHASE_CREATE, FIELD_CREATED, FIELD_DELETED, FIELD_VALUE_UPDATED
110
+ ].freeze
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module Kit
6
+ module Webhooks
7
+ # Raised when a delivery's X-Kit-Signature is missing, malformed, stale, or
8
+ # does not match the body under any supplied secret.
9
+ class SignatureError < Error; end
10
+
11
+ # Verifies the `X-Kit-Signature` header Kit sends with every webhook-endpoint
12
+ # delivery (verified against developers.kit.com/webhooks/verifying-signatures,
13
+ # 2026-09-06):
14
+ #
15
+ # X-Kit-Signature: t=1753797130,v1=<hex>[,v1=<hex>]
16
+ #
17
+ # Each v1 is hex(HMAC-SHA256(secret, "#{t}.#{raw_body}")). During a secret
18
+ # rotation the header carries two v1 entries (new and previous secret), so
19
+ # a delivery is valid when any v1 matches any of the secrets you hold.
20
+ #
21
+ # Kit::Webhooks::Signature.verify!(request.raw_post, request.headers["X-Kit-Signature"],
22
+ # secret: ENV["KIT_WEBHOOK_SECRET"])
23
+ module Signature
24
+ HEADER = "X-Kit-Signature"
25
+ SCHEME = "v1"
26
+ DEFAULT_TOLERANCE = 300 # seconds; Kit's recommended replay window
27
+
28
+ Parsed = Data.define(:timestamp, :signatures)
29
+
30
+ # hex(HMAC-SHA256(secret, "t.payload")) — what Kit puts in v1.
31
+ def self.compute(secret, timestamp, payload)
32
+ OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{payload}")
33
+ end
34
+
35
+ # Splits the header into its timestamp and v1 signatures. Pairs are kept
36
+ # as a list, not a Hash: a rotation sends two v1 entries.
37
+ def self.parse(header)
38
+ raise SignatureError, "missing #{HEADER} header" if header.nil? || header.strip.empty?
39
+
40
+ pairs = header.split(",").map { |pair| pair.strip.split("=", 2) }
41
+ signatures = pairs.filter_map { |key, value| value if key == SCHEME }
42
+ raise SignatureError, "malformed #{HEADER} header: #{header.inspect}" if signatures.empty?
43
+
44
+ Parsed.new(timestamp: parse_timestamp(pairs, header), signatures: signatures)
45
+ end
46
+
47
+ def self.parse_timestamp(pairs, header)
48
+ value = pairs.find { |key, _| key == "t" }&.last
49
+ raise SignatureError, "malformed #{HEADER} header: #{header.inspect}" if value.nil?
50
+ raise SignatureError, "malformed timestamp in #{HEADER}: #{value.inspect}" unless value.match?(/\A\d+\z/)
51
+
52
+ value.to_i
53
+ end
54
+ private_class_method :parse_timestamp
55
+
56
+ # True when the payload was signed by one of `secret` (a String, or an
57
+ # Array of Strings while you hold both sides of a rotation) within
58
+ # `tolerance` seconds of `now`. Constant-time comparison.
59
+ def self.verify?(payload, header, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now.to_i)
60
+ verify!(payload, header, secret: secret, tolerance: tolerance, now: now)
61
+ true
62
+ rescue SignatureError
63
+ false
64
+ end
65
+
66
+ # As #verify?, but raises SignatureError describing why the delivery was
67
+ # rejected. `payload` must be the raw request body, byte for byte.
68
+ def self.verify!(payload, header, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now.to_i)
69
+ secrets = secrets_from(secret)
70
+ parsed = parse(header)
71
+ check_freshness(parsed.timestamp, tolerance, now)
72
+ return true if secrets.any? { |value| matches?(value, parsed, payload) }
73
+
74
+ raise SignatureError, "no #{SCHEME} signature matched the payload"
75
+ end
76
+
77
+ def self.secrets_from(secret)
78
+ secrets = Array(secret).reject { |value| value.nil? || value.empty? }
79
+ raise ArgumentError, "at least one webhook secret is required" if secrets.empty?
80
+
81
+ secrets
82
+ end
83
+ private_class_method :secrets_from
84
+
85
+ def self.check_freshness(timestamp, tolerance, now)
86
+ return unless tolerance
87
+
88
+ age = (now - timestamp).abs
89
+ raise SignatureError, "timestamp outside tolerance (#{age}s > #{tolerance}s)" if age > tolerance
90
+ end
91
+ private_class_method :check_freshness
92
+
93
+ def self.matches?(secret, parsed, payload)
94
+ expected = compute(secret, parsed.timestamp, payload)
95
+ parsed.signatures.any? { |given| OpenSSL.secure_compare(expected, given) }
96
+ end
97
+ private_class_method :matches?
98
+ end
99
+ end
100
+ end
data/lib/kit-rb.rb CHANGED
@@ -15,6 +15,7 @@ end
15
15
  require "kit/version"
16
16
  require "kit/errors"
17
17
  require "kit/configuration"
18
+ require "kit/auth/credential"
18
19
  require "kit/auth/api_key"
19
20
  require "kit/auth/oauth"
20
21
  require "kit/connection"
@@ -43,6 +44,10 @@ require "kit/objects/segment"
43
44
  require "kit/objects/post"
44
45
  require "kit/objects/snippet"
45
46
  require "kit/objects/purchase"
47
+ require "kit/objects/bulk_result"
48
+ require "kit/webhooks/signature"
49
+ require "kit/webhooks/delivery"
50
+ require "kit/webhooks/events"
46
51
  require "kit/resources/base"
47
52
  require "kit/resources/account"
48
53
  require "kit/resources/subscribers"
data/sig/kit-rb.rbs CHANGED
@@ -1,22 +1,35 @@
1
- # RBS type signatures for the public surface of kit-rb (P0: account slice).
2
- # Expanded per-resource as coverage grows in P1/P2.
1
+ # RBS type signatures for the public surface of kit-rb, checked by Steep
2
+ # (`bundle exec rake steep`). Keep in step with lib/.
3
3
 
4
4
  module Kit
5
5
  VERSION: String
6
6
  DEFAULT_BASE_URL: String
7
7
 
8
8
  class Error < StandardError
9
+ STATUS_CLASSES: Hash[Integer, singleton(APIError)]
9
10
  def self.class_for: (Integer status) -> singleton(APIError)
10
11
  end
11
12
  class ConfigurationError < Error
12
13
  end
14
+ class TransportError < Error
15
+ end
16
+ class TimeoutError < TransportError
17
+ end
18
+ class ConnectionError < TransportError
19
+ end
20
+ class UnexpectedResponseError < Error
21
+ attr_reader body: untyped
22
+ def initialize: (String message, ?body: untyped) -> void
23
+ end
13
24
 
14
25
  class APIError < Error
15
26
  attr_reader status: Integer
16
27
  attr_reader body: untyped
17
28
  attr_reader errors: Array[untyped]
18
29
  attr_reader response: untyped
19
- def initialize: (?String? message, status: Integer, ?body: untyped, ?response: untyped) -> void
30
+ attr_reader method: Symbol?
31
+ attr_reader path: String?
32
+ def initialize: (?String? message, status: Integer, ?body: untyped, ?response: untyped, ?method: Symbol?, ?path: String?) -> void
20
33
  end
21
34
  class AuthenticationError < APIError
22
35
  end
@@ -24,11 +37,15 @@ module Kit
24
37
  end
25
38
  class NotFoundError < APIError
26
39
  end
40
+ class ConflictError < APIError
41
+ end
42
+ class PayloadTooLargeError < APIError
43
+ end
27
44
  class UnprocessableEntityError < APIError
28
45
  end
29
46
  class RateLimitError < APIError
30
47
  attr_reader retry_after: Integer?
31
- def initialize: (?String? message, status: Integer, ?body: untyped, ?response: untyped, ?retry_after: Integer?) -> void
48
+ def initialize: (?String? message, status: Integer, ?body: untyped, ?response: untyped, ?method: Symbol?, ?path: String?, ?retry_after: Integer?) -> void
32
49
  end
33
50
  class ServerError < APIError
34
51
  end
@@ -57,6 +74,8 @@ module Kit
57
74
  attr_reader scope: String?
58
75
  attr_reader created_at: Integer?
59
76
  def self.from: (Hash[String, untyped] hash) -> Token
77
+ def inspect: () -> String
78
+ def to_s: () -> String
60
79
  def expires_at: () -> Integer?
61
80
  def expired?: (?now: Integer, ?leeway: Integer) -> bool
62
81
  end
@@ -72,18 +91,70 @@ module Kit
72
91
  end
73
92
  end
74
93
 
94
+ module Webhooks
95
+ class SignatureError < Error
96
+ end
97
+ module Signature
98
+ HEADER: String
99
+ SCHEME: String
100
+ DEFAULT_TOLERANCE: Integer
101
+ class Parsed
102
+ attr_reader timestamp: Integer
103
+ attr_reader signatures: Array[String]
104
+ end
105
+ def self.compute: (String secret, Integer | String timestamp, String payload) -> String
106
+ def self.parse: (String? header) -> Parsed
107
+ def self.verify?: (String payload, String? header, secret: String | Array[String], ?tolerance: Integer?, ?now: Integer) -> bool
108
+ def self.verify!: (String payload, String? header, secret: String | Array[String], ?tolerance: Integer?, ?now: Integer) -> bool
109
+ end
110
+ class Event
111
+ attr_reader id: String?
112
+ attr_reader type: String?
113
+ attr_reader created: String?
114
+ attr_reader data: Hash[String, untyped]
115
+ def self.from: (Hash[String, untyped] hash) -> Event
116
+ end
117
+ module Events
118
+ DATA_KEYS: Hash[String, Array[String]]
119
+ PLANNED: Array[String]
120
+ ALL: Array[String]
121
+ AVAILABLE: Array[String]
122
+ end
123
+ module LegacyEvents
124
+ REQUIRED_PARAM: Hash[String, Symbol]
125
+ ALL: Array[String]
126
+ end
127
+ DELIVERY_HEADER: String
128
+ class Delivery
129
+ attr_reader delivery_id: Integer?
130
+ attr_reader events: Array[Event]
131
+ def self.from: (Hash[String, untyped] hash) -> Delivery
132
+ def self.parse: (String payload) -> Delivery
133
+ def self.from_request: (String payload, String? signature_header, secret: String | Array[String], ?tolerance: Integer?, ?now: Integer) -> Delivery
134
+ def type: () -> String?
135
+ end
136
+ end
137
+
75
138
  module Auth
139
+ module Credential
140
+ MASK: String
141
+ VISIBLE: Integer
142
+ def self.mask: (untyped secret) -> String
143
+ end
144
+
76
145
  class ApiKey
77
146
  HEADER: String
78
147
  def initialize: (String key) -> void
79
148
  def headers: () -> Hash[String, String]
149
+ def inspect: () -> String
150
+ def to_s: () -> String
80
151
  end
81
152
 
82
153
  class OAuth
83
- AUTHORIZE_URL: String
84
- TOKEN_URL: String
85
154
  def initialize: (String access_token) -> void
86
155
  def headers: () -> Hash[String, String]
156
+ def inspect: () -> String
157
+ def to_s: () -> String
87
158
  end
88
159
  end
89
160
 
@@ -92,17 +163,21 @@ module Kit
92
163
  attr_reader base_url: String
93
164
  attr_reader open_timeout: Integer
94
165
  attr_reader read_timeout: Integer
166
+ attr_reader write_timeout: Integer
95
167
  attr_reader max_retries: Integer
96
168
  attr_reader retry_backoff: Float
97
169
  attr_reader max_backoff: Numeric
98
- def initialize: (?api_key: String?, ?access_token: String?, ?base_url: String, ?open_timeout: Integer, ?read_timeout: Integer, ?max_retries: Integer, ?retry_backoff: Float, ?max_backoff: Numeric) -> void
170
+ def initialize: (?api_key: String?, ?access_token: String?, ?base_url: String, ?open_timeout: Integer, ?read_timeout: Integer, ?write_timeout: Integer, ?max_retries: Integer, ?retry_backoff: Float, ?max_backoff: Numeric) -> void
99
171
  end
100
172
 
101
173
  class Connection
102
174
  JSON_TYPE: String
103
- RETRYABLE: Array[singleton(APIError)]
175
+ RETRYABLE: Array[singleton(Error)]
176
+ IDEMPOTENT: Array[Symbol]
104
177
  def initialize: (Configuration config) -> void
105
178
  def request: (Symbol method, String path, ?params: Hash[untyped, untyped], ?body: Hash[untyped, untyped]?) -> untyped
179
+ def request_with_status: (Symbol method, String path, ?params: Hash[untyped, untyped], ?body: Hash[untyped, untyped]?) -> [Integer, untyped]
180
+ def inspect: () -> String
106
181
  end
107
182
 
108
183
  class Pagination
@@ -111,11 +186,20 @@ module Kit
111
186
  attr_reader start_cursor: String?
112
187
  attr_reader end_cursor: String?
113
188
  attr_reader per_page: Integer?
189
+ attr_reader total_count: Integer?
114
190
  def self.from: (Hash[String, untyped] hash) -> Pagination
115
191
  end
116
192
 
117
193
  class Collection[T]
118
194
  include Enumerable[T]
195
+ FIRST_PAGE_ONLY: Array[Symbol]
196
+ def self.next_page_params: (Hash[Symbol, untyped] params, String? after) -> Hash[Symbol, untyped]
197
+ def total_count: () -> Integer?
198
+ def size: () -> Integer
199
+ def length: () -> Integer
200
+ def empty?: () -> bool
201
+ def []: (Integer index) -> T?
202
+ def inspect: () -> String
119
203
  attr_reader data: Array[T]
120
204
  attr_reader pagination: Pagination
121
205
  def initialize: (data: Array[T], pagination: Pagination) { (String end_cursor) -> Collection[T] } -> void
@@ -131,12 +215,40 @@ module Kit
131
215
  attr_reader id: Integer?
132
216
  def self.from: (Hash[String, untyped] hash) -> User
133
217
  end
218
+ class Timezone
219
+ attr_reader name: String?
220
+ attr_reader friendly_name: String?
221
+ attr_reader utc_offset: String?
222
+ def self.from: (Hash[String, untyped] hash) -> Timezone
223
+ end
224
+ class Plan
225
+ attr_reader plan_type: String?
226
+ attr_reader interval: String?
227
+ attr_reader subscriber_limit: Integer?
228
+ attr_reader on_trial: bool?
229
+ attr_reader trial_lapse_date: String?
230
+ attr_reader renews_at: String?
231
+ attr_reader cancels_at: String?
232
+ def self.from: (Hash[String, untyped] hash) -> Plan
233
+ end
234
+ class SendingAddress
235
+ attr_reader email_address: String?
236
+ attr_reader from_name: String?
237
+ attr_reader status: String?
238
+ attr_reader is_default: bool?
239
+ attr_reader is_verified: bool?
240
+ attr_reader is_dmarc_configured: bool?
241
+ def self.from: (Hash[String, untyped] hash) -> SendingAddress
242
+ end
134
243
  class Account
135
244
  attr_reader id: Integer?
136
245
  attr_reader name: String?
137
246
  attr_reader plan_type: String?
138
247
  attr_reader primary_email_address: String?
139
248
  attr_reader created_at: String?
249
+ attr_reader timezone: Timezone?
250
+ attr_reader plan: Plan?
251
+ attr_reader sending_addresses: Array[SendingAddress]
140
252
  def self.from: (Hash[String, untyped] hash) -> Account
141
253
  end
142
254
  class AccountInfo
@@ -157,6 +269,7 @@ module Kit
157
269
  attr_reader name: String?
158
270
  attr_reader created_at: String?
159
271
  attr_reader subscriber_count: Integer?
272
+ attr_reader tagged_at: String?
160
273
  def self.from: (Hash[String, untyped] hash) -> Tag
161
274
  end
162
275
  class CustomField
@@ -164,6 +277,7 @@ module Kit
164
277
  attr_reader name: String?
165
278
  attr_reader key: String?
166
279
  attr_reader label: String?
280
+ attr_reader created_at: String?
167
281
  def self.from: (Hash[String, untyped] hash) -> CustomField
168
282
  end
169
283
  class Form
@@ -236,6 +350,7 @@ module Kit
236
350
  attr_reader created_by_app: untyped
237
351
  attr_reader created_at: String?
238
352
  attr_reader previous_secret_expires_at: String?
353
+ attr_reader secret: String?
239
354
  def self.from: (Hash[String, untyped] hash) -> WebhookEndpoint
240
355
  end
241
356
  class EmailTemplate
@@ -266,6 +381,7 @@ module Kit
266
381
  attr_reader thumbnail_url: String?
267
382
  attr_reader is_paid: bool?
268
383
  attr_reader public_url: String?
384
+ attr_reader content: String?
269
385
  def self.from: (Hash[String, untyped] hash) -> Post
270
386
  end
271
387
  class Snippet
@@ -280,6 +396,19 @@ module Kit
280
396
  attr_reader document: untyped
281
397
  def self.from: (Hash[String, untyped] hash) -> Snippet
282
398
  end
399
+ class BulkFailure
400
+ attr_reader item: untyped
401
+ attr_reader errors: Array[String]
402
+ def self.from: (Hash[String, untyped] hash) -> BulkFailure
403
+ end
404
+ class BulkResult
405
+ attr_reader items: Array[Hash[String, untyped]]
406
+ attr_reader failures: Array[BulkFailure]
407
+ attr_reader async: bool
408
+ def self.from: (Integer status, untyped body, String? key) -> BulkResult
409
+ def async?: () -> bool
410
+ def success?: () -> bool
411
+ end
283
412
  class Purchase
284
413
  attr_reader id: Integer?
285
414
  attr_reader transaction_id: String?
@@ -309,6 +438,7 @@ module Kit
309
438
  attr_reader delay_unit: String?
310
439
  attr_reader send_days: Array[untyped]?
311
440
  attr_reader stats: Hash[String, untyped]?
441
+ attr_reader content: String?
312
442
  def self.from: (Hash[String, untyped] hash) -> SequenceEmail
313
443
  end
314
444
  class BroadcastStats
@@ -385,12 +515,22 @@ module Kit
385
515
  attr_reader canceled_at: String?
386
516
  attr_reader location: Hash[String, untyped]?
387
517
  attr_reader fields: Hash[String, untyped]?
518
+ attr_reader added_at: String?
519
+ attr_reader tagged_at: String?
520
+ attr_reader referrer: String?
521
+ attr_reader referrer_utm_parameters: Hash[String, untyped]?
522
+ attr_reader attribution: Hash[String, untyped]?
523
+ attr_reader tags: Array[Hash[String, untyped]]?
524
+ attr_reader tag_names: Array[String]?
525
+ attr_reader tag_ids: Array[Integer]?
526
+ attr_reader stats: Hash[String, untyped]?
388
527
  def self.from: (Hash[String, untyped] hash) -> Subscriber
389
528
  end
390
529
  end
391
530
 
392
531
  module Resources
393
532
  class Base
533
+ OMIT: Object
394
534
  def initialize: (Connection connection) -> void
395
535
  end
396
536
  class Account < Base
@@ -406,11 +546,12 @@ module Kit
406
546
  def get: (Integer id) -> Objects::Subscriber
407
547
  def create: (email_address: String, ?first_name: String?, ?state: String?, ?fields: Hash[untyped, untyped]?) -> Objects::Subscriber
408
548
  def update: (Integer id, ?first_name: String?, ?email_address: String?, ?fields: Hash[untyped, untyped]?) -> Objects::Subscriber
409
- def unsubscribe: (Integer id) -> Objects::Subscriber
549
+ def unsubscribe: (Integer id) -> Objects::Subscriber?
410
550
  def filter: (**untyped params) -> Collection[Objects::Subscriber]
411
551
  def tags: (Integer id, **untyped params) -> Collection[Objects::Tag]
412
- def stats: (Integer id) -> Objects::SubscriberStats
552
+ def stats: (Integer id, ?email_sent_after: String?, ?email_sent_before: String?) -> Objects::SubscriberStats
413
553
  def set_location: (Integer id, location: Hash[untyped, untyped]) -> Objects::Subscriber
554
+ def update_location: (Integer id, location: Hash[untyped, untyped]) -> Objects::Subscriber
414
555
  def remove_location: (Integer id) -> void
415
556
  end
416
557
  class Tags < Base
@@ -437,24 +578,24 @@ module Kit
437
578
  end
438
579
  class Sequences < Base
439
580
  def list: (**untyped params) -> Collection[Objects::Sequence]
440
- def get: (Integer id) -> Objects::Sequence
441
- def create: (name: String, **untyped attributes) -> Objects::Sequence
442
- def update: (Integer id, **untyped attributes) -> Objects::Sequence
581
+ def get: (Integer id, ?include: String?) -> Objects::Sequence
582
+ def create: (name: String, ?email_address: String?, ?email_template_id: Integer?, ?send_days: Array[String]?, ?send_hour: Integer?, ?time_zone: String?, ?active: bool?, ?repeat: bool?, ?hold: bool?, ?exclude_subscriber_sources: Array[Hash[Symbol | String, untyped]]?) -> Objects::Sequence
583
+ def update: (Integer id, ?name: String?, ?email_address: String?, ?email_template_id: Integer?, ?send_days: Array[String]?, ?send_hour: Integer?, ?time_zone: String?, ?active: bool?, ?repeat: bool?, ?hold: bool?, ?exclude_subscriber_sources: Array[Hash[Symbol | String, untyped]]?) -> Objects::Sequence
443
584
  def delete: (Integer id) -> void
444
585
  def subscribers: (Integer sequence_id, **untyped params) -> Collection[Objects::Subscriber]
445
586
  def add_subscriber: (Integer sequence_id, Integer subscriber_id) -> Objects::Subscriber
446
587
  def add_subscriber_by_email: (Integer sequence_id, email_address: String) -> Objects::Subscriber
447
588
  def emails: (Integer sequence_id, **untyped params) -> Collection[Objects::SequenceEmail]
448
- def email: (Integer sequence_id, Integer id) -> Objects::SequenceEmail
449
- def create_email: (Integer sequence_id, **untyped attributes) -> Objects::SequenceEmail
450
- def update_email: (Integer sequence_id, Integer id, **untyped attributes) -> Objects::SequenceEmail
589
+ def email: (Integer sequence_id, Integer id, ?include: String?) -> Objects::SequenceEmail
590
+ def create_email: (Integer sequence_id, subject: String, delay_value: Integer, delay_unit: String, ?preview_text: String?, ?content: String?, ?email_template_id: Integer?, ?published: bool?, ?send_days: Array[String]?, ?position: Integer?) -> Objects::SequenceEmail
591
+ def update_email: (Integer sequence_id, Integer id, ?subject: String?, ?delay_value: Integer?, ?delay_unit: String?, ?preview_text: String?, ?content: String?, ?email_template_id: Integer?, ?published: bool?, ?send_days: Array[String]?, ?position: Integer?) -> Objects::SequenceEmail
451
592
  def delete_email: (Integer sequence_id, Integer id) -> void
452
593
  end
453
594
  class Broadcasts < Base
454
595
  def list: (**untyped params) -> Collection[Objects::Broadcast]
455
596
  def get: (Integer id) -> Objects::Broadcast
456
- def create: (**untyped attributes) -> Objects::Broadcast
457
- def update: (Integer id, **untyped attributes) -> Objects::Broadcast
597
+ def create: (?subject: String?, ?preview_text: String?, ?content: String?, ?description: String?, ?public: bool?, ?published_at: String?, ?send_at: String?, ?email_address: String?, ?email_template_id: Integer?, ?thumbnail_alt: String?, ?thumbnail_url: String?, ?subscriber_filter: Array[Hash[Symbol | String, untyped]]?) -> Objects::Broadcast
598
+ def update: (Integer id, ?subject: String?, ?preview_text: String?, ?content: String?, ?description: String?, ?public: bool?, ?published_at: String?, ?send_at: String?, ?email_address: String?, ?email_template_id: Integer?, ?thumbnail_alt: String?, ?thumbnail_url: String?, ?subscriber_filter: Array[Hash[Symbol | String, untyped]]?) -> Objects::Broadcast
458
599
  def delete: (Integer id) -> void
459
600
  def stats_list: (**untyped params) -> Collection[Objects::BroadcastStats]
460
601
  def stats: (Integer id) -> Objects::BroadcastStats
@@ -470,23 +611,23 @@ module Kit
470
611
  class Purchases < Base
471
612
  def list: (**untyped params) -> Collection[Objects::Purchase]
472
613
  def get: (Integer id) -> Objects::Purchase
473
- def create: (**untyped attributes) -> Objects::Purchase
614
+ def create: (email_address: String, transaction_id: String, status: String, currency: String, ?transaction_time: String?, ?subtotal: Numeric?, ?tax: Numeric?, ?shipping: Numeric?, ?discount: Numeric?, ?total: Numeric?, ?products: Array[Hash[Symbol | String, untyped]]?) -> Objects::Purchase
474
615
  end
475
616
  class Bulk < Base
476
- def create_subscribers: (Array[untyped] subscribers, ?callback_url: String?) -> Hash[String, untyped]
477
- def create_custom_fields: (Array[untyped] custom_fields, ?callback_url: String?) -> Hash[String, untyped]
478
- def update_custom_field_values: (Array[untyped] custom_field_values, ?callback_url: String?) -> Hash[String, untyped]
479
- def add_subscribers_to_forms: (Array[untyped] additions, ?callback_url: String?) -> Hash[String, untyped]
480
- def create_tags: (Array[untyped] tags, ?callback_url: String?) -> Hash[String, untyped]
481
- def delete_tags: (Array[untyped] tags, ?callback_url: String?) -> Hash[String, untyped]
482
- def tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) -> Hash[String, untyped]
483
- def remove_tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) -> Hash[String, untyped]
617
+ def create_subscribers: (Array[untyped] subscribers, ?callback_url: String?) -> Objects::BulkResult
618
+ def create_custom_fields: (Array[untyped] custom_fields, ?callback_url: String?) -> Objects::BulkResult
619
+ def update_custom_field_values: (Array[untyped] custom_field_values, ?callback_url: String?) -> Objects::BulkResult
620
+ def add_subscribers_to_forms: (Array[untyped] additions, ?callback_url: String?) -> Objects::BulkResult
621
+ def create_tags: (Array[untyped] tags, ?callback_url: String?) -> Objects::BulkResult
622
+ def delete_tags: (Array[untyped] tags, ?callback_url: String?) -> Objects::BulkResult
623
+ def tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) -> Objects::BulkResult
624
+ def remove_tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) -> Objects::BulkResult
484
625
  end
485
626
  class Snippets < Base
486
627
  def list: (**untyped params) -> Collection[Objects::Snippet]
487
628
  def get: (Integer id) -> Objects::Snippet
488
- def create: (**untyped attributes) -> Objects::Snippet
489
- def update: (Integer id, **untyped attributes) -> Objects::Snippet
629
+ def create: (name: String, snippet_type: String, ?content: String?, ?document_attributes: Hash[Symbol | String, untyped]?) -> Objects::Snippet
630
+ def update: (Integer id, ?name: String?, ?archived: bool?, ?content: String?, ?document_attributes: Hash[Symbol | String, untyped]?) -> Objects::Snippet
490
631
  end
491
632
  class Segments < Base
492
633
  def list: (**untyped params) -> Collection[Objects::Segment]
@@ -500,6 +641,7 @@ module Kit
500
641
  def list: (**untyped params) -> Collection[Objects::WebhookEndpoint]
501
642
  def get: (Integer id) -> Objects::WebhookEndpoint
502
643
  def create: (url: String, events: Array[untyped], ?name: String?, ?description: String?) -> Objects::WebhookEndpoint
644
+ def update: (Integer id, ?name: String?, ?url: String?, ?description: String?, ?status: String?, ?events: Array[String]?) -> Objects::WebhookEndpoint
503
645
  def delete: (Integer id) -> void
504
646
  def rotate_secret: (Integer id, ?force: bool?) -> Objects::WebhookEndpoint
505
647
  def revoke_previous_secret: (Integer id) -> Objects::WebhookEndpoint
@@ -508,7 +650,7 @@ module Kit
508
650
 
509
651
  class Client
510
652
  attr_reader config: Configuration
511
- def initialize: (?api_key: String?, ?access_token: String?, **untyped options) -> void
653
+ def initialize: (?api_key: String?, ?access_token: String?, ?base_url: String, ?open_timeout: Integer, ?read_timeout: Integer, ?write_timeout: Integer, ?max_retries: Integer, ?retry_backoff: Float, ?max_backoff: Numeric) -> void
512
654
  def account: () -> Resources::Account
513
655
  def subscribers: () -> Resources::Subscribers
514
656
  def tags: () -> Resources::Tags