Skip to content

Commit 966ef18

Browse files
authored
Merge pull request #27 from didww/feat/api-2026-04-16
Add API version 2026-04-16 support
2 parents b3f29f1 + 0213ff7 commit 966ef18

166 files changed

Lines changed: 3576 additions & 454 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Claude AI
2+
.claude/
3+
4+
# Coverage
5+
coverage.out
6+
7+
# Compiled example binaries (go build artifacts)
8+
address_verifications
9+
did_groups
10+
did_history
11+
dids
12+
emergency_calling_services
13+
emergency_requirement_validations
14+
emergency_requirements
15+
emergency_verifications
16+
exports
17+
identities
18+
orders
19+
orders_emergency
20+
shared_capacity_groups
21+
voice_in_trunk_groups
22+
voice_out_trunks
23+
/emergency_scenario
24+
/did_trunk_assignment

README.md

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ This SDK implements JSON:API serialization and deserialization without external
1515
Read more https://doc.didww.com/api
1616

1717
This SDK targets DIDWW API v3 documentation version:
18-
[https://doc.didww.com/api3/2022-05-10/index.html](https://doc.didww.com/api3/2022-05-10/index.html)
18+
[https://doc.didww.com/api3/2026-04-16/index.html](https://doc.didww.com/api3/2026-04-16/index.html)
1919

20-
The client sends the `X-DIDWW-API-Version: 2022-05-10` header with each request.
20+
The client sends the `X-DIDWW-API-Version: 2026-04-16` header with each request.
21+
22+
Version **3.x** targets API version `2026-04-16`.
23+
Version **2.x** (branch `release-2`) targets API version `2022-05-10`.
2124

2225
## Requirements
2326

@@ -29,6 +32,11 @@ The client sends the `X-DIDWW-API-Version: 2022-05-10` header with each request.
2932
go get github.com/didww/didww-api-3-go-sdk
3033
```
3134

35+
**Note on module path:** This module intentionally does not use a `/v3` suffix,
36+
following the same convention established in v2. The major version is bumped in
37+
`go.mod` and tagged releases, but the import path stays
38+
`github.com/didww/didww-api-3-go-sdk` for backward compatibility.
39+
3240
## Usage
3341

3442
```go
@@ -161,8 +169,14 @@ proofTypes, _ := client.ProofTypes().List(ctx, nil)
161169
// Public Keys
162170
publicKeys, _ := client.PublicKeys().List(ctx, nil)
163171

164-
// Requirements
165-
requirements, _ := client.Requirements().List(ctx, nil)
172+
// Address Requirements
173+
requirements, _ := client.AddressRequirements().List(ctx, nil)
174+
175+
// Emergency Requirements (2026-04-16)
176+
emergReqs, _ := client.EmergencyRequirements().List(ctx, nil)
177+
178+
// DID History (2026-04-16)
179+
history, _ := client.DIDHistory().List(ctx, nil)
166180

167181
// Supporting Document Templates
168182
templates, _ := client.SupportingDocumentTemplates().List(ctx, nil)
@@ -232,19 +246,32 @@ created, _ := client.VoiceInTrunkGroups().Create(ctx, group)
232246
> **Note:** Voice Out Trunks require additional account configuration. Contact DIDWW support to enable.
233247
> The `replace_cli` and `randomize_cli` values of `OnCliMismatchAction` also require account configuration.
234248
249+
Voice Out Trunks use a polymorphic `AuthenticationMethod` (2026-04-16). Three types are supported:
250+
251+
- **`credentials_and_ip`** -- default method; `Username` and `Password` are server-generated and returned in the response.
252+
- **`twilio`** -- requires a `TwilioAccountSid`.
253+
- **`ip_only`** -- read-only; can only be configured by DIDWW staff upon request. Cannot be set via the API.
254+
235255
```go
236-
import "github.com/didww/didww-api-3-go-sdk/resource/enums"
256+
import (
257+
"github.com/didww/didww-api-3-go-sdk/resource/authenticationmethod"
258+
"github.com/didww/didww-api-3-go-sdk/resource/enums"
259+
)
237260

261+
// NOTE: 203.0.113.0/24 is RFC 5737 TEST-NET-3 documentation space.
262+
// Replace with the real CIDR of your SIP infrastructure.
238263
trunk := &didww.VoiceOutTrunk{
239-
Name: "My Outbound Trunk",
240-
AllowedSipIPs: []string{"0.0.0.0/0"},
241-
AllowedRtpIPs: []string{"0.0.0.0/0"},
242-
DstPrefixes: []string{},
264+
Name: "My Outbound Trunk",
265+
AuthenticationMethod: &authenticationmethod.CredentialsAndIp{
266+
AllowedSipIPs: []string{"203.0.113.0/24"},
267+
},
243268
DefaultDstAction: enums.DefaultDstActionAllowAll,
244269
OnCliMismatchAction: enums.OnCliMismatchActionRejectCall,
245270
MediaEncryptionMode: enums.MediaEncryptionModeDisabled,
246271
}
247272
created, _ := client.VoiceOutTrunks().Create(ctx, trunk)
273+
// created.AuthenticationMethod.(*authenticationmethod.CredentialsAndIp).Username -- server-generated
274+
// created.AuthenticationMethod.(*authenticationmethod.CredentialsAndIp).Password -- server-generated
248275
```
249276

250277
### Orders
@@ -335,14 +362,41 @@ verification := &didww.AddressVerification{
335362
created, _ := client.AddressVerifications().Create(ctx, verification)
336363
```
337364

365+
### Emergency Services (2026-04-16)
366+
367+
```go
368+
// List emergency requirements filtered by country
369+
params := didww.NewQueryParams().Filter("country.id", "country-uuid")
370+
reqs, _ := client.EmergencyRequirements().List(ctx, params)
371+
372+
// Create emergency verification
373+
verification := &didww.EmergencyVerification{
374+
AddressID: "address-uuid",
375+
IdentityID: "identity-uuid",
376+
DIDIDs: []string{"did-uuid"},
377+
}
378+
created, _ := client.EmergencyVerifications().Create(ctx, verification)
379+
380+
// List emergency calling services
381+
services, _ := client.EmergencyCallingServices().List(ctx, nil)
382+
```
383+
384+
### DID History (2026-04-16)
385+
386+
```go
387+
// List DID history entries
388+
params := didww.NewQueryParams().Filter("did.id", "did-uuid")
389+
history, _ := client.DIDHistory().List(ctx, params)
390+
```
391+
338392
### Exports
339393

340394
```go
341395
import "github.com/didww/didww-api-3-go-sdk/resource/enums"
342396

343397
export := &didww.Export{
344398
ExportType: enums.ExportTypeCdrIn,
345-
Filters: map[string]interface{}{"year": 2025, "month": 1},
399+
Filters: map[string]interface{}{"from": "2026-04-01 00:00:00", "to": "2026-04-16 00:00:00"},
346400
}
347401
created, _ := client.Exports().Create(ctx, export)
348402
```
@@ -439,6 +493,7 @@ updated, _ := client.VoiceInTrunks().Update(ctx, trunk)
439493
| Available DID | `available_did_order_items` |
440494
| Reservation DID | `reservation_did_order_items` |
441495
| Capacity | `capacity_order_items` |
496+
| Emergency | `emergency_order_items` |
442497
| Generic (response only) | `generic_order_items` |
443498

444499
## Error Handling
@@ -478,7 +533,9 @@ if err != nil {
478533
| AvailableDID | `client.AvailableDIDs()` | list, find |
479534
| ProofType | `client.ProofTypes()` | list, find |
480535
| PublicKey | `client.PublicKeys()` | list |
481-
| Requirement | `client.Requirements()` | list, find |
536+
| AddressRequirement | `client.AddressRequirements()` | list, find |
537+
| EmergencyRequirement | `client.EmergencyRequirements()` | list, find |
538+
| DIDHistory | `client.DIDHistory()` | list |
482539
| SupportingDocumentTemplate | `client.SupportingDocumentTemplates()` | list, find |
483540
| Balance | `client.Balance()` | find |
484541
| DID | `client.DIDs()` | list, find, update, delete |
@@ -490,14 +547,17 @@ if err != nil {
490547
| CapacityPool | `client.CapacityPools()` | list, find, update |
491548
| SharedCapacityGroup | `client.SharedCapacityGroups()` | list, find, create, update, delete |
492549
| Order | `client.Orders()` | list, find, create, delete |
493-
| Export | `client.Exports()` | list, find, create |
550+
| Export | `client.Exports()` | list, find, create, update |
494551
| Address | `client.Addresses()` | list, find, create, update, delete |
495-
| AddressVerification | `client.AddressVerifications()` | list, find, create |
552+
| AddressVerification | `client.AddressVerifications()` | list, find, create, update |
553+
| EmergencyCallingService | `client.EmergencyCallingServices()` | list, find, delete |
554+
| EmergencyVerification | `client.EmergencyVerifications()` | list, find, create, update |
555+
| EmergencyRequirementValidation | `client.EmergencyRequirementValidations()` | create |
496556
| Identity | `client.Identities()` | list, find, create, update, delete |
497557
| EncryptedFile | `client.EncryptedFiles()` | list, find, delete |
498558
| PermanentSupportingDocument | `client.PermanentSupportingDocuments()` | create |
499559
| Proof | `client.Proofs()` | create |
500-
| RequirementValidation | `client.RequirementValidations()` | create |
560+
| AddressRequirementValidation | `client.AddressRequirementValidations()` | create |
501561
| StockKeepingUnit | include on `DIDGroups` ||
502562
| QtyBasedPricing | include on `CapacityPools` ||
503563

@@ -509,9 +569,22 @@ if err != nil {
509569
The SDK distinguishes between date-only and datetime fields:
510570

511571
- **Datetime fields** are deserialized as `time.Time` (UTC) when always present, or `*time.Time` when optional (nil if the API omits the value):
512-
- All `CreatedAt` fields — `time.Time`, present on most resources
513-
- Expiry fields — `*time.Time`: `DID.ExpiresAt`, `Proof.ExpiresAt`, `EncryptedFile.ExpireAt`; `DIDReservation.ExpireAt` is `time.Time` (always present)
514-
- **Date-only fields** (`Identity.BirthDate`, `CapacityPool.RenewDate`, order item `BilledFrom`/`BilledTo`) remain as `string` in `"YYYY-MM-DD"` format — Go has no separate date-only type, so the raw string avoids timezone ambiguity.
572+
- `CreatedAt``time.Time`, present on most resources
573+
- `ExpiresAt``*time.Time`: `DID`, `DIDReservation`, `Proof`, `EncryptedFile`
574+
- `ActivatedAt``*time.Time`: `EmergencyCallingService` (nullable)
575+
- `CanceledAt``*time.Time`: `EmergencyCallingService` (nullable)
576+
- **Date-only fields** remain as `string` in `"YYYY-MM-DD"` format — Go has no separate date-only type, so the raw string avoids timezone ambiguity:
577+
- `Identity.BirthDate`
578+
- `CapacityPool.RenewDate`, `EmergencyCallingService.RenewDate` (nullable)
579+
- Order item `BilledFrom` / `BilledTo`
580+
- **String fields** (not numeric):
581+
- `EmergencyRequirement.EstimateSetupTime` — e.g. `"7-14 days"`, `"1"`
582+
- `EmergencyRequirement.RequirementRestrictionMessage` — nullable
583+
584+
**Important changes from previous API versions:**
585+
- `ExpiresAt` replaces `ExpireAt` on `DIDReservation` and `EncryptedFile`
586+
- `RenewDate` is a date-only string, NOT a `time.Time`
587+
- `EstimateSetupTime` is a string, NOT an integer
515588

516589
```go
517590
did, _ := client.DIDs().Find(ctx, "uuid")
@@ -528,6 +601,7 @@ The SDK provides enum types in `github.com/didww/didww-api-3-go-sdk/resource/enu
528601

529602
`CallbackMethod`, `IdentityType`, `OrderStatus`, `ExportType`, `ExportStatus`, `CliFormat`,
530603
`OnCliMismatchAction`\*, `MediaEncryptionMode`, `DefaultDstAction`, `VoiceOutTrunkStatus`,
604+
`EmergencyCallingServiceStatus`, `EmergencyVerificationStatus`, `DiversionRelayPolicy`,
531605
`TransportProtocol`, `Codec`, `RxDtmfFormat`, `TxDtmfFormat`, `SstRefreshMethod`,
532606
`ReroutingDisconnectCode`, `Feature`, `AreaLevel`, `AddressVerificationStatus`, `StirShakenMode`
533607

address_verifications_test.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func TestAddressVerificationsCreate(t *testing.T) {
2929
})
3030

3131
cbURL := "http://example.com"
32-
cbMethod := "GET"
32+
cbMethod := "get"
3333
av, err := server.client.AddressVerifications().Create(context.Background(), &resource.AddressVerification{
3434
CallbackURL: &cbURL,
3535
CallbackMethod: &cbMethod,
@@ -63,6 +63,47 @@ func TestAddressVerificationsFind(t *testing.T) {
6363
assert.Nil(t, av.RejectReasons)
6464
}
6565

66+
func TestAddressVerificationsUpdateExternalReferenceID(t *testing.T) {
67+
server, capturedBodyPtr := captureRequestBody(t, map[string]testRoute{
68+
"PATCH /v3/address_verifications/c8e004b0-87ec-4987-b4fb-ee89db099f0e": {status: http.StatusOK, fixture: "address_verifications/update.json"},
69+
})
70+
71+
extRef := "ext-ref-123"
72+
av, err := server.client.AddressVerifications().Update(context.Background(), &resource.AddressVerification{
73+
ID: "c8e004b0-87ec-4987-b4fb-ee89db099f0e",
74+
ExternalReferenceID: &extRef,
75+
})
76+
require.NoError(t, err)
77+
78+
assert.Equal(t, "c8e004b0-87ec-4987-b4fb-ee89db099f0e", av.ID)
79+
require.NotNil(t, av.ExternalReferenceID)
80+
assert.Equal(t, "ext-ref-123", *av.ExternalReferenceID)
81+
82+
assertRequestJSON(t, *capturedBodyPtr, "address_verifications/update_request.json")
83+
}
84+
85+
func TestAddressVerificationsUpdateExternalReferenceIDFromLoaded(t *testing.T) {
86+
server, capturedBodyPtr := captureRequestBody(t, map[string]testRoute{
87+
"GET /v3/address_verifications/c8e004b0-87ec-4987-b4fb-ee89db099f0e": {status: http.StatusOK, fixture: "address_verifications/show.json"},
88+
"PATCH /v3/address_verifications/c8e004b0-87ec-4987-b4fb-ee89db099f0e": {status: http.StatusOK, fixture: "address_verifications/update.json"},
89+
})
90+
91+
av, err := server.client.AddressVerifications().Find(context.Background(), "c8e004b0-87ec-4987-b4fb-ee89db099f0e")
92+
require.NoError(t, err)
93+
94+
extRef := "ext-ref-123"
95+
av.ExternalReferenceID = &extRef
96+
97+
av, err = server.client.AddressVerifications().Update(context.Background(), av)
98+
require.NoError(t, err)
99+
100+
assert.Equal(t, "c8e004b0-87ec-4987-b4fb-ee89db099f0e", av.ID)
101+
require.NotNil(t, av.ExternalReferenceID)
102+
assert.Equal(t, "ext-ref-123", *av.ExternalReferenceID)
103+
104+
assertRequestJSON(t, *capturedBodyPtr, "address_verifications/update_request.json")
105+
}
106+
66107
func TestAddressVerificationsFindRejected(t *testing.T) {
67108
_, client := newTestServer(t, map[string]testRoute{
68109
"GET /v3/address_verifications/429e6d4e-2ee9-4953-aa98-0b3ac07f0f96": {status: http.StatusOK, fixture: "address_verifications/show_rejected.json"},

client.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,11 @@ func (c *Client) Proofs() *Repository[resource.Proof] { return NewRepository[res
171171
func (c *Client) ProofTypes() *Repository[resource.ProofType] {
172172
return NewRepository[resource.ProofType](c)
173173
}
174-
func (c *Client) Requirements() *Repository[resource.Requirement] {
175-
return NewRepository[resource.Requirement](c)
174+
func (c *Client) AddressRequirements() *Repository[resource.AddressRequirement] {
175+
return NewRepository[resource.AddressRequirement](c)
176176
}
177-
func (c *Client) RequirementValidations() *Repository[resource.RequirementValidation] {
178-
return NewRepository[resource.RequirementValidation](c)
177+
func (c *Client) AddressRequirementValidations() *Repository[resource.AddressRequirementValidation] {
178+
return NewRepository[resource.AddressRequirementValidation](c)
179179
}
180180
func (c *Client) Exports() *Repository[resource.Export] { return NewRepository[resource.Export](c) }
181181
func (c *Client) CapacityPools() *Repository[resource.CapacityPool] {
@@ -199,6 +199,21 @@ func (c *Client) PermanentSupportingDocuments() *Repository[resource.PermanentSu
199199
func (c *Client) NanpaPrefixes() *Repository[resource.NanpaPrefix] {
200200
return NewRepository[resource.NanpaPrefix](c)
201201
}
202+
func (c *Client) EmergencyCallingServices() *Repository[resource.EmergencyCallingService] {
203+
return NewRepository[resource.EmergencyCallingService](c)
204+
}
205+
func (c *Client) EmergencyVerifications() *Repository[resource.EmergencyVerification] {
206+
return NewRepository[resource.EmergencyVerification](c)
207+
}
208+
func (c *Client) EmergencyRequirementValidations() *Repository[resource.EmergencyRequirementValidation] {
209+
return NewRepository[resource.EmergencyRequirementValidation](c)
210+
}
211+
func (c *Client) EmergencyRequirements() *Repository[resource.EmergencyRequirement] {
212+
return NewRepository[resource.EmergencyRequirement](c)
213+
}
214+
func (c *Client) DIDHistory() *Repository[resource.DIDHistory] {
215+
return NewRepository[resource.DIDHistory](c)
216+
}
202217
func (c *Client) VoiceOutTrunkRegenerateCredentials() *Repository[resource.VoiceOutTrunkRegenerateCredential] {
203218
return NewRepository[resource.VoiceOutTrunkRegenerateCredential](c)
204219
}

client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func TestClientSendsCorrectHeaders(t *testing.T) {
4444
assert.Equal(t, "application/vnd.api+json", receivedAccept)
4545
assert.Equal(t, "test-api-key", receivedAPIKey)
4646
assert.Equal(t, apiVersion, receivedAPIVersion)
47-
assert.Equal(t, "didww-go-sdk/1.0.0", receivedUserAgent)
47+
assert.Equal(t, "didww-go-sdk/3.0.0-dev", receivedUserAgent)
4848
}
4949

5050
func TestClientHandlesHTTPErrors(t *testing.T) {

did_groups_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func TestDIDGroupsFindWithIncludedRequirement(t *testing.T) {
6060
"GET /v3/did_groups/2187c36d-28fb-436f-8861-5a0f5b5a3ee1": {status: http.StatusOK, fixture: "did_groups/show_with_requirement.json"},
6161
})
6262

63-
params := NewQueryParams().Include("requirement")
63+
params := NewQueryParams().Include("address_requirement")
6464
group, err := client.DIDGroups().Find(context.Background(), "2187c36d-28fb-436f-8861-5a0f5b5a3ee1", params)
6565
require.NoError(t, err)
6666

@@ -70,15 +70,15 @@ func TestDIDGroupsFindWithIncludedRequirement(t *testing.T) {
7070
assert.False(t, group.IsMetered)
7171
assert.True(t, group.AllowAdditionalChannels)
7272

73-
// Verify included requirement
74-
require.NotNil(t, group.Requirement)
75-
assert.Equal(t, "8da1e0b2-047c-4baf-9c57-57143f09b9ce", group.Requirement.ID)
76-
assert.Equal(t, "Any", group.Requirement.IdentityType)
77-
assert.Equal(t, "WorldWide", group.Requirement.PersonalAreaLevel)
78-
assert.Equal(t, "Country", group.Requirement.BusinessAreaLevel)
79-
assert.Equal(t, "City", group.Requirement.AddressAreaLevel)
80-
assert.Equal(t, 1, group.Requirement.PersonalProofQty)
81-
assert.Equal(t, 1, group.Requirement.BusinessProofQty)
82-
assert.Equal(t, 1, group.Requirement.AddressProofQty)
83-
assert.False(t, group.Requirement.ServiceDescriptionRequired)
73+
// Verify included address_requirement
74+
require.NotNil(t, group.AddressRequirement)
75+
assert.Equal(t, "8da1e0b2-047c-4baf-9c57-57143f09b9ce", group.AddressRequirement.ID)
76+
assert.Equal(t, "any", group.AddressRequirement.IdentityType)
77+
assert.Equal(t, "world_wide", group.AddressRequirement.PersonalAreaLevel)
78+
assert.Equal(t, "country", group.AddressRequirement.BusinessAreaLevel)
79+
assert.Equal(t, "city", group.AddressRequirement.AddressAreaLevel)
80+
assert.Equal(t, 1, group.AddressRequirement.PersonalProofQty)
81+
assert.Equal(t, 1, group.AddressRequirement.BusinessProofQty)
82+
assert.Equal(t, 1, group.AddressRequirement.AddressProofQty)
83+
assert.False(t, group.AddressRequirement.ServiceDescriptionRequired)
8484
}

0 commit comments

Comments
 (0)