voiceml 0.9.2 → 0.9.3
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/README.md +17 -13
- data/lib/voiceml/client.rb +53 -13
- data/lib/voiceml/errors.rb +15 -2
- data/lib/voiceml/models/accounts.rb +47 -3
- data/lib/voiceml/models/applications.rb +6 -0
- data/lib/voiceml/models/assistants_v1.rb +40 -0
- data/lib/voiceml/models/calls.rb +4 -0
- data/lib/voiceml/models/common.rb +35 -4
- data/lib/voiceml/models/conferences.rb +8 -0
- data/lib/voiceml/models/conversations_v1.rb +131 -29
- data/lib/voiceml/models/diagnostics.rb +33 -12
- data/lib/voiceml/models/exports.rb +58 -0
- data/lib/voiceml/models/incoming_phone_numbers.rb +4 -0
- data/lib/voiceml/models/media.rb +4 -0
- data/lib/voiceml/models/messages.rb +29 -0
- data/lib/voiceml/models/messaging_v1.rb +35 -2
- data/lib/voiceml/models/monitor_v1.rb +54 -0
- data/lib/voiceml/models/notifications.rb +2 -0
- data/lib/voiceml/models/outgoing_caller_ids.rb +6 -0
- data/lib/voiceml/models/payments.rb +2 -0
- data/lib/voiceml/models/pricing.rb +38 -4
- data/lib/voiceml/models/queues.rb +8 -0
- data/lib/voiceml/models/recordings.rb +5 -1
- data/lib/voiceml/models/routes_v2.rb +6 -2
- data/lib/voiceml/models/sip.rb +34 -6
- data/lib/voiceml/models/sip_registrations.rb +49 -0
- data/lib/voiceml/models/siprec.rb +5 -17
- data/lib/voiceml/models/streams.rb +4 -16
- data/lib/voiceml/models/transcriptions.rb +35 -4
- data/lib/voiceml/models/usage_records.rb +48 -0
- data/lib/voiceml/models/user_defined_messages.rb +2 -0
- data/lib/voiceml/models/voice_v1.rb +29 -19
- data/lib/voiceml/request_validator.rb +125 -0
- data/lib/voiceml/resources/accounts.rb +75 -0
- data/lib/voiceml/resources/applications.rb +3 -1
- data/lib/voiceml/resources/assistants_v1.rb +53 -77
- data/lib/voiceml/resources/base.rb +138 -16
- data/lib/voiceml/resources/calls.rb +34 -49
- data/lib/voiceml/resources/conferences.rb +26 -5
- data/lib/voiceml/resources/conversations_v1.rb +147 -172
- data/lib/voiceml/resources/diagnostics.rb +20 -35
- data/lib/voiceml/resources/exports.rb +36 -0
- data/lib/voiceml/resources/incoming_phone_numbers.rb +53 -32
- data/lib/voiceml/resources/messages.rb +79 -15
- data/lib/voiceml/resources/messaging_v1.rb +47 -13
- data/lib/voiceml/resources/monitor_v1.rb +68 -0
- data/lib/voiceml/resources/notifications.rb +1 -1
- data/lib/voiceml/resources/outgoing_caller_ids.rb +77 -0
- data/lib/voiceml/resources/pricing.rb +13 -5
- data/lib/voiceml/resources/queues.rb +6 -5
- data/lib/voiceml/resources/recordings.rb +39 -5
- data/lib/voiceml/resources/routes_v2.rb +10 -4
- data/lib/voiceml/resources/sip.rb +30 -11
- data/lib/voiceml/resources/transcriptions.rb +40 -0
- data/lib/voiceml/resources/usage_records.rb +51 -0
- data/lib/voiceml/resources/voice_v1.rb +31 -38
- data/lib/voiceml/transport.rb +277 -230
- data/lib/voiceml/version.rb +1 -1
- data/lib/voiceml.rb +14 -15
- metadata +18 -6
|
@@ -10,7 +10,7 @@ module VoiceML
|
|
|
10
10
|
# - List pages carry a `meta` envelope (URLs + page/page_size + key) instead of
|
|
11
11
|
# the flat Twilio2010 `next_page_uri` shape.
|
|
12
12
|
#
|
|
13
|
-
# Six resources implemented
|
|
13
|
+
# Six resources implemented:
|
|
14
14
|
# - ByocTrunk (BY...) — bring-your-own-carrier trunk
|
|
15
15
|
# - ConnectionPolicy (NY...) — origination policy
|
|
16
16
|
# - ConnectionPolicyTarget (NE...)
|
|
@@ -18,35 +18,27 @@ module VoiceML
|
|
|
18
18
|
# - SourceIpMapping (IB...) — bind IpRecord -> SipDomain
|
|
19
19
|
# - IpRecord (IL...) — standalone allowed source IP
|
|
20
20
|
|
|
21
|
-
# Shared `meta` envelope for `/v1/*` list responses.
|
|
22
|
-
module V1Pageable
|
|
23
|
-
META_FIELDS = %w[first_page_url next_page_url previous_page_url url page page_size key].freeze
|
|
24
|
-
|
|
25
|
-
META_FIELDS.each { |f| attr_reader f.to_sym }
|
|
26
|
-
|
|
27
|
-
def assign_meta_fields(hash)
|
|
28
|
-
meta = hash['meta'] || {}
|
|
29
|
-
META_FIELDS.each { |f| instance_variable_set("@#{f}", meta[f]) }
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
21
|
# VoiceV1IpRecord — `IL...`.
|
|
34
22
|
class VoiceV1IpRecord
|
|
23
|
+
include RawAttributes
|
|
35
24
|
ATTRIBUTES = %w[
|
|
36
25
|
account_sid sid friendly_name ip_address cidr_prefix_length
|
|
37
26
|
date_created date_updated url
|
|
38
27
|
].freeze
|
|
39
28
|
attr_reader(*ATTRIBUTES.map(&:to_sym))
|
|
40
29
|
def initialize(attrs = {})
|
|
41
|
-
|
|
30
|
+
@raw_attributes = attrs
|
|
31
|
+
ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
|
|
42
32
|
end
|
|
43
33
|
def self.from_hash(h); h.nil? ? nil : new(h); end
|
|
44
34
|
end
|
|
45
35
|
|
|
46
36
|
class VoiceV1IpRecordList
|
|
37
|
+
include RawAttributes
|
|
47
38
|
include V1Pageable
|
|
48
39
|
attr_reader :ip_records
|
|
49
40
|
def initialize(hash = {})
|
|
41
|
+
@raw_attributes = hash
|
|
50
42
|
assign_meta_fields(hash)
|
|
51
43
|
@ip_records = (hash['ip_records'] || []).map { |h| VoiceV1IpRecord.from_hash(h) }
|
|
52
44
|
end
|
|
@@ -54,20 +46,24 @@ module VoiceML
|
|
|
54
46
|
|
|
55
47
|
# VoiceV1SourceIpMapping — `IB...`. Binds an IpRecord to a SipDomain.
|
|
56
48
|
class VoiceV1SourceIpMapping
|
|
49
|
+
include RawAttributes
|
|
57
50
|
ATTRIBUTES = %w[
|
|
58
51
|
sid ip_record_sid sip_domain_sid date_created date_updated url
|
|
59
52
|
].freeze
|
|
60
53
|
attr_reader(*ATTRIBUTES.map(&:to_sym))
|
|
61
54
|
def initialize(attrs = {})
|
|
62
|
-
|
|
55
|
+
@raw_attributes = attrs
|
|
56
|
+
ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
|
|
63
57
|
end
|
|
64
58
|
def self.from_hash(h); h.nil? ? nil : new(h); end
|
|
65
59
|
end
|
|
66
60
|
|
|
67
61
|
class VoiceV1SourceIpMappingList
|
|
62
|
+
include RawAttributes
|
|
68
63
|
include V1Pageable
|
|
69
64
|
attr_reader :source_ip_mappings
|
|
70
65
|
def initialize(hash = {})
|
|
66
|
+
@raw_attributes = hash
|
|
71
67
|
assign_meta_fields(hash)
|
|
72
68
|
@source_ip_mappings = (hash['source_ip_mappings'] || []).map { |h| VoiceV1SourceIpMapping.from_hash(h) }
|
|
73
69
|
end
|
|
@@ -75,6 +71,7 @@ module VoiceML
|
|
|
75
71
|
|
|
76
72
|
# VoiceV1ByocTrunk — `BY...`. Bring-your-own-carrier trunk.
|
|
77
73
|
class VoiceV1ByocTrunk
|
|
74
|
+
include RawAttributes
|
|
78
75
|
ATTRIBUTES = %w[
|
|
79
76
|
account_sid sid friendly_name voice_url voice_method voice_fallback_url
|
|
80
77
|
voice_fallback_method status_callback_url status_callback_method
|
|
@@ -83,15 +80,18 @@ module VoiceML
|
|
|
83
80
|
].freeze
|
|
84
81
|
attr_reader(*ATTRIBUTES.map(&:to_sym))
|
|
85
82
|
def initialize(attrs = {})
|
|
86
|
-
|
|
83
|
+
@raw_attributes = attrs
|
|
84
|
+
ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
|
|
87
85
|
end
|
|
88
86
|
def self.from_hash(h); h.nil? ? nil : new(h); end
|
|
89
87
|
end
|
|
90
88
|
|
|
91
89
|
class VoiceV1ByocTrunkList
|
|
90
|
+
include RawAttributes
|
|
92
91
|
include V1Pageable
|
|
93
92
|
attr_reader :byoc_trunks
|
|
94
93
|
def initialize(hash = {})
|
|
94
|
+
@raw_attributes = hash
|
|
95
95
|
assign_meta_fields(hash)
|
|
96
96
|
@byoc_trunks = (hash['byoc_trunks'] || []).map { |h| VoiceV1ByocTrunk.from_hash(h) }
|
|
97
97
|
end
|
|
@@ -99,20 +99,24 @@ module VoiceML
|
|
|
99
99
|
|
|
100
100
|
# VoiceV1ConnectionPolicy — `NY...`.
|
|
101
101
|
class VoiceV1ConnectionPolicy
|
|
102
|
+
include RawAttributes
|
|
102
103
|
ATTRIBUTES = %w[
|
|
103
104
|
account_sid sid friendly_name date_created date_updated url links
|
|
104
105
|
].freeze
|
|
105
106
|
attr_reader(*ATTRIBUTES.map(&:to_sym))
|
|
106
107
|
def initialize(attrs = {})
|
|
107
|
-
|
|
108
|
+
@raw_attributes = attrs
|
|
109
|
+
ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
|
|
108
110
|
end
|
|
109
111
|
def self.from_hash(h); h.nil? ? nil : new(h); end
|
|
110
112
|
end
|
|
111
113
|
|
|
112
114
|
class VoiceV1ConnectionPolicyList
|
|
115
|
+
include RawAttributes
|
|
113
116
|
include V1Pageable
|
|
114
117
|
attr_reader :connection_policies
|
|
115
118
|
def initialize(hash = {})
|
|
119
|
+
@raw_attributes = hash
|
|
116
120
|
assign_meta_fields(hash)
|
|
117
121
|
@connection_policies = (hash['connection_policies'] || []).map { |h| VoiceV1ConnectionPolicy.from_hash(h) }
|
|
118
122
|
end
|
|
@@ -120,21 +124,25 @@ module VoiceML
|
|
|
120
124
|
|
|
121
125
|
# VoiceV1ConnectionPolicyTarget — `NE...`. Scoped to a parent ConnectionPolicy.
|
|
122
126
|
class VoiceV1ConnectionPolicyTarget
|
|
127
|
+
include RawAttributes
|
|
123
128
|
ATTRIBUTES = %w[
|
|
124
129
|
account_sid connection_policy_sid sid friendly_name target priority weight
|
|
125
130
|
enabled date_created date_updated url
|
|
126
131
|
].freeze
|
|
127
132
|
attr_reader(*ATTRIBUTES.map(&:to_sym))
|
|
128
133
|
def initialize(attrs = {})
|
|
129
|
-
|
|
134
|
+
@raw_attributes = attrs
|
|
135
|
+
ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
|
|
130
136
|
end
|
|
131
137
|
def self.from_hash(h); h.nil? ? nil : new(h); end
|
|
132
138
|
end
|
|
133
139
|
|
|
134
140
|
class VoiceV1ConnectionPolicyTargetList
|
|
141
|
+
include RawAttributes
|
|
135
142
|
include V1Pageable
|
|
136
143
|
attr_reader :targets
|
|
137
144
|
def initialize(hash = {})
|
|
145
|
+
@raw_attributes = hash
|
|
138
146
|
assign_meta_fields(hash)
|
|
139
147
|
@targets = (hash['targets'] || []).map { |h| VoiceV1ConnectionPolicyTarget.from_hash(h) }
|
|
140
148
|
end
|
|
@@ -142,10 +150,12 @@ module VoiceML
|
|
|
142
150
|
|
|
143
151
|
# VoiceV1DialingPermissionsSettings — `/v1/Settings`.
|
|
144
152
|
class VoiceV1DialingPermissionsSettings
|
|
153
|
+
include RawAttributes
|
|
145
154
|
ATTRIBUTES = %w[dialing_permissions_inheritance url].freeze
|
|
146
155
|
attr_reader(*ATTRIBUTES.map(&:to_sym))
|
|
147
156
|
def initialize(attrs = {})
|
|
148
|
-
|
|
157
|
+
@raw_attributes = attrs
|
|
158
|
+
ATTRIBUTES.each { |f| instance_variable_set("@#{f}", attrs.key?(f) ? attrs[f] : attrs[f.to_sym]) }
|
|
149
159
|
end
|
|
150
160
|
def self.from_hash(h); h.nil? ? nil : new(h); end
|
|
151
161
|
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
|
|
5
|
+
module VoiceML
|
|
6
|
+
# Validates the `X-Twilio-Signature` header on inbound VoiceML webhooks, and
|
|
7
|
+
# computes signatures for testing server-side handlers.
|
|
8
|
+
#
|
|
9
|
+
# The algorithm (matching the VoiceML server):
|
|
10
|
+
#
|
|
11
|
+
# 1. Take the full request URL with any `#fragment` stripped.
|
|
12
|
+
# 2. For a POST, append `key + value` for every form pair, sorted ascending
|
|
13
|
+
# by key in byte order; repeated keys keep their received value order.
|
|
14
|
+
# For a GET, the URL alone is signed.
|
|
15
|
+
# 3. HMAC-SHA1 with the account API key as the key, then standard Base64
|
|
16
|
+
# (padded).
|
|
17
|
+
#
|
|
18
|
+
# Comparison is constant-time. Like Twilio's validators, a URL that differs
|
|
19
|
+
# only by an explicit default port (`:443` for https, `:80` for http) also
|
|
20
|
+
# validates, in both directions.
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# validator = VoiceML::RequestValidator.new(api_key)
|
|
24
|
+
# valid = validator.valid?(request.url, request.params, request.headers['X-Twilio-Signature'])
|
|
25
|
+
class RequestValidator
|
|
26
|
+
# @param api_key [String] the account API key the server signs with.
|
|
27
|
+
def initialize(api_key)
|
|
28
|
+
@credential = -> { api_key }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Compute the signature for a URL and params. `params` may be a Hash (use
|
|
32
|
+
# an Array value for repeated keys) or an Array of `[key, value]` pairs in
|
|
33
|
+
# their received order.
|
|
34
|
+
#
|
|
35
|
+
# @return [String] Base64 HMAC-SHA1 digest.
|
|
36
|
+
def signature(url, params = {}, method: 'POST')
|
|
37
|
+
data = signature_base(url, method.to_s.upcase == 'POST' ? params : nil)
|
|
38
|
+
digest = OpenSSL::HMAC.digest('SHA1', @credential.call, data)
|
|
39
|
+
[digest].pack('m0')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Validate an incoming webhook. `params` may be a Hash (Array value for
|
|
43
|
+
# repeated keys) or an Array of `[key, value]` pairs in received order.
|
|
44
|
+
#
|
|
45
|
+
# @return [Boolean]
|
|
46
|
+
def valid?(url, params, signature, method: 'POST')
|
|
47
|
+
given = signature.to_s
|
|
48
|
+
candidates = [signature(url, params, method: method)]
|
|
49
|
+
variant = port_variant(url)
|
|
50
|
+
candidates << signature(variant, params, method: method) unless variant.nil?
|
|
51
|
+
|
|
52
|
+
candidates.map { |expected| constant_time_equal?(expected, given) }.any?
|
|
53
|
+
end
|
|
54
|
+
alias validate valid?
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# The string that gets HMAC'd: URL (fragment stripped) plus, for POSTs,
|
|
59
|
+
# `key + value` per pair sorted by key byte order, repeated keys keeping
|
|
60
|
+
# their received value order.
|
|
61
|
+
def signature_base(url, params)
|
|
62
|
+
base = url.to_s.sub(/#.*\z/, '')
|
|
63
|
+
return base if params.nil?
|
|
64
|
+
|
|
65
|
+
pairs = pairs_from(params)
|
|
66
|
+
return base if pairs.empty?
|
|
67
|
+
|
|
68
|
+
sorted = pairs.each_with_index.to_a
|
|
69
|
+
.sort_by { |(pair, index)| [pair.first.b, index] }
|
|
70
|
+
.map(&:first)
|
|
71
|
+
base + sorted.map { |key, value| "#{key}#{value}" }.join
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def pairs_from(params)
|
|
75
|
+
case params
|
|
76
|
+
when Array then params.map { |key, value| [key.to_s, value.to_s] }
|
|
77
|
+
else
|
|
78
|
+
pairs = []
|
|
79
|
+
params.each do |key, value|
|
|
80
|
+
if value.is_a?(Array)
|
|
81
|
+
value.each { |entry| pairs << [key.to_s, entry.to_s] }
|
|
82
|
+
else
|
|
83
|
+
pairs << [key.to_s, value.to_s]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
pairs
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The same URL with the default port added or removed (`:443` for https,
|
|
91
|
+
# `:80` for http), or nil when the URL has no such variant. String-based:
|
|
92
|
+
# URI round-trips normalise an explicit default port away.
|
|
93
|
+
def port_variant(url)
|
|
94
|
+
s = url.to_s
|
|
95
|
+
match = s.match(%r{\Ahttps?://[^/?#]*})
|
|
96
|
+
return nil if match.nil?
|
|
97
|
+
|
|
98
|
+
prefix = match[0]
|
|
99
|
+
rest = s[prefix.length..] || ''
|
|
100
|
+
if prefix =~ %r{\Ahttps://[^/?#]+:443\z}
|
|
101
|
+
prefix.sub(/:443\z/, '') + rest
|
|
102
|
+
elsif prefix =~ %r{\Ahttps://[^/?#:]+\z}
|
|
103
|
+
"#{prefix}:443#{rest}"
|
|
104
|
+
elsif prefix =~ %r{\Ahttp://[^/?#]+:80\z}
|
|
105
|
+
prefix.sub(/:80\z/, '') + rest
|
|
106
|
+
elsif prefix =~ %r{\Ahttp://[^/?#:]+\z}
|
|
107
|
+
"#{prefix}:80#{rest}"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def constant_time_equal?(a, b)
|
|
112
|
+
return false unless a.bytesize == b.bytesize
|
|
113
|
+
|
|
114
|
+
OpenSSL.fixed_length_secure_compare(a, b)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Convenience wrapper: validate one webhook without instantiating a
|
|
119
|
+
# {RequestValidator}.
|
|
120
|
+
#
|
|
121
|
+
# @return [Boolean]
|
|
122
|
+
def self.validate_request(api_key, url, params, signature, method: 'POST')
|
|
123
|
+
RequestValidator.new(api_key).valid?(url, params, signature, method: method)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative '../models/accounts'
|
|
5
|
+
|
|
6
|
+
module VoiceML
|
|
7
|
+
# Operations on `/2010-04-01/Accounts/{Sid}` — the account record itself and
|
|
8
|
+
# its balance. `client.accounts` reads the authenticated account; `fetch`/
|
|
9
|
+
# `update` accept any AccountSid the credentials can see (in practice the
|
|
10
|
+
# credential's own account).
|
|
11
|
+
class AccountsResource < BaseResource
|
|
12
|
+
UPDATE_FIELDS = {
|
|
13
|
+
'FriendlyName' => :friendly_name,
|
|
14
|
+
'VoiceUrl' => :voice_url,
|
|
15
|
+
'VoiceMethod' => :voice_method,
|
|
16
|
+
'VoiceFallbackUrl' => :voice_fallback_url,
|
|
17
|
+
'VoiceFallbackMethod' => :voice_fallback_method,
|
|
18
|
+
'SttFsVendor' => :stt_fs_vendor,
|
|
19
|
+
'SttHttpUrl' => :stt_http_url,
|
|
20
|
+
'SttHttpToken' => :stt_http_token,
|
|
21
|
+
'TtsHttpUrl' => :tts_http_url,
|
|
22
|
+
'TtsHttpToken' => :tts_http_token,
|
|
23
|
+
'TtsEngineName' => :tts_engine_name,
|
|
24
|
+
'TtsDefaultVoice' => :tts_default_voice,
|
|
25
|
+
'TtsDefaultLanguage' => :tts_default_language,
|
|
26
|
+
'AwsBucket' => :aws_bucket,
|
|
27
|
+
'AwsRegion' => :aws_region,
|
|
28
|
+
'AwsPrefix' => :aws_prefix,
|
|
29
|
+
'AwsAccessKeyId' => :aws_access_key_id,
|
|
30
|
+
'AwsSecretAccessKey' => :aws_secret_access_key,
|
|
31
|
+
'AwsKmsKeyId' => :aws_kms_key_id,
|
|
32
|
+
'AllowCidr' => :allow_cidr
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
# Fetch the account record. Omit `sid` to read the authenticated account.
|
|
36
|
+
#
|
|
37
|
+
# @return [VoiceML::Account]
|
|
38
|
+
def fetch(sid = @transport.account_sid)
|
|
39
|
+
Account.from_hash(
|
|
40
|
+
@transport.request(:get, "/2010-04-01/Accounts/#{encode_path_segment(sid)}.json")
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Update the account record. A concurrent modification surfaces as
|
|
45
|
+
# `VoiceML::ConflictError` (HTTP 409 / code 20409 — optimistic locking).
|
|
46
|
+
#
|
|
47
|
+
# @return [VoiceML::Account]
|
|
48
|
+
def update(sid = @transport.account_sid, **kwargs)
|
|
49
|
+
Account.from_hash(
|
|
50
|
+
@transport.request(:post, "/2010-04-01/Accounts/#{encode_path_segment(sid)}.json",
|
|
51
|
+
form: form_params(UPDATE_FIELDS, kwargs))
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Fetch the account balance.
|
|
56
|
+
#
|
|
57
|
+
# @return [VoiceML::Balance]
|
|
58
|
+
def fetch_balance
|
|
59
|
+
Balance.from_hash(@transport.request(:get, path('Balance')))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Rotate the account's API key (`POST /Accounts/{Sid}/RotateKey.json`,
|
|
63
|
+
# no request body). The previous key stops working immediately. The new
|
|
64
|
+
# key rides the returned {VoiceML::AccountWithKey} as `api_key` — no
|
|
65
|
+
# read endpoint exposes it, so store it right away. It never appears in
|
|
66
|
+
# the model's `inspect` / `to_s`.
|
|
67
|
+
#
|
|
68
|
+
# @return [VoiceML::AccountWithKey]
|
|
69
|
+
def rotate_key(sid = @transport.account_sid)
|
|
70
|
+
AccountWithKey.from_hash(
|
|
71
|
+
@transport.request(:post, "/2010-04-01/Accounts/#{encode_path_segment(sid)}/RotateKey.json")
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -15,7 +15,9 @@ module VoiceML
|
|
|
15
15
|
'VoiceCallerIdLookup' => :voice_caller_id_lookup,
|
|
16
16
|
'StatusCallback' => :status_callback,
|
|
17
17
|
'StatusCallbackMethod' => :status_callback_method,
|
|
18
|
-
'StatusCallbackEvent' => :status_callback_event
|
|
18
|
+
'StatusCallbackEvent' => :status_callback_event,
|
|
19
|
+
'MessageStatusCallback' => :message_status_callback,
|
|
20
|
+
'SmsStatusCallback' => :sms_status_callback
|
|
19
21
|
}.freeze
|
|
20
22
|
|
|
21
23
|
LIST_FIELDS = {
|