@ didww/ sdk
DDWW API v3 的 SDK 类型脚本.
关于WW API v3
DDWW API 提供了一个简单而强大的界面,允许您将自己的应用程序与 DDWW 服务完全整合. 可使用此API进行一大批动作,如订购和配置电话号码,设置容量,创建SIP中继器并检索CDR和其他操作数据等.
DIDWW API v3 完全遵守 JSON API 规格。 。 。 .
这个 SDK 使用 betu-core 常规 jSON:API序列化和去序列化.
更多信息 https://doc.didww.com/api (英语)
API 版本
这个SDK发送 X-DIDWW-API-Version: 2026-04-16 默认情况下每个请求的页眉 .
| 国家防范机制版本 | 处 | DidWW API 版本 存档副本 |
|---|---|---|
| 3.x 运算 | main | 2026-04-16 |
| 2.x 数据 | 2022-05-10 | 2022-05-10 |
安装
npm install @didww/sdk
需要节点.js 18+.
快速启动
import { DidwwClient, Environment } from '@didww/sdk';
const client = new DidwwClient({
apiKey: 'your-api-key',
environment: Environment.SANDBOX, // or Environment.PRODUCTION
});
// Check balance
const balance = await client.balance().find();
console.log(balance.data.totalBalance);
// List countries
const countries = await client.countries().list();
for (const country of countries.data) {
console.log(`${country.iso} - ${country.name}`);
}
更多实例访问 实例。 。 。 .
获取您的 API 密钥的详细信息请访问 https://doc.didww.com/api3/configulation.html (中文(简体) )
实例
- 源代码 : 实例/
- 如何运行 : 实例/README.md
配置
const client = new DidwwClient({
apiKey: 'your-api-key',
environment: Environment.PRODUCTION,
timeout: 30_000, // 30 seconds
});
环境
| 环境 | 基础 URL |
|---|---|
Environment.PRODUCTION | https://api.didww.com/v3 |
Environment.SANDBOX | https://sandbox-api.didww.com/v3 |
HTTPS 代理服务器
你可以通过一个习惯 fetch 函数,用于通过 HTTPS 代理服务器传送请求.
说明: 未编辑 必须单独安装:
npm install undici
import { DidwwClient, Environment } from '@didww/sdk';
import { ProxyAgent } from 'undici';
const proxyAgent = new ProxyAgent('https://proxy.example.com:8080');
const client = new DidwwClient({
apiKey: 'your-api-key',
environment: Environment.PRODUCTION,
fetch: (input, init) => fetch(input, { ...init, dispatcher: proxyAgent } as RequestInit),
});
资源
仅读资源
// Countries
const countries = await client.countries().list();
const country = await client.countries().find('uuid');
// Regions
const regions = await client.regions().list();
// Cities, Areas, POPs
const cities = await client.cities().list();
const areas = await client.areas().list();
const pops = await client.pops().list();
// DID Groups (with stock keeping units)
const groups = await client.didGroups().list({
include: 'stock_keeping_units',
});
// Access included SKUs directly on each group
for (const group of groups.data) {
const skus = group.stockKeepingUnits; // resolved array
console.log(group.prefix, skus?.length, 'SKUs');
}
// Available DIDs (with DID group and stock keeping units)
const available = await client.availableDids().list({
include: 'did_group.stock_keeping_units',
});
// Access nested includes
const ad = available.data[0];
const didGroup = ad.didGroup; // resolved DidGroup
const skus = didGroup?.stockKeepingUnits; // resolved StockKeepingUnit[]
console.log(ad.number, didGroup?.prefix, skus?.[0]?.monthlyPrice);
// Other read-only resources
client.didGroupTypes();
client.nanpaPrefixes();
client.proofTypes();
client.publicKeys();
client.addressRequirements();
client.supportingDocumentTemplates();
// Stock keeping units and qty-based pricings are include-only resources:
// - StockKeepingUnit: include on didGroups (see above)
// - QtyBasedPricing: include on capacityPools
const pools = await client.capacityPools().list({ include: 'qty_based_pricings' });
// Balance (singleton)
const balance = await client.balance().find();
已经
import { ref } from '@didww/sdk';
// List DIDs
const dids = await client.dids().list();
// Assign a trunk to a DID (automatically nullifies trunk group)
await client.dids().update({
id: 'did-id',
voiceInTrunk: ref('voice_in_trunks', 'trunk-id'),
});
// Assign capacity pool and update attributes in one call
await client.dids().update({
id: 'did-id',
capacityPool: ref('capacity_pools', 'pool-id'),
dedicatedChannelsCount: 1,
capacityLimit: 5,
description: 'Updated',
});
// Unassign a trunk (set to null)
await client.dids().update({
id: 'did-id',
voiceInTrunk: null,
});
在特伦克语中的声音
import { sipConfiguration, pstnConfiguration, ref, Codec, TransportProtocol } from '@didww/sdk';
// Create SIP trunk
const trunk = await client.voiceInTrunks().create({
name: 'My SIP Trunk',
configuration: sipConfiguration({
host: 'sip.example.com',
port: 5060,
codecIds: [Codec.PCMU, Codec.PCMA],
transportProtocolId: TransportProtocol.UDP,
}),
pop: ref('pops', 'pop-id'),
});
// Create PSTN trunk
const pstnTrunk = await client.voiceInTrunks().create({
name: 'My PSTN Trunk',
configuration: pstnConfiguration({ dst: '12125551234' }),
});
// Update trunk
await client.voiceInTrunks().update({
id: trunk.data.id,
name: 'Updated Name',
});
// Delete trunk
await client.voiceInTrunks().remove(trunk.data.id);
SIP登记(2026-04-16)
A Voice In Trunk 也可以通过 SIP 注册认证入境电话
dIDWW 生成的证书 。 SDK 自动拼接附属字段
服务器需要内部 serializeTrunkConfiguration数字 :
enabledSipRegistration: true条形host编号 :port从铁丝网 (服务器用另外422个拒绝);- 无空
host翻转enabledSipRegistration改为false财务报告和审定财务报表 部队useDidInRuri: false在电线上让服务器接受 禁用 PACH.
服务器生成 incomingAuthUsername 财务报告和审定财务报表 incomingAuthPassword
并启用 SIP 注册后在响应中显示它们.
const trunk = await client.voiceInTrunks().create({
name: 'Office (registered)',
configuration: sipConfiguration({
enabledSipRegistration: true,
useDidInRuri: true,
cnamLookup: true,
}),
});
const config = trunk.data.configuration as SipConfiguration;
// config.incomingAuthUsername — server-generated
// config.incomingAuthPassword — server-generated
要禁用已有干线上的 SIP 注册, 只需设置 host - 那个
级联翻转 enabledSipRegistration 财务报告和审定财务报表 useDidInRuri 改为 false
自动接线:
await client.voiceInTrunks().update({
id: trunk.data.id,
configuration: sipConfiguration({ host: 'sip.example.com' }),
});
在 Trunk 组中的声音
const group = await client.voiceInTrunkGroups().create({
name: 'Primary Group',
capacityLimit: 50,
});
声出中弦
说明: 声出特龙克和一些
OnCliMismatchAction数值(REPLACE_CLI, (中文(简体) ),RANDOMIZE_CLI)需要额外的账户配置. 联系DIDWW支持实现这些功能.
声出特朗克斯使用多态 authenticationMethod (2026-04-16) (中文(中国大陆) ). 支持三种类型:
credentials_and_ip-- 默认方法;username财务报告和审定财务报表password由服务器生成并在响应中返回.twilio- 需要一个twilioAccountSid。 。 。 .ip_only- 只读;只有在DIDWW工作人员提出要求时才能配置。 无法通过 API 设置 .
import { DefaultDstAction, OnCliMismatchAction, credentialsAndIpAuthenticationMethod } from '@didww/sdk';
// NOTE: 203.0.113.0/24 is RFC 5737 TEST-NET-3 documentation space.
// Replace with the real CIDR of your SIP infrastructure.
const voTrunk = await client.voiceOutTrunks().create({
name: 'My Outbound Trunk',
authenticationMethod: credentialsAndIpAuthenticationMethod({
allowedSipIps: ['203.0.113.0/24'],
techPrefix: '',
}),
defaultDstAction: DefaultDstAction.ALLOW_ALL,
onCliMismatchAction: OnCliMismatchAction.REJECT_CALL,
});
// voTrunk.data.authenticationMethod.username -- server-generated
// voTrunk.data.authenticationMethod.password -- server-generated
命令
import { didOrderItem, availableDidOrderItem, reservationDidOrderItem, capacityOrderItem } from '@didww/sdk';
// Order by SKU
const order = await client.orders().create({
allowBackOrdering: true,
items: [didOrderItem({ skuId: 'sku-id', qty: 2 })],
});
// Order a specific available DID
const order2 = await client.orders().create({
items: [availableDidOrderItem({ skuId: 'sku-id', availableDidId: 'available-did-id' })],
});
// Order a reserved DID
const order3 = await client.orders().create({
items: [reservationDidOrderItem({ skuId: 'sku-id', didReservationId: 'reservation-id' })],
});
// Order capacity
const order4 = await client.orders().create({
items: [capacityOrderItem({ capacityPoolId: 'pool-id', qty: 1 })],
});
是否保留
const reservation = await client.didReservations().create({
description: 'Reserved for client',
availableDid: ref('available_dids', 'available-did-id'),
});
await client.didReservations().remove(reservation.data.id);
共享能力小组
const scg = await client.sharedCapacityGroups().create({
name: 'Shared Group',
sharedChannelsCount: 20,
meteredChannelsCount: 5,
capacityPool: ref('capacity_pools', 'pool-id'),
});
身份
import { IdentityType, ref } from '@didww/sdk';
const identity = await client.identities().create({
identityType: IdentityType.PERSONAL,
firstName: 'John',
lastName: 'Doe',
phoneNumber: '12125551234',
country: ref('countries', 'country-id'),
});
地址
const address = await client.addresses().create({
cityName: 'New York',
postalCode: '10001',
address: '123 Main St',
identity: ref('identities', 'identity-id'),
country: ref('countries', 'country-id'),
});
出口
import { ExportType } from '@didww/sdk';
const exp = await client.exports().create({
exportType: ExportType.CDR_IN,
filters: {
from: '2026-04-15 00:00:00', // inclusive (time_start >= from)
to: '2026-04-16 00:00:00', // exclusive (time_start < to)
},
});
// Download when completed
const data = await client.downloadExport(exp.data.url);
地址验证
// List address verifications
const verifications = await client.addressVerifications().list();
// Create address verification
const verification = await client.addressVerifications().create({
callbackUrl: 'https://example.com/callback',
callbackMethod: CallbackMethod.POST,
address: ref('addresses', 'address-id'),
dids: [ref('dids', 'did-id')],
});
紧急服务(2026-04-16)
// List emergency requirements
const emergReqs = await client.emergencyRequirements().list();
// Create emergency verification
const emergVerification = await client.emergencyVerifications().create({
callbackUrl: 'https://example.com/callback',
callbackMethod: CallbackMethod.POST,
address: ref('addresses', 'address-id'),
dids: [ref('dids', 'did-id')],
});
// List emergency calling services
const emergServices = await client.emergencyCallingServices().list();
Did History (2026-04-16) (英语)
// List DID history
const history = await client.didHistory().list();
for (const entry of history.data) {
console.log(entry.action, entry.createdAt);
}
资源
使用 include 用于边载相关资源的参数。 包含的关系直接由母对象解决:
// Regions with their country
const regions = await client.regions().list({
include: 'country',
});
for (const region of regions.data) {
console.log(region.name, region.country?.name);
}
// DID groups with nested stock keeping units
const groups = await client.didGroups().list({
include: 'stock_keeping_units',
});
for (const group of groups.data) {
for (const sku of group.stockKeepingUnits || []) {
console.log(group.prefix, sku.monthlyPrice);
}
}
// Available DIDs with nested includes (did_group → stock_keeping_units)
const available = await client.availableDids().list({
include: 'did_group.stock_keeping_units',
});
const ad = available.data[0];
const skuId = ad.didGroup?.stockKeepingUnits?.[0]?.id;
// DIDs with voice trunk and capacity pool
const dids = await client.dids().list({
include: 'voice_in_trunk,capacity_pool',
});
for (const did of dids.data) {
console.log(did.number, did.voiceInTrunk?.name, did.capacityPool?.name);
}
查询参数
const result = await client.regions().list({
filter: { 'country.id': 'uuid', name: 'Arizona' },
include: ['country'],
sort: '-name',
page: { number: 1, size: 25 },
});
肮脏追踪(PATCH优化)
当你从 API 中获取资源时, SDK 将快取其可写属性 。 更新时, PATCH 请求中只发送已更改的字段 :
// Load a DID from the API
const response = await client.dids().find('did-id', { include: 'voice_in_trunk' });
const did = response.data;
// Modify only what you need
did.description = 'Updated description';
// PATCH request body (JSON:API format):
// {
// "data": {
// "id": "did-id",
// "type": "dids",
// "attributes": { "description": "Updated description" }
// }
// }
await client.dids().update(did);
当您手动构建对象( 不首先获取) 时, 所有已提供的字段都会被发送 :
// All provided fields are sent because there is no clean snapshot
// PATCH request body (JSON:API format):
// {
// "data": {
// "id": "did-id",
// "type": "dids",
// "attributes": { "description": "Updated", "capacity_limit": 5 }
// }
// }
await client.dids().update({
id: 'did-id',
description: 'Updated',
capacityLimit: 5,
});
所包括的资源也被跟踪——你可以直接修改和更新:
const response = await client.dids().find('did-id', { include: 'voice_in_trunk' });
const trunk = response.data.voiceInTrunk;
// trunk has a clean snapshot — only dirty fields are sent
trunk.name = 'Renamed trunk';
// PATCH request body (JSON:API format):
// {
// "data": {
// "id": "<trunk-id>",
// "type": "voice_in_trunks",
// "attributes": { "name": "Renamed trunk" }
// }
// }
await client.voiceInTrunks().update(trunk);
设置字段为 null 明确规定:
const response = await client.dids().find('did-id', { include: 'voice_in_trunk' });
const did = response.data;
// Explicitly clear the trunk relationship
did.voiceInTrunk = null;
// PATCH request body (JSON:API format):
// {
// "data": {
// "id": "did-id",
// "type": "dids",
// "relationships": {
// "voice_in_trunk": { "data": null }
// }
// }
// }
await client.dids().update(did);
Trunk 配置类型
| 类型 | 工厂 |
|---|---|
| SIP 执行方案 | sipConfiguration({ host, port, codecIds, ... }) |
| PSTN 网络 | pstnConfiguration({ dst }) |
排序项目类型
| 类型 | 工厂 |
|---|---|
| (由SKU翻译) | didOrderItem({ skuId, qty }) |
| 可用已执行 | availableDidOrderItem({ skuId, availableDidId }) |
| 保留 | reservationDidOrderItem({ skuId, didReservationId }) |
| 能力 | capacityOrderItem({ capacityPoolId, qty }) |
| 紧急情况 | emergencyOrderItem({ emergencyCallingServiceId, qty }) |
日期和日期
所有日期和日期字段作为 ISO 8601 字符串返回。 Java 脚本 Date 对象已知有陷阱(可移动、时区处理、0个索引月),因此SDK有意将这些错误保留为 string 让你用自己选择的图书馆来分析.
- 日期字段 ——ISO 8601字符串等.
"2024-01-15T10:00:00.000Z"数字 :createdAt- 大部分资源expiresAt—— 说Did, (中文(简体) ),DidReservation, (中文(简体) ),Proof, (中文(简体) ),EncryptedFile(可撤销)activatedAt—— 说EmergencyCallingService(可撤销)canceledAt—— 说EmergencyCallingService(可撤销)
- 只有日期的字段 例如,日期字符串.
"1990-05-20"数字 :Identity.birthDateCapacityPool.renewDate, (中文(简体) ),EmergencyCallingService.renewDate(可撤销)DidOrderItem.billedFrom, (中文(简体) ),DidOrderItem.billedTo
- 字符串字段 (不是数字):
EmergencyRequirement.estimateSetupTime- 比如说."7-14 days", (中文(简体) ),"1"EmergencyRequirement.requirementRestrictionMessage- 无效
与以往API版本相比的重要变化 :
expireAt重新命名为expiresAt打开DidReservation财务报告和审定财务报表EncryptedFilerenewDate是只写日期的字符串,不是日期时间estimateSetupTime是字符串,不是数字
const did = await client.dids().find('uuid');
console.log(did.createdAt); // "2024-01-15T10:00:00.000Z"
console.log(did.expiresAt); // null or "2025-01-15T10:00:00.000Z"
// Parse with your preferred library:
import { parseISO } from 'date-fns';
const date = parseISO(did.createdAt);
数字
所有 API 常数字段都提供类型安全enum:
import {
// String enums
CallbackMethod, // POST, GET
AddressVerificationStatus, // PENDING, APPROVED, REJECTED
ExportType, // CDR_IN, CDR_OUT
ExportStatus, // PENDING, PROCESSING, COMPLETED
IdentityType, // PERSONAL, BUSINESS, ANY
OrderStatus, // PENDING, CANCELED, COMPLETED
OnCliMismatchAction, // SEND_ORIGINAL_CLI, REJECT_CALL, REPLACE_CLI*, RANDOMIZE_CLI* (requires account config)
MediaEncryptionMode, // DISABLED, SRTP_SDES, SRTP_DTLS, ZRTP
DefaultDstAction, // ALLOW_ALL, REJECT_ALL
VoiceOutTrunkStatus, // ACTIVE, BLOCKED
CliFormat, // RAW, E164, LOCAL
AreaLevel, // WORLDWIDE, COUNTRY, AREA, CITY
Feature, // VOICE, VOICE_IN, VOICE_OUT, T38, SMS, SMS_IN, SMS_OUT, P2P, A2P, EMERGENCY, CNAM_OUT
StirShakenMode, // DISABLED, ORIGINAL, PAI, ORIGINAL_PAI, VERSTAT
EmergencyCallingServiceStatus, // ACTIVE, CANCELED, CHANGES_REQUIRED, IN_PROCESS, NEW, PENDING_UPDATE
EmergencyVerificationStatus, // PENDING, APPROVED, REJECTED
DiversionRelayPolicy, // NONE, AS_IS, SIP, TEL
// Integer enums
TransportProtocol, // UDP=1, TCP=2, TLS=3
RxDtmfFormat, // RFC_2833=1, SIP_INFO=2, RFC_2833_OR_SIP_INFO=3
TxDtmfFormat, // DISABLED=1, RFC_2833=2, SIP_INFO_RELAY=3, SIP_INFO_DTMF=4
SstRefreshMethod, // INVITE=1, UPDATE=2, UPDATE_FALLBACK_INVITE=3
Codec, // TELEPHONE_EVENT=6, G723=7, G729=8, PCMU=9, PCMA=10, ...
} from '@didww/sdk';
文件加密和上传
import { Encrypt } from '@didww/sdk';
// Instance-based: fetches public keys from the API automatically
const enc = new Encrypt(client);
const fingerprint = await enc.getFingerprint();
const encrypted = await enc.encrypt(fileBuffer);
const ids = await client.uploadEncryptedFiles(fingerprint, [
{ data: encrypted, description: 'My document', filename: 'document.pdf.enc' },
]);
静态功能也可用于手动密钥管理:
import { encryptWithKeys, calculateFingerprint, ref } from '@didww/sdk';
import { readFileSync } from 'node:fs';
const keys = await client.publicKeys().list();
const pems: [string, string] = [keys.data[0].key, keys.data[1].key];
const fingerprint = calculateFingerprint(pems);
const encrypted = encryptWithKeys(readFileSync('document.pdf'), pems);
const ids = await client.uploadEncryptedFiles(fingerprint, [
{ data: encrypted, description: 'My document', filename: 'document.pdf.enc' },
]);
// Create proof with uploaded files
await client.proofs().create({
proofType: ref('proof_types', 'type-id'),
entity: ref('identities', 'identity-id'),
files: ids.map((id) => ref('encrypted_files', id)),
});
Webhook 签名验证
使用 HMAC-SHA1 签名验证来自 DIDWWW 的 webhook 回调 .
初始化 DIDWW 用户面板中启用的召回密钥验证器( N)APIS – DidWWW API 3 – 回叫秘诀DIDWW 与它一起签下每一次召回,并在启用召回秘密时才发出召回.
import { RequestValidator } from '@didww/sdk';
const validator = new RequestValidator('your-callback-secret');
// In your webhook handler:
const valid = validator.validate(
requestUrl, // full original URL
payload, // Record<string, string> of payload key-value pairs
signature, // value of X-DIDWW-Signature header
);
处理错误
import { DidwwApiError } from '@didww/sdk';
try {
await client.orders().create({
/* ... */
});
} catch (error) {
if (error instanceof DidwwApiError) {
console.log('Status:', error.status);
for (const err of error.errors) {
console.log(`Error: ${err.detail} (code: ${err.code})`);
}
}
}
所有支助资源
| 资源 | 仓库 | 业务 |
|---|---|---|
| 国家 | client.countries() | 列表中,查找 |
| 地区 | client.regions() | 列表中,查找 |
| 城市 | client.cities() | 列表中,查找 |
| 区域 | client.areas() | 列表中,查找 |
| 楠柏前缀 | client.nanpaPrefixes() | 列表中,查找 |
| 弹 | client.pops() | 列表中,查找 |
| 组合类型 | client.didGroupTypes() | 列表中,查找 |
| 已编组 | client.didGroups() | 列表中,查找 |
| 可用 | client.availableDids() | 列表中,查找 |
| 证明Type | client.proofTypes() | 列表中,查找 |
| 公钥 | client.publicKeys() | 列表中,查找 |
| 地址要求 | client.addressRequirements() | 列表中,查找 |
| 支持文档模板 | client.supportingDocumentTemplates() | 列表中,查找 |
| 库存保留单位 | 包括 didGroups() | 仅包含 |
| 基于 Qty 定价 | 包括 capacityPools() | 仅包含 |
| 余额 | client.balance() | 寻找 |
| 已经 | client.dids() | 列表、查找、更新、删除 |
| 语音 Trunk | client.voiceInTrunks() | 列表、查找、创建、更新、删除 |
| 语音 Trunk 组 | client.voiceInTrunkGroups() | 列表、查找、创建、更新、删除 |
| 语音输出 | client.voiceOutTrunks() | 列表、查找、创建、更新、删除 |
| 语音输出 Trunk 复制证书 | client.voiceOutTrunkRegenerateCredentials() | 创建 |
| 保留 | client.didReservations() | 列表、查找、创建、更新、删除 |
| 能力波尔 | client.capacityPools() | 列表、查找、更新 |
| 共享能力组 | client.sharedCapacityGroups() | 列表、查找、创建、更新、删除 |
| 顺序 | client.orders() | 列表、查找、创建、更新、删除 |
| 导出 | client.exports() | 列表、查找、创建、更新、删除 |
| 地址 | client.addresses() | 列表、查找、创建、更新、删除 |
| 地址验证 | client.addressVerifications() | 列表、查找、创建、更新、删除 |
| 身份 | client.identities() | 列表、查找、创建、更新、删除 |
| 加密文件 | client.encryptedFiles() | 列表、查找、删除 |
| 永久支持文件 | client.permanentSupportingDocuments() | 创建, 删除 |
| 证据 | client.proofs() | 创建, 删除 |
| 地址要求调整 | client.addressRequirementValidations() | 创建 |
| 历史 | client.didHistory() | 列表中,查找 |
| 紧急需要 | client.emergencyRequirements() | 列表中,查找 |
| 紧急要求 | client.emergencyRequirementValidations() | 创建 |
| 紧急呼叫 服务 | client.emergencyCallingServices() | 列表、查找、删除 |
| 紧急核查 | client.emergencyVerifications() | 列表、查找、创建、更新 |
捐款
GitHub 上欢迎错误报告和拉取请求 https://github.com/didww/didww-api-3-typescript-sdk (中文(简体) )
释放
- 保证你在
main已经承诺了所有的改变. npm ci && npm run typecheck && npm run lint && npm run format:check确保所有检查通过npm version X.Y.Z(创建 git 承诺和标签)npm run build构建软件包npm publish --access public释放 npm 软件包git push --follow-tags origin main推动 git 承诺和标记到 GitHub
说明: 如果 npm publish 404等错误失败 '@didww/[email protected]' is not in this registry. 您需要通过 npm login。 。 。 .
许可证
麻省理工学院