sendly 4.0.0 → 4.2.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 +4 -4
- data/CHANGELOG.md +34 -0
- data/Gemfile.lock +3 -3
- data/README.md +168 -0
- data/lib/sendly/calls_resource.rb +413 -0
- data/lib/sendly/client.rb +24 -2
- data/lib/sendly/errors.rb +36 -17
- data/lib/sendly/numbers_resource.rb +23 -2
- data/lib/sendly/templates_resource.rb +0 -4
- data/lib/sendly/version.rb +1 -1
- data/lib/sendly/voice_resource.rb +721 -0
- data/lib/sendly/webhooks.rb +4 -0
- data/lib/sendly.rb +2 -0
- metadata +4 -2
data/lib/sendly/client.rb
CHANGED
|
@@ -192,6 +192,24 @@ module Sendly
|
|
|
192
192
|
@rcs ||= RcsResource.new(self)
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
+
# Access the Calls resource (phone calls handled by your AI agents)
|
|
196
|
+
#
|
|
197
|
+
# @return [Sendly::CallsResource]
|
|
198
|
+
def calls
|
|
199
|
+
@calls ||= CallsResource.new(self)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Access the Voice resource (numbers, AI agents and voices for phone calls)
|
|
203
|
+
#
|
|
204
|
+
# @return [Sendly::VoiceResource]
|
|
205
|
+
#
|
|
206
|
+
# @example
|
|
207
|
+
# agent = client.voice.agents.create(name: "Front desk")
|
|
208
|
+
# client.voice.numbers.update("+15555550188", voice_enabled: true, voice_mode: "agent", agent_id: agent.id)
|
|
209
|
+
def voice
|
|
210
|
+
@voice ||= VoiceResource.new(self)
|
|
211
|
+
end
|
|
212
|
+
|
|
195
213
|
# Make a GET request
|
|
196
214
|
#
|
|
197
215
|
# @param path [String] API path
|
|
@@ -248,10 +266,14 @@ module Sendly
|
|
|
248
266
|
|
|
249
267
|
# Make a DELETE request
|
|
250
268
|
#
|
|
269
|
+
# No Idempotency-Key is generated for a DELETE; pass +idempotency_key+
|
|
270
|
+
# to send one (1-255 printable ASCII characters).
|
|
271
|
+
#
|
|
251
272
|
# @param path [String] API path
|
|
273
|
+
# @param idempotency_key [String, nil] Caller-supplied idempotency key (optional)
|
|
252
274
|
# @return [Hash] Response body
|
|
253
|
-
def delete(path)
|
|
254
|
-
request(:delete, path)
|
|
275
|
+
def delete(path, idempotency_key: nil)
|
|
276
|
+
request(:delete, path, idempotency_key: idempotency_key)
|
|
255
277
|
end
|
|
256
278
|
|
|
257
279
|
# Make a GET request against the API origin, bypassing the +/api/v1+ base.
|
data/lib/sendly/errors.rb
CHANGED
|
@@ -12,12 +12,30 @@ module Sendly
|
|
|
12
12
|
# @return [Integer, nil] HTTP status code
|
|
13
13
|
attr_reader :status_code
|
|
14
14
|
|
|
15
|
+
# @return [Hash, nil] The parsed JSON body of the API's error response,
|
|
16
|
+
# or +nil+ for errors raised before a request was sent. The API's own
|
|
17
|
+
# error code is under +"error"+, and some refusals carry more than a
|
|
18
|
+
# message: a 409 +agent_in_use+ lists the numbers the agent still
|
|
19
|
+
# answers under +"numbers"+, and a 422 +invalid_address+ carries a
|
|
20
|
+
# corrected address (or +nil+) under +"suggested"+.
|
|
21
|
+
attr_reader :response_body
|
|
22
|
+
|
|
15
23
|
def initialize(message = nil, code: nil, details: nil, status_code: nil)
|
|
16
24
|
@code = code
|
|
17
25
|
@details = details
|
|
18
26
|
@status_code = status_code
|
|
19
27
|
super(message)
|
|
20
28
|
end
|
|
29
|
+
|
|
30
|
+
# Attach the parsed body of the API response this error came from.
|
|
31
|
+
#
|
|
32
|
+
# @api private
|
|
33
|
+
# @param body [Hash, nil]
|
|
34
|
+
# @return [self]
|
|
35
|
+
def with_response_body(body)
|
|
36
|
+
@response_body = body
|
|
37
|
+
self
|
|
38
|
+
end
|
|
21
39
|
end
|
|
22
40
|
|
|
23
41
|
# Raised when the API key is invalid or missing
|
|
@@ -101,23 +119,24 @@ module Sendly
|
|
|
101
119
|
code = body["code"]
|
|
102
120
|
details = body["details"]
|
|
103
121
|
|
|
104
|
-
case status
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
error = case status
|
|
123
|
+
when 400, 422
|
|
124
|
+
ValidationError.new(message, details: details, field_errors: body["errors"])
|
|
125
|
+
when 401
|
|
126
|
+
AuthenticationError.new(message)
|
|
127
|
+
when 402
|
|
128
|
+
InsufficientCreditsError.new(message)
|
|
129
|
+
when 404
|
|
130
|
+
NotFoundError.new(message)
|
|
131
|
+
when 429
|
|
132
|
+
retry_after = body["retry_after"] || body["retryAfter"]
|
|
133
|
+
RateLimitError.new(message, retry_after: retry_after)
|
|
134
|
+
when 500..599
|
|
135
|
+
ServerError.new(message, status_code: status)
|
|
136
|
+
else
|
|
137
|
+
APIError.new(message, status_code: status, code: code, details: details)
|
|
138
|
+
end
|
|
139
|
+
error.with_response_body(body)
|
|
121
140
|
end
|
|
122
141
|
end
|
|
123
142
|
end
|
|
@@ -53,9 +53,22 @@ module Sendly
|
|
|
53
53
|
# true when the number is scheduled for release at period end.
|
|
54
54
|
:pending_cancellation,
|
|
55
55
|
# ISO-8601 timestamp string, or nil when no release is scheduled.
|
|
56
|
-
:scheduled_release_at
|
|
56
|
+
:scheduled_release_at,
|
|
57
|
+
# true when the number can place and receive phone calls
|
|
58
|
+
# (switched on with {VoiceNumbersResource#update} or in the
|
|
59
|
+
# dashboard under Calls, then Settings).
|
|
60
|
+
:voice_enabled,
|
|
61
|
+
# One of {VOICE_MODES}: "none" when voice is off, "ring_dashboard"
|
|
62
|
+
# when inbound calls ring the team, "agent" when an AI agent answers.
|
|
63
|
+
:voice_mode
|
|
64
|
+
|
|
65
|
+
# @return [Hash] The raw parsed response
|
|
66
|
+
attr_reader :raw
|
|
67
|
+
|
|
68
|
+
VOICE_MODES = %w[none ring_dashboard agent].freeze
|
|
57
69
|
|
|
58
70
|
def initialize(data)
|
|
71
|
+
@raw = data
|
|
59
72
|
@id = data["id"]
|
|
60
73
|
@phone_number = data["phoneNumber"] || data["phone_number"]
|
|
61
74
|
@status = data["status"]
|
|
@@ -67,6 +80,13 @@ module Sendly
|
|
|
67
80
|
@requirements_submitted_at = data["requirementsSubmittedAt"] || data["requirements_submitted_at"]
|
|
68
81
|
@pending_cancellation = data.key?("pendingCancellation") ? data["pendingCancellation"] : data["pending_cancellation"]
|
|
69
82
|
@scheduled_release_at = data["scheduledReleaseAt"] || data["scheduled_release_at"]
|
|
83
|
+
@voice_enabled = data.key?("voiceEnabled") ? data["voiceEnabled"] : data["voice_enabled"]
|
|
84
|
+
@voice_mode = data["voiceMode"] || data["voice_mode"]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @return [Boolean] Whether the number can be used as the +from+ of a call
|
|
88
|
+
def voice_enabled?
|
|
89
|
+
voice_enabled == true
|
|
70
90
|
end
|
|
71
91
|
|
|
72
92
|
def to_h
|
|
@@ -76,7 +96,8 @@ module Sendly
|
|
|
76
96
|
monthly_cost_cents: monthly_cost_cents, is_default: is_default,
|
|
77
97
|
requirements_submitted_at: requirements_submitted_at,
|
|
78
98
|
pending_cancellation: pending_cancellation,
|
|
79
|
-
scheduled_release_at: scheduled_release_at
|
|
99
|
+
scheduled_release_at: scheduled_release_at,
|
|
100
|
+
voice_enabled: voice_enabled, voice_mode: voice_mode
|
|
80
101
|
}.compact
|
|
81
102
|
end
|
|
82
103
|
end
|
|
@@ -222,10 +222,6 @@ module Sendly
|
|
|
222
222
|
end
|
|
223
223
|
|
|
224
224
|
# Copy an existing template into a new draft.
|
|
225
|
-
#
|
|
226
|
-
# @note Not available yet: the versioned API serves no clone route, so
|
|
227
|
-
# this call fails with a 404. To copy a template today, read it with
|
|
228
|
-
# {#get} and pass its {Template#text} to {#create}.
|
|
229
225
|
def clone(id, name: nil)
|
|
230
226
|
body = {}
|
|
231
227
|
body[:name] = name if name
|
data/lib/sendly/version.rb
CHANGED