late-sdk 0.0.725 → 0.0.727

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -0
  3. data/docs/AttachNumberToSipTrunk200Response.md +22 -0
  4. data/docs/AttachNumberToSipTrunkRequest.md +18 -0
  5. data/docs/CreateSipTrunk201Response.md +34 -0
  6. data/docs/CreateSipTrunk201ResponseTermination.md +20 -0
  7. data/docs/CreateSipTrunkRequest.md +24 -0
  8. data/docs/DetachNumberFromSipTrunk200Response.md +20 -0
  9. data/docs/GetPhoneNumber200ResponsePhoneNumber.md +3 -1
  10. data/docs/GetSipTrunk200Response.md +34 -0
  11. data/docs/GetSipTrunk200ResponseNumbersInner.md +20 -0
  12. data/docs/ListPhoneNumbers200ResponseNumbersInner.md +2 -0
  13. data/docs/ListSipTrunks200Response.md +20 -0
  14. data/docs/ListSipTrunks200ResponseTrunksInner.md +32 -0
  15. data/docs/ListSipTrunks200ResponseTrunksInnerTermination.md +20 -0
  16. data/docs/MessagesApi.md +1 -1
  17. data/docs/RotateSipTrunkCredentials200Response.md +20 -0
  18. data/docs/SendInboxMessage200ResponseData.md +4 -6
  19. data/docs/SendInboxMessage200ResponseDataAttachmentsInner.md +20 -0
  20. data/docs/VoiceApi.md +485 -0
  21. data/lib/zernio-sdk/api/messages_api.rb +2 -2
  22. data/lib/zernio-sdk/api/voice_api.rb +447 -0
  23. data/lib/zernio-sdk/models/attach_number_to_sip_trunk200_response.rb +165 -0
  24. data/lib/zernio-sdk/models/attach_number_to_sip_trunk_request.rb +165 -0
  25. data/lib/zernio-sdk/models/create_sip_trunk201_response.rb +255 -0
  26. data/lib/zernio-sdk/models/create_sip_trunk201_response_termination.rb +158 -0
  27. data/lib/zernio-sdk/models/create_sip_trunk_request.rb +283 -0
  28. data/lib/zernio-sdk/models/detach_number_from_sip_trunk200_response.rb +157 -0
  29. data/lib/zernio-sdk/models/get_phone_number200_response_phone_number.rb +15 -4
  30. data/lib/zernio-sdk/models/get_sip_trunk200_response.rb +256 -0
  31. data/lib/zernio-sdk/models/get_sip_trunk200_response_numbers_inner.rb +157 -0
  32. data/lib/zernio-sdk/models/list_phone_numbers200_response_numbers_inner.rb +12 -1
  33. data/lib/zernio-sdk/models/list_sip_trunks200_response.rb +159 -0
  34. data/lib/zernio-sdk/models/list_sip_trunks200_response_trunks_inner.rb +245 -0
  35. data/lib/zernio-sdk/models/list_sip_trunks200_response_trunks_inner_termination.rb +156 -0
  36. data/lib/zernio-sdk/models/rotate_sip_trunk_credentials200_response.rb +156 -0
  37. data/lib/zernio-sdk/models/send_inbox_message200_response_data.rb +12 -23
  38. data/lib/zernio-sdk/models/send_inbox_message200_response_data_attachments_inner.rb +156 -0
  39. data/lib/zernio-sdk/version.rb +1 -1
  40. data/lib/zernio-sdk.rb +13 -0
  41. data/openapi.yaml +293 -4
  42. data/spec/api/messages_api_spec.rb +1 -1
  43. data/spec/api/voice_api_spec.rb +82 -0
  44. data/spec/models/attach_number_to_sip_trunk200_response_spec.rb +48 -0
  45. data/spec/models/attach_number_to_sip_trunk_request_spec.rb +36 -0
  46. data/spec/models/create_sip_trunk201_response_spec.rb +88 -0
  47. data/spec/models/create_sip_trunk201_response_termination_spec.rb +42 -0
  48. data/spec/models/create_sip_trunk_request_spec.rb +58 -0
  49. data/spec/models/detach_number_from_sip_trunk200_response_spec.rb +42 -0
  50. data/spec/models/get_phone_number200_response_phone_number_spec.rb +6 -0
  51. data/spec/models/get_sip_trunk200_response_numbers_inner_spec.rb +42 -0
  52. data/spec/models/get_sip_trunk200_response_spec.rb +88 -0
  53. data/spec/models/list_phone_numbers200_response_numbers_inner_spec.rb +6 -0
  54. data/spec/models/list_sip_trunks200_response_spec.rb +42 -0
  55. data/spec/models/list_sip_trunks200_response_trunks_inner_spec.rb +82 -0
  56. data/spec/models/list_sip_trunks200_response_trunks_inner_termination_spec.rb +42 -0
  57. data/spec/models/rotate_sip_trunk_credentials200_response_spec.rb +42 -0
  58. data/spec/models/send_inbox_message200_response_data_attachments_inner_spec.rb +42 -0
  59. data/spec/models/send_inbox_message200_response_data_spec.rb +1 -7
  60. data/zernio-sdk-0.0.727.gem +0 -0
  61. metadata +55 -3
  62. data/zernio-sdk-0.0.725.gem +0 -0
@@ -0,0 +1,156 @@
1
+ =begin
2
+ #Zernio API
3
+
4
+ #API reference for Zernio. Authenticate with a Bearer API key. Base URL: https://zernio.com/api
5
+
6
+ The version of the OpenAPI document: 1.0.4
7
+ Contact: support@zernio.com
8
+ Generated by: https://openapi-generator.tech
9
+ Generator version: 7.19.0
10
+
11
+ =end
12
+
13
+ require 'date'
14
+ require 'time'
15
+
16
+ module Zernio
17
+ class SendInboxMessage200ResponseDataAttachmentsInner < ApiModelBase
18
+ attr_accessor :type
19
+
20
+ attr_accessor :url
21
+
22
+ # Attribute mapping from ruby-style variable name to JSON key.
23
+ def self.attribute_map
24
+ {
25
+ :'type' => :'type',
26
+ :'url' => :'url'
27
+ }
28
+ end
29
+
30
+ # Returns attribute mapping this model knows about
31
+ def self.acceptable_attribute_map
32
+ attribute_map
33
+ end
34
+
35
+ # Returns all the JSON keys this model knows about
36
+ def self.acceptable_attributes
37
+ acceptable_attribute_map.values
38
+ end
39
+
40
+ # Attribute type mapping.
41
+ def self.openapi_types
42
+ {
43
+ :'type' => :'String',
44
+ :'url' => :'String'
45
+ }
46
+ end
47
+
48
+ # List of attributes with nullable: true
49
+ def self.openapi_nullable
50
+ Set.new([
51
+ ])
52
+ end
53
+
54
+ # Initializes the object
55
+ # @param [Hash] attributes Model attributes in the form of hash
56
+ def initialize(attributes = {})
57
+ if (!attributes.is_a?(Hash))
58
+ fail ArgumentError, "The input argument (attributes) must be a hash in `Zernio::SendInboxMessage200ResponseDataAttachmentsInner` initialize method"
59
+ end
60
+
61
+ # check to see if the attribute exists and convert string to symbol for hash key
62
+ acceptable_attribute_map = self.class.acceptable_attribute_map
63
+ attributes = attributes.each_with_object({}) { |(k, v), h|
64
+ if (!acceptable_attribute_map.key?(k.to_sym))
65
+ fail ArgumentError, "`#{k}` is not a valid attribute in `Zernio::SendInboxMessage200ResponseDataAttachmentsInner`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
66
+ end
67
+ h[k.to_sym] = v
68
+ }
69
+
70
+ if attributes.key?(:'type')
71
+ self.type = attributes[:'type']
72
+ end
73
+
74
+ if attributes.key?(:'url')
75
+ self.url = attributes[:'url']
76
+ end
77
+ end
78
+
79
+ # Show invalid properties with the reasons. Usually used together with valid?
80
+ # @return Array for valid properties with the reasons
81
+ def list_invalid_properties
82
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
83
+ invalid_properties = Array.new
84
+ invalid_properties
85
+ end
86
+
87
+ # Check to see if the all the properties in the model are valid
88
+ # @return true if the model is valid
89
+ def valid?
90
+ warn '[DEPRECATED] the `valid?` method is obsolete'
91
+ true
92
+ end
93
+
94
+ # Checks equality by comparing each attribute.
95
+ # @param [Object] Object to be compared
96
+ def ==(o)
97
+ return true if self.equal?(o)
98
+ self.class == o.class &&
99
+ type == o.type &&
100
+ url == o.url
101
+ end
102
+
103
+ # @see the `==` method
104
+ # @param [Object] Object to be compared
105
+ def eql?(o)
106
+ self == o
107
+ end
108
+
109
+ # Calculates hash code according to all attributes.
110
+ # @return [Integer] Hash code
111
+ def hash
112
+ [type, url].hash
113
+ end
114
+
115
+ # Builds the object from hash
116
+ # @param [Hash] attributes Model attributes in the form of hash
117
+ # @return [Object] Returns the model itself
118
+ def self.build_from_hash(attributes)
119
+ return nil unless attributes.is_a?(Hash)
120
+ attributes = attributes.transform_keys(&:to_sym)
121
+ transformed_hash = {}
122
+ openapi_types.each_pair do |key, type|
123
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
124
+ transformed_hash["#{key}"] = nil
125
+ elsif type =~ /\AArray<(.*)>/i
126
+ # check to ensure the input is an array given that the attribute
127
+ # is documented as an array but the input is not
128
+ if attributes[attribute_map[key]].is_a?(Array)
129
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
130
+ end
131
+ elsif !attributes[attribute_map[key]].nil?
132
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
133
+ end
134
+ end
135
+ new(transformed_hash)
136
+ end
137
+
138
+ # Returns the object in the form of hash
139
+ # @return [Hash] Returns the object in the form of hash
140
+ def to_hash
141
+ hash = {}
142
+ self.class.attribute_map.each_pair do |attr, param|
143
+ value = self.send(attr)
144
+ if value.nil?
145
+ is_nullable = self.class.openapi_nullable.include?(attr)
146
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
147
+ end
148
+
149
+ hash[param] = _to_hash(value)
150
+ end
151
+ hash
152
+ end
153
+
154
+ end
155
+
156
+ end
@@ -11,5 +11,5 @@ Generator version: 7.19.0
11
11
  =end
12
12
 
13
13
  module Zernio
14
- VERSION = '0.0.725'
14
+ VERSION = '0.0.727'
15
15
  end
data/lib/zernio-sdk.rb CHANGED
@@ -86,6 +86,8 @@ require 'zernio-sdk/models/archive_lead_form200_response'
86
86
  require 'zernio-sdk/models/assign_google_business_location200_response'
87
87
  require 'zernio-sdk/models/assign_google_business_location200_response_account'
88
88
  require 'zernio-sdk/models/assign_google_business_location_request'
89
+ require 'zernio-sdk/models/attach_number_to_sip_trunk200_response'
90
+ require 'zernio-sdk/models/attach_number_to_sip_trunk_request'
89
91
  require 'zernio-sdk/models/batch_get_google_business_reviews200_response'
90
92
  require 'zernio-sdk/models/batch_get_google_business_reviews200_response_location_reviews_inner'
91
93
  require 'zernio-sdk/models/batch_get_google_business_reviews_request'
@@ -287,6 +289,9 @@ require 'zernio-sdk/models/create_sequence_request'
287
289
  require 'zernio-sdk/models/create_sequence_request_steps_inner'
288
290
  require 'zernio-sdk/models/create_sequence_request_steps_inner_template'
289
291
  require 'zernio-sdk/models/create_sequence_request_steps_inner_template_variable_mapping_value'
292
+ require 'zernio-sdk/models/create_sip_trunk201_response'
293
+ require 'zernio-sdk/models/create_sip_trunk201_response_termination'
294
+ require 'zernio-sdk/models/create_sip_trunk_request'
290
295
  require 'zernio-sdk/models/create_sms_sender_id200_response'
291
296
  require 'zernio-sdk/models/create_sms_sender_id_request'
292
297
  require 'zernio-sdk/models/create_standalone_ad200_response'
@@ -374,6 +379,7 @@ require 'zernio-sdk/models/delete_inbox_review_reply_request'
374
379
  require 'zernio-sdk/models/delete_sms_sender_id200_response'
375
380
  require 'zernio-sdk/models/delete_value_rule_set200_response'
376
381
  require 'zernio-sdk/models/delete_whatsapp_business_username_request'
382
+ require 'zernio-sdk/models/detach_number_from_sip_trunk200_response'
377
383
  require 'zernio-sdk/models/dial_voice_web_call200_response'
378
384
  require 'zernio-sdk/models/dial_voice_web_call_request'
379
385
  require 'zernio-sdk/models/disable_sms_on_number200_response'
@@ -697,6 +703,8 @@ require 'zernio-sdk/models/get_sequence200_response'
697
703
  require 'zernio-sdk/models/get_sequence200_response_sequence'
698
704
  require 'zernio-sdk/models/get_sequence200_response_sequence_steps_inner'
699
705
  require 'zernio-sdk/models/get_sequence200_response_sequence_steps_inner_template'
706
+ require 'zernio-sdk/models/get_sip_trunk200_response'
707
+ require 'zernio-sdk/models/get_sip_trunk200_response_numbers_inner'
700
708
  require 'zernio-sdk/models/get_slack_settings200_response'
701
709
  require 'zernio-sdk/models/get_slack_settings200_response_account'
702
710
  require 'zernio-sdk/models/get_sms_registration200_response'
@@ -986,6 +994,9 @@ require 'zernio-sdk/models/list_sequence_enrollments200_response'
986
994
  require 'zernio-sdk/models/list_sequence_enrollments200_response_enrollments_inner'
987
995
  require 'zernio-sdk/models/list_sequences200_response'
988
996
  require 'zernio-sdk/models/list_sequences200_response_sequences_inner'
997
+ require 'zernio-sdk/models/list_sip_trunks200_response'
998
+ require 'zernio-sdk/models/list_sip_trunks200_response_trunks_inner'
999
+ require 'zernio-sdk/models/list_sip_trunks200_response_trunks_inner_termination'
989
1000
  require 'zernio-sdk/models/list_slack_members200_response'
990
1001
  require 'zernio-sdk/models/list_slack_members200_response_members_inner'
991
1002
  require 'zernio-sdk/models/list_sms_opt_outs200_response'
@@ -1174,6 +1185,7 @@ require 'zernio-sdk/models/review_webhook_review_reply'
1174
1185
  require 'zernio-sdk/models/review_webhook_review_reviewer'
1175
1186
  require 'zernio-sdk/models/revoke_connected_app200_response'
1176
1187
  require 'zernio-sdk/models/rf_prediction'
1188
+ require 'zernio-sdk/models/rotate_sip_trunk_credentials200_response'
1177
1189
  require 'zernio-sdk/models/saved_targeting_audience'
1178
1190
  require 'zernio-sdk/models/schedule_broadcast200_response'
1179
1191
  require 'zernio-sdk/models/schedule_broadcast200_response_broadcast'
@@ -1237,6 +1249,7 @@ require 'zernio-sdk/models/send_discord_direct_message_request'
1237
1249
  require 'zernio-sdk/models/send_discord_direct_message_request_attachments_inner'
1238
1250
  require 'zernio-sdk/models/send_inbox_message200_response'
1239
1251
  require 'zernio-sdk/models/send_inbox_message200_response_data'
1252
+ require 'zernio-sdk/models/send_inbox_message200_response_data_attachments_inner'
1240
1253
  require 'zernio-sdk/models/send_inbox_message400_response'
1241
1254
  require 'zernio-sdk/models/send_inbox_message400_response_platform_error'
1242
1255
  require 'zernio-sdk/models/send_inbox_message_request'
data/openapi.yaml CHANGED
@@ -25894,6 +25894,17 @@ paths:
25894
25894
  instead of sending the message a second time; same key + different body
25895
25895
  returns 422; a key still in flight returns 409. Works for JSON and
25896
25896
  multipart (file upload) requests alike. Keys are retained for 24 hours.
25897
+
25898
+ Only successful (2xx) responses are stored for replay: if the request
25899
+ throws or returns a non-2xx status, the key is released so the same key
25900
+ can be retried once the problem is fixed. The header therefore protects
25901
+ the "request succeeded but the response was lost" case. For an ambiguous
25902
+ failure (a 5xx or a network timeout), reconcile before retrying: a
25903
+ failure after the platform already accepted the message also releases
25904
+ the key, and a blind retry could send it twice. List the conversation's
25905
+ messages first, and treat an empty result as inconclusive rather than
25906
+ as proof nothing was sent, since a send that failed while being recorded
25907
+ leaves no trace on our side.
25897
25908
  tags: [Messages]
25898
25909
  security: [{ bearerAuth: [] }]
25899
25910
  parameters:
@@ -26434,10 +26445,20 @@ paths:
26434
26445
  data:
26435
26446
  type: object
26436
26447
  properties:
26437
- messageId: { type: string, description: ID of the sent message (not returned for Reddit) }
26438
- conversationId: { type: [string, "null"], description: Twitter conversation ID }
26439
- sentAt: { type: [string, "null"], format: date-time, description: Bluesky sent timestamp }
26440
- message: { type: [string, "null"], description: Success message (Reddit only) }
26448
+ messageId:
26449
+ type: string
26450
+ description: 'Platform id of the sent message (not returned for Reddit). For WhatsApp this is the raw Meta wamid, the same id delivered as message.platformMessageId on webhooks and delivery-status updates, and the value to pass as replyTo to quote-reply.'
26451
+ conversationId:
26452
+ type: string
26453
+ description: 'Zernio conversation id, echoed so the thread can be read back or replied to. It equals the id the list-conversations endpoint returns for Telegram, WhatsApp, SMS and Slack; for Facebook, Instagram, Bluesky and Reddit that endpoint returns the platform thread id instead, so do not correlate the two by equality. For X (Twitter), when the request addressed the conversation by its Twitter dm_conversation_id, that platform id is echoed back instead. Omitted when the send succeeded but the conversation could not be resolved to a stored record.'
26454
+ attachments:
26455
+ type: array
26456
+ description: 'Echo of the sent attachment with its resolved public URL, when one is available (Facebook, Instagram, Telegram, WhatsApp).'
26457
+ items:
26458
+ type: object
26459
+ properties:
26460
+ type: { type: string }
26461
+ url: { type: string }
26441
26462
  '400':
26442
26463
  description: 'Bad request (e.g., attachment not supported for platform, validation error, category combined with a template or attachment, category used on a non-WhatsApp account, or the WhatsApp Business Account is not eligible for Direct Send)'
26443
26464
  content:
@@ -31917,6 +31938,7 @@ paths:
31917
31938
  telnyxOrderId: { type: [string, "null"], description: Present once the number order has been placed (i.e. the requirement group was approved). Absent while still in identity review. }
31918
31939
  monthlyCents: { type: integer, description: Per-country monthly price in cents ($2..$25). }
31919
31940
  hostedByZernio: { type: boolean, description: "False for numbers you brought yourself (connected via Meta embedded signup) — they live on your own carrier, so SMS/Calls can't be enabled on them." }
31941
+ sipTrunkId: { type: [string, "null"], description: 'SIP trunk the number is attached to; null when not trunked. While attached, enabling Calls or WhatsApp calling, requesting WhatsApp verification, and releasing the number all return 409.' }
31920
31942
  profileId: { type: object }
31921
31943
  provisionedAt: { type: string, format: date-time }
31922
31944
  metaPreverifiedId: { type: string }
@@ -32007,6 +32029,7 @@ paths:
32007
32029
  endUserLastName: { type: [string, "null"] }
32008
32030
  regulatoryDeclineReason: { type: [string, "null"], description: Reviewer rejection reason when status is regulatory_declined. }
32009
32031
  provisionedAt: { type: string, format: date-time }
32032
+ sipTrunkId: { type: [string, "null"], description: 'SIP trunk the number is attached to; null when not trunked. While attached, enabling Calls or WhatsApp calling, requesting WhatsApp verification, and releasing the number all return 409.' }
32010
32033
  '401': { $ref: '#/components/responses/Unauthorized' }
32011
32034
  '404': { $ref: '#/components/responses/NotFound' }
32012
32035
  delete:
@@ -32048,6 +32071,7 @@ paths:
32048
32071
  '400': { description: Phone number is already released or being released }
32049
32072
  '401': { $ref: '#/components/responses/Unauthorized' }
32050
32073
  '404': { $ref: '#/components/responses/NotFound' }
32074
+ '409': { description: 'The number is attached to a SIP trunk; detach it first (code invalid_resource_state).' }
32051
32075
 
32052
32076
  /v1/phone-numbers/purchase:
32053
32077
  post:
@@ -32458,6 +32482,7 @@ paths:
32458
32482
  telnyxOrderId: { type: [string, "null"], description: Present once the number order has been placed (i.e. the requirement group was approved). Absent while still in identity review. }
32459
32483
  monthlyCents: { type: integer, description: Per-country monthly price in cents ($2..$25). }
32460
32484
  hostedByZernio: { type: boolean, description: "False for numbers you brought yourself (connected via Meta embedded signup) — they live on your own carrier, so SMS/Calls can't be enabled on them." }
32485
+ sipTrunkId: { type: [string, "null"], description: 'SIP trunk the number is attached to; null when not trunked. While attached, enabling Calls or WhatsApp calling, requesting WhatsApp verification, and releasing the number all return 409.' }
32461
32486
  profileId: { type: object }
32462
32487
  provisionedAt: { type: string, format: date-time }
32463
32488
  metaPreverifiedId: { type: string }
@@ -34355,6 +34380,7 @@ paths:
34355
34380
  ivrOptions: { type: array, items: { type: object, properties: { digit: { type: string }, forwardTo: { type: string }, label: { type: string } } } }
34356
34381
  '401': { $ref: '#/components/responses/Unauthorized' }
34357
34382
  '404': { description: Number not found }
34383
+ '409': { description: 'This number is attached to a SIP trunk; detach it first (code invalid_resource_state).' }
34358
34384
  '422': { description: "This number is hosted by your own carrier (brought via WhatsApp embedded signup), so calls can't be enabled on it." }
34359
34385
  delete:
34360
34386
  x-resource-group: "telephony"
@@ -34381,6 +34407,266 @@ paths:
34381
34407
  '401': { $ref: '#/components/responses/Unauthorized' }
34382
34408
  '404': { description: Number not found }
34383
34409
 
34410
+ /v1/phone-numbers/sip-trunks:
34411
+ post:
34412
+ x-resource-group: "telephony"
34413
+ operationId: createSipTrunk
34414
+ tags: [Voice]
34415
+ summary: Create a SIP trunk
34416
+ description: |
34417
+ Creates a SIP trunk an external voice platform (Retell, ElevenLabs,
34418
+ Vapi, or any SIP endpoint) can import your Zernio numbers into. The
34419
+ trunk carries both directions: inbound calls on attached numbers are
34420
+ delivered to `sipHost`, and the platform originates outbound calls
34421
+ through `termination.uri` with the digest credentials.
34422
+
34423
+ The `digestPassword` is returned only by this call (and by
34424
+ rotate-credentials); store it immediately. Attach any number of numbers
34425
+ to a trunk. Several trunks may point at the same host — each carries its
34426
+ own credentials and spend cap, so separate destination workspaces (e.g.
34427
+ an agency's clients) stay isolated.
34428
+
34429
+ security:
34430
+ - bearerAuth: []
34431
+ requestBody:
34432
+ required: true
34433
+ content:
34434
+ application/json:
34435
+ schema:
34436
+ type: object
34437
+ required: [label, sipHost]
34438
+ properties:
34439
+ label: { type: string, maxLength: 80, description: Display name for the trunk. }
34440
+ sipHost: { type: string, description: 'Fully-qualified hostname inbound calls are delivered to (e.g. sip.rtc.elevenlabs.io, sip.retellai.com).' }
34441
+ sipPort: { type: integer, minimum: 1, maximum: 65535, description: 'Defaults to 5061 for tls, 5060 otherwise.' }
34442
+ transport: { type: string, enum: [tls, tcp, udp], description: 'Signaling transport toward sipHost. Default tls (with SRTP media).' }
34443
+ responses:
34444
+ '201':
34445
+ description: Trunk created. The digest password is shown only here and on rotate.
34446
+ content:
34447
+ application/json:
34448
+ schema:
34449
+ type: object
34450
+ properties:
34451
+ id: { type: string }
34452
+ label: { type: string }
34453
+ sipHost: { type: string }
34454
+ sipPort: { type: integer }
34455
+ transport: { type: string, enum: [tls, tcp, udp] }
34456
+ termination:
34457
+ type: object
34458
+ properties:
34459
+ uri: { type: string, description: 'Telnyx termination host the platform dials for outbound (sip.telnyx.com).' }
34460
+ username: { type: string, description: SIP digest username. }
34461
+ numbersAttached: { type: integer }
34462
+ createdAt: { type: [string, "null"], format: date-time }
34463
+ digestPassword: { type: string, description: 'SIP digest password, shown only in this response.' }
34464
+ '400': { $ref: '#/components/responses/BadRequest' }
34465
+ '401': { $ref: '#/components/responses/Unauthorized' }
34466
+ '403': { description: 'SIP trunking is not enabled for this workspace, or the workspace is on legacy (non-usage-based) billing, which cannot invoice trunk call costs (code feature_not_available).' }
34467
+ '409': { description: 'The workspace trunk limit was reached (code invalid_resource_state).' }
34468
+ '422': { description: 'The host cannot be used as a trunk destination (e.g. a Zernio or carrier host).' }
34469
+ get:
34470
+ x-resource-group: "telephony"
34471
+ operationId: listSipTrunks
34472
+ tags: [Voice]
34473
+ summary: List SIP trunks
34474
+ security:
34475
+ - bearerAuth: []
34476
+ responses:
34477
+ '200':
34478
+ description: The workspace's trunks. Passwords are never included.
34479
+ content:
34480
+ application/json:
34481
+ schema:
34482
+ type: object
34483
+ properties:
34484
+ trunks:
34485
+ type: array
34486
+ items:
34487
+ type: object
34488
+ properties:
34489
+ id: { type: string }
34490
+ label: { type: string }
34491
+ sipHost: { type: string }
34492
+ sipPort: { type: integer }
34493
+ transport: { type: string, enum: [tls, tcp, udp] }
34494
+ termination:
34495
+ type: object
34496
+ properties:
34497
+ uri: { type: string }
34498
+ username: { type: string }
34499
+ numbersAttached: { type: integer }
34500
+ createdAt: { type: [string, "null"], format: date-time }
34501
+ enabled: { type: boolean, description: 'Whether this workspace can create SIP trunks. Managing existing trunks always works.' }
34502
+ '401': { $ref: '#/components/responses/Unauthorized' }
34503
+
34504
+ /v1/phone-numbers/sip-trunks/{id}:
34505
+ get:
34506
+ x-resource-group: "telephony"
34507
+ operationId: getSipTrunk
34508
+ tags: [Voice]
34509
+ summary: Get a SIP trunk
34510
+ security:
34511
+ - bearerAuth: []
34512
+ parameters:
34513
+ - { name: id, in: path, required: true, schema: { type: string } }
34514
+ responses:
34515
+ '200':
34516
+ description: Trunk detail, including the attached numbers.
34517
+ content:
34518
+ application/json:
34519
+ schema:
34520
+ type: object
34521
+ properties:
34522
+ id: { type: string }
34523
+ label: { type: string }
34524
+ sipHost: { type: string }
34525
+ sipPort: { type: integer }
34526
+ transport: { type: string, enum: [tls, tcp, udp] }
34527
+ termination:
34528
+ type: object
34529
+ properties:
34530
+ uri: { type: string }
34531
+ username: { type: string }
34532
+ numbersAttached: { type: integer }
34533
+ createdAt: { type: [string, "null"], format: date-time }
34534
+ numbers:
34535
+ type: array
34536
+ items:
34537
+ type: object
34538
+ properties:
34539
+ id: { type: string, description: 'Phone number record ID.' }
34540
+ phoneNumber: { type: string }
34541
+ '400': { $ref: '#/components/responses/BadRequest' }
34542
+ '401': { $ref: '#/components/responses/Unauthorized' }
34543
+ '404': { description: SIP trunk not found }
34544
+ delete:
34545
+ x-resource-group: "telephony"
34546
+ operationId: deleteSipTrunk
34547
+ tags: [Voice]
34548
+ summary: Delete a SIP trunk
34549
+ description: |
34550
+ Tears down the trunk and its carrier-side objects. Refused while any
34551
+ number is still attached: detach them first.
34552
+ security:
34553
+ - bearerAuth: []
34554
+ parameters:
34555
+ - { name: id, in: path, required: true, schema: { type: string } }
34556
+ responses:
34557
+ '200':
34558
+ description: Trunk deleted.
34559
+ content:
34560
+ application/json:
34561
+ schema:
34562
+ type: object
34563
+ properties:
34564
+ deleted: { type: boolean }
34565
+ '400': { $ref: '#/components/responses/BadRequest' }
34566
+ '401': { $ref: '#/components/responses/Unauthorized' }
34567
+ '404': { description: SIP trunk not found }
34568
+ '409': { description: 'Numbers are still attached to this trunk (code invalid_resource_state).' }
34569
+
34570
+ /v1/phone-numbers/sip-trunks/{id}/rotate-credentials:
34571
+ post:
34572
+ x-resource-group: "telephony"
34573
+ operationId: rotateSipTrunkCredentials
34574
+ tags: [Voice]
34575
+ summary: Rotate a SIP trunk's password
34576
+ description: |
34577
+ Mints a new digest password on the trunk. The old password stops
34578
+ working immediately, so update the destination platform right away.
34579
+ security:
34580
+ - bearerAuth: []
34581
+ parameters:
34582
+ - { name: id, in: path, required: true, schema: { type: string } }
34583
+ responses:
34584
+ '200':
34585
+ description: New credentials. The password is shown only here.
34586
+ content:
34587
+ application/json:
34588
+ schema:
34589
+ type: object
34590
+ properties:
34591
+ termination:
34592
+ type: object
34593
+ properties:
34594
+ uri: { type: string, description: 'Telnyx termination host the platform dials for outbound (sip.telnyx.com).' }
34595
+ username: { type: string, description: SIP digest username. }
34596
+ digestPassword: { type: string }
34597
+ '400': { $ref: '#/components/responses/BadRequest' }
34598
+ '401': { $ref: '#/components/responses/Unauthorized' }
34599
+ '404': { description: SIP trunk not found }
34600
+
34601
+ /v1/phone-numbers/{id}/sip-trunk:
34602
+ post:
34603
+ x-resource-group: "telephony"
34604
+ operationId: attachNumberToSipTrunk
34605
+ tags: [Voice]
34606
+ summary: Attach a number to a SIP trunk
34607
+ description: |
34608
+ Routes the number's calls to the trunk: the external platform receives
34609
+ its inbound directly and can present it as outbound caller ID. While
34610
+ attached, Zernio-side voice features are off for this number (call
34611
+ forwarding, IVR, voicemail, recording, the softphone, and WhatsApp
34612
+ calling), so the number must have Calls and WhatsApp calling disabled
34613
+ before attaching. SMS and WhatsApp messaging are unaffected.
34614
+ security:
34615
+ - bearerAuth: []
34616
+ parameters:
34617
+ - { name: id, in: path, required: true, schema: { type: string }, description: "Phone number record ID (from GET /v1/phone-numbers)." }
34618
+ requestBody:
34619
+ required: true
34620
+ content:
34621
+ application/json:
34622
+ schema:
34623
+ type: object
34624
+ required: [trunkId]
34625
+ properties:
34626
+ trunkId: { type: string, description: 'SIP trunk ID (from POST /v1/phone-numbers/sip-trunks).' }
34627
+ responses:
34628
+ '200':
34629
+ description: Number attached (idempotent for the same trunk).
34630
+ content:
34631
+ application/json:
34632
+ schema:
34633
+ type: object
34634
+ properties:
34635
+ attached: { type: boolean }
34636
+ phoneNumber: { type: string }
34637
+ trunkId: { type: string }
34638
+ '400': { $ref: '#/components/responses/BadRequest' }
34639
+ '401': { $ref: '#/components/responses/Unauthorized' }
34640
+ '403': { description: 'SIP trunking is not enabled for this workspace, or the workspace is on legacy (non-usage-based) billing, which cannot invoice trunk call costs (code feature_not_available).' }
34641
+ '404': { description: Number or trunk not found }
34642
+ '409': { description: 'The number still has Calls or WhatsApp calling enabled, is mid WhatsApp verification, is not active, or is attached to another trunk (code invalid_resource_state).' }
34643
+ '422': { description: 'This number is hosted by your own carrier (brought via WhatsApp embedded signup), so it cannot be trunked.' }
34644
+ delete:
34645
+ x-resource-group: "telephony"
34646
+ operationId: detachNumberFromSipTrunk
34647
+ tags: [Voice]
34648
+ summary: Detach a number from its SIP trunk
34649
+ description: |
34650
+ Returns the number's calls to Zernio routing. Idempotent when the
34651
+ number is not attached to any trunk.
34652
+ security:
34653
+ - bearerAuth: []
34654
+ parameters:
34655
+ - { name: id, in: path, required: true, schema: { type: string } }
34656
+ responses:
34657
+ '200':
34658
+ description: Number detached.
34659
+ content:
34660
+ application/json:
34661
+ schema:
34662
+ type: object
34663
+ properties:
34664
+ attached: { type: boolean, description: Always false after a successful detach. }
34665
+ phoneNumber: { type: string }
34666
+ '400': { $ref: '#/components/responses/BadRequest' }
34667
+ '401': { $ref: '#/components/responses/Unauthorized' }
34668
+ '404': { description: Number not found }
34669
+
34384
34670
  /v1/phone-numbers/{id}/sms:
34385
34671
  post:
34386
34672
  x-resource-group: "telephony"
@@ -34594,6 +34880,7 @@ paths:
34594
34880
  '400': { description: 'Invalid request (including forwardTo set to the number itself)' }
34595
34881
  '401': { $ref: '#/components/responses/Unauthorized' }
34596
34882
  '404': { description: Phone number not found }
34883
+ '409': { description: 'This number is attached to a SIP trunk; detach it first (code invalid_resource_state).' }
34597
34884
  '422': { description: "Not eligible to enable calling: not on usage-based billing, or the number's messaging limit is below Meta's ~2,000-daily-recipient threshold (TIER_250). Warm the number up to raise the limit." }
34598
34885
  patch:
34599
34886
  x-resource-group: "telephony"
@@ -34781,6 +35068,7 @@ paths:
34781
35068
  endUserLastName: { type: [string, "null"] }
34782
35069
  regulatoryDeclineReason: { type: [string, "null"], description: Reviewer rejection reason when status is regulatory_declined. }
34783
35070
  provisionedAt: { type: string, format: date-time }
35071
+ sipTrunkId: { type: [string, "null"], description: 'SIP trunk the number is attached to; null when not trunked. While attached, enabling Calls or WhatsApp calling, requesting WhatsApp verification, and releasing the number all return 409.' }
34784
35072
  '401': { $ref: '#/components/responses/Unauthorized' }
34785
35073
  '404': { $ref: '#/components/responses/NotFound' }
34786
35074
  delete:
@@ -34826,6 +35114,7 @@ paths:
34826
35114
  '400': { description: Phone number is already released or being released }
34827
35115
  '401': { $ref: '#/components/responses/Unauthorized' }
34828
35116
  '404': { $ref: '#/components/responses/NotFound' }
35117
+ '409': { description: 'The number is attached to a SIP trunk; detach it first (code invalid_resource_state).' }
34829
35118
 
34830
35119
  # ─── WhatsApp Sandbox (shared test number) ──────────────────────
34831
35120
  #
@@ -196,7 +196,7 @@ describe 'MessagesApi' do
196
196
 
197
197
  # unit tests for send_inbox_message
198
198
  # Send message
199
- # Send a message in a conversation. Supports text, attachments, quick replies, buttons, templates, and message tags. Attachment and interactive message support varies by platform. WhatsApp template messages: to send an approved template into this conversation (required when the 24-hour customer-service window is closed), use the &#x60;template&#x60; field with a single element carrying the template reference: &#x60;{ \&quot;elements\&quot;: [{ \&quot;name\&quot;: ..., \&quot;language\&quot;: ..., \&quot;components\&quot;: [...] }] }&#x60;. See the &#x60;template&#x60; field below for the exact shape. To send a template to a phone number you have no conversation with yet, use the create-conversation endpoint (POST /v1/inbox/conversations) instead. WhatsApp rich interactive messages (list, CTA URL, Flow, location request) are available via the &#x60;interactive&#x60; field. Tap events are delivered through the &#x60;message.received&#x60; webhook with WhatsApp-specific &#x60;metadata&#x60; fields (&#x60;interactiveType&#x60;, &#x60;interactiveId&#x60;, &#x60;flowResponseJson&#x60;, &#x60;flowResponseData&#x60;). **Idempotency:** send an &#x60;Idempotency-Key&#x60; header to make retries safe (e.g. after a client-side timeout where delivery is unknown): same key + same body replays the original response (with &#x60;Idempotent-Replayed: true&#x60;) instead of sending the message a second time; same key + different body returns 422; a key still in flight returns 409. Works for JSON and multipart (file upload) requests alike. Keys are retained for 24 hours.
199
+ # Send a message in a conversation. Supports text, attachments, quick replies, buttons, templates, and message tags. Attachment and interactive message support varies by platform. WhatsApp template messages: to send an approved template into this conversation (required when the 24-hour customer-service window is closed), use the &#x60;template&#x60; field with a single element carrying the template reference: &#x60;{ \&quot;elements\&quot;: [{ \&quot;name\&quot;: ..., \&quot;language\&quot;: ..., \&quot;components\&quot;: [...] }] }&#x60;. See the &#x60;template&#x60; field below for the exact shape. To send a template to a phone number you have no conversation with yet, use the create-conversation endpoint (POST /v1/inbox/conversations) instead. WhatsApp rich interactive messages (list, CTA URL, Flow, location request) are available via the &#x60;interactive&#x60; field. Tap events are delivered through the &#x60;message.received&#x60; webhook with WhatsApp-specific &#x60;metadata&#x60; fields (&#x60;interactiveType&#x60;, &#x60;interactiveId&#x60;, &#x60;flowResponseJson&#x60;, &#x60;flowResponseData&#x60;). **Idempotency:** send an &#x60;Idempotency-Key&#x60; header to make retries safe (e.g. after a client-side timeout where delivery is unknown): same key + same body replays the original response (with &#x60;Idempotent-Replayed: true&#x60;) instead of sending the message a second time; same key + different body returns 422; a key still in flight returns 409. Works for JSON and multipart (file upload) requests alike. Keys are retained for 24 hours. Only successful (2xx) responses are stored for replay: if the request throws or returns a non-2xx status, the key is released so the same key can be retried once the problem is fixed. The header therefore protects the \&quot;request succeeded but the response was lost\&quot; case. For an ambiguous failure (a 5xx or a network timeout), reconcile before retrying: a failure after the platform already accepted the message also releases the key, and a blind retry could send it twice. List the conversation&#39;s messages first, and treat an empty result as inconclusive rather than as proof nothing was sent, since a send that failed while being recorded leaves no trace on our side.
200
200
  # @param conversation_id Opaque conversation identifier, accepted verbatim from the list endpoint or from the conversationId on inbox webhooks. Format not to be assumed.
201
201
  # @param send_inbox_message_request
202
202
  # @param [Hash] opts the optional parameters