didww-v3 5.3.0 → 6.1.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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +7 -0
  3. data/CHANGELOG.md +68 -0
  4. data/README.md +214 -11
  5. data/didww-v3.gemspec +1 -0
  6. data/examples/README.md +14 -2
  7. data/examples/address_verifications.rb +50 -0
  8. data/examples/did_groups.rb +3 -1
  9. data/examples/did_history.rb +56 -0
  10. data/examples/dids.rb +7 -1
  11. data/examples/emergency_calling_services.rb +61 -0
  12. data/examples/emergency_requirement_validations.rb +47 -0
  13. data/examples/emergency_requirements.rb +48 -0
  14. data/examples/emergency_scenario.rb +204 -0
  15. data/examples/emergency_verifications.rb +66 -0
  16. data/examples/exports.rb +40 -6
  17. data/examples/identities_and_proofs.rb +7 -2
  18. data/examples/orders.rb +11 -2
  19. data/examples/orders_emergency.rb +63 -0
  20. data/examples/orders_reservation_dids.rb +1 -1
  21. data/examples/shared_capacity_groups.rb +1 -0
  22. data/examples/voice_in_trunk_groups.rb +6 -3
  23. data/examples/voice_in_trunks.rb +65 -0
  24. data/examples/voice_out_trunks.rb +41 -12
  25. data/lib/didww/callback/const.rb +2 -2
  26. data/lib/didww/client.rb +15 -5
  27. data/lib/didww/complex_objects/authentication_method.rb +18 -0
  28. data/lib/didww/complex_objects/authentication_methods/base.rb +29 -0
  29. data/lib/didww/complex_objects/authentication_methods/credentials_and_ip.rb +20 -0
  30. data/lib/didww/complex_objects/authentication_methods/generic.rb +34 -0
  31. data/lib/didww/complex_objects/authentication_methods/ip_only.rb +21 -0
  32. data/lib/didww/complex_objects/authentication_methods/twilio.rb +15 -0
  33. data/lib/didww/complex_objects/base.rb +35 -2
  34. data/lib/didww/complex_objects/configurations/sip_configuration.rb +142 -1
  35. data/lib/didww/complex_objects/emergency_order_item.rb +17 -0
  36. data/lib/didww/complex_objects/export_filters.rb +2 -3
  37. data/lib/didww/resource/address.rb +4 -0
  38. data/lib/didww/resource/{requirement.rb → address_requirement.rb} +1 -1
  39. data/lib/didww/resource/address_requirement_validation.rb +15 -0
  40. data/lib/didww/resource/address_verification.rb +13 -10
  41. data/lib/didww/resource/did.rb +8 -1
  42. data/lib/didww/resource/did_group.rb +10 -13
  43. data/lib/didww/resource/did_history.rb +72 -0
  44. data/lib/didww/resource/did_reservation.rb +1 -1
  45. data/lib/didww/resource/emergency_calling_service.rb +78 -0
  46. data/lib/didww/resource/emergency_requirement.rb +55 -0
  47. data/lib/didww/resource/emergency_requirement_validation.rb +24 -0
  48. data/lib/didww/resource/emergency_verification.rb +65 -0
  49. data/lib/didww/resource/encrypted_file.rb +17 -16
  50. data/lib/didww/resource/export.rb +15 -1
  51. data/lib/didww/resource/identity.rb +4 -3
  52. data/lib/didww/resource/order.rb +10 -5
  53. data/lib/didww/resource/permanent_supporting_document.rb +4 -0
  54. data/lib/didww/resource/proof.rb +4 -0
  55. data/lib/didww/resource/shared_capacity_group.rb +4 -0
  56. data/lib/didww/resource/voice_in_trunk.rb +4 -0
  57. data/lib/didww/resource/voice_in_trunk_group.rb +4 -0
  58. data/lib/didww/resource/voice_out_trunk.rb +47 -17
  59. data/lib/didww/types.rb +3 -1
  60. data/lib/didww/version.rb +1 -1
  61. metadata +38 -4
  62. data/lib/didww/resource/requirement_validation.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c780bc13f7f052f1733482ee69b37994b6bd8c9926a498857947faf23865c8d2
4
- data.tar.gz: 22be4b92ea6dd9616dc2e67b77b51b3305876b51b6fed40cba6ce7abb8d015e5
3
+ metadata.gz: 9cdeedbb317141c84de28bbedb4f652b858793e257d8d84f70155b08fc54623c
4
+ data.tar.gz: c48319beb5292ef53595e7905d4ac62ff0e59685bcb57eb48c2f7e6efbb7bd3e
5
5
  SHA512:
6
- metadata.gz: 0c80b1331067374890afec7505eeff625af09f4c36d6e093585483005091c56db11cfd36a4a6ba62547c3e3a300d9b7acc37d78502c8db45f8a91f6cf86c9b83
7
- data.tar.gz: 549b86aadb2a060b2ed3ae5f84e4d9f0140c012080087ee84b7feb800137957b0726f39c347a1574ea1109e09e8d39b875b2f416389e8633f4cd008f092ca44a
6
+ metadata.gz: 42be04280b6673a827de5f4bc680f032f28d72437137d081455f2168aa02e92d3861b7fc842e6c70406e6495d1f377745f90a23ead254fdf0a5a6caf40417d63
7
+ data.tar.gz: e6aeccf261500d0307c5cbc7eae427880a853d008ce28639ff2512daa1ea1cb40c54962b3d0f972f7e3ea0a73274bc40a6a8a57b9df45689b9c0ddfb83987acb
data/.gitignore CHANGED
@@ -10,3 +10,10 @@
10
10
 
11
11
  # rspec failure tracking
12
12
  .rspec_status
13
+
14
+ # claude settings (local-only, per-developer)
15
+ .claude/
16
+
17
+ # build artifacts
18
+ /*.gem
19
+
data/CHANGELOG.md CHANGED
@@ -4,6 +4,74 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [6.1.0] - 2026-05-04
8
+ ### Added
9
+ - Complete the 2026-04-16 `SipConfiguration` attribute set that was missed during the 2026-04-16 rollout (and is not present in the public Postman collection — server form is the source of truth):
10
+ - `enabled_sip_registration` (bool) — enables SIP registration; the API generates `incoming_auth_*` credentials when set true and requires `port` to be blank.
11
+ - `use_did_in_ruri` (bool) — uses DID number in R-URI; requires SIP registration to be enabled.
12
+ - `network_protocol_priority` (string enum) — `force_ipv4` / `force_ipv6` / `any` / `prefer_ipv4` / `prefer_ipv6`. Constants exposed as `NETWORK_PROTOCOL_PRIORITY_*` and `NETWORK_PROTOCOL_PRIORITIES`.
13
+ - `diversion_inject_mode` (string enum) — `none` / `did_number`. Constants exposed as `DIVERSION_INJECT_MODE_*` and `DIVERSION_INJECT_MODES`.
14
+ - `cnam_lookup` (bool) — enables CNAM resolution for inbound calls.
15
+ - `SipConfiguration#incoming_auth_username` and `#incoming_auth_password` — read-only server-generated SIP authentication credentials returned on `voice_in_trunks` when `enabled_sip_registration` is true.
16
+ - `ComplexObject::Base.property` accepts `read_only: true`. Read-only attributes are still parsed from server responses but are stripped from the JSON:API write payload, so round-tripping a loaded configuration through PATCH no longer triggers `400 Param not allowed`.
17
+ - `ComplexObject::Base.property` accepts `sensitive: true`. The default `#inspect` representation redacts those values with `[FILTERED]` so credentials never leak through default logging / REPL / unhandled exception output. Marked as sensitive: `SipConfiguration#auth_password`, `SipConfiguration#incoming_auth_username/password`, and `AuthenticationMethod::CredentialsAndIp#username/password`.
18
+ - `SipConfiguration` auto-cascades dependent fields whose constraints are server-enforced when set via property setters: `enabled_sip_registration = true` clears `host`/`port` (when previously set), `enabled_sip_registration = false` forces `use_did_in_ruri = false`, and assigning a non-blank `host` flips both flags. Constructor / `[]=` assignments bypass the cascade so server responses deserialize as-is.
19
+
20
+ ## [6.0.0] - 2026-04-24
21
+ Support for DIDWW API version **2026-04-16**. The gem now sends `X-DIDWW-API-Version: 2026-04-16` with every request by default. Users staying on API `2022-05-10` should pin to the [`2022-05-10`](https://github.com/didww/didww-v3-ruby/tree/2022-05-10) branch (5.x).
22
+
23
+ ### Breaking Changes
24
+ - Default `X-DIDWW-API-Version` header is now `2026-04-16`.
25
+ - Resource `requirement_validations` renamed to `address_requirement_validations`.
26
+ - Resource `requirements` renamed to `address_requirements`.
27
+ - `AddressRequirementValidation#requirement` relationship renamed to `address_requirement`.
28
+ - `DidGroup#requirement` relationship renamed to `address_requirement`.
29
+ - `DidReservation#expire_at` renamed to `expires_at`.
30
+ - `EncryptedFile#expire_at` renamed to `expires_at`.
31
+ - `AddressVerification#reject_reasons` is now an array of strings.
32
+ - Dropped `sms_out` from `DidGroup` features.
33
+ - `EncryptedFile` POST now accepts a single file per request.
34
+ - `Export` year/month filters replaced with `from`/`to` datetime range (`from` inclusive, `to` exclusive).
35
+ - `VoiceInTrunk` SIP configuration gains `diversion_relay_policy`.
36
+ - Attribute values standardized to lowercase `snake_case` (status/area-level enums, etc.).
37
+ - `Order#cancelled?` renamed to `canceled?`; `Order::STATUS_CANCELLED` → `STATUS_CANCELED` to match wire format (`status: "canceled"`).
38
+ - `VoiceOutTrunk`: flat credentials (`username`/`password`/`auth_type`) replaced with a polymorphic `authentication_method` relationship. Supported types: `CredentialsAuthenticationMethod`, `IpAuthenticationMethod`, `CredentialsAndIpAuthenticationMethod`, `TwilioAuthenticationMethod`, and `GenericAuthenticationMethod` (forward-compatible fallback).
39
+ - `DidGroup`: removed `features_human` and `FEATURES` constant.
40
+
41
+ ### Added
42
+ - New resource `DidHistory` (`/v3/did_history`) with `meta.billing_cycles_count_changed`.
43
+ - New resource `EmergencyRequirement` (`/v3/emergency_requirements`).
44
+ - New resource `EmergencyRequirementValidation` (`/v3/emergency_requirement_validations`).
45
+ - New resource `EmergencyCallingService` (`/v3/emergency_calling_services`) with `address` has-one relationship.
46
+ - New resource `EmergencyVerification` (`/v3/emergency_verifications`).
47
+ - `external_reference_id` attribute on `Address`, `AddressVerification`, `Export`, `EmergencyVerification`, `Order`, `PermanentSupportingDocument`, `Proof`, `SharedCapacityGroup`, `VoiceInTrunkGroup`, `VoiceInTrunk`, `VoiceOutTrunk`.
48
+ - PATCH support for `external_reference_id` on `/v3/address_verifications/:id`, `/v3/exports/:id`, `/v3/emergency_verifications/:id`.
49
+ - `VoiceOutTrunk`: `emergency_enable_all`, `rtp_timeout`, `emergency_dids` has-many relationship, status predicate helpers.
50
+ - `Did`: `emergency_enabled`, `emergency_calling_service` / `emergency_verification` / `identity` has-one relationships, PATCH support for unassigning `emergency_calling_service`.
51
+ - `Identity`: `birth_country` has-one relationship.
52
+ - `DidGroup`: new features `p2p`, `a2p`, `emergency`, `cnam_out`; `service_restrictions` attribute.
53
+ - `AddressVerification`: `reject_comment` attribute.
54
+ - `Order`: new `EmergencyOrderItem` complex object.
55
+ - Status predicate helpers on `VoiceOutTrunk`, `EmergencyCallingService`, `AddressVerification`, `EmergencyVerification`, and `Order`.
56
+ - `Export` exposes `STATUS_PENDING` and `STATUS_PROCESSING`.
57
+ - `randomize_cli` added to `OnCliMismatchAction` constants.
58
+ - Examples for all new 2026-04-16 resources and an end-to-end `emergency_scenario` example.
59
+
60
+ ### Changed
61
+ - Unknown `authentication_method` types are wrapped in `Generic` for forward compatibility.
62
+ - Resource-level meta support for `EmergencyCallingService` and `EmergencyRequirement`.
63
+ - `ExportFilters` from/to semantics clarified (from=inclusive, to=exclusive).
64
+ - Examples: fixtures use RFC 5737 documentation IPs (`203.0.113.0/24`); `SecureRandom.hex(4)` used for random IDs.
65
+ - Shared rspec examples extracted for PATCH `external_reference_id` and requirement validations POST→204.
66
+
67
+ ### Fixed
68
+ - Declare `has_one :address` on `EmergencyCallingService` to mirror server.
69
+ - `features_human` falls back to raw key for unknown features (superseded by removal in 6.0).
70
+
71
+ ## [5.3.1]
72
+ ### Fixed
73
+ - Correct `VoiceOutTrunk` constant wire values to match API responses.
74
+
7
75
  ## [5.3.0]
8
76
  ### Added
9
77
  - `DIDWW::Client.customize_connection` to allow customizing the Faraday connection (e.g. proxy, timeouts, custom middleware).
data/README.md CHANGED
@@ -16,15 +16,17 @@ This SDK uses [json_api_client](https://github.com/JsonApiClient/json_api_client
16
16
 
17
17
  Read more https://doc.didww.com/api
18
18
 
19
- This SDK sends the `X-DIDWW-API-Version: 2022-05-10` header with every request by default.
19
+ ## API Version
20
20
 
21
- Gem Versions **4.X.X** and branch [master](https://github.com/didww/didww-v3-ruby) are intended to use with DIDWW API 3 version [2022-05-10](https://doc.didww.com/api3/2022-05-10/index.html).
21
+ This SDK sends the `X-DIDWW-API-Version: 2026-04-16` header with every request by default.
22
22
 
23
- Gem Versions **3.X.X** and branch [release-3](https://github.com/didww/didww-v3-ruby/tree/release-3) are intended to use with DIDWW API 3 version [2021-12-15](https://doc.didww.com/api3/2021-12-15/index.html).
24
-
25
- Gem Versions **2.X.X** and branch [release-2](https://github.com/didww/didww-v3-ruby/tree/release-2) are intended to use with DIDWW API 3 version [2021-04-19](https://doc.didww.com/api3/2021-04-19/index.html).
26
-
27
- Gem Versions **1.X.X** and branch [release-1](https://github.com/didww/didww-v3-ruby/tree/release-1) are intended to use with DIDWW API 3 version [2017-09-18](https://doc.didww.com/api3/2017-09-18/index.html).
23
+ | Gem Version | Branch | DIDWW API Version |
24
+ |-------------|--------|-------------------|
25
+ | **6.x** | [`master`](https://github.com/didww/didww-v3-ruby) | [`2026-04-16`](https://doc.didww.com/api3/2026-04-16/index.html) |
26
+ | **5.x** | [`2022-05-10`](https://github.com/didww/didww-v3-ruby/tree/2022-05-10) | [`2022-05-10`](https://doc.didww.com/api3/2022-05-10/index.html) |
27
+ | **3.x** | [`release-3`](https://github.com/didww/didww-v3-ruby/tree/release-3) | [`2021-12-15`](https://doc.didww.com/api3/2021-12-15/index.html) |
28
+ | **2.x** | [`release-2`](https://github.com/didww/didww-v3-ruby/tree/release-2) | [`2021-04-19`](https://doc.didww.com/api3/2021-04-19/index.html) |
29
+ | **1.x** | [`release-1`](https://github.com/didww/didww-v3-ruby/tree/release-1) | [`2017-09-18`](https://doc.didww.com/api3/2017-09-18/index.html) |
28
30
 
29
31
  ## Installation
30
32
 
@@ -132,7 +134,7 @@ end
132
134
 
133
135
  ### API Version
134
136
 
135
- The SDK sends `X-DIDWW-API-Version: 2022-05-10` by default. You can override it per block:
137
+ The SDK sends `X-DIDWW-API-Version: 2026-04-16` by default. You can override it per block (e.g., to pin to a previous API version during migration):
136
138
 
137
139
  ```ruby
138
140
  DIDWW::Client.with_api_version('2022-05-10') do
@@ -203,6 +205,70 @@ trunk = DIDWW::Client.voice_in_trunks.new(
203
205
  trunk.save
204
206
  ```
205
207
 
208
+ #### SIP Registration (2026-04-16)
209
+
210
+ A Voice In Trunk can also be configured to authenticate inbound calls
211
+ via SIP registration credentials generated by DIDWW. The SDK
212
+ auto-cascades the dependent fields the server requires:
213
+
214
+ * setting `enabled_sip_registration = true` clears any previously-set
215
+ `host` / `port` (the server rejects them with 422 otherwise);
216
+ * setting `host` to a non-blank value flips `enabled_sip_registration`
217
+ back to `false` and forces `use_did_in_ruri = false` so the server
218
+ accepts the disable PATCH.
219
+
220
+ DIDWW generates `incoming_auth_username` and `incoming_auth_password`
221
+ and surfaces them in the response when SIP registration is enabled.
222
+
223
+ ```ruby
224
+ trunk = DIDWW::Client.voice_in_trunks.new(
225
+ name: 'Office (registered)',
226
+ configuration: DIDWW::ComplexObject::SipConfiguration.new.tap do |c|
227
+ c.enabled_sip_registration = true
228
+ c.use_did_in_ruri = true
229
+ c.cnam_lookup = true
230
+ end
231
+ )
232
+ trunk.save
233
+ # trunk.configuration.incoming_auth_username -- server-generated
234
+ # trunk.configuration.incoming_auth_password -- server-generated
235
+ ```
236
+
237
+ To disable SIP registration on an existing trunk, just set `host` —
238
+ the cascade flips `enabled_sip_registration` to `false` and
239
+ `use_did_in_ruri` to `false` automatically:
240
+
241
+ ```ruby
242
+ trunk.configuration = DIDWW::ComplexObject::SipConfiguration.new.tap do |c|
243
+ c.host = 'sip.example.com'
244
+ end
245
+ trunk.save
246
+ ```
247
+
248
+ ### Voice Out Trunks
249
+
250
+ Voice Out Trunks use a polymorphic `authentication_method` (2026-04-16). Three types are supported:
251
+
252
+ - **`credentials_and_ip`** -- default method; `username` and `password` are server-generated and returned in the response.
253
+ - **`twilio`** -- requires a `twilio_account_sid`.
254
+ - **`ip_only`** -- read-only; can only be configured by DIDWW staff upon request. Cannot be set via the API.
255
+
256
+ ```ruby
257
+ # NOTE: 203.0.113.0/24 is RFC 5737 TEST-NET-3 documentation space.
258
+ # Replace with the real CIDR of your SIP infrastructure.
259
+ trunk = DIDWW::Client.voice_out_trunks.new(
260
+ name: 'My Outbound Trunk',
261
+ on_cli_mismatch_action: 'reject_call',
262
+ authentication_method: DIDWW::ComplexObject::AuthenticationMethod::CredentialsAndIp.new(
263
+ allowed_sip_ips: ['203.0.113.0/24']
264
+ )
265
+ )
266
+
267
+ trunk.save
268
+ # trunk.authentication_method.username -- server-generated
269
+ # trunk.authentication_method.password -- server-generated
270
+ ```
271
+
206
272
  ### Orders
207
273
 
208
274
  ```ruby
@@ -219,16 +285,141 @@ order = DIDWW::Client.orders.new(
219
285
  order.save
220
286
  ```
221
287
 
288
+ ### Emergency Services
289
+
290
+ ```ruby
291
+ # List emergency requirements with filters
292
+ requirements = DIDWW::Client.emergency_requirements
293
+ .includes(:country, :did_group_type)
294
+ .all
295
+
296
+ # Filter by country
297
+ requirements = DIDWW::Client.emergency_requirements
298
+ .where('country.id': 'country-uuid')
299
+ .all
300
+
301
+ requirements.each do |req|
302
+ puts req.identity_type
303
+ puts req.address_area_level
304
+ puts req.estimate_setup_time # e.g. "7-14 days"
305
+ puts req.requirement_restriction_message
306
+ end
307
+
308
+ # Create an emergency verification
309
+ verification = DIDWW::Client.emergency_verifications.new(
310
+ address: DIDWW::Resource::Address.load(id: 'address-uuid'),
311
+ emergency_calling_service:
312
+ DIDWW::Resource::EmergencyCallingService.load(id: 'ecs-uuid'),
313
+ dids: [DIDWW::Resource::Did.load(id: 'did-uuid')],
314
+ external_reference_id: 'my-ref-123'
315
+ )
316
+
317
+ if verification.save
318
+ puts "Created: #{verification.id} (status: #{verification.status})"
319
+ else
320
+ puts "Errors: #{verification.errors.full_messages}"
321
+ end
322
+
323
+ # List emergency calling services
324
+ services = DIDWW::Client.emergency_calling_services
325
+ .includes(:country, :did_group_type, :dids)
326
+ .all
327
+
328
+ services.each do |svc|
329
+ puts "#{svc.name} — #{svc.status}"
330
+ end
331
+
332
+ # Cancel (destroy) an emergency calling service
333
+ svc = DIDWW::Client.emergency_calling_services.find('uuid').first
334
+ svc.destroy
335
+ ```
336
+
337
+ ### DID History
338
+
339
+ ```ruby
340
+ # List recent DID history events (retained for the last 90 days)
341
+ events = DIDWW::Client.did_history.all
342
+
343
+ events.each do |event|
344
+ puts "#{event.created_at.iso8601} #{event.did_number} #{event.action} via #{event.method}"
345
+ end
346
+
347
+ # Filter by action
348
+ assigned = DIDWW::Client.did_history
349
+ .where(action: DIDWW::Resource::DidHistory::ACTION_ASSIGNED)
350
+ .all
351
+
352
+ # Filter by DID number
353
+ per_number = DIDWW::Client.did_history
354
+ .where(did_number: '12125551234')
355
+ .all
356
+
357
+ # Filter by date range
358
+ seven_days_ago = (Time.now - 7 * 24 * 60 * 60).iso8601
359
+ recent = DIDWW::Client.did_history
360
+ .where(created_at_gteq: seven_days_ago)
361
+ .all
362
+ ```
363
+
364
+ ## Error Handling
365
+
366
+ The SDK uses [json_api_client](https://github.com/JsonApiClient/json_api_client) which raises exceptions for HTTP-level errors. Validation errors from the API are returned on the resource's `errors` collection after a failed `save`.
367
+
368
+ ```ruby
369
+ # Validation errors (422 Unprocessable Entity)
370
+ trunk = DIDWW::Client.voice_in_trunks.new(name: '')
371
+ unless trunk.save
372
+ trunk.errors.full_messages.each do |msg|
373
+ puts "Validation error: #{msg}"
374
+ end
375
+ end
376
+
377
+ # HTTP errors (404, 401, 500, etc.)
378
+ begin
379
+ DIDWW::Client.dids.find('nonexistent-uuid')
380
+ rescue JsonApiClient::Errors::NotFound => e
381
+ puts "Not found: #{e.message}"
382
+ rescue JsonApiClient::Errors::AccessDenied => e
383
+ puts "Access denied: #{e.message}"
384
+ rescue JsonApiClient::Errors::ServerError => e
385
+ puts "Server error: #{e.message}"
386
+ rescue JsonApiClient::Errors::ConnectionError => e
387
+ puts "Connection error: #{e.message}"
388
+ end
389
+ ```
390
+
391
+ ## Dirty Tracking
392
+
393
+ The SDK (via `json_api_client`) tracks which attributes have been modified. When you call `save` on a fetched resource, the resulting PATCH request sends only the changed attributes, avoiding unintended overwrites of server-side values.
394
+
395
+ ```ruby
396
+ did = DIDWW::Client.dids.find('uuid').first
397
+ did.description = 'Updated'
398
+ did.save
399
+ # PATCH payload includes only "description", not all attributes
400
+ ```
401
+
222
402
  ## Date and Datetime Fields
223
403
 
224
404
  The SDK distinguishes between date-only and datetime fields:
225
405
 
226
406
  - **Datetime fields** — deserialized as `Time`:
227
- - All `created_at` fields — present on most resources (`EncryptedFile` has no `created_at`)
228
- - Expiry fields: `Did#expires_at`, `DidReservation#expire_at`, `Proof#expires_at`, `EncryptedFile#expire_at`
407
+ - `created_at` — present on most resources
408
+ - `expires_at` `Did`, `DidReservation`, `Proof`, `EncryptedFile` (nullable)
409
+ - `activated_at` — `EmergencyCallingService` (nullable)
410
+ - `canceled_at` — `EmergencyCallingService` (nullable)
229
411
  - **Date-only fields** — deserialized as `Date`:
230
412
  - `Identity#birth_date`
231
- - **Date-only fields kept as strings** (`CapacityPool#renew_date`) remain as `String`.
413
+ - **Date-only fields kept as strings** remain as `String`:
414
+ - `CapacityPool#renew_date`, `EmergencyCallingService#renew_date` — `"YYYY-MM-DD"` (nullable)
415
+ - **String fields** (not numeric):
416
+ - `EmergencyRequirement#estimate_setup_time` — e.g. `"7-14 days"`, `"1"`
417
+ - `EmergencyRequirement#requirement_restriction_message` — nullable
418
+
419
+ **Important changes from previous API versions:**
420
+ - `expire_at` renamed to `expires_at` on `DidReservation` and `EncryptedFile`
421
+ - `renew_date` is a date-only string, NOT a datetime
422
+ - `estimate_setup_time` is a string, NOT an integer
232
423
 
233
424
  ```ruby
234
425
  did = DIDWW::Client.dids.find("uuid").first
@@ -283,6 +474,18 @@ class WebhooksController < ApplicationController
283
474
  end
284
475
  ```
285
476
 
477
+ ## Enums
478
+
479
+ The SDK provides constants for all API option fields. These are defined as constants on their respective modules/classes:
480
+
481
+ `CallbackMethod`, `IdentityType`, `OrderStatus`, `ExportType`, `ExportStatus`, `CliFormat`,
482
+ `OnCliMismatchAction`\*, `MediaEncryptionMode`, `DefaultDstAction`, `VoiceOutTrunkStatus`,
483
+ `EmergencyCallingServiceStatus`, `EmergencyVerificationStatus`, `DiversionRelayPolicy`,
484
+ `TransportProtocol`, `Codec`, `RxDtmfFormat`, `TxDtmfFormat`, `SstRefreshMethod`,
485
+ `ReroutingDisconnectCode`, `Feature`, `AreaLevel`, `AddressVerificationStatus`, `StirShakenMode`
486
+
487
+ \* `REPLACE_CLI` and `RANDOMIZE_CLI` require additional account configuration. Contact DIDWW support to enable these values.
488
+
286
489
  ## Development
287
490
 
288
491
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/didww-v3.gemspec CHANGED
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
26
26
 
27
27
  spec.add_dependency 'activesupport'
28
28
  spec.add_dependency 'faraday'
29
+ spec.add_dependency 'faraday-multipart'
29
30
  spec.add_dependency 'json_api_client', '1.23.0'
30
31
  spec.add_dependency 'http'
31
32
  spec.add_dependency 'down'
data/examples/README.md CHANGED
@@ -34,7 +34,8 @@ DIDWW_API_KEY=your_api_key ruby examples/orders_nanpa.rb
34
34
  | [`regions.rb`](regions.rb) | Lists regions with filters/includes and fetches a specific region. |
35
35
  | [`did_groups.rb`](did_groups.rb) | Fetches DID groups with included SKUs and shows group details. |
36
36
  | [`dids.rb`](dids.rb) | Updates DID routing/capacity by assigning trunk and capacity pool. |
37
- | [`exports.rb`](exports.rb) | Lists CDR exports and their details. |
37
+ | [`did_history.rb`](did_history.rb) | Lists DID ownership history (last 90 days, 2026-04-16). |
38
+ | [`exports.rb`](exports.rb) | Creates and lists CDR exports, with 2026-04-16 external_reference_id. |
38
39
 
39
40
  ### Voice In (Inbound)
40
41
  | Script | Description |
@@ -77,7 +78,18 @@ DIDWW_API_KEY=your_api_key ruby examples/orders_nanpa.rb
77
78
  ### Compliance & Verification
78
79
  | Script | Description |
79
80
  |---|---|
80
- | [`identities_and_proofs.rb`](identities_and_proofs.rb) | Creates identities, addresses, and demonstrates proof workflow. |
81
+ | [`identities_and_proofs.rb`](identities_and_proofs.rb) | Creates identities, addresses, and demonstrates proof workflow (2026-04-16 birth_country). |
82
+ | [`address_verifications.rb`](address_verifications.rb) | Lists address verifications with 2026-04-16 reject_comment / external_reference_id. |
83
+
84
+ ### Emergency Services (2026-04-16)
85
+ | Script | Description |
86
+ |---|---|
87
+ | [`emergency_requirements.rb`](emergency_requirements.rb) | Lists emergency service requirements per country/did_group_type. |
88
+ | [`emergency_calling_services.rb`](emergency_calling_services.rb) | Lists and cancels customer emergency calling services. |
89
+ | [`emergency_verifications.rb`](emergency_verifications.rb) | Lists and creates emergency verifications. |
90
+ | [`emergency_requirement_validations.rb`](emergency_requirement_validations.rb) | Pre-validates an emergency order triple (requirement + address + identity). |
91
+ | [`orders_emergency.rb`](orders_emergency.rb) | Inspects server-created Emergency orders and `emergency_order_items`. |
92
+ | [`emergency_scenario.rb`](emergency_scenario.rb) | End-to-end: find DID → check requirements → validate → create verification → get service. |
81
93
 
82
94
  ## Troubleshooting
83
95
 
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ # Lists Address Verifications (with 2026-04-16 reject_comment / external_reference_id).
3
+ #
4
+ # AddressVerification ties an address to one or more DIDs and a set of
5
+ # supporting documents so DIDWW compliance can approve or reject the
6
+ # declaration. 2026-04-16 adds:
7
+ # * reject_comment — free-form comment accompanying a rejection
8
+ # * external_reference_id — customer-supplied reference (max 100 chars)
9
+ #
10
+ # Usage: DIDWW_API_KEY=your_api_key ruby examples/address_verifications.rb
11
+
12
+ require 'bundler/setup'
13
+ require 'didww'
14
+
15
+ DIDWW::Client.configure do |client|
16
+ client.api_key = ENV.fetch('DIDWW_API_KEY') { abort 'Please set DIDWW_API_KEY' }
17
+ client.api_mode = :sandbox
18
+ end
19
+
20
+ puts '=== Address Verifications ==='
21
+ verifications = DIDWW::Client.address_verifications
22
+ .includes(:address, :dids)
23
+ .all
24
+ puts "Found #{verifications.size} address verifications"
25
+
26
+ verifications.first(5).each do |av|
27
+ puts "\nVerification: #{av.id}"
28
+ puts " Reference: #{av.reference}"
29
+ puts " Status: #{av.status}"
30
+ puts " External Reference: #{av.external_reference_id}" if av.external_reference_id
31
+ puts " Service description: #{av.service_description}" if av.service_description
32
+ puts " Address: #{av.address&.id}"
33
+ puts " DIDs: #{av.dids.map(&:number).join(', ')}" if av.dids&.any?
34
+ if av.rejected?
35
+ puts " Reject reasons: #{Array(av.reject_reasons).join(', ')}"
36
+ puts " Reject comment: #{av.reject_comment}" if av.reject_comment
37
+ end
38
+ end
39
+
40
+ # Filter: only rejected verifications
41
+ puts "\n=== Rejected verifications ==="
42
+ rejected = DIDWW::Client.address_verifications
43
+ .where(status: DIDWW::Resource::AddressVerification::STATUS_REJECTED)
44
+ .all
45
+ puts "Found #{rejected.size} rejected verifications"
46
+ rejected.first(3).each do |av|
47
+ puts " #{av.reference}: #{av.reject_comment || Array(av.reject_reasons).join(', ')}"
48
+ end
49
+
50
+ puts "\nAvailable statuses: #{DIDWW::Resource::AddressVerification::STATUSES.join(', ')}"
@@ -24,7 +24,9 @@ did_groups.first(3).each do |did_group|
24
24
  puts " Area: #{did_group.area_name}"
25
25
  puts " Prefix: #{did_group.prefix}"
26
26
  puts " Metered: #{did_group.is_metered}"
27
- puts " Features: #{did_group.features_human}"
27
+ puts " Features: #{did_group.features.join(', ')}" # 2026-04-16 adds p2p / a2p / emergency / cnam_out
28
+ puts " Allow additional channels: #{did_group.allow_additional_channels}" # 2026-04-16
29
+ puts " Service restrictions: #{did_group.service_restrictions}" if did_group.service_restrictions # 2026-04-16
28
30
 
29
31
  if did_group.stock_keeping_units && !did_group.stock_keeping_units.empty?
30
32
  puts ' SKUs:'
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ # Lists DID ownership history (2026-04-16).
3
+ # Records are retained for the last 90 days only.
4
+ #
5
+ # Server-side filters supported:
6
+ # did_number (eq), action (eq), method (eq),
7
+ # created_at_gteq, created_at_lteq
8
+ #
9
+ # Usage: DIDWW_API_KEY=your_api_key ruby examples/did_history.rb
10
+
11
+ require 'bundler/setup'
12
+ require 'didww'
13
+
14
+ DIDWW::Client.configure do |client|
15
+ client.api_key = ENV.fetch('DIDWW_API_KEY') { abort 'Please set DIDWW_API_KEY' }
16
+ client.api_mode = :sandbox
17
+ end
18
+
19
+ # List most recent DID history events (server sorts by created_at desc)
20
+ puts '=== Recent DID History ==='
21
+ events = DIDWW::Client.did_history.all
22
+ puts "Found #{events.size} events in the last 90 days"
23
+
24
+ events.first(10).each do |event|
25
+ puts "#{event.created_at.iso8601} #{event.did_number.ljust(16)} #{event.action.ljust(28)} via #{event.method}"
26
+ end
27
+
28
+ # Filter by action
29
+ puts "\n=== Only 'assigned' events ==="
30
+ assigned = DIDWW::Client.did_history
31
+ .where(action: DIDWW::Resource::DidHistory::ACTION_ASSIGNED)
32
+ .all
33
+ puts "Found #{assigned.size} assignments"
34
+
35
+ # Filter by a specific DID number
36
+ if (first_event = events.first)
37
+ number = first_event.did_number
38
+ puts "\n=== History for DID #{number} ==="
39
+ per_number = DIDWW::Client.did_history
40
+ .where(did_number: number)
41
+ .all
42
+ per_number.each do |event|
43
+ puts "#{event.created_at.iso8601} #{event.action} via #{event.method}"
44
+ end
45
+ end
46
+
47
+ # Filter by date range (last 7 days)
48
+ seven_days_ago = (Time.now - 7 * 24 * 60 * 60).iso8601
49
+ puts "\n=== History since #{seven_days_ago} ==="
50
+ recent = DIDWW::Client.did_history
51
+ .where(created_at_gteq: seven_days_ago)
52
+ .all
53
+ puts "Found #{recent.size} events in the last 7 days"
54
+
55
+ puts "\nAvailable actions: #{DIDWW::Resource::DidHistory::ACTIONS.join(', ')}"
56
+ puts "Available methods: #{DIDWW::Resource::DidHistory::METHODS.join(', ')}"
data/examples/dids.rb CHANGED
@@ -11,9 +11,10 @@ DIDWW::Client.configure do |client|
11
11
  client.api_mode = :sandbox
12
12
  end
13
13
 
14
- # Get the last ordered DID
14
+ # Get the last ordered DID (include 2026-04-16 emergency relationships)
15
15
  puts '=== Finding last ordered DID ==='
16
16
  dids = DIDWW::Client.dids
17
+ .includes(:identity, :emergency_calling_service, :emergency_verification)
17
18
  .all
18
19
 
19
20
  if dids.empty?
@@ -23,6 +24,11 @@ end
23
24
 
24
25
  did = dids.first
25
26
  puts "Selected DID: #{did.id}"
27
+ puts " Number: #{did.number}"
28
+ puts " Emergency enabled: #{did.emergency_enabled}" if did.respond_to?(:emergency_enabled)
29
+ puts " Emergency Calling Service: #{did.emergency_calling_service&.id}" if did.emergency_calling_service
30
+ puts " Emergency Verification: #{did.emergency_verification&.id}" if did.emergency_verification
31
+ puts " Identity: #{did.identity&.id}" if did.identity
26
32
 
27
33
  # Get last SIP trunk
28
34
  puts "\n=== Finding SIP trunk ==="
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ # Lists and cancels customer Emergency Calling Services (2026-04-16).
3
+ #
4
+ # An EmergencyCallingService represents a customer's 911/112 subscription
5
+ # attached to one or more DIDs. It ties an address, identity, DID group type
6
+ # and country together with a pricing snapshot (meta.setup_price, meta.monthly_price).
7
+ #
8
+ # Supported operations: index, show, destroy (cancel).
9
+ #
10
+ # Usage: DIDWW_API_KEY=your_api_key ruby examples/emergency_calling_services.rb
11
+
12
+ require 'bundler/setup'
13
+ require 'didww'
14
+
15
+ DIDWW::Client.configure do |client|
16
+ client.api_key = ENV.fetch('DIDWW_API_KEY') { abort 'Please set DIDWW_API_KEY' }
17
+ client.api_mode = :sandbox
18
+ end
19
+
20
+ puts '=== Emergency Calling Services ==='
21
+ services = DIDWW::Client.emergency_calling_services
22
+ .includes(:country, :did_group_type, :dids)
23
+ .all
24
+ puts "Found #{services.size} emergency calling services"
25
+
26
+ services.first(5).each do |svc|
27
+ puts "\nService: #{svc.id}"
28
+ puts " Name: #{svc.name}"
29
+ puts " Reference: #{svc.reference}"
30
+ puts " Status: #{svc.status}"
31
+ puts " Country: #{svc.country&.name}"
32
+ puts " DID Group Type: #{svc.did_group_type&.name}"
33
+ puts " Activated: #{svc.activated_at}"
34
+ puts " Canceled: #{svc.canceled_at}" if svc.canceled_at
35
+ puts " Renews: #{svc.renew_date}" if svc.renew_date
36
+ puts " Attached DIDs: #{svc.dids.map(&:number).join(', ')}" if svc.dids&.any?
37
+ if svc.meta
38
+ puts " Setup price: #{svc.meta[:setup_price]}" if svc.meta[:setup_price]
39
+ puts " Monthly price: #{svc.meta[:monthly_price]}" if svc.meta[:monthly_price]
40
+ end
41
+ end
42
+
43
+ # Filter by status
44
+ puts "\n=== Only active emergency calling services ==="
45
+ active = DIDWW::Client.emergency_calling_services
46
+ .where(status: 'active')
47
+ .all
48
+ puts "Found #{active.size} active services"
49
+
50
+ # Cancel a service (destroy) — uncomment to try:
51
+ #
52
+ # if (svc = services.find { |s| s.status == 'active' })
53
+ # puts "\nCancelling service #{svc.id}..."
54
+ # if svc.destroy
55
+ # puts 'Service canceled'
56
+ # else
57
+ # puts "Error: #{svc.errors.full_messages}"
58
+ # end
59
+ # end
60
+
61
+ puts "\nAvailable statuses: #{DIDWW::Resource::EmergencyCallingService::STATUSES.join(', ')}"
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+ # Validates an emergency calling service order before placing it (2026-04-16).
3
+ #
4
+ # EmergencyRequirementValidation is a write-only endpoint: POST the
5
+ # intended (emergency_requirement, address, identity) triple and the server
6
+ # either returns 204 No Content (OK to order) or JSONAPI errors describing
7
+ # what the customer must fix (missing address fields, wrong identity type,
8
+ # unsupported area level, etc.).
9
+ #
10
+ # Usage: DIDWW_API_KEY=your_api_key ruby examples/emergency_requirement_validations.rb
11
+
12
+ require 'bundler/setup'
13
+ require 'didww'
14
+
15
+ DIDWW::Client.configure do |client|
16
+ client.api_key = ENV.fetch('DIDWW_API_KEY') { abort 'Please set DIDWW_API_KEY' }
17
+ client.api_mode = :sandbox
18
+ end
19
+
20
+ # Pick any emergency requirement + address + identity from your account.
21
+ requirement = DIDWW::Client.emergency_requirements.all.first
22
+ address = DIDWW::Client.addresses.all.first
23
+ identity = DIDWW::Client.identities.all.first
24
+
25
+ abort 'No emergency_requirements found on this account' unless requirement
26
+ abort 'No addresses found on this account' unless address
27
+ abort 'No identities found on this account' unless identity
28
+
29
+ puts 'Validating order setup with:'
30
+ puts " Emergency Requirement: #{requirement.id}"
31
+ puts " Address: #{address.id}"
32
+ puts " Identity: #{identity.id}"
33
+
34
+ validation = DIDWW::Client.emergency_requirement_validation.new(
35
+ relationships: {
36
+ emergency_requirement: requirement,
37
+ address: address,
38
+ identity: identity
39
+ }
40
+ )
41
+
42
+ if validation.save
43
+ puts "\nValidation passed — this combination can be used to order emergency calling."
44
+ else
45
+ puts "\nValidation failed:"
46
+ validation.errors.full_messages.each { |msg| puts " * #{msg}" }
47
+ end