Python SDK for DIDWW API v3
Project description
Python client for DIDWW API v3.
About DIDWW API v3
The DIDWW API provides a simple yet powerful interface that allows you to fully integrate your own applications with DIDWW services. An extensive set of actions may be performed using this API, such as ordering and configuring phone numbers, setting capacity, creating SIP trunks and retrieving CDRs and other operational data.
The DIDWW API v3 is a fully compliant implementation of the JSON API specification.
This SDK uses jsonapi-requests for JSON:API serialization and deserialization.
Read more https://doc.didww.com/api
API Version
This SDK sends the X-DIDWW-API-Version: 2026-04-16 header with every request by default.
| Package Version | Branch | DIDWW API Version |
|---|---|---|
| 3.x | main |
2026-04-16 |
| 2.x | 2022-05-10 |
2022-05-10 |
Requirements
- Python 3.9+
Installation
pip install didww
Or install from source:
pip install -e .
Usage
from didww.client import DidwwClient
from didww.configuration import Environment
from didww.query_params import QueryParams
client = DidwwClient(api_key="YOUR_API_KEY", environment=Environment.SANDBOX)
# Check balance
balance = client.balance().find().data
print(f"Balance: {balance.total_balance}")
# List DID groups with stock keeping units
params = QueryParams().include("stock_keeping_units").filter("area_name", "Acapulco")
did_groups = client.did_groups().list(params).data
For more examples visit examples.
For details on obtaining your API key please visit https://doc.didww.com/api3/configuration.html
Examples
- Source code: examples/
- How to run: examples/README.md
Configuration
from didww.client import DidwwClient
from didww.configuration import Environment
# Sandbox
client = DidwwClient(api_key="YOUR_API_KEY", environment=Environment.SANDBOX)
# Production
client = DidwwClient(api_key="YOUR_API_KEY", environment=Environment.PRODUCTION)
Environments
| Environment | Base URL |
|---|---|
Environment.PRODUCTION |
https://api.didww.com/v3 |
Environment.SANDBOX |
https://sandbox-api.didww.com/v3 |
HTTPS Proxy
import requests
from didww.client import DidwwClient
from didww.configuration import Environment
session = requests.Session()
session.proxies = {
"https": "http://proxy.example.com:8080",
}
client = DidwwClient(
api_key="YOUR_API_KEY",
environment=Environment.PRODUCTION,
session=session,
)
Resources
Read-Only Resources
from didww.client import DidwwClient
from didww.configuration import Environment
from didww.query_params import QueryParams
client = DidwwClient(api_key="YOUR_API_KEY", environment=Environment.SANDBOX)
# Countries
countries = client.countries().list().data
country = client.countries().find("uuid").data
# Regions
regions = client.regions().list().data
# Cities
cities = client.cities().list().data
# Areas
areas = client.areas().list().data
# NANPA Prefixes
prefixes = client.nanpa_prefixes().list().data
# POPs (Points of Presence)
pops = client.pops().list().data
# DID Group Types
types = client.did_group_types().list().data
# DID Groups (with stock keeping units)
params = QueryParams().include("stock_keeping_units")
groups = client.did_groups().list(params).data
# Available DIDs (with DID group and stock keeping units)
params = QueryParams().include("did_group.stock_keeping_units")
available = client.available_dids().list(params).data
# Proof Types
proof_types = client.proof_types().list().data
# Public Keys
public_keys = client.public_keys().list().data
# Address Requirements
requirements = client.address_requirements().list().data
# Supporting Document Templates
templates = client.supporting_document_templates().list().data
# Balance (singleton)
balance = client.balance().find().data
DIDs
from didww.resources.voice_in_trunk import VoiceInTrunk
# List DIDs
dids = client.dids().list().data
# Update DID - assign trunk and capacity
did = client.dids().find("uuid").data
did.description = "Updated"
did.capacity_limit = 20
did.voice_in_trunk = VoiceInTrunk.build("trunk-uuid")
client.dids().update(did)
Dirty Serialization
The SDK tracks which attributes and relationships have been changed since a resource was loaded or built.
When update() is called, only the modified fields are sent in the PATCH request.
from didww.resources.did import Did
from didww.resources.voice_in_trunk import VoiceInTrunk
# Only "description" is sent in the PATCH body
did = client.dids().find("uuid").data
did.description = "Updated"
client.dids().update(did)
# Explicit None sends null to clear a field
did = Did.build("uuid")
did.description = None
client.dids().update(did)
# Exclusive relationships: setting voice_in_trunk automatically nullifies voice_in_trunk_group
did = Did.build("uuid")
did.voice_in_trunk = VoiceInTrunk.build("trunk-uuid")
client.dids().update(did)
# PATCH body: {"data": {"type": "dids", "id": "uuid", "attributes": {}, "relationships": {"voice_in_trunk": {"data": {"type": "voice_in_trunks", "id": "trunk-uuid"}}, "voice_in_trunk_group": {"data": null}}}}
# Included resources also support dirty tracking
params = QueryParams().include("voice_in_trunk")
did = client.dids().find("uuid", params).data
trunk = did.voice_in_trunk
trunk.description = "Updated via include"
client.voice_in_trunks().update(trunk)
Voice In Trunks
from didww.enums import CliFormat, Codec, TransportProtocol
from didww.resources.voice_in_trunk import VoiceInTrunk
from didww.resources.configuration.sip import SipConfiguration
# Create SIP trunk
trunk = VoiceInTrunk()
trunk.name = "My SIP Trunk"
trunk.priority = 1
trunk.weight = 100
trunk.cli_format = CliFormat.E164
trunk.ringing_timeout = 30
sip = SipConfiguration()
sip.host = "sip.example.com"
sip.port = 5060
sip.codec_ids = [Codec.PCMU, Codec.PCMA]
sip.transport_protocol_id = TransportProtocol.UDP
trunk.configuration = sip
created = client.voice_in_trunks().create(trunk).data
# Update trunk
created.description = "Updated"
client.voice_in_trunks().update(created)
# Delete trunk
client.voice_in_trunks().delete(created.id)
Voice In Trunk Groups
from didww.resources.voice_in_trunk_group import VoiceInTrunkGroup
group = VoiceInTrunkGroup()
group.name = "Primary Group"
group.capacity_limit = 50
created = client.voice_in_trunk_groups().create(group).data
Voice Out Trunks
Note: Voice Out Trunks require additional account configuration. Contact DIDWW support to enable.
Voice Out Trunks use a polymorphic authentication_method (2026-04-16). Three types are supported:
credentials_and_ip-- default method;usernameandpasswordare server-generated and returned in the response.twilio-- requires atwilio_account_sid.ip_only-- read-only; can only be configured by DIDWW staff upon request. Cannot be set via the API.
from didww.enums import DefaultDstAction, OnCliMismatchAction
from didww.resources.voice_out_trunk import VoiceOutTrunk
from didww.resources.authentication_method import CredentialsAndIpAuthenticationMethod
trunk = VoiceOutTrunk()
trunk.name = "My Outbound Trunk"
# NOTE: 203.0.113.0/24 is RFC 5737 TEST-NET-3 documentation space.
# Replace with the real CIDR of your SIP infrastructure.
trunk.authentication_method = CredentialsAndIpAuthenticationMethod(
allowed_sip_ips=["203.0.113.0/24"],
)
trunk.default_dst_action = DefaultDstAction.ALLOW_ALL
trunk.on_cli_mismatch_action = OnCliMismatchAction.REJECT_CALL
created = client.voice_out_trunks().create(trunk).data
# created.authentication_method.username -- server-generated
# created.authentication_method.password -- server-generated
Orders
from didww.resources.order import Order
from didww.resources.order_item.did_order_item import DidOrderItem
from didww.resources.order_item.capacity_order_item import CapacityOrderItem
from didww.resources.order_item.available_did_order_item import AvailableDidOrderItem
from didww.resources.order_item.reservation_did_order_item import ReservationDidOrderItem
# Order by SKU
order = Order()
item = DidOrderItem()
item.sku_id = "sku-uuid"
item.qty = 2
order.items = [item]
created = client.orders().create(order).data
# Order available DID
item = AvailableDidOrderItem()
item.available_did_id = "available-did-uuid"
item.sku_id = "sku-uuid"
# Order reserved DID
item = ReservationDidOrderItem()
item.did_reservation_id = "reservation-uuid"
item.sku_id = "sku-uuid"
# Order capacity
item = CapacityOrderItem()
item.capacity_pool_id = "pool-uuid"
item.qty = 1
DID Reservations
from didww.resources.did_reservation import DidReservation
from didww.resources.available_did import AvailableDid
reservation = DidReservation()
reservation.description = "Reserved for client"
reservation.available_did = AvailableDid.build("available-did-uuid")
created = client.did_reservations().create(reservation).data
# Delete reservation
client.did_reservations().delete(created.id)
Shared Capacity Groups
from didww.resources.shared_capacity_group import SharedCapacityGroup
from didww.resources.capacity_pool import CapacityPool
scg = SharedCapacityGroup()
scg.name = "Shared Group"
scg.shared_channels_count = 20
scg.metered_channels_count = 5
scg.capacity_pool = CapacityPool.build("pool-uuid")
created = client.shared_capacity_groups().create(scg).data
Address Verifications
from didww.resources.address_verification import AddressVerification
from didww.resources.address import Address
# List address verifications
verifications = client.address_verifications().list().data
# Create address verification
verification = AddressVerification()
verification.address = Address.build("address-uuid")
created = client.address_verifications().create(verification).data
# Update external_reference_id
created.external_reference_id = "my-ref-123"
client.address_verifications().update(created)
Emergency Requirements
# List emergency requirements
emergency_reqs = client.emergency_requirements().list().data
for req in emergency_reqs:
print(f"{req.name}: {req.estimate_setup_time}")
# Find a specific emergency requirement
req = client.emergency_requirements().find("uuid").data
Emergency Verifications
from didww.resources.emergency_verification import EmergencyVerification
from didww.resources.address import Address
# List emergency verifications
verifications = client.emergency_verifications().list().data
# Create emergency verification
verification = EmergencyVerification()
verification.address = Address.build("address-uuid")
created = client.emergency_verifications().create(verification).data
# Update external_reference_id
created.external_reference_id = "ext-ref-456"
client.emergency_verifications().update(created)
Emergency Calling Services
# List emergency calling services
services = client.emergency_calling_services().list().data
for svc in services:
print(f"Status: {svc.status}, Renew: {svc.renew_date}")
# Find a specific emergency calling service
service = client.emergency_calling_services().find("uuid").data
# Delete an emergency calling service
client.emergency_calling_services().delete("uuid")
DID History
from didww.query_params import QueryParams
# List DID history records
params = QueryParams().filter("did.id", "did-uuid")
history = client.did_history().list(params).data
for record in history:
print(f"DID: {record.number}, Order Date: {record.order_created_at}")
# Find a specific DID history record
record = client.did_history().find("uuid").data
Identities
from didww.enums import IdentityType
from didww.resources.identity import Identity
from didww.resources.country import Country
identity = Identity()
identity.first_name = "John"
identity.last_name = "Doe"
identity.phone_number = "12125551234"
identity.identity_type = IdentityType.PERSONAL
identity.country = Country.build("country-uuid")
created = client.identities().create(identity).data
Addresses
from didww.resources.address import Address
from didww.resources.identity import Identity
from didww.resources.country import Country
address = Address()
address.city_name = "New York"
address.postal_code = "10001"
address.address = "123 Main St"
address.identity = Identity.build("identity-uuid")
address.country = Country.build("country-uuid")
created = client.addresses().create(address).data
Exports
from didww.enums import ExportType
from didww.resources.export import Export
export = Export()
export.export_type = ExportType.CDR_IN
export.filters = {"from": "2025-01-01T00:00:00", "to": "2025-02-01T00:00:00"}
created = client.exports().create(export).data
# Download the export when completed
client.download_export(created.url, "/tmp/export.csv")
Filtering, Sorting, and Pagination
from didww.query_params import QueryParams
params = (
QueryParams()
.filter("country.id", "uuid")
.filter("name", "Arizona")
.include("country")
.sort("name")
.page(1, 25)
)
regions = client.regions().list(params).data
Date and Datetime Fields
The SDK distinguishes between date-only and datetime fields:
- Datetime fields are deserialized as
datetime.datetimewithtimezone.utc:created_at— present on most resourcesexpires_at—Did,DidReservation,Proof,EncryptedFile(nullable)activated_at—EmergencyCallingService(nullable)canceled_at—EmergencyCallingService(nullable)
- Date-only fields remain as
strin"YYYY-MM-DD"format:Identity.birth_dateCapacityPool.renew_date,EmergencyCallingService.renew_date(nullable)DidOrderItem.billed_from/billed_to
- String fields (not numeric):
EmergencyRequirement.estimate_setup_time— e.g."7-14 days","1"EmergencyRequirement.requirement_restriction_message— nullable
Important changes from previous API versions:
expire_atrenamed toexpires_atonDidReservationandEncryptedFilerenew_dateis a date-only string, NOT a datetimeestimate_setup_timeis a string, NOT an integer
from datetime import timezone
did = client.dids().find("uuid").data
print(did.created_at) # datetime(2024, 1, 15, 10, 0, 0, tzinfo=timezone.utc)
print(did.expires_at) # None or datetime(...)
identity = client.identities().find("uuid").data
print(identity.birth_date) # "1990-05-20"
Enums
The SDK provides enum classes for all API option fields:
CallbackMethod, IdentityType, OrderStatus, ExportType, ExportStatus, CliFormat,
OnCliMismatchAction*, MediaEncryptionMode, DefaultDstAction, VoiceOutTrunkStatus,
EmergencyCallingServiceStatus, EmergencyVerificationStatus, DiversionRelayPolicy,
TransportProtocol, Codec, RxDtmfFormat, TxDtmfFormat, SstRefreshMethod,
ReroutingDisconnectCode, Feature, AreaLevel, AddressVerificationStatus, StirShakenMode
* REPLACE_CLI and RANDOMIZE_CLI require additional account configuration. Contact DIDWW support to enable these values.
from didww.enums import CallbackMethod, IdentityType
from didww.resources.order import Order
from didww.resources.identity import Identity
order = Order()
order.callback_method = CallbackMethod.POST
identity = Identity()
identity.identity_type = IdentityType.BUSINESS
File Encryption
The SDK provides an Encrypt utility for encrypting files before upload, using RSA-OAEP + AES-256-CBC (matching DIDWW's encryption requirements).
from didww.encrypt import Encrypt
# Provide your own public key PEM strings
public_keys = [public_key_pem_1, public_key_pem_2]
fingerprint = Encrypt.calculate_fingerprint(public_keys)
encrypted = Encrypt.encrypt_with_keys(file_bytes, public_keys)
Upload encrypted file:
file_ids = client.upload_encrypted_files(
fingerprint=fingerprint,
files=[
{"data": encrypted, "description": "document.pdf", "filename": "document.pdf.enc"},
],
)
Webhook Signature Validation
Validate incoming webhook callbacks from DIDWW using HMAC-SHA1 signature verification.
from didww.callback.request_validator import RequestValidator
validator = RequestValidator("YOUR_API_KEY")
# In your webhook handler:
valid = validator.validate(
url=request_url, # full original URL
payload=payload_dict, # dict of payload key-value pairs
signature=signature, # value of X-DIDWW-Signature header
)
Error Handling
from didww.exceptions import DidwwApiError, DidwwClientError
try:
client.voice_in_trunks().find("nonexistent")
except DidwwApiError as e:
print(f"HTTP Status: {e.status_code}")
for error in e.errors:
print(f"Error: {error.get('detail', error.get('title'))}")
except DidwwClientError as e:
print(f"Client error: {e}")
Trunk Configuration Types
| Type | Class |
|---|---|
| SIP | SipConfiguration |
| PSTN | PstnConfiguration |
Order Item Types
| Type | Class |
|---|---|
| DID | DidOrderItem |
| Available DID | AvailableDidOrderItem |
| Reservation DID | ReservationDidOrderItem |
| Capacity | CapacityOrderItem |
| Generic | GenericOrderItem |
All Supported Resources
| Resource | Repository | Operations |
|---|---|---|
| Country | client.countries() |
list, find |
| Region | client.regions() |
list, find |
| City | client.cities() |
list, find |
| Area | client.areas() |
list, find |
| NanpaPrefix | client.nanpa_prefixes() |
list, find |
| Pop | client.pops() |
list, find |
| DidGroupType | client.did_group_types() |
list, find |
| DidGroup | client.did_groups() |
list, find |
| AvailableDid | client.available_dids() |
list, find |
| ProofType | client.proof_types() |
list, find |
| PublicKey | client.public_keys() |
list, find |
| AddressRequirement | client.address_requirements() |
list, find |
| SupportingDocumentTemplate | client.supporting_document_templates() |
list, find |
| Balance | client.balance() |
find |
| Did | client.dids() |
list, find, update, delete |
| VoiceInTrunk | client.voice_in_trunks() |
list, find, create, update, delete |
| VoiceInTrunkGroup | client.voice_in_trunk_groups() |
list, find, create, update, delete |
| VoiceOutTrunk | client.voice_out_trunks() |
list, find, create, update, delete |
| VoiceOutTrunkRegenerateCredential | client.voice_out_trunk_regenerate_credentials() |
create |
| DidReservation | client.did_reservations() |
list, find, create, delete |
| CapacityPool | client.capacity_pools() |
list, find |
| SharedCapacityGroup | client.shared_capacity_groups() |
list, find, create, update, delete |
| Order | client.orders() |
list, find, create |
| Export | client.exports() |
list, find, create, update |
| Address | client.addresses() |
list, find, create, delete |
| AddressVerification | client.address_verifications() |
list, find, create, update |
| Identity | client.identities() |
list, find, create, delete |
| EncryptedFile | client.encrypted_files() |
list, find, delete |
| PermanentSupportingDocument | client.permanent_supporting_documents() |
create, delete |
| Proof | client.proofs() |
create, delete |
| AddressRequirementValidation | client.address_requirement_validations() |
create |
| DidHistory | client.did_history() |
list, find |
| EmergencyRequirement | client.emergency_requirements() |
list, find |
| EmergencyRequirementValidation | client.emergency_requirement_validations() |
create |
| EmergencyCallingService | client.emergency_calling_services() |
list, find, delete |
| EmergencyVerification | client.emergency_verifications() |
list, find, create, update |
| StockKeepingUnit | (include-only) | included via did_groups, available_dids |
| QtyBasedPricing | (include-only) | included via capacity_pools |
Include-only resources have no standalone API endpoint. They are returned as included relationships when fetching their parent resources with the appropriate
includeparameter.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/didww/didww-api-3-python-sdk
Build and Release
Preconditions
- prepare venv
- install build and twine packages
python3 -m pip install twine
python3 -m pip install build
1. Change version in pyproject.toml to the new version (e.g. 1.1.0)
2. Build the package
python3 -m build --sdist
python3 -m build --wheel
3. Check the package
twine check dist/*
4. Publish new version to PyPI
twine upload dist/*
5. Commit pyproject.toml and create tag
git add pyproject.toml
git commit -m "1.1.0"
git tag -a v1.1.0 -m "Release version 1.1.0"
6. Push to GitHub
git push origin main --follow-tags
License
The package is available as open source under the terms of the MIT License.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file didww-3.0.0.tar.gz.
File metadata
- Download URL: didww-3.0.0.tar.gz
- Upload date:
- Size: 44.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4b98f92765379a9a0ccddd63f3de860465c1d635e8e7ff80767e97613d0f1a2
|
|
| MD5 |
b5b01c430369aeb4067084c1cde72e96
|
|
| BLAKE2b-256 |
b74b4a286bea671a3330464cbf6e606a1fb9a7162aece38611c02511cc9e93e5
|
File details
Details for the file didww-3.0.0-py3-none-any.whl.
File metadata
- Download URL: didww-3.0.0-py3-none-any.whl
- Upload date:
- Size: 46.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2290da1097b8c46f5f13bfee8033bd9692a949ee840dbab835a4929d33d9d32
|
|
| MD5 |
1744fc9506e191adc1afefeb8197161b
|
|
| BLAKE2b-256 |
b251a06650145a32900d55894eff77733929f654fb579fce9ae847445f87d419
|