wxcli 1.2.0__py3-none-any.whl

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 (413) hide show
  1. wxcli/__init__.py +4 -0
  2. wxcli/_playbook/.claude/agents/migration-advisor.md +239 -0
  3. wxcli/_playbook/.claude/agents/wxc-calling-builder.md +946 -0
  4. wxcli/_playbook/.claude/rules/cleanup.md +29 -0
  5. wxcli/_playbook/.claude/rules/cucm-migration.md +58 -0
  6. wxcli/_playbook/.claude/rules/org-health.md +14 -0
  7. wxcli/_playbook/.claude/settings.json +8 -0
  8. wxcli/_playbook/.claude/skills/audit-compliance/SKILL.md +515 -0
  9. wxcli/_playbook/.claude/skills/call-control/SKILL.md +747 -0
  10. wxcli/_playbook/.claude/skills/configure-features/SKILL.md +623 -0
  11. wxcli/_playbook/.claude/skills/configure-routing/SKILL.md +627 -0
  12. wxcli/_playbook/.claude/skills/contact-center/SKILL.md +1181 -0
  13. wxcli/_playbook/.claude/skills/cucm-migrate/SKILL.md +820 -0
  14. wxcli/_playbook/.claude/skills/customer-assist/SKILL.md +443 -0
  15. wxcli/_playbook/.claude/skills/device-platform/SKILL.md +407 -0
  16. wxcli/_playbook/.claude/skills/manage-call-settings/SKILL.md +757 -0
  17. wxcli/_playbook/.claude/skills/manage-devices/SKILL.md +790 -0
  18. wxcli/_playbook/.claude/skills/manage-identity/SKILL.md +638 -0
  19. wxcli/_playbook/.claude/skills/manage-licensing/SKILL.md +528 -0
  20. wxcli/_playbook/.claude/skills/manage-meetings/SKILL.md +708 -0
  21. wxcli/_playbook/.claude/skills/messaging-bots/SKILL.md +365 -0
  22. wxcli/_playbook/.claude/skills/messaging-spaces/SKILL.md +520 -0
  23. wxcli/_playbook/.claude/skills/org-health/SKILL.md +159 -0
  24. wxcli/_playbook/.claude/skills/provision-calling/SKILL.md +421 -0
  25. wxcli/_playbook/.claude/skills/query-live/SKILL.md +183 -0
  26. wxcli/_playbook/.claude/skills/query-live/domains/call-flow-trace.md +223 -0
  27. wxcli/_playbook/.claude/skills/query-live/domains/call-history.md +44 -0
  28. wxcli/_playbook/.claude/skills/query-live/domains/features.md +169 -0
  29. wxcli/_playbook/.claude/skills/query-live/domains/numbers.md +93 -0
  30. wxcli/_playbook/.claude/skills/query-live/domains/people-and-settings.md +146 -0
  31. wxcli/_playbook/.claude/skills/query-live/domains/routing.md +124 -0
  32. wxcli/_playbook/.claude/skills/reporting/SKILL.md +501 -0
  33. wxcli/_playbook/.claude/skills/reporting/references/cdr-recipes.md +1628 -0
  34. wxcli/_playbook/.claude/skills/reporting-cc/SKILL.md +251 -0
  35. wxcli/_playbook/.claude/skills/reporting-meetings/SKILL.md +290 -0
  36. wxcli/_playbook/.claude/skills/teardown/SKILL.md +214 -0
  37. wxcli/_playbook/.claude/skills/video-mesh/SKILL.md +379 -0
  38. wxcli/_playbook/.claude/skills/wxc-calling-debug/SKILL.md +475 -0
  39. wxcli/_playbook/CLAUDE.md +373 -0
  40. wxcli/_playbook/docs/reference/CLAUDE.md +25 -0
  41. wxcli/_playbook/docs/reference/admin-apps-data.md +643 -0
  42. wxcli/_playbook/docs/reference/admin-audit-security.md +322 -0
  43. wxcli/_playbook/docs/reference/admin-hybrid.md +383 -0
  44. wxcli/_playbook/docs/reference/admin-identity-scim.md +1007 -0
  45. wxcli/_playbook/docs/reference/admin-licensing.md +414 -0
  46. wxcli/_playbook/docs/reference/admin-org-management.md +586 -0
  47. wxcli/_playbook/docs/reference/admin-partner.md +357 -0
  48. wxcli/_playbook/docs/reference/archive/wxc-sdk-patterns.md +1229 -0
  49. wxcli/_playbook/docs/reference/archive/wxcadm-advanced.md +835 -0
  50. wxcli/_playbook/docs/reference/archive/wxcadm-core.md +743 -0
  51. wxcli/_playbook/docs/reference/archive/wxcadm-devices-workspaces.md +1305 -0
  52. wxcli/_playbook/docs/reference/archive/wxcadm-features.md +849 -0
  53. wxcli/_playbook/docs/reference/archive/wxcadm-locations.md +938 -0
  54. wxcli/_playbook/docs/reference/archive/wxcadm-person.md +1255 -0
  55. wxcli/_playbook/docs/reference/archive/wxcadm-routing.md +1262 -0
  56. wxcli/_playbook/docs/reference/authentication.md +965 -0
  57. wxcli/_playbook/docs/reference/call-control.md +1205 -0
  58. wxcli/_playbook/docs/reference/call-features-additional.md +2541 -0
  59. wxcli/_playbook/docs/reference/call-features-major.md +1320 -0
  60. wxcli/_playbook/docs/reference/call-routing.md +2304 -0
  61. wxcli/_playbook/docs/reference/contact-center-analytics.md +1049 -0
  62. wxcli/_playbook/docs/reference/contact-center-core.md +1614 -0
  63. wxcli/_playbook/docs/reference/contact-center-journey.md +457 -0
  64. wxcli/_playbook/docs/reference/contact-center-routing.md +1138 -0
  65. wxcli/_playbook/docs/reference/devices-core.md +1869 -0
  66. wxcli/_playbook/docs/reference/devices-dect.md +1150 -0
  67. wxcli/_playbook/docs/reference/devices-platform.md +467 -0
  68. wxcli/_playbook/docs/reference/devices-workspaces.md +1426 -0
  69. wxcli/_playbook/docs/reference/emergency-services.md +1122 -0
  70. wxcli/_playbook/docs/reference/location-calling-core.md +2037 -0
  71. wxcli/_playbook/docs/reference/location-calling-media.md +1334 -0
  72. wxcli/_playbook/docs/reference/location-recording-advanced.md +1482 -0
  73. wxcli/_playbook/docs/reference/meetings-content.md +473 -0
  74. wxcli/_playbook/docs/reference/meetings-core.md +903 -0
  75. wxcli/_playbook/docs/reference/meetings-infrastructure.md +899 -0
  76. wxcli/_playbook/docs/reference/meetings-settings.md +850 -0
  77. wxcli/_playbook/docs/reference/messaging-bots.md +1091 -0
  78. wxcli/_playbook/docs/reference/messaging-spaces.md +695 -0
  79. wxcli/_playbook/docs/reference/person-call-settings-behavior.md +1639 -0
  80. wxcli/_playbook/docs/reference/person-call-settings-handling.md +1394 -0
  81. wxcli/_playbook/docs/reference/person-call-settings-media.md +1898 -0
  82. wxcli/_playbook/docs/reference/person-call-settings-permissions.md +1329 -0
  83. wxcli/_playbook/docs/reference/provisioning.md +1366 -0
  84. wxcli/_playbook/docs/reference/reporting-analytics.md +1318 -0
  85. wxcli/_playbook/docs/reference/self-service-call-settings.md +1020 -0
  86. wxcli/_playbook/docs/reference/virtual-lines.md +1132 -0
  87. wxcli/_playbook/docs/reference/webhooks-events.md +1444 -0
  88. wxcli/_playbook/docs/reference/wxcadm-xsi-realtime.md +953 -0
  89. wxcli/_version.py +24 -0
  90. wxcli/auth.py +137 -0
  91. wxcli/commands/__init__.py +0 -0
  92. wxcli/commands/_registry.py +181 -0
  93. wxcli/commands/activation_email.py +103 -0
  94. wxcli/commands/admin_recordings.py +585 -0
  95. wxcli/commands/analytics.py +105 -0
  96. wxcli/commands/announcement_playlists.py +219 -0
  97. wxcli/commands/announcements.py +519 -0
  98. wxcli/commands/archive_users.py +36 -0
  99. wxcli/commands/attachment_actions.py +60 -0
  100. wxcli/commands/audit_events.py +83 -0
  101. wxcli/commands/authorizations.py +113 -0
  102. wxcli/commands/auto_attendant.py +642 -0
  103. wxcli/commands/broadworks_billing_reports.py +126 -0
  104. wxcli/commands/broadworks_enterprises.py +160 -0
  105. wxcli/commands/broadworks_subscribers.py +319 -0
  106. wxcli/commands/broadworks_workspaces.py +108 -0
  107. wxcli/commands/call_controls.py +1304 -0
  108. wxcli/commands/call_park.py +494 -0
  109. wxcli/commands/call_pickup.py +229 -0
  110. wxcli/commands/call_queue.py +1878 -0
  111. wxcli/commands/call_recording.py +707 -0
  112. wxcli/commands/call_routing.py +1610 -0
  113. wxcli/commands/call_settings_for_me_phase_5.py +212 -0
  114. wxcli/commands/caller_reputation.py +169 -0
  115. wxcli/commands/calling_service.py +305 -0
  116. wxcli/commands/cc_address_book.py +770 -0
  117. wxcli/commands/cc_agent_greetings.py +242 -0
  118. wxcli/commands/cc_agent_summaries.py +39 -0
  119. wxcli/commands/cc_agent_wellbeing.py +216 -0
  120. wxcli/commands/cc_agents.py +425 -0
  121. wxcli/commands/cc_ai_assistant.py +55 -0
  122. wxcli/commands/cc_ai_feature.py +294 -0
  123. wxcli/commands/cc_audio_files.py +149 -0
  124. wxcli/commands/cc_auto_csat.py +311 -0
  125. wxcli/commands/cc_aux_code.py +458 -0
  126. wxcli/commands/cc_business_hour.py +353 -0
  127. wxcli/commands/cc_call_monitoring.py +228 -0
  128. wxcli/commands/cc_callbacks.py +194 -0
  129. wxcli/commands/cc_campaign.py +212 -0
  130. wxcli/commands/cc_captures.py +39 -0
  131. wxcli/commands/cc_contact_list.py +169 -0
  132. wxcli/commands/cc_contact_number.py +295 -0
  133. wxcli/commands/cc_data_sources.py +235 -0
  134. wxcli/commands/cc_desktop_layout.py +380 -0
  135. wxcli/commands/cc_desktop_profile.py +322 -0
  136. wxcli/commands/cc_dial_number.py +511 -0
  137. wxcli/commands/cc_dial_plan.py +347 -0
  138. wxcli/commands/cc_dnc.py +101 -0
  139. wxcli/commands/cc_entry_point.py +545 -0
  140. wxcli/commands/cc_ewt.py +51 -0
  141. wxcli/commands/cc_flow.py +477 -0
  142. wxcli/commands/cc_global_vars.py +430 -0
  143. wxcli/commands/cc_holiday_list.py +335 -0
  144. wxcli/commands/cc_journey.py +1261 -0
  145. wxcli/commands/cc_legacy_flows.py +49 -0
  146. wxcli/commands/cc_multimedia_profile.py +436 -0
  147. wxcli/commands/cc_notification.py +51 -0
  148. wxcli/commands/cc_outdial_ani.py +604 -0
  149. wxcli/commands/cc_overrides.py +341 -0
  150. wxcli/commands/cc_queue.py +1083 -0
  151. wxcli/commands/cc_queue_stats.py +55 -0
  152. wxcli/commands/cc_realtime.py +51 -0
  153. wxcli/commands/cc_resource_collection.py +312 -0
  154. wxcli/commands/cc_search.py +46 -0
  155. wxcli/commands/cc_site.py +400 -0
  156. wxcli/commands/cc_skill.py +350 -0
  157. wxcli/commands/cc_skill_profile.py +289 -0
  158. wxcli/commands/cc_subscriptions.py +426 -0
  159. wxcli/commands/cc_summaries.py +130 -0
  160. wxcli/commands/cc_tasks.py +844 -0
  161. wxcli/commands/cc_team.py +325 -0
  162. wxcli/commands/cc_user_profiles.py +349 -0
  163. wxcli/commands/cc_users.py +701 -0
  164. wxcli/commands/cc_work_types.py +363 -0
  165. wxcli/commands/cdr.py +89 -0
  166. wxcli/commands/classifications.py +38 -0
  167. wxcli/commands/cleanup.py +1427 -0
  168. wxcli/commands/client_settings.py +72 -0
  169. wxcli/commands/conference.py +328 -0
  170. wxcli/commands/configure.py +72 -0
  171. wxcli/commands/converged_recordings.py +347 -0
  172. wxcli/commands/converged_recordings_export.py +310 -0
  173. wxcli/commands/cq_playlists.py +42 -0
  174. wxcli/commands/cucm.py +2794 -0
  175. wxcli/commands/cucm_config.py +129 -0
  176. wxcli/commands/customer_assist.py +373 -0
  177. wxcli/commands/data_sources.py +227 -0
  178. wxcli/commands/dect_devices.py +787 -0
  179. wxcli/commands/device_configurations.py +77 -0
  180. wxcli/commands/device_dynamic_settings.py +320 -0
  181. wxcli/commands/device_settings.py +1489 -0
  182. wxcli/commands/devices.py +280 -0
  183. wxcli/commands/domains.py +142 -0
  184. wxcli/commands/ecm.py +178 -0
  185. wxcli/commands/emergency_services.py +827 -0
  186. wxcli/commands/events.py +83 -0
  187. wxcli/commands/external_voicemail.py +52 -0
  188. wxcli/commands/groups.py +206 -0
  189. wxcli/commands/guest_management.py +76 -0
  190. wxcli/commands/hds.py +340 -0
  191. wxcli/commands/hot_desk.py +67 -0
  192. wxcli/commands/hot_desking_members.py +118 -0
  193. wxcli/commands/hot_desking_portal.py +124 -0
  194. wxcli/commands/hunt_group.py +577 -0
  195. wxcli/commands/hybrid_clusters.py +71 -0
  196. wxcli/commands/hybrid_connectors.py +67 -0
  197. wxcli/commands/identity_org.py +159 -0
  198. wxcli/commands/init_playbook.py +155 -0
  199. wxcli/commands/licenses.py +106 -0
  200. wxcli/commands/live_monitoring.py +40 -0
  201. wxcli/commands/location_call_handling.py +577 -0
  202. wxcli/commands/location_schedules.py +341 -0
  203. wxcli/commands/location_settings.py +1297 -0
  204. wxcli/commands/location_voicemail.py +494 -0
  205. wxcli/commands/locations.py +336 -0
  206. wxcli/commands/meeting_captions.py +110 -0
  207. wxcli/commands/meeting_chats.py +63 -0
  208. wxcli/commands/meeting_invitees.py +234 -0
  209. wxcli/commands/meeting_messages.py +28 -0
  210. wxcli/commands/meeting_participants.py +278 -0
  211. wxcli/commands/meeting_polls.py +111 -0
  212. wxcli/commands/meeting_preferences.py +515 -0
  213. wxcli/commands/meeting_qa.py +78 -0
  214. wxcli/commands/meeting_qualities.py +43 -0
  215. wxcli/commands/meeting_reports.py +101 -0
  216. wxcli/commands/meeting_session_types.py +105 -0
  217. wxcli/commands/meeting_site.py +57 -0
  218. wxcli/commands/meeting_slido.py +44 -0
  219. wxcli/commands/meeting_summaries.py +92 -0
  220. wxcli/commands/meeting_tracking_codes.py +233 -0
  221. wxcli/commands/meeting_transcripts.py +230 -0
  222. wxcli/commands/meetings.py +1997 -0
  223. wxcli/commands/memberships.py +164 -0
  224. wxcli/commands/messages.py +213 -0
  225. wxcli/commands/mode_management.py +244 -0
  226. wxcli/commands/my_call_settings.py +3577 -0
  227. wxcli/commands/numbers.py +405 -0
  228. wxcli/commands/operating_modes.py +410 -0
  229. wxcli/commands/org_contacts.py +284 -0
  230. wxcli/commands/org_health_cli.py +55 -0
  231. wxcli/commands/org_settings.py +71 -0
  232. wxcli/commands/organizations.py +83 -0
  233. wxcli/commands/paging_group.py +257 -0
  234. wxcli/commands/partner_admins.py +141 -0
  235. wxcli/commands/partner_reports.py +188 -0
  236. wxcli/commands/partner_tags.py +216 -0
  237. wxcli/commands/people.py +279 -0
  238. wxcli/commands/person_call_settings.py +71 -0
  239. wxcli/commands/pstn.py +280 -0
  240. wxcli/commands/recording_report.py +154 -0
  241. wxcli/commands/report_templates.py +38 -0
  242. wxcli/commands/reports.py +144 -0
  243. wxcli/commands/resource_group_memberships.py +160 -0
  244. wxcli/commands/resource_groups.py +67 -0
  245. wxcli/commands/roles.py +63 -0
  246. wxcli/commands/room_tabs.py +160 -0
  247. wxcli/commands/rooms.py +225 -0
  248. wxcli/commands/scim_bulk.py +46 -0
  249. wxcli/commands/scim_groups.py +246 -0
  250. wxcli/commands/scim_schemas.py +82 -0
  251. wxcli/commands/scim_users.py +283 -0
  252. wxcli/commands/security_audit.py +54 -0
  253. wxcli/commands/service_apps.py +51 -0
  254. wxcli/commands/single_number_reach.py +209 -0
  255. wxcli/commands/team_memberships.py +155 -0
  256. wxcli/commands/teams.py +153 -0
  257. wxcli/commands/ucm_profile.py +42 -0
  258. wxcli/commands/update.py +219 -0
  259. wxcli/commands/user_settings.py +5116 -0
  260. wxcli/commands/video_mesh.py +1090 -0
  261. wxcli/commands/virtual_extensions.py +502 -0
  262. wxcli/commands/virtual_line_settings.py +2072 -0
  263. wxcli/commands/webhooks.py +176 -0
  264. wxcli/commands/wholesale_billing_reports.py +147 -0
  265. wxcli/commands/wholesale_provisioning.py +523 -0
  266. wxcli/commands/workspace_locations.py +339 -0
  267. wxcli/commands/workspace_metrics.py +103 -0
  268. wxcli/commands/workspace_personalization.py +70 -0
  269. wxcli/commands/workspace_settings.py +3347 -0
  270. wxcli/commands/workspaces.py +279 -0
  271. wxcli/commands/xapi.py +114 -0
  272. wxcli/config.py +139 -0
  273. wxcli/errors.py +77 -0
  274. wxcli/main.py +210 -0
  275. wxcli/migration/CLAUDE.md +106 -0
  276. wxcli/migration/__init__.py +1 -0
  277. wxcli/migration/advisory/CLAUDE.md +196 -0
  278. wxcli/migration/advisory/__init__.py +46 -0
  279. wxcli/migration/advisory/advisor.py +107 -0
  280. wxcli/migration/advisory/advisory_patterns.py +2552 -0
  281. wxcli/migration/advisory/recommendation_rules.py +835 -0
  282. wxcli/migration/cucm/CLAUDE.md +74 -0
  283. wxcli/migration/cucm/__init__.py +5 -0
  284. wxcli/migration/cucm/connection.py +265 -0
  285. wxcli/migration/cucm/discovery.py +225 -0
  286. wxcli/migration/cucm/extractors/__init__.py +1 -0
  287. wxcli/migration/cucm/extractors/announcements.py +92 -0
  288. wxcli/migration/cucm/extractors/base.py +90 -0
  289. wxcli/migration/cucm/extractors/device_profiles.py +98 -0
  290. wxcli/migration/cucm/extractors/devices.py +193 -0
  291. wxcli/migration/cucm/extractors/e911.py +103 -0
  292. wxcli/migration/cucm/extractors/features.py +309 -0
  293. wxcli/migration/cucm/extractors/helpers.py +73 -0
  294. wxcli/migration/cucm/extractors/informational.py +295 -0
  295. wxcli/migration/cucm/extractors/locations.py +221 -0
  296. wxcli/migration/cucm/extractors/moh.py +100 -0
  297. wxcli/migration/cucm/extractors/remote_destinations.py +96 -0
  298. wxcli/migration/cucm/extractors/routing.py +493 -0
  299. wxcli/migration/cucm/extractors/shared_lines.py +146 -0
  300. wxcli/migration/cucm/extractors/templates.py +229 -0
  301. wxcli/migration/cucm/extractors/tier4.py +173 -0
  302. wxcli/migration/cucm/extractors/users.py +240 -0
  303. wxcli/migration/cucm/extractors/voicemail.py +243 -0
  304. wxcli/migration/cucm/extractors/workspaces.py +88 -0
  305. wxcli/migration/cucm/unity_connection.py +304 -0
  306. wxcli/migration/execute/CLAUDE.md +372 -0
  307. wxcli/migration/execute/__init__.py +303 -0
  308. wxcli/migration/execute/batch.py +277 -0
  309. wxcli/migration/execute/dependency.py +483 -0
  310. wxcli/migration/execute/engine.py +964 -0
  311. wxcli/migration/execute/handlers.py +2232 -0
  312. wxcli/migration/execute/planner.py +1939 -0
  313. wxcli/migration/execute/runtime.py +571 -0
  314. wxcli/migration/export/CLAUDE.md +49 -0
  315. wxcli/migration/export/__init__.py +8 -0
  316. wxcli/migration/export/csv_export.py +126 -0
  317. wxcli/migration/export/deployment_plan.py +479 -0
  318. wxcli/migration/export/json_export.py +60 -0
  319. wxcli/migration/models.py +941 -0
  320. wxcli/migration/phone_models.py +50 -0
  321. wxcli/migration/preflight/CLAUDE.md +52 -0
  322. wxcli/migration/preflight/__init__.py +89 -0
  323. wxcli/migration/preflight/checks.py +911 -0
  324. wxcli/migration/preflight/runner.py +293 -0
  325. wxcli/migration/rate_limiter.py +144 -0
  326. wxcli/migration/report/CLAUDE.md +245 -0
  327. wxcli/migration/report/__init__.py +7 -0
  328. wxcli/migration/report/appendix.py +2437 -0
  329. wxcli/migration/report/assembler.py +183 -0
  330. wxcli/migration/report/charts.py +263 -0
  331. wxcli/migration/report/executive.py +776 -0
  332. wxcli/migration/report/explainer.py +704 -0
  333. wxcli/migration/report/helpers.py +74 -0
  334. wxcli/migration/report/ingest.py +152 -0
  335. wxcli/migration/report/notice_templates.py +192 -0
  336. wxcli/migration/report/score.py +375 -0
  337. wxcli/migration/report/styles.py +1052 -0
  338. wxcli/migration/report/user_diff.py +1052 -0
  339. wxcli/migration/report/user_notice.py +425 -0
  340. wxcli/migration/state.py +139 -0
  341. wxcli/migration/store.py +860 -0
  342. wxcli/migration/transform/CLAUDE.md +107 -0
  343. wxcli/migration/transform/__init__.py +12 -0
  344. wxcli/migration/transform/analysis_pipeline.py +453 -0
  345. wxcli/migration/transform/analyzers/__init__.py +142 -0
  346. wxcli/migration/transform/analyzers/css_permission.py +189 -0
  347. wxcli/migration/transform/analyzers/css_routing.py +335 -0
  348. wxcli/migration/transform/analyzers/device_compatibility.py +122 -0
  349. wxcli/migration/transform/analyzers/dn_ambiguity.py +131 -0
  350. wxcli/migration/transform/analyzers/duplicate_user.py +221 -0
  351. wxcli/migration/transform/analyzers/extension_conflict.py +173 -0
  352. wxcli/migration/transform/analyzers/feature_approximation.py +227 -0
  353. wxcli/migration/transform/analyzers/layout_overflow.py +174 -0
  354. wxcli/migration/transform/analyzers/location_ambiguity.py +184 -0
  355. wxcli/migration/transform/analyzers/missing_data.py +300 -0
  356. wxcli/migration/transform/analyzers/selective_call_handling.py +394 -0
  357. wxcli/migration/transform/analyzers/shared_line.py +170 -0
  358. wxcli/migration/transform/analyzers/voicemail_compatibility.py +267 -0
  359. wxcli/migration/transform/analyzers/workspace_license.py +212 -0
  360. wxcli/migration/transform/cross_reference.py +1136 -0
  361. wxcli/migration/transform/cucm_pattern.py +246 -0
  362. wxcli/migration/transform/decisions.py +301 -0
  363. wxcli/migration/transform/e164.py +145 -0
  364. wxcli/migration/transform/engine.py +256 -0
  365. wxcli/migration/transform/mappers/CLAUDE.md +123 -0
  366. wxcli/migration/transform/mappers/__init__.py +76 -0
  367. wxcli/migration/transform/mappers/announcement_mapper.py +122 -0
  368. wxcli/migration/transform/mappers/base.py +190 -0
  369. wxcli/migration/transform/mappers/button_template_mapper.py +186 -0
  370. wxcli/migration/transform/mappers/call_forwarding_mapper.py +228 -0
  371. wxcli/migration/transform/mappers/call_settings_mapper.py +144 -0
  372. wxcli/migration/transform/mappers/css_mapper.py +868 -0
  373. wxcli/migration/transform/mappers/dect_mapper.py +367 -0
  374. wxcli/migration/transform/mappers/device_layout_mapper.py +289 -0
  375. wxcli/migration/transform/mappers/device_mapper.py +227 -0
  376. wxcli/migration/transform/mappers/device_profile_mapper.py +190 -0
  377. wxcli/migration/transform/mappers/device_settings_mapper.py +464 -0
  378. wxcli/migration/transform/mappers/e911_mapper.py +129 -0
  379. wxcli/migration/transform/mappers/ecbn_mapper.py +263 -0
  380. wxcli/migration/transform/mappers/executive_assistant_mapper.py +156 -0
  381. wxcli/migration/transform/mappers/feature_mapper.py +1179 -0
  382. wxcli/migration/transform/mappers/line_mapper.py +310 -0
  383. wxcli/migration/transform/mappers/location_mapper.py +235 -0
  384. wxcli/migration/transform/mappers/moh_mapper.py +114 -0
  385. wxcli/migration/transform/mappers/monitoring_mapper.py +221 -0
  386. wxcli/migration/transform/mappers/receptionist_mapper.py +245 -0
  387. wxcli/migration/transform/mappers/routing_mapper.py +821 -0
  388. wxcli/migration/transform/mappers/snr_mapper.py +201 -0
  389. wxcli/migration/transform/mappers/softkey_mapper.py +205 -0
  390. wxcli/migration/transform/mappers/user_mapper.py +264 -0
  391. wxcli/migration/transform/mappers/voicemail_group_mapper.py +286 -0
  392. wxcli/migration/transform/mappers/voicemail_mapper.py +514 -0
  393. wxcli/migration/transform/mappers/workspace_mapper.py +464 -0
  394. wxcli/migration/transform/normalizers.py +2008 -0
  395. wxcli/migration/transform/pattern_converter.py +106 -0
  396. wxcli/migration/transform/pipeline.py +271 -0
  397. wxcli/migration/transform/rules.py +255 -0
  398. wxcli/org_health/CLAUDE.md +63 -0
  399. wxcli/org_health/__init__.py +7 -0
  400. wxcli/org_health/analyze.py +93 -0
  401. wxcli/org_health/checks.py +458 -0
  402. wxcli/org_health/collector.py +62 -0
  403. wxcli/org_health/models.py +78 -0
  404. wxcli/org_health/report.py +300 -0
  405. wxcli/output.py +101 -0
  406. wxcli-1.2.0.dist-info/METADATA +505 -0
  407. wxcli-1.2.0.dist-info/RECORD +413 -0
  408. wxcli-1.2.0.dist-info/WHEEL +5 -0
  409. wxcli-1.2.0.dist-info/entry_points.txt +2 -0
  410. wxcli-1.2.0.dist-info/licenses/LICENSE +191 -0
  411. wxcli-1.2.0.dist-info/scm_file_list.json +751 -0
  412. wxcli-1.2.0.dist-info/scm_version.json +8 -0
  413. wxcli-1.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2304 @@
1
+ <!-- Updated by playbook session 2026-03-18 -->
2
+ # Call Routing & PSTN Reference
3
+
4
+ ## Sources
5
+ - wxc_sdk v1.30.0
6
+ - OpenAPI spec: specs/webex-cloud-calling.json
7
+ - developer.webex.com Call Routing APIs
8
+
9
+ Comprehensive reference for Webex Calling dial plans, trunks, route groups, route lists, translation patterns, PSTN configuration, and call routing validation using the `wxc_sdk` and raw HTTP via `api.session`.
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ 1. [Architecture Overview](#architecture-overview)
16
+ 2. [SDK Access Paths](#sdk-access-paths)
17
+ 3. [Required Scopes](#required-scopes)
18
+ 4. [Dial Plans](#dial-plans)
19
+ 5. [Trunks](#trunks)
20
+ 6. [Route Groups](#route-groups)
21
+ 7. [Route Lists](#route-lists)
22
+ 8. [Translation Patterns](#translation-patterns)
23
+ 9. [PSTN Configuration](#pstn-configuration)
24
+ 10. [Premises PSTN](#premises-pstn)
25
+ 11. [Private Network Connect (PNC)](#private-network-connect-pnc)
26
+ 12. [Route Choices](#route-choices)
27
+ 13. [Call Routing Test](#call-routing-test)
28
+ 14. [Phone Number Management](#phone-number-management)
29
+ 15. [Data Models Quick Reference](#data-models-quick-reference)
30
+ 16. [Common Gotchas](#common-gotchas)
31
+ 17. [See Also](#see-also)
32
+
33
+ ---
34
+
35
+ ## Architecture Overview
36
+
37
+ Webex Calling routes outbound calls from cloud-hosted users to on-premises or PSTN destinations through a layered chain:
38
+
39
+ ```
40
+ Dial Plan (pattern match)
41
+ |
42
+ v
43
+ Route Choice (trunk or route group)
44
+ |
45
+ +---> Trunk (direct) -----> Local Gateway / SBC -----> PSTN / PBX
46
+ |
47
+ +---> Route Group ---------> Trunk(s) with priority/failover
48
+ |
49
+ v
50
+ Route List (number patterns for cloud PSTN / Dedicated Instance)
51
+ ```
52
+
53
+ ### How the chain works
54
+
55
+ 1. **Dial Plans** are configured globally (org-wide, not per-location). Each dial plan contains one or more **dial patterns** (e.g., `+1919!`, `9XXX`) and is associated with a single **routing choice** -- either a trunk or a route group.
56
+ 2. When a user dials a number, the platform matches it against all dial plan patterns. The matching dial plan's routing choice determines where the call goes.
57
+ 3. A **Trunk** is a direct SIP connection between Webex Calling and an on-premises local gateway or SBC.
58
+ 4. A **Route Group** bundles up to 10 trunks (from different locations) with priority-based failover.
59
+ 5. A **Route List** is a list of phone numbers reachable via a route group. Route lists provide cloud PSTN connectivity to Webex Calling Dedicated Instance.
60
+ 6. **Translation Patterns** manipulate dialed digits before routing (outbound calls only). They can be applied at the organization level or location level.
61
+ 7. **PSTN Connection** settings at the location level determine which PSTN provider (Cisco PSTN, Cloud-Connected PSTN, or Local Gateway) handles calls for that location.
62
+
63
+ ---
64
+
65
+ ## SDK Access Paths
66
+
67
+ All call routing APIs are accessed through the `TelephonyApi` instance on the SDK:
68
+
69
+ ```python
70
+ from wxc_sdk import WebexSimpleApi
71
+
72
+ api = WebexSimpleApi(tokens='...')
73
+
74
+ # Premises PSTN sub-APIs
75
+ api.telephony.prem_pstn.dial_plan # DialPlanApi
76
+ api.telephony.prem_pstn.trunk # TrunkApi
77
+ api.telephony.prem_pstn.route_group # RouteGroupApi
78
+ api.telephony.prem_pstn.route_list # RouteListApi
79
+
80
+ # Call routing sub-APIs
81
+ api.telephony.call_routing.tp # TranslationPatternsApi
82
+
83
+ # PSTN location connection
84
+ api.telephony.pstn # PSTNApi
85
+
86
+ # Private Network Connect
87
+ api.telephony.pnc # PrivateNetworkConnectApi
88
+
89
+ # Top-level telephony methods
90
+ api.telephony.route_choices(...)
91
+ api.telephony.test_call_routing(...)
92
+ api.telephony.validate_phone_numbers(...)
93
+ api.telephony.phone_numbers(...)
94
+ ```
95
+
96
+ ### API base paths
97
+
98
+ | API Class | REST Base Path |
99
+ |-----------|---------------|
100
+ | `DialPlanApi` | `telephony/config/premisePstn/dialPlans` |
101
+ | `TrunkApi` | `telephony/config/premisePstn/trunks` |
102
+ | `RouteGroupApi` | `telephony/config/premisePstn/routeGroups` |
103
+ | `RouteListApi` | `telephony/config/premisePstn/routeLists` |
104
+ | `TranslationPatternsApi` | `telephony/config/callRouting/translationPatterns` |
105
+ | `PSTNApi` | `telephony/pstn/locations` |
106
+ | `PrivateNetworkConnectApi` | `telephony/config/locations` |
107
+ | `PremisePstnApi` (parent) | `telephony/config/premisePstn` |
108
+
109
+ ---
110
+
111
+ ## Required Scopes
112
+
113
+ | Operation | Scope |
114
+ |-----------|-------|
115
+ | Read dial plans, trunks, route groups, route lists, translation patterns | `spark-admin:telephony_config_read` |
116
+ | Create/update/delete dial plans, trunks, route groups, route lists, translation patterns | `spark-admin:telephony_config_write` |
117
+ | Test call routing | `spark-admin:telephony_config_write` |
118
+ | Validate phone numbers | `spark-admin:telephony_config_write` |
119
+ | Read phone numbers | `spark-admin:telephony_config_read` |
120
+ | Read PSTN location connection | `spark-admin:telephony_pstn_read` |
121
+ | Configure PSTN location connection | `spark-admin:telephony_pstn_write` |
122
+ | Read/update Private Network Connect | `spark-admin:telephony_config_read` / `spark-admin:telephony_config_write` |
123
+
124
+ ---
125
+
126
+ ## Dial Plans
127
+
128
+ Dial plans route calls to on-premises destinations by use of trunks or route groups. They are configured **globally for an enterprise** and apply to all users, regardless of location.
129
+
130
+ ### Data Models
131
+
132
+ ```python
133
+ class DialPlan(ApiModel):
134
+ dial_plan_id: Optional[str] # alias 'id'
135
+ name: Optional[str]
136
+ route_id: str # ID of trunk or route group
137
+ route_name: Optional[str]
138
+ route_type: RouteType # 'ROUTE_GROUP' | 'TRUNK'
139
+ customer: Optional[Customer]
140
+
141
+ class RouteType(str, Enum):
142
+ route_group = 'ROUTE_GROUP'
143
+ trunk = 'TRUNK'
144
+ cisco_pstn = 'CISCO_PSTN'
145
+ cloud_connected_pstn = 'CLOUD_CONNECTED_PSTN'
146
+
147
+ class CreateResponse(ApiModel):
148
+ dial_plan_id: str # alias 'id'
149
+ dial_pattern_errors: list[DialPatternValidate]
150
+ # .ok property: True if no errors
151
+
152
+ class PatternAndAction(ApiModel):
153
+ dial_pattern: str
154
+ action: PatternAction # 'ADD' | 'DELETE'
155
+ # helper statics: PatternAndAction.add(pattern), PatternAndAction.delete(pattern)
156
+ ```
157
+
158
+ ### Dial Pattern Rules
159
+
160
+ - A dial pattern is a sequence of digits (1-9), followed by optional wildcard characters.
161
+ - `!` matches any sequence of digits. Can only occur once at the end. Only valid in E.164 patterns.
162
+ - `X` matches a single digit (0-9).
163
+ - Example E.164 pattern: `+1408!` matches any number starting with +1408.
164
+ - Example short pattern: `9XXX` matches any 4-digit string starting with 9.
165
+
166
+ ### API Methods
167
+
168
+ #### List Dial Plans
169
+
170
+ ```python
171
+ DialPlanApi.list(
172
+ dial_plan_name: str = None,
173
+ route_group_name: str = None,
174
+ trunk_name: str = None,
175
+ order: str = None, # sort fields: name, routeName, routeType
176
+ org_id: str = None
177
+ ) -> Generator[DialPlan, None, None]
178
+ ```
179
+
180
+ #### Create Dial Plan
181
+
182
+ ```python
183
+ DialPlanApi.create(
184
+ name: str,
185
+ route_id: str,
186
+ route_type: RouteType,
187
+ dial_patterns: List[str] = None,
188
+ org_id: str = None
189
+ ) -> CreateResponse
190
+ ```
191
+
192
+ Returns a `CreateResponse` with the new dial plan ID and any pattern validation errors. Check `response.ok` to confirm no errors.
193
+
194
+ #### Get Dial Plan Details
195
+
196
+ ```python
197
+ DialPlanApi.details(
198
+ dial_plan_id: str,
199
+ org_id: str = None
200
+ ) -> DialPlan
201
+ ```
202
+
203
+ #### Update Dial Plan
204
+
205
+ ```python
206
+ DialPlanApi.update(
207
+ update: DialPlan, # name, route_id, route_type required
208
+ org_id: str = None
209
+ ) -> None
210
+ ```
211
+
212
+ All three fields (`name`, `route_id`, `route_type`) must be set on the `update` object.
213
+
214
+ #### Delete Dial Plan
215
+
216
+ ```python
217
+ DialPlanApi.delete_dial_plan(
218
+ dial_plan_id: str,
219
+ org_id: str = None
220
+ ) -> None
221
+ ```
222
+
223
+ #### List Dial Patterns
224
+
225
+ ```python
226
+ DialPlanApi.patterns(
227
+ dial_plan_id: str,
228
+ org_id: str = None,
229
+ dial_pattern: str = None
230
+ ) -> Generator[str, None, None]
231
+ ```
232
+
233
+ Returns the raw pattern strings.
234
+
235
+ #### Modify Dial Patterns (Add/Delete)
236
+
237
+ ```python
238
+ DialPlanApi.modify_patterns(
239
+ dial_plan_id: str,
240
+ dial_patterns: List[PatternAndAction],
241
+ org_id: str = None
242
+ ) -> None
243
+ ```
244
+
245
+ Patterns not present in the request are not modified. Use `PatternAndAction.add(pattern)` and `PatternAndAction.delete(pattern)` helper methods.
246
+
247
+ #### Delete All Dial Patterns
248
+
249
+ ```python
250
+ DialPlanApi.delete_all_patterns(
251
+ dial_plan_id: str,
252
+ org_id: str = None
253
+ ) -> None
254
+ ```
255
+
256
+ ### Validate Dial Patterns
257
+
258
+ This method is on the parent `PremisePstnApi`:
259
+
260
+ ```python
261
+ PremisePstnApi.validate_pattern(
262
+ dial_patterns: Union[str, List[str]],
263
+ org_id: str = None
264
+ ) -> DialPatternValidationResult
265
+ ```
266
+
267
+ ```python
268
+ class DialPatternValidationResult(ApiModel):
269
+ status: ValidationStatus # 'OK' | 'ERRORS'
270
+ dial_pattern_status: list[DialPatternValidate]
271
+ # .ok property: True if status == 'OK'
272
+
273
+ class DialPatternValidate(ApiModel):
274
+ dial_pattern: str
275
+ pattern_status: DialPatternStatus # 'INVALID' | 'DUPLICATE' | 'DUPLICATE_IN_LIST'
276
+ message: str
277
+ ```
278
+
279
+ ### Usage Example
280
+
281
+ ```python
282
+ from wxc_sdk.common import RouteType
283
+
284
+ # Create a dial plan pointing to a trunk
285
+ response = api.telephony.prem_pstn.dial_plan.create(
286
+ name='US-Outbound',
287
+ route_id=trunk_id,
288
+ route_type=RouteType.trunk,
289
+ dial_patterns=['+1!']
290
+ )
291
+ if not response.ok:
292
+ print(f"Pattern errors: {response.dial_pattern_errors}")
293
+
294
+ # Add patterns later
295
+ from wxc_sdk.telephony.prem_pstn.dial_plan import PatternAndAction
296
+
297
+ api.telephony.prem_pstn.dial_plan.modify_patterns(
298
+ dial_plan_id=response.dial_plan_id,
299
+ dial_patterns=[
300
+ PatternAndAction.add('+44!'),
301
+ PatternAndAction.delete('+1!')
302
+ ]
303
+ )
304
+ ```
305
+
306
+ ### CLI Examples
307
+
308
+ ```bash
309
+ # List all dial plans
310
+ wxcli call-routing list-dial-plans
311
+
312
+ # Filter dial plans by name
313
+ wxcli call-routing list-dial-plans --dial-plan-name "US-Outbound"
314
+
315
+ # Filter by trunk name
316
+ wxcli call-routing list-dial-plans --trunk-name "HQ-LGW-01"
317
+
318
+ # Filter by route group name
319
+ wxcli call-routing list-dial-plans --route-group-name "US-East-RG"
320
+
321
+ # Get dial plan details
322
+ wxcli call-routing show-dial-plans Y2lzY29zcGFyazovL_DIAL_PLAN_ID
323
+
324
+ # Create a dial plan (patterns require --json-body since dialPatterns is an array)
325
+ wxcli call-routing create --name "US-Outbound" --route-id Y2lzY29zcGFyazovL_TRUNK_ID
326
+
327
+ # Create with dial patterns via --json-body
328
+ wxcli call-routing create --json-body '{
329
+ "name": "US-Outbound",
330
+ "routeId": "Y2lzY29zcGFyazovL_TRUNK_ID",
331
+ "routeType": "TRUNK",
332
+ "dialPatterns": ["+1!", "+44!"]
333
+ }'
334
+
335
+ # Update a dial plan name or route assignment
336
+ wxcli call-routing update-dial-plans Y2lzY29zcGFyazovL_DIAL_PLAN_ID \
337
+ --name "US-Outbound-v2" --route-id Y2lzY29zcGFyazovL_NEW_TRUNK_ID
338
+
339
+ # Modify dial patterns (add/delete patterns on an existing dial plan)
340
+ wxcli call-routing update Y2lzY29zcGFyazovL_DIAL_PLAN_ID --json-body '{
341
+ "dialPatterns": [
342
+ {"dialPattern": "+44!", "action": "ADD"},
343
+ {"dialPattern": "+1!", "action": "DELETE"}
344
+ ]
345
+ }'
346
+
347
+ # Delete all dial patterns from a dial plan
348
+ wxcli call-routing update Y2lzY29zcGFyazovL_DIAL_PLAN_ID --delete-all-dial-patterns
349
+
350
+ # Validate dial patterns before creating a dial plan
351
+ wxcli call-routing validate-a-dial --json-body '{"dialPatterns": ["+1408!", "+44!", "9XXX"]}'
352
+
353
+ # Delete a dial plan
354
+ wxcli call-routing delete Y2lzY29zcGFyazovL_DIAL_PLAN_ID --force
355
+ ```
356
+
357
+ ### Raw HTTP
358
+ <!-- Updated by playbook session 2026-03-18 -->
359
+
360
+ All dial plan endpoints live under the `/premisePstn/` prefix -- NOT `/dialPlans` at the top level.
361
+
362
+ ```python
363
+ from wxc_sdk import WebexSimpleApi
364
+ api = WebexSimpleApi()
365
+ BASE = "https://webexapis.com/v1"
366
+
367
+ # List dial plans
368
+ plans = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/dialPlans",
369
+ params={"max": 1000})
370
+ # plans["dialPlans"] -> list of dicts
371
+
372
+ # Create dial plan
373
+ result = api.session.rest_post(f"{BASE}/telephony/config/premisePstn/dialPlans", json={
374
+ "name": "US-Outbound",
375
+ "routeId": trunk_id,
376
+ "routeType": "TRUNK",
377
+ "dialPatterns": ["+1!"]
378
+ })
379
+ # result["id"] -> new dial plan ID
380
+
381
+ # Get dial plan details
382
+ dp = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/dialPlans/{dial_plan_id}")
383
+
384
+ # Update dial plan
385
+ api.session.rest_put(f"{BASE}/telephony/config/premisePstn/dialPlans/{dial_plan_id}", json={
386
+ "name": "US-Outbound-v2",
387
+ "routeId": trunk_id,
388
+ "routeType": "TRUNK"
389
+ })
390
+
391
+ # Delete dial plan
392
+ api.session.rest_delete(f"{BASE}/telephony/config/premisePstn/dialPlans/{dial_plan_id}")
393
+
394
+ # List dial patterns for a dial plan
395
+ patterns = api.session.rest_get(
396
+ f"{BASE}/telephony/config/premisePstn/dialPlans/{dial_plan_id}/dialPatterns",
397
+ params={"max": 1000})
398
+
399
+ # Modify dial patterns (add/delete)
400
+ api.session.rest_put(
401
+ f"{BASE}/telephony/config/premisePstn/dialPlans/{dial_plan_id}/dialPatterns", json={
402
+ "dialPatterns": [
403
+ {"dialPattern": "+44!", "action": "ADD"},
404
+ {"dialPattern": "+1!", "action": "DELETE"}
405
+ ]
406
+ })
407
+
408
+ # Delete all dial patterns
409
+ api.session.rest_put(
410
+ f"{BASE}/telephony/config/premisePstn/dialPlans/{dial_plan_id}/dialPatterns", json={
411
+ "deleteAllDialPatterns": True
412
+ })
413
+
414
+ # Validate dial patterns
415
+ result = api.session.rest_post(
416
+ f"{BASE}/telephony/config/premisePstn/actions/validateDialPatterns/invoke", json={
417
+ "dialPatterns": ["+1408!", "+44!", "9XXX"]
418
+ })
419
+ # result["status"] -> "OK" or "ERRORS"
420
+ ```
421
+
422
+ **URL summary:**
423
+
424
+ | Operation | Method | URL |
425
+ |-----------|--------|-----|
426
+ | List | GET | `{BASE}/telephony/config/premisePstn/dialPlans` |
427
+ | Create | POST | `{BASE}/telephony/config/premisePstn/dialPlans` |
428
+ | Get | GET | `{BASE}/telephony/config/premisePstn/dialPlans/{id}` |
429
+ | Update | PUT | `{BASE}/telephony/config/premisePstn/dialPlans/{id}` |
430
+ | Delete | DELETE | `{BASE}/telephony/config/premisePstn/dialPlans/{id}` |
431
+ | List patterns | GET | `{BASE}/telephony/config/premisePstn/dialPlans/{id}/dialPatterns` |
432
+ | Modify patterns | PUT | `{BASE}/telephony/config/premisePstn/dialPlans/{id}/dialPatterns` |
433
+ | Validate patterns | POST | `{BASE}/telephony/config/premisePstn/actions/validateDialPatterns/invoke` |
434
+
435
+ ---
436
+
437
+ ## Trunks
438
+
439
+ A Trunk is a SIP connection between Webex Calling and on-premises infrastructure (local gateway or SBC). Trunks can be assigned to route groups for failover/load distribution.
440
+
441
+ ### Data Models
442
+
443
+ ```python
444
+ class TrunkType(str, Enum):
445
+ registering = 'REGISTERING' # Cisco CUBE Local Gateway
446
+ certificate_base = 'CERTIFICATE_BASED' # Cisco UBE, Oracle ACME SBC,
447
+ # AudioCodes SBC, Ribbon SBC
448
+
449
+ class Trunk(ApiModel):
450
+ trunk_id: str # alias 'id'
451
+ name: str
452
+ location: IdAndName
453
+ in_use: bool
454
+ trunk_type: TrunkType
455
+ is_restricted_to_dedicated_instance: bool
456
+
457
+ class TrunkDetail(ApiModel):
458
+ trunk_id: str # alias 'id'
459
+ name: str
460
+ organization: Customer
461
+ location: IdAndName
462
+ otg_dtg_id: str # outgoing/destination trunk group ID
463
+ line_port: str # device endpoint / SIP URI
464
+ locations_using_trunk: list[IdAndName]
465
+ pilot_user_id: str
466
+ outbound_proxy: Any
467
+ sip_authentication_user_name: str
468
+ status: DeviceStatus # 'online' | 'offline' | 'unknown'
469
+ error_codes: list[str]
470
+ response_status: list[ResponseStatus]
471
+ dual_identity_support_enabled: bool
472
+ trunk_type: TrunkType
473
+ device_type: str
474
+ address: Optional[str] # FQDN/SRV -- certificate-based only
475
+ domain: Optional[str] # certificate-based only
476
+ port: Optional[int] # certificate-based only
477
+ max_concurrent_calls: int
478
+ is_restricted_to_dedicated_instance: Optional[bool]
479
+ p_charge_info_support_policy: Optional[PChargeInfoSupportPolicy]
480
+
481
+ class TrunkDeviceType(ApiModel):
482
+ device_type: str
483
+ min_concurrent_calls: int
484
+ max_concurrent_calls: int
485
+
486
+ class TrunkTypeWithDeviceType(ApiModel):
487
+ trunk_type: TrunkType
488
+ device_types: list[TrunkDeviceType]
489
+
490
+ class DeviceStatus(str, Enum):
491
+ online = 'online'
492
+ offline = 'offline'
493
+ unknown = 'unknown'
494
+
495
+ class PChargeInfoSupportPolicy(str, Enum):
496
+ disabled = 'DISABLED'
497
+ asserted_identity = 'ASSERTED_IDENTITY'
498
+ configurable_charge_number = 'CONFIGURABLE_CHARGE_NUMBER'
499
+
500
+ class TrunkUsage(ApiModel):
501
+ pstn_connection_count: int
502
+ call_to_extension_count: int
503
+ dial_plan_count: int
504
+ route_group_count: int
505
+ ```
506
+
507
+ ### Trunk Types
508
+
509
+ | Type | Value | Use Case |
510
+ |------|-------|----------|
511
+ | Registering | `REGISTERING` | Cisco CUBE Local Gateway. Registers with Webex Calling cloud. Requires password. |
512
+ | Certificate-based | `CERTIFICATE_BASED` | Cisco UBE, Oracle ACME SBC, AudioCodes SBC, Ribbon SBC. Uses mutual TLS. Requires FQDN/SRV address, domain, port, and max concurrent calls. |
513
+
514
+ ### Dual Identity Support
515
+
516
+ The `dual_identity_support_enabled` setting controls the From and P-Asserted-Identity (PAI) headers on outbound SIP INVITEs sent to the trunk. When enabled, the From header may differ from the PAI, allowing the called party to see the user's identity while the trunk authenticates with a different identity.
517
+
518
+ ### P-Charge-Info Support Policy
519
+
520
+ Controls the P-Charge-Info header on outbound PSTN calls:
521
+
522
+ - **DISABLED**: No P-Charge-Info header sent.
523
+ - **ASSERTED_IDENTITY**: Always uses the Webex Calling primary number or the location's main number.
524
+ - **CONFIGURABLE_CHARGE_NUMBER**: Uses the originating entity's location charge number if set, else the entity's primary number (non-toll-free), else the location main number (non-toll-free), else falls back to ASSERTED_IDENTITY behavior.
525
+
526
+ ### API Methods
527
+
528
+ #### List Trunks
529
+
530
+ ```python
531
+ TrunkApi.list(
532
+ name: str = None,
533
+ location_name: str = None,
534
+ trunk_type: str = None,
535
+ order: str = None, # sort fields: name, locationName
536
+ org_id: str = None
537
+ ) -> Generator[Trunk, None, None]
538
+ ```
539
+
540
+ #### Create Trunk
541
+
542
+ ```python
543
+ TrunkApi.create(
544
+ name: str,
545
+ location_id: str,
546
+ password: str,
547
+ trunk_type: TrunkType = TrunkType.registering,
548
+ dual_identity_support_enabled: bool = None,
549
+ device_type: TrunkDeviceType = None,
550
+ address: str = None, # FQDN/SRV -- required for certificate-based
551
+ domain: str = None, # required for certificate-based
552
+ port: int = None, # required for certificate-based
553
+ max_concurrent_calls: int = None, # required for certificate-based
554
+ p_charge_info_support_policy: PChargeInfoSupportPolicy = None,
555
+ org_id: str = None
556
+ ) -> str # returns new trunk ID
557
+ ```
558
+
559
+ #### Get Trunk Details
560
+
561
+ ```python
562
+ TrunkApi.details(
563
+ trunk_id: str,
564
+ org_id: str = None
565
+ ) -> TrunkDetail
566
+ ```
567
+
568
+ #### Update Trunk
569
+
570
+ ```python
571
+ TrunkApi.update(
572
+ trunk_id: str,
573
+ name: str,
574
+ password: str,
575
+ dual_identity_support_enabled: bool = None,
576
+ max_concurrent_calls: int = None,
577
+ p_charge_info_support_policy: PChargeInfoSupportPolicy = None,
578
+ org_id: str = None
579
+ ) -> None
580
+ ```
581
+
582
+ Note: `name` and `password` are always required for updates.
583
+
584
+ **Limitation**: You cannot change `trunk_type`, `location_id`, or `device_type` after creation. To change these properties, you must delete and recreate the trunk.
585
+
586
+ #### Delete Trunk
587
+
588
+ ```python
589
+ TrunkApi.delete_trunk(
590
+ trunk_id: str,
591
+ org_id: str = None
592
+ ) -> None
593
+ ```
594
+
595
+ #### List Trunk Types with Device Types
596
+
597
+ ```python
598
+ TrunkApi.trunk_types(
599
+ org_id: str = None
600
+ ) -> List[TrunkTypeWithDeviceType]
601
+ ```
602
+
603
+ Returns the available trunk types and their supported device types (with min/max concurrent call limits).
604
+
605
+ #### Validate FQDN and Domain
606
+
607
+ ```python
608
+ TrunkApi.validate_fqdn_and_domain(
609
+ address: str,
610
+ domain: str,
611
+ port: int = None,
612
+ org_id: str = None
613
+ ) -> None
614
+ ```
615
+
616
+ Validates the FQDN/SRV address and domain before creating a certificate-based trunk. Raises an exception on validation failure.
617
+
618
+ #### Get Trunk Usage
619
+
620
+ ```python
621
+ TrunkApi.usage(
622
+ trunk_id: str,
623
+ org_id: str = None
624
+ ) -> TrunkUsage
625
+ ```
626
+
627
+ Returns counts of PSTN connections, call-to-extension locations, dial plans, and route groups using this trunk.
628
+
629
+ #### Trunk Usage Detail Methods
630
+
631
+ ```python
632
+ TrunkApi.usage_call_to_extension(trunk_id, order=None, name=None, org_id=None) -> Generator[IdAndName]
633
+ TrunkApi.usage_dial_plan(trunk_id, order=None, name=None, org_id=None) -> Generator[IdAndName]
634
+ TrunkApi.usage_location_pstn(trunk_id, org_id=None) -> Generator[IdAndName]
635
+ TrunkApi.usage_route_group(trunk_id, org_id=None) -> Generator[IdAndName]
636
+ ```
637
+
638
+ ### Usage Example
639
+
640
+ ```python
641
+ from wxc_sdk.telephony.prem_pstn.trunk import TrunkType
642
+
643
+ # Create a registering trunk
644
+ trunk_id = api.telephony.prem_pstn.trunk.create(
645
+ name='HQ-LGW-01',
646
+ location_id=location_id,
647
+ password='SecurePass123!',
648
+ trunk_type=TrunkType.registering
649
+ )
650
+
651
+ # Check trunk status
652
+ detail = api.telephony.prem_pstn.trunk.details(trunk_id=trunk_id)
653
+ print(f"Status: {detail.status}") # online / offline / unknown
654
+ print(f"SIP User: {detail.sip_authentication_user_name}")
655
+ print(f"Line/Port: {detail.line_port}")
656
+ print(f"OTG/DTG: {detail.otg_dtg_id}")
657
+ ```
658
+
659
+ ### CLI Examples
660
+
661
+ ```bash
662
+ # List all trunks
663
+ wxcli call-routing list-trunks
664
+
665
+ # Filter trunks by name
666
+ wxcli call-routing list-trunks --name "HQ-LGW"
667
+
668
+ # Filter by location
669
+ wxcli call-routing list-trunks --location-name "Raleigh"
670
+
671
+ # Filter by trunk type
672
+ wxcli call-routing list-trunks --trunk-type REGISTERING
673
+
674
+ # Get trunk details
675
+ wxcli call-routing show-trunks Y2lzY29zcGFyazovL_TRUNK_ID
676
+
677
+ # List available trunk types and their device types
678
+ wxcli call-routing list-trunk-types
679
+
680
+ # Create a registering trunk (Cisco CUBE Local Gateway)
681
+ wxcli call-routing create-trunks --name "HQ-LGW-01" \
682
+ --location-id Y2lzY29zcGFyazovL_LOC_ID \
683
+ --password "SecurePass123!"
684
+
685
+ # Create a certificate-based trunk with FQDN
686
+ wxcli call-routing create-trunks --name "HQ-SBC-01" \
687
+ --location-id Y2lzY29zcGFyazovL_LOC_ID \
688
+ --password "SecurePass123!" \
689
+ --address "sbc.example.com" \
690
+ --domain "example.com" \
691
+ --port 5061 \
692
+ --max-concurrent-calls 100
693
+
694
+ # Create with full JSON body (includes device type and dual identity)
695
+ wxcli call-routing create-trunks --json-body '{
696
+ "name": "HQ-LGW-02",
697
+ "locationId": "Y2lzY29zcGFyazovL_LOC_ID",
698
+ "password": "SecurePass123!",
699
+ "trunkType": "REGISTERING",
700
+ "dualIdentitySupportEnabled": true,
701
+ "deviceType": "Cisco Unified Border Element"
702
+ }'
703
+
704
+ # Update a trunk (rename and change password)
705
+ wxcli call-routing update-trunks Y2lzY29zcGFyazovL_TRUNK_ID \
706
+ --name "HQ-LGW-01-v2" --password "NewPass456!"
707
+
708
+ # Validate FQDN and domain before creating a certificate-based trunk
709
+ wxcli call-routing validate-local-gateway \
710
+ --address "sbc.example.com" --domain "example.com" --port 5061
711
+
712
+ # Get trunk usage count (how many dial plans, route groups, etc. use it)
713
+ wxcli call-routing show Y2lzY29zcGFyazovL_TRUNK_ID
714
+
715
+ # List dial plans using this trunk
716
+ wxcli call-routing list Y2lzY29zcGFyazovL_TRUNK_ID
717
+
718
+ # List route groups using this trunk
719
+ wxcli call-routing list-usage-route-group Y2lzY29zcGFyazovL_TRUNK_ID
720
+
721
+ # List locations using this trunk as PSTN connection
722
+ wxcli call-routing list-usage-pstn-connection-trunks Y2lzY29zcGFyazovL_TRUNK_ID
723
+
724
+ # List call-to-extension locations for this trunk
725
+ wxcli call-routing list-usage-call-to-extension-trunks Y2lzY29zcGFyazovL_TRUNK_ID
726
+
727
+ # Delete a trunk
728
+ wxcli call-routing delete-trunks Y2lzY29zcGFyazovL_TRUNK_ID --force
729
+ ```
730
+
731
+ ### Raw HTTP
732
+ <!-- Updated by playbook session 2026-03-18 -->
733
+
734
+ All trunk endpoints live under the `/premisePstn/trunks` prefix.
735
+
736
+ ```python
737
+ BASE = "https://webexapis.com/v1"
738
+
739
+ # List trunks
740
+ trunks = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks",
741
+ params={"max": 1000})
742
+ # trunks["trunks"] -> list of dicts
743
+
744
+ # Create trunk (registering type)
745
+ result = api.session.rest_post(f"{BASE}/telephony/config/premisePstn/trunks", json={
746
+ "name": "HQ-LGW-01",
747
+ "locationId": location_id,
748
+ "password": "SecurePass123!",
749
+ "trunkType": "REGISTERING"
750
+ })
751
+ # result["id"] -> new trunk ID
752
+
753
+ # Create trunk (certificate-based)
754
+ result = api.session.rest_post(f"{BASE}/telephony/config/premisePstn/trunks", json={
755
+ "name": "HQ-SBC-01",
756
+ "locationId": location_id,
757
+ "password": "SecurePass123!",
758
+ "trunkType": "CERTIFICATE_BASED",
759
+ "address": "sbc.example.com",
760
+ "domain": "example.com",
761
+ "port": 5061,
762
+ "maxConcurrentCalls": 100
763
+ })
764
+
765
+ # Get trunk details
766
+ detail = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}")
767
+
768
+ # Update trunk (name + password always required)
769
+ api.session.rest_put(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}", json={
770
+ "name": "HQ-LGW-01-v2",
771
+ "password": "NewPass456!"
772
+ })
773
+
774
+ # Delete trunk
775
+ api.session.rest_delete(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}")
776
+
777
+ # List trunk types with device types
778
+ types = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/trunkTypes")
779
+ # types["trunkTypes"] -> list of dicts
780
+
781
+ # Validate FQDN and domain (certificate-based)
782
+ api.session.rest_post(
783
+ f"{BASE}/telephony/config/premisePstn/trunks/actions/fqdnValidation/invoke", json={
784
+ "address": "sbc.example.com",
785
+ "domain": "example.com",
786
+ "port": 5061
787
+ })
788
+
789
+ # Trunk usage count
790
+ usage = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}/usage")
791
+
792
+ # Trunk usage details
793
+ api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}/usageDialPlan")
794
+ api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}/usagePstnConnection")
795
+ api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}/usageRouteGroup")
796
+ api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}/usageCallToExtension")
797
+ ```
798
+
799
+ **URL summary:**
800
+
801
+ | Operation | Method | URL |
802
+ |-----------|--------|-----|
803
+ | List | GET | `{BASE}/telephony/config/premisePstn/trunks` |
804
+ | Create | POST | `{BASE}/telephony/config/premisePstn/trunks` |
805
+ | Get | GET | `{BASE}/telephony/config/premisePstn/trunks/{id}` |
806
+ | Update | PUT | `{BASE}/telephony/config/premisePstn/trunks/{id}` |
807
+ | Delete | DELETE | `{BASE}/telephony/config/premisePstn/trunks/{id}` |
808
+ | List types | GET | `{BASE}/telephony/config/premisePstn/trunks/trunkTypes` |
809
+ | Validate FQDN | POST | `{BASE}/telephony/config/premisePstn/trunks/actions/fqdnValidation/invoke` |
810
+ | Usage count | GET | `{BASE}/telephony/config/premisePstn/trunks/{id}/usage` |
811
+ | Usage dial plan | GET | `{BASE}/telephony/config/premisePstn/trunks/{id}/usageDialPlan` |
812
+ | Usage PSTN | GET | `{BASE}/telephony/config/premisePstn/trunks/{id}/usagePstnConnection` |
813
+ | Usage route group | GET | `{BASE}/telephony/config/premisePstn/trunks/{id}/usageRouteGroup` |
814
+ | Usage call-to-ext | GET | `{BASE}/telephony/config/premisePstn/trunks/{id}/usageCallToExtension` |
815
+
816
+ ---
817
+
818
+ ## Route Groups
819
+
820
+ A Route Group is a collection of trunks (up to 10, from different locations) that enables failover and load distribution for on-premises call routing.
821
+
822
+ ### Data Models
823
+
824
+ ```python
825
+ class RGTrunk(ApiModel):
826
+ trunk_id: str # alias 'id'
827
+ name: Optional[str]
828
+ location_id: Optional[str]
829
+ priority: int
830
+
831
+ class RouteGroup(ApiModel):
832
+ rg_id: Optional[str] # alias 'id' -- only in list(), not detail()
833
+ name: str
834
+ in_use: Optional[bool] # only in list()
835
+ organization: Optional[Customer] # only in detail()
836
+ local_gateways: Optional[list[RGTrunk]] # only in detail()
837
+
838
+ class RouteGroupUsage(ApiModel):
839
+ pstn_connection_count: int
840
+ call_to_extension_count: int
841
+ dial_plan_count: int
842
+ route_list_count: int
843
+
844
+ class UsageRouteLists(ApiModel):
845
+ rl_id: str # alias 'id'
846
+ rl_name: str # alias 'name'
847
+ location_id: str
848
+ location_name: str
849
+ ```
850
+
851
+ ### API Methods
852
+
853
+ #### List Route Groups
854
+
855
+ ```python
856
+ RouteGroupApi.list(
857
+ name: str = None,
858
+ order: str = None, # sort orders: asc, desc
859
+ org_id: str = None
860
+ ) -> Generator[RouteGroup, None, None]
861
+ ```
862
+
863
+ #### Create Route Group
864
+
865
+ ```python
866
+ RouteGroupApi.create(
867
+ route_group: RouteGroup,
868
+ org_id: str = None
869
+ ) -> str # returns new route group ID
870
+ ```
871
+
872
+ The `RouteGroup` object must have `name` and `local_gateways` set. Each `RGTrunk` in `local_gateways` must have `trunk_id` and `priority` set.
873
+
874
+ #### Get Route Group Details
875
+
876
+ ```python
877
+ RouteGroupApi.details(
878
+ rg_id: str,
879
+ org_id: str = None
880
+ ) -> RouteGroup
881
+ ```
882
+
883
+ Returns the route group with `organization` and `local_gateways` populated.
884
+
885
+ #### Update Route Group
886
+
887
+ ```python
888
+ RouteGroupApi.update(
889
+ rg_id: str,
890
+ update: RouteGroup,
891
+ org_id: str = None
892
+ ) -> None
893
+ ```
894
+
895
+ #### Delete Route Group
896
+
897
+ ```python
898
+ RouteGroupApi.delete_route_group(
899
+ rg_id: str,
900
+ org_id: str = None
901
+ ) -> None
902
+ ```
903
+
904
+ #### Get Route Group Usage
905
+
906
+ ```python
907
+ RouteGroupApi.usage(
908
+ rg_id: str,
909
+ org_id: str = None
910
+ ) -> RouteGroupUsage
911
+ ```
912
+
913
+ #### Route Group Usage Detail Methods
914
+
915
+ ```python
916
+ RouteGroupApi.usage_call_to_extension(rg_id, location_name=None, order=None, org_id=None) -> Generator[IdAndName]
917
+ RouteGroupApi.usage_dial_plan(rg_id, location_name=None, order=None, org_id=None) -> Generator[IdAndName]
918
+ RouteGroupApi.usage_location_pstn(rg_id, location_name=None, order=None, org_id=None) -> Generator[IdAndName]
919
+ RouteGroupApi.usage_route_lists(rg_id, name=None, order=None, org_id=None) -> Generator[UsageRouteLists]
920
+ ```
921
+
922
+ ### Usage Example
923
+
924
+ ```python
925
+ from wxc_sdk.telephony.prem_pstn.route_group import RouteGroup, RGTrunk
926
+
927
+ # Create a route group with two trunks (priority-based failover)
928
+ rg = RouteGroup(
929
+ name='US-East-RG',
930
+ local_gateways=[
931
+ RGTrunk(trunk_id=primary_trunk_id, priority=1),
932
+ RGTrunk(trunk_id=backup_trunk_id, priority=2)
933
+ ]
934
+ )
935
+ rg_id = api.telephony.prem_pstn.route_group.create(route_group=rg)
936
+
937
+ # Check what's using this route group
938
+ usage = api.telephony.prem_pstn.route_group.usage(rg_id=rg_id)
939
+ print(f"Dial plans: {usage.dial_plan_count}, Route lists: {usage.route_list_count}")
940
+ ```
941
+
942
+ ### CLI Examples
943
+
944
+ ```bash
945
+ # List all route groups
946
+ wxcli call-routing list-route-groups
947
+
948
+ # Filter by name
949
+ wxcli call-routing list-route-groups --name "US-East"
950
+
951
+ # Get route group details (includes trunk assignments)
952
+ wxcli call-routing show-route-groups Y2lzY29zcGFyazovL_RG_ID
953
+
954
+ # Create a route group with trunk assignments (requires --json-body for localGateways array)
955
+ wxcli call-routing create-route-groups --json-body '{
956
+ "name": "US-East-RG",
957
+ "localGateways": [
958
+ {"id": "Y2lzY29zcGFyazovL_PRIMARY_TRUNK_ID", "priority": "1"},
959
+ {"id": "Y2lzY29zcGFyazovL_BACKUP_TRUNK_ID", "priority": "2"}
960
+ ]
961
+ }'
962
+
963
+ # Create with just a name (trunks can be added later via update)
964
+ wxcli call-routing create-route-groups --name "US-East-RG"
965
+
966
+ # Update a route group (rename or change trunk assignments)
967
+ wxcli call-routing update-route-groups Y2lzY29zcGFyazovL_RG_ID \
968
+ --name "US-East-RG-v2"
969
+
970
+ # Update trunk assignments via --json-body
971
+ wxcli call-routing update-route-groups Y2lzY29zcGFyazovL_RG_ID --json-body '{
972
+ "name": "US-East-RG-v2",
973
+ "localGateways": [
974
+ {"id": "Y2lzY29zcGFyazovL_PRIMARY_TRUNK_ID", "priority": "1"}
975
+ ]
976
+ }'
977
+
978
+ # Get route group usage count
979
+ wxcli call-routing show-usage Y2lzY29zcGFyazovL_RG_ID
980
+
981
+ # List dial plan locations using this route group
982
+ wxcli call-routing list-usage-dial-plan Y2lzY29zcGFyazovL_RG_ID
983
+
984
+ # List PSTN connection locations using this route group
985
+ wxcli call-routing list-usage-pstn-connection-route-groups Y2lzY29zcGFyazovL_RG_ID
986
+
987
+ # List route lists using this route group
988
+ wxcli call-routing list-usage-route-list Y2lzY29zcGFyazovL_RG_ID
989
+
990
+ # List call-to-extension locations using this route group
991
+ wxcli call-routing list-usage-call-to-extension-route-groups Y2lzY29zcGFyazovL_RG_ID
992
+
993
+ # Delete a route group
994
+ wxcli call-routing delete-route-groups Y2lzY29zcGFyazovL_RG_ID --force
995
+ ```
996
+
997
+ ### Raw HTTP
998
+ <!-- Updated by playbook session 2026-03-18 -->
999
+
1000
+ All route group endpoints live under the `/premisePstn/routeGroups` prefix.
1001
+
1002
+ ```python
1003
+ BASE = "https://webexapis.com/v1"
1004
+
1005
+ # List route groups
1006
+ groups = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeGroups",
1007
+ params={"max": 1000})
1008
+ # groups["routeGroups"] -> list of dicts
1009
+
1010
+ # Create route group
1011
+ result = api.session.rest_post(f"{BASE}/telephony/config/premisePstn/routeGroups", json={
1012
+ "name": "US-East-RG",
1013
+ "localGateways": [
1014
+ {"trunkId": primary_trunk_id, "priority": 1},
1015
+ {"trunkId": backup_trunk_id, "priority": 2}
1016
+ ]
1017
+ })
1018
+ # result["id"] -> new route group ID
1019
+
1020
+ # Get route group details
1021
+ rg = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}")
1022
+
1023
+ # Update route group
1024
+ api.session.rest_put(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}", json={
1025
+ "name": "US-East-RG-v2",
1026
+ "localGateways": [
1027
+ {"trunkId": primary_trunk_id, "priority": 1}
1028
+ ]
1029
+ })
1030
+
1031
+ # Delete route group
1032
+ api.session.rest_delete(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}")
1033
+
1034
+ # Route group usage count
1035
+ usage = api.session.rest_get(
1036
+ f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}/usage")
1037
+
1038
+ # Route group usage details
1039
+ api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}/usageCallToExtension")
1040
+ api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}/usageDialPlan")
1041
+ api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}/usagePstnConnection")
1042
+ api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}/usageRouteList")
1043
+ ```
1044
+
1045
+ **URL summary:**
1046
+
1047
+ | Operation | Method | URL |
1048
+ |-----------|--------|-----|
1049
+ | List | GET | `{BASE}/telephony/config/premisePstn/routeGroups` |
1050
+ | Create | POST | `{BASE}/telephony/config/premisePstn/routeGroups` |
1051
+ | Get | GET | `{BASE}/telephony/config/premisePstn/routeGroups/{id}` |
1052
+ | Update | PUT | `{BASE}/telephony/config/premisePstn/routeGroups/{id}` |
1053
+ | Delete | DELETE | `{BASE}/telephony/config/premisePstn/routeGroups/{id}` |
1054
+ | Usage count | GET | `{BASE}/telephony/config/premisePstn/routeGroups/{id}/usage` |
1055
+ | Usage call-to-ext | GET | `{BASE}/telephony/config/premisePstn/routeGroups/{id}/usageCallToExtension` |
1056
+ | Usage dial plan | GET | `{BASE}/telephony/config/premisePstn/routeGroups/{id}/usageDialPlan` |
1057
+ | Usage PSTN | GET | `{BASE}/telephony/config/premisePstn/routeGroups/{id}/usagePstnConnection` |
1058
+ | Usage route list | GET | `{BASE}/telephony/config/premisePstn/routeGroups/{id}/usageRouteList` |
1059
+
1060
+ ---
1061
+
1062
+ ## Route Lists
1063
+
1064
+ A Route List is a list of phone numbers that can be reached via a Route Group. Route lists are used to provide cloud PSTN connectivity to Webex Calling Dedicated Instance.
1065
+
1066
+ ### Data Models
1067
+
1068
+ ```python
1069
+ class RouteListDetail(ApiModel):
1070
+ rl_id: str # alias 'id'
1071
+ name: str
1072
+ location: IdAndName
1073
+ route_group: IdAndName
1074
+
1075
+ class RouteList(ApiModel):
1076
+ rl_id: str # alias 'id'
1077
+ name: str
1078
+ location_id: str
1079
+ location_name: str
1080
+ rg_id: str # alias 'routeGroupId'
1081
+ rg_name: str # alias 'routeGroupName'
1082
+ peak_active_route_list_calls_org: Optional[int]
1083
+ current_active_route_list_calls_org: Optional[int]
1084
+ route_list_calls_volume_org: Optional[int]
1085
+ peak_active_route_list_calls: Optional[int]
1086
+ current_active_route_list_calls: Optional[int]
1087
+
1088
+ class NumberAndAction(ApiModel):
1089
+ number: str
1090
+ action: PatternAction # 'ADD' | 'DELETE'
1091
+ # helper statics: NumberAndAction.add(number), NumberAndAction.delete(number)
1092
+
1093
+ class UpdateNumbersResponse(ApiModel):
1094
+ number: str
1095
+ number_status: str
1096
+ message: str
1097
+ ```
1098
+
1099
+ ### API Methods
1100
+
1101
+ #### List Route Lists
1102
+
1103
+ ```python
1104
+ RouteListApi.list(
1105
+ name: list[str] = None,
1106
+ location_id: list[str] = None,
1107
+ order: str = None, # sort fields: name, locationId
1108
+ org_id: str = None
1109
+ ) -> Generator[RouteList, None, None]
1110
+ ```
1111
+
1112
+ Note: `name` and `location_id` accept lists for multi-value filtering.
1113
+
1114
+ #### Create Route List
1115
+
1116
+ ```python
1117
+ RouteListApi.create(
1118
+ name: str,
1119
+ location_id: str,
1120
+ rg_id: str, # route group ID
1121
+ org_id: str = None
1122
+ ) -> str # returns new route list ID
1123
+ ```
1124
+
1125
+ #### Get Route List Details
1126
+
1127
+ ```python
1128
+ RouteListApi.details(
1129
+ rl_id: str,
1130
+ org_id: str = None
1131
+ ) -> RouteListDetail
1132
+ ```
1133
+
1134
+ #### Update Route List
1135
+
1136
+ ```python
1137
+ RouteListApi.update(
1138
+ rl_id: str,
1139
+ name: str = None,
1140
+ rg_id: str = None,
1141
+ org_id: str = None
1142
+ ) -> None
1143
+ ```
1144
+
1145
+ #### Delete Route List
1146
+
1147
+ ```python
1148
+ RouteListApi.delete_route_list(
1149
+ rl_id: str,
1150
+ org_id: str = None
1151
+ ) -> None
1152
+ ```
1153
+
1154
+ #### List Numbers on a Route List
1155
+
1156
+ ```python
1157
+ RouteListApi.numbers(
1158
+ rl_id: str,
1159
+ order: str = None,
1160
+ number: str = None,
1161
+ org_id: str = None
1162
+ ) -> Generator[str, None, None]
1163
+ ```
1164
+
1165
+ #### Modify Numbers on a Route List (Add/Delete)
1166
+
1167
+ ```python
1168
+ RouteListApi.update_numbers(
1169
+ rl_id: str,
1170
+ numbers: List[NumberAndAction] = None,
1171
+ delete_all_numbers: bool = None,
1172
+ org_id: str = None
1173
+ ) -> List[UpdateNumbersResponse]
1174
+ ```
1175
+
1176
+ If `delete_all_numbers` is set, the `numbers` array is ignored and all numbers are removed.
1177
+
1178
+ #### Delete All Numbers from a Route List
1179
+
1180
+ ```python
1181
+ RouteListApi.delete_all_numbers(
1182
+ rl_id: str,
1183
+ org_id: str = None
1184
+ ) -> None
1185
+ ```
1186
+
1187
+ ### Usage Example
1188
+
1189
+ ```python
1190
+ from wxc_sdk.telephony.prem_pstn.route_list import NumberAndAction
1191
+
1192
+ # Create route list
1193
+ rl_id = api.telephony.prem_pstn.route_list.create(
1194
+ name='US-East-Numbers',
1195
+ location_id=location_id,
1196
+ rg_id=rg_id
1197
+ )
1198
+
1199
+ # Add numbers
1200
+ result = api.telephony.prem_pstn.route_list.update_numbers(
1201
+ rl_id=rl_id,
1202
+ numbers=[
1203
+ NumberAndAction.add('+19195551234'),
1204
+ NumberAndAction.add('+19195555678')
1205
+ ]
1206
+ )
1207
+ for r in result:
1208
+ print(f"{r.number}: {r.number_status} - {r.message}")
1209
+ ```
1210
+
1211
+ ### CLI Examples
1212
+
1213
+ ```bash
1214
+ # List all route lists
1215
+ wxcli call-routing list-route-lists
1216
+
1217
+ # Filter by name
1218
+ wxcli call-routing list-route-lists --name "US-East-Numbers"
1219
+
1220
+ # Filter by location
1221
+ wxcli call-routing list-route-lists --location-id Y2lzY29zcGFyazovL_LOC_ID
1222
+
1223
+ # Get route list details
1224
+ wxcli call-routing show-route-lists Y2lzY29zcGFyazovL_RL_ID
1225
+
1226
+ # Create a route list
1227
+ wxcli call-routing create-route-lists \
1228
+ --name "US-East-Numbers" \
1229
+ --location-id Y2lzY29zcGFyazovL_LOC_ID \
1230
+ --route-group-id Y2lzY29zcGFyazovL_RG_ID
1231
+
1232
+ # Update a route list (rename or change route group)
1233
+ wxcli call-routing update-route-lists Y2lzY29zcGFyazovL_RL_ID \
1234
+ --name "US-East-Numbers-v2"
1235
+
1236
+ # List numbers assigned to a route list
1237
+ wxcli call-routing list-numbers Y2lzY29zcGFyazovL_RL_ID
1238
+
1239
+ # Add/delete numbers on a route list (requires --json-body for numbers array)
1240
+ wxcli call-routing update-numbers Y2lzY29zcGFyazovL_RL_ID --json-body '{
1241
+ "numbers": [
1242
+ {"number": "+19195551234", "action": "ADD"},
1243
+ {"number": "+19195555678", "action": "ADD"}
1244
+ ]
1245
+ }'
1246
+
1247
+ # Delete all numbers from a route list
1248
+ wxcli call-routing update-numbers Y2lzY29zcGFyazovL_RL_ID --delete-all-numbers
1249
+
1250
+ # Delete a route list
1251
+ wxcli call-routing delete-route-lists Y2lzY29zcGFyazovL_RL_ID --force
1252
+ ```
1253
+
1254
+ ### Raw HTTP
1255
+ <!-- Updated by playbook session 2026-03-18 -->
1256
+
1257
+ All route list endpoints live under the `/premisePstn/routeLists` prefix.
1258
+
1259
+ ```python
1260
+ BASE = "https://webexapis.com/v1"
1261
+
1262
+ # List route lists
1263
+ lists = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeLists",
1264
+ params={"max": 1000})
1265
+ # lists["routeLists"] -> list of dicts
1266
+
1267
+ # Create route list
1268
+ result = api.session.rest_post(f"{BASE}/telephony/config/premisePstn/routeLists", json={
1269
+ "name": "US-East-Numbers",
1270
+ "locationId": location_id,
1271
+ "routeGroupId": rg_id
1272
+ })
1273
+ # result["id"] -> new route list ID
1274
+
1275
+ # Get route list details
1276
+ rl = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeLists/{rl_id}")
1277
+
1278
+ # Update route list
1279
+ api.session.rest_put(f"{BASE}/telephony/config/premisePstn/routeLists/{rl_id}", json={
1280
+ "name": "US-East-Numbers-v2",
1281
+ "routeGroupId": new_rg_id
1282
+ })
1283
+
1284
+ # Delete route list
1285
+ api.session.rest_delete(f"{BASE}/telephony/config/premisePstn/routeLists/{rl_id}")
1286
+
1287
+ # List numbers on route list
1288
+ nums = api.session.rest_get(
1289
+ f"{BASE}/telephony/config/premisePstn/routeLists/{rl_id}/numbers",
1290
+ params={"max": 1000})
1291
+ # nums["numbers"] -> list of dicts
1292
+
1293
+ # Modify numbers on route list (add/delete)
1294
+ api.session.rest_put(
1295
+ f"{BASE}/telephony/config/premisePstn/routeLists/{rl_id}/numbers", json={
1296
+ "numbers": [
1297
+ {"number": "+19195551234", "action": "ADD"},
1298
+ {"number": "+19195555678", "action": "ADD"}
1299
+ ]
1300
+ })
1301
+
1302
+ # Delete all numbers from route list
1303
+ api.session.rest_put(
1304
+ f"{BASE}/telephony/config/premisePstn/routeLists/{rl_id}/numbers", json={
1305
+ "deleteAllNumbers": True
1306
+ })
1307
+ ```
1308
+
1309
+ **URL summary:**
1310
+
1311
+ | Operation | Method | URL |
1312
+ |-----------|--------|-----|
1313
+ | List | GET | `{BASE}/telephony/config/premisePstn/routeLists` |
1314
+ | Create | POST | `{BASE}/telephony/config/premisePstn/routeLists` |
1315
+ | Get | GET | `{BASE}/telephony/config/premisePstn/routeLists/{id}` |
1316
+ | Update | PUT | `{BASE}/telephony/config/premisePstn/routeLists/{id}` |
1317
+ | Delete | DELETE | `{BASE}/telephony/config/premisePstn/routeLists/{id}` |
1318
+ | List numbers | GET | `{BASE}/telephony/config/premisePstn/routeLists/{id}/numbers` |
1319
+ | Modify numbers | PUT | `{BASE}/telephony/config/premisePstn/routeLists/{id}/numbers` |
1320
+
1321
+ ---
1322
+
1323
+ ## Translation Patterns
1324
+
1325
+ Translation patterns manipulate dialed digits before routing a call. They apply to **outbound calls only**. Patterns can be configured at the **organization level** or the **location level**.
1326
+
1327
+ ### Data Models
1328
+
1329
+ ```python
1330
+ class TranslationPatternLevel(str, Enum):
1331
+ location = 'Location'
1332
+ organization = 'Organization'
1333
+
1334
+ class TranslationPattern(ApiModel):
1335
+ id: Optional[str]
1336
+ name: Optional[str]
1337
+ matching_pattern: Optional[str]
1338
+ replacement_pattern: Optional[str]
1339
+ level: Optional[TranslationPatternLevel]
1340
+ location: Optional[IdAndName]
1341
+ ```
1342
+
1343
+ ### API Methods
1344
+
1345
+ #### List Translation Patterns
1346
+
1347
+ ```python
1348
+ TranslationPatternsApi.list(
1349
+ limit_to_location_id: str = None, # filter to specific location
1350
+ limit_to_org_level_enabled: bool = None, # filter to org-level only
1351
+ order: str = None,
1352
+ name: str = None,
1353
+ matching_pattern: str = None,
1354
+ org_id: str = None
1355
+ ) -> Generator[TranslationPattern, None, None]
1356
+ ```
1357
+
1358
+ #### Create Translation Pattern
1359
+
1360
+ ```python
1361
+ TranslationPatternsApi.create(
1362
+ pattern: TranslationPattern,
1363
+ location_id: str = None, # omit for org-level, set for location-level
1364
+ org_id: str = None
1365
+ ) -> str # returns new pattern ID
1366
+ ```
1367
+
1368
+ The `TranslationPattern` must have `name`, `matching_pattern`, and `replacement_pattern` set. The `id`, `level`, and `location` fields are excluded from the create payload.
1369
+
1370
+ #### Get Translation Pattern Details
1371
+
1372
+ ```python
1373
+ TranslationPatternsApi.details(
1374
+ translation_id: str,
1375
+ location_id: str = None,
1376
+ org_id: str = None
1377
+ ) -> TranslationPattern
1378
+ ```
1379
+
1380
+ #### Update Translation Pattern
1381
+
1382
+ ```python
1383
+ TranslationPatternsApi.update(
1384
+ pattern: TranslationPattern, # must have .id set
1385
+ location_id: str = None,
1386
+ org_id: str = None
1387
+ ) -> None
1388
+ ```
1389
+
1390
+ #### Delete Translation Pattern
1391
+
1392
+ ```python
1393
+ TranslationPatternsApi.delete(
1394
+ translation_id: str,
1395
+ location_id: str = None,
1396
+ org_id: str = None
1397
+ ) -> None
1398
+ ```
1399
+
1400
+ ### Endpoint Routing
1401
+
1402
+ - **Org-level**: `telephony/config/callRouting/translationPatterns`
1403
+ - **Location-level**: `telephony/config/locations/{location_id}/callRouting/translationPatterns`
1404
+
1405
+ ### Usage Example
1406
+
1407
+ ```python
1408
+ from wxc_sdk.telephony.call_routing.translation_pattern import TranslationPattern
1409
+
1410
+ # Create an org-level translation pattern
1411
+ # Rewrites 4-digit 9xxx extensions to E.164
1412
+ pattern = TranslationPattern(
1413
+ name='Ext-to-E164',
1414
+ matching_pattern='9XXX',
1415
+ replacement_pattern='+15125559000'
1416
+ )
1417
+ tp_id = api.telephony.call_routing.tp.create(pattern=pattern)
1418
+
1419
+ # Create a location-level translation pattern
1420
+ # NOTE: replacement_pattern must use literal digits only — X wildcards are rejected (error 28043)
1421
+ pattern_loc = TranslationPattern(
1422
+ name='Local-Rewrite',
1423
+ matching_pattern='+1919555XXXX',
1424
+ replacement_pattern='+19195550000'
1425
+ )
1426
+ tp_id_loc = api.telephony.call_routing.tp.create(
1427
+ pattern=pattern_loc,
1428
+ location_id=location_id
1429
+ )
1430
+ ```
1431
+
1432
+ ### CLI Examples
1433
+
1434
+ ```bash
1435
+ # List all translation patterns (org-level and location-level)
1436
+ wxcli call-routing list-translation-patterns
1437
+
1438
+ # Filter to org-level patterns only
1439
+ wxcli call-routing list-translation-patterns --limit-to-org-level-enabled true
1440
+
1441
+ # Filter to patterns at a specific location
1442
+ wxcli call-routing list-translation-patterns \
1443
+ --limit-to-location-id Y2lzY29zcGFyazovL_LOC_ID
1444
+
1445
+ # Filter by name or matching pattern
1446
+ wxcli call-routing list-translation-patterns --name "Strip-9-Prefix"
1447
+ wxcli call-routing list-translation-patterns --matching-pattern "9XXX"
1448
+
1449
+ # Get org-level translation pattern details
1450
+ wxcli call-routing show-translation-patterns-call-routing Y2lzY29zcGFyazovL_TP_ID
1451
+
1452
+ # Get location-level translation pattern details (location_id then translation_id)
1453
+ wxcli call-routing show-translation-patterns-call-routing-1 \
1454
+ Y2lzY29zcGFyazovL_LOC_ID Y2lzY29zcGFyazovL_TP_ID
1455
+
1456
+ # Create an org-level translation pattern (strip leading 9)
1457
+ wxcli call-routing create-translation-patterns-call-routing \
1458
+ --name "Strip-9-Prefix" \
1459
+ --matching-pattern "9XXX" \
1460
+ --replacement-pattern "XXX"
1461
+
1462
+ # Create a location-level translation pattern
1463
+ wxcli call-routing create-translation-patterns-call-routing-1 Y2lzY29zcGFyazovL_LOC_ID \
1464
+ --name "Local-Rewrite" \
1465
+ --matching-pattern "+1919555XXXX" \
1466
+ --replacement-pattern "555XXXX"
1467
+
1468
+ # Update an org-level translation pattern
1469
+ wxcli call-routing update-translation-patterns-call-routing Y2lzY29zcGFyazovL_TP_ID \
1470
+ --name "Strip-9-Prefix-v2" \
1471
+ --matching-pattern "9XXX" \
1472
+ --replacement-pattern "XXX"
1473
+
1474
+ # Update a location-level translation pattern (location_id then translation_id)
1475
+ wxcli call-routing update-translation-patterns-call-routing-1 \
1476
+ Y2lzY29zcGFyazovL_LOC_ID Y2lzY29zcGFyazovL_TP_ID \
1477
+ --name "Local-Rewrite-v2"
1478
+
1479
+ # Delete an org-level translation pattern
1480
+ wxcli call-routing delete-translation-patterns-call-routing Y2lzY29zcGFyazovL_TP_ID --force
1481
+
1482
+ # Delete a location-level translation pattern (location_id then translation_id)
1483
+ wxcli call-routing delete-translation-patterns-call-routing-1 \
1484
+ Y2lzY29zcGFyazovL_LOC_ID Y2lzY29zcGFyazovL_TP_ID --force
1485
+ ```
1486
+
1487
+ ### Raw HTTP
1488
+ <!-- Updated by playbook session 2026-03-18 -->
1489
+
1490
+ Translation patterns use the `/callRouting/` prefix (NOT `/premisePstn/`). Org-level and location-level have different URL paths.
1491
+
1492
+ ```python
1493
+ BASE = "https://webexapis.com/v1"
1494
+
1495
+ # --- Org-level translation patterns ---
1496
+
1497
+ # List org-level translation patterns
1498
+ tps = api.session.rest_get(
1499
+ f"{BASE}/telephony/config/callRouting/translationPatterns",
1500
+ params={"max": 1000})
1501
+ # tps["translationPatterns"] -> list of dicts
1502
+
1503
+ # Create org-level translation pattern
1504
+ result = api.session.rest_post(
1505
+ f"{BASE}/telephony/config/callRouting/translationPatterns", json={
1506
+ "name": "Strip-9-Prefix",
1507
+ "matchingPattern": "9XXX",
1508
+ "replacementPattern": "XXX"
1509
+ })
1510
+ # result["id"] -> new pattern ID
1511
+
1512
+ # Get org-level translation pattern
1513
+ tp = api.session.rest_get(
1514
+ f"{BASE}/telephony/config/callRouting/translationPatterns/{translation_id}")
1515
+
1516
+ # Update org-level translation pattern
1517
+ api.session.rest_put(
1518
+ f"{BASE}/telephony/config/callRouting/translationPatterns/{translation_id}", json={
1519
+ "name": "Strip-9-Prefix-v2",
1520
+ "matchingPattern": "9XXX",
1521
+ "replacementPattern": "XXX"
1522
+ })
1523
+
1524
+ # Delete org-level translation pattern
1525
+ api.session.rest_delete(
1526
+ f"{BASE}/telephony/config/callRouting/translationPatterns/{translation_id}")
1527
+
1528
+ # --- Location-level translation patterns ---
1529
+
1530
+ # Create location-level translation pattern
1531
+ result = api.session.rest_post(
1532
+ f"{BASE}/telephony/config/locations/{location_id}/callRouting/translationPatterns", json={
1533
+ "name": "Local-Rewrite",
1534
+ "matchingPattern": "+1919555XXXX",
1535
+ "replacementPattern": "+19196660000"
1536
+ })
1537
+
1538
+ # Get/update/delete location-level: same pattern with /locations/{location_id}/ prefix
1539
+ api.session.rest_get(
1540
+ f"{BASE}/telephony/config/locations/{location_id}/callRouting/translationPatterns/{translation_id}")
1541
+ api.session.rest_put(
1542
+ f"{BASE}/telephony/config/locations/{location_id}/callRouting/translationPatterns/{translation_id}",
1543
+ json={...})
1544
+ api.session.rest_delete(
1545
+ f"{BASE}/telephony/config/locations/{location_id}/callRouting/translationPatterns/{translation_id}")
1546
+ ```
1547
+
1548
+ **URL summary:**
1549
+
1550
+ | Operation | Method | URL |
1551
+ |-----------|--------|-----|
1552
+ | List (org) | GET | `{BASE}/telephony/config/callRouting/translationPatterns` |
1553
+ | Create (org) | POST | `{BASE}/telephony/config/callRouting/translationPatterns` |
1554
+ | Get (org) | GET | `{BASE}/telephony/config/callRouting/translationPatterns/{id}` |
1555
+ | Update (org) | PUT | `{BASE}/telephony/config/callRouting/translationPatterns/{id}` |
1556
+ | Delete (org) | DELETE | `{BASE}/telephony/config/callRouting/translationPatterns/{id}` |
1557
+ | List (location) | GET | `{BASE}/telephony/config/locations/{locId}/callRouting/translationPatterns` |
1558
+ | Create (location) | POST | `{BASE}/telephony/config/locations/{locId}/callRouting/translationPatterns` |
1559
+ | Get (location) | GET | `{BASE}/telephony/config/locations/{locId}/callRouting/translationPatterns/{id}` |
1560
+ | Update (location) | PUT | `{BASE}/telephony/config/locations/{locId}/callRouting/translationPatterns/{id}` |
1561
+ | Delete (location) | DELETE | `{BASE}/telephony/config/locations/{locId}/callRouting/translationPatterns/{id}` |
1562
+
1563
+ ---
1564
+
1565
+ ## PSTN Configuration
1566
+
1567
+ The PSTN API manages the PSTN connection settings for a location -- which provider handles calls and how it connects.
1568
+
1569
+ ### Data Models
1570
+
1571
+ ```python
1572
+ class PSTNType(str, Enum):
1573
+ local_gateway = 'LOCAL_GATEWAY' # Premises-based PSTN
1574
+ non_integrated_ccp = 'NON_INTEGRATED_CCP' # Non-Integrated Cloud Connected PSTN
1575
+ integrated_ccp = 'INTEGRATED_CCP' # Integrated CCP (not API-configurable)
1576
+ cisco_pstn = 'CISCO_PSTN' # Cisco PSTN (not API-configurable)
1577
+
1578
+ class PSTNServiceType(str, Enum):
1579
+ geographic_numbers = 'GEOGRAPHIC_NUMBERS'
1580
+ tollfree_numbers = 'TOLLFREE_NUMBERS'
1581
+ business_texting = 'BUSINESS_TEXTING'
1582
+ contact_center = 'CONTACT_CENTER'
1583
+ service_numbers = 'SERVICE_NUMBERS'
1584
+ non_geographic_numbers = 'NON_GEOGRAPHIC_NUMBERS'
1585
+ mobile_numbers = 'MOBILE_NUMBERS'
1586
+
1587
+ class PSTNConnectionOption(ApiModel):
1588
+ id: Optional[str]
1589
+ display_name: Optional[str]
1590
+ pstn_services: Optional[list[PSTNServiceType]]
1591
+ pstn_connection_type: Optional[PSTNType]
1592
+ route_type: Optional[RouteType] # required if LOCAL_GATEWAY
1593
+ route_id: Optional[str] # trunk ID or route group ID
1594
+ ```
1595
+
1596
+ ### API Methods
1597
+
1598
+ #### List PSTN Connection Options for a Location
1599
+
1600
+ ```python
1601
+ PSTNApi.list(
1602
+ location_id: str,
1603
+ service_types: list[PSTNServiceType] = None,
1604
+ org_id: str = None
1605
+ ) -> list[PSTNConnectionOption]
1606
+ ```
1607
+
1608
+ Returns all available PSTN connection options for the given location.
1609
+
1610
+ #### Configure PSTN Connection for a Location
1611
+
1612
+ ```python
1613
+ PSTNApi.configure(
1614
+ location_id: str,
1615
+ id: str = None, # connection ID -- required for non-integrated CCP
1616
+ premise_route_type: str = None, # 'TRUNK' or 'ROUTE_GROUP' -- required for local gateway
1617
+ premise_route_id: str = None, # trunk or route group ID -- required for local gateway
1618
+ org_id: str = None
1619
+ ) -> None
1620
+ ```
1621
+
1622
+ **Important**: Only `LOCAL_GATEWAY` and `NON_INTEGRATED_CCP` types can be configured via the API. `INTEGRATED_CCP` and `CISCO_PSTN` must be configured through the Control Hub UI.
1623
+
1624
+ #### Read Current PSTN Connection for a Location
1625
+
1626
+ ```python
1627
+ PSTNApi.read(
1628
+ location_id: str,
1629
+ org_id: str = None
1630
+ ) -> PSTNConnectionOption
1631
+ ```
1632
+
1633
+ ### Usage Example
1634
+
1635
+ ```python
1636
+ # Set a location to use a local gateway trunk for PSTN
1637
+ api.telephony.pstn.configure(
1638
+ location_id=location_id,
1639
+ premise_route_type='TRUNK',
1640
+ premise_route_id=trunk_id
1641
+ )
1642
+
1643
+ # Or use a route group
1644
+ api.telephony.pstn.configure(
1645
+ location_id=location_id,
1646
+ premise_route_type='ROUTE_GROUP',
1647
+ premise_route_id=rg_id
1648
+ )
1649
+
1650
+ # Read current configuration
1651
+ current = api.telephony.pstn.read(location_id=location_id)
1652
+ print(f"Connection: {current.pstn_connection_type}")
1653
+ print(f"Route: {current.route_type} -> {current.route_id}")
1654
+ ```
1655
+
1656
+ ### Raw HTTP
1657
+ <!-- Updated by playbook session 2026-03-18 -->
1658
+
1659
+ PSTN endpoints use the `/telephony/pstn/` prefix (NOT `/premisePstn/` and NOT `/telephony/config/`).
1660
+
1661
+ ```python
1662
+ BASE = "https://webexapis.com/v1"
1663
+
1664
+ # List PSTN connection options for a location
1665
+ options = api.session.rest_get(
1666
+ f"{BASE}/telephony/pstn/locations/{location_id}/connectionOptions",
1667
+ params={"max": 1000})
1668
+ # options["connectionOptions"] -> list of dicts
1669
+
1670
+ # Read current PSTN connection for a location
1671
+ conn = api.session.rest_get(
1672
+ f"{BASE}/telephony/pstn/locations/{location_id}/connection")
1673
+
1674
+ # Setup/update PSTN connection for a location (local gateway)
1675
+ api.session.rest_put(
1676
+ f"{BASE}/telephony/pstn/locations/{location_id}/connection", json={
1677
+ "premiseRouteType": "TRUNK",
1678
+ "premiseRouteId": trunk_id
1679
+ })
1680
+
1681
+ # Setup/update PSTN connection (non-integrated CCP)
1682
+ api.session.rest_put(
1683
+ f"{BASE}/telephony/pstn/locations/{location_id}/connection", json={
1684
+ "id": ccp_provider_id
1685
+ })
1686
+
1687
+ # Emergency address lookup
1688
+ result = api.session.rest_post(
1689
+ f"{BASE}/telephony/pstn/locations/{location_id}/emergencyAddress/lookup", json={
1690
+ "address1": "123 Main St",
1691
+ "city": "Raleigh",
1692
+ "state": "NC",
1693
+ "postalCode": "27601",
1694
+ "country": "US"
1695
+ })
1696
+
1697
+ # Add emergency address to location
1698
+ api.session.rest_post(
1699
+ f"{BASE}/telephony/pstn/locations/{location_id}/emergencyAddress", json={
1700
+ "address1": "123 Main St",
1701
+ "city": "Raleigh",
1702
+ "state": "NC",
1703
+ "postalCode": "27601",
1704
+ "country": "US"
1705
+ })
1706
+
1707
+ # Update emergency address for a phone number
1708
+ api.session.rest_put(
1709
+ f"{BASE}/telephony/pstn/numbers/{phone_number}/emergencyAddress", json={...})
1710
+ ```
1711
+
1712
+ **URL summary:**
1713
+
1714
+ | Operation | Method | URL |
1715
+ |-----------|--------|-----|
1716
+ | List connection options | GET | `{BASE}/telephony/pstn/locations/{locId}/connectionOptions` |
1717
+ | Read connection | GET | `{BASE}/telephony/pstn/locations/{locId}/connection` |
1718
+ | Setup connection | PUT | `{BASE}/telephony/pstn/locations/{locId}/connection` |
1719
+ | Emergency lookup | POST | `{BASE}/telephony/pstn/locations/{locId}/emergencyAddress/lookup` |
1720
+ | Add emergency addr | POST | `{BASE}/telephony/pstn/locations/{locId}/emergencyAddress` |
1721
+ | Update emergency addr (location) | PUT | `{BASE}/telephony/pstn/locations/{locId}/emergencyAddresses/{addrId}` |
1722
+ | Update emergency addr (number) | PUT | `{BASE}/telephony/pstn/numbers/{phoneNumber}/emergencyAddress` |
1723
+
1724
+ ---
1725
+
1726
+ ## Premises PSTN
1727
+
1728
+ The `PremisePstnApi` is the parent API that groups all premises-based PSTN sub-APIs and provides the `validate_pattern()` method.
1729
+
1730
+ ### SDK Access
1731
+
1732
+ ```python
1733
+ api.telephony.prem_pstn # PremisePstnApi
1734
+ api.telephony.prem_pstn.dial_plan # DialPlanApi
1735
+ api.telephony.prem_pstn.trunk # TrunkApi
1736
+ api.telephony.prem_pstn.route_group # RouteGroupApi
1737
+ api.telephony.prem_pstn.route_list # RouteListApi
1738
+ ```
1739
+
1740
+ ### Validate Dial Pattern
1741
+
1742
+ ```python
1743
+ PremisePstnApi.validate_pattern(
1744
+ dial_patterns: Union[str, List[str]],
1745
+ org_id: str = None
1746
+ ) -> DialPatternValidationResult
1747
+ ```
1748
+
1749
+ Accepts a single pattern string or a list. Returns validation status and per-pattern details.
1750
+
1751
+ ```python
1752
+ result = api.telephony.prem_pstn.validate_pattern(['+1408!', '+44!', '9XXX'])
1753
+ if result.ok:
1754
+ print("All patterns valid")
1755
+ else:
1756
+ for p in result.dial_pattern_status:
1757
+ print(f"{p.dial_pattern}: {p.pattern_status} - {p.message}")
1758
+ ```
1759
+
1760
+ ---
1761
+
1762
+ ## Private Network Connect (PNC)
1763
+
1764
+ Private Network Connect determines whether a location uses the public internet or a private network for its connection to Webex Calling.
1765
+
1766
+ ### Data Models
1767
+
1768
+ ```python
1769
+ class NetworkConnectionType(str, Enum):
1770
+ public_internet = 'PUBLIC_INTERNET'
1771
+ private_network = 'PRIVATE_NETWORK'
1772
+ ```
1773
+
1774
+ ### API Methods
1775
+
1776
+ #### Read PNC Setting
1777
+
1778
+ ```python
1779
+ PrivateNetworkConnectApi.read(
1780
+ location_id: str,
1781
+ org_id: str = None
1782
+ ) -> NetworkConnectionType
1783
+ ```
1784
+
1785
+ #### Update PNC Setting
1786
+
1787
+ ```python
1788
+ PrivateNetworkConnectApi.update(
1789
+ location_id: str,
1790
+ connection_type: NetworkConnectionType,
1791
+ org_id: str = None
1792
+ ) -> None
1793
+ ```
1794
+
1795
+ ### Endpoint
1796
+
1797
+ Both methods use: `telephony/config/locations/{location_id}/privateNetworkConnect`
1798
+
1799
+ ### Usage Example
1800
+
1801
+ ```python
1802
+ from wxc_sdk.telephony.pnc import NetworkConnectionType
1803
+
1804
+ # Read current setting
1805
+ pnc = api.telephony.pnc.read(location_id=location_id)
1806
+ print(f"Connection type: {pnc}")
1807
+
1808
+ # Switch to private network
1809
+ api.telephony.pnc.update(
1810
+ location_id=location_id,
1811
+ connection_type=NetworkConnectionType.private_network
1812
+ )
1813
+ ```
1814
+
1815
+ ### Raw HTTP
1816
+ <!-- Updated by playbook session 2026-03-18 -->
1817
+
1818
+ ```python
1819
+ BASE = "https://webexapis.com/v1"
1820
+
1821
+ # Read PNC setting for a location
1822
+ pnc = api.session.rest_get(
1823
+ f"{BASE}/telephony/config/locations/{location_id}/privateNetworkConnect")
1824
+ # pnc["networkConnectionType"] -> "PUBLIC_INTERNET" or "PRIVATE_NETWORK"
1825
+
1826
+ # Update PNC setting
1827
+ api.session.rest_put(
1828
+ f"{BASE}/telephony/config/locations/{location_id}/privateNetworkConnect", json={
1829
+ "networkConnectionType": "PRIVATE_NETWORK"
1830
+ })
1831
+ ```
1832
+
1833
+ | Operation | Method | URL |
1834
+ |-----------|--------|-----|
1835
+ | Read PNC | GET | `{BASE}/telephony/config/locations/{locId}/privateNetworkConnect` |
1836
+ | Update PNC | PUT | `{BASE}/telephony/config/locations/{locId}/privateNetworkConnect` |
1837
+
1838
+ ---
1839
+
1840
+ ## Route Choices
1841
+
1842
+ The `route_choices()` method lists all available routing targets (trunks and route groups) for the organization. This is useful when building dial plans or configuring PSTN connections and you need to enumerate what routes are available.
1843
+
1844
+ ### Data Model
1845
+
1846
+ ```python
1847
+ class RouteIdentity(ApiModel):
1848
+ route_id: str # alias 'id'
1849
+ name: Optional[str]
1850
+ route_type: RouteType # alias 'type' -- 'ROUTE_GROUP' | 'TRUNK'
1851
+ ```
1852
+
1853
+ ### API Method
1854
+
1855
+ ```python
1856
+ TelephonyApi.route_choices(
1857
+ route_group_name: str = None,
1858
+ trunk_name: str = None,
1859
+ order: str = None, # sort fields: routeName, routeType
1860
+ org_id: str = None
1861
+ ) -> Generator[RouteIdentity, None, None]
1862
+ ```
1863
+
1864
+ ### Usage Example
1865
+
1866
+ ```python
1867
+ # List all available route choices
1868
+ for route in api.telephony.route_choices():
1869
+ print(f"{route.name} ({route.route_type}): {route.route_id}")
1870
+
1871
+ # Filter to trunks only
1872
+ for route in api.telephony.route_choices(trunk_name='HQ'):
1873
+ print(f"Trunk: {route.name}")
1874
+ ```
1875
+
1876
+ ### Raw HTTP
1877
+ <!-- Updated by playbook session 2026-03-18 -->
1878
+
1879
+ ```python
1880
+ BASE = "https://webexapis.com/v1"
1881
+
1882
+ # List all available route choices (trunks + route groups)
1883
+ choices = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeChoices",
1884
+ params={"max": 1000})
1885
+ # choices["routeIdentities"] -> list of dicts with id, name, type
1886
+ ```
1887
+
1888
+ | Operation | Method | URL |
1889
+ |-----------|--------|-----|
1890
+ | List route choices | GET | `{BASE}/telephony/config/premisePstn/routeChoices` |
1891
+
1892
+ ---
1893
+
1894
+ ## Call Routing Test
1895
+
1896
+ The `test_call_routing()` method validates that an incoming call can be routed. It simulates the routing logic and returns the destination type and details.
1897
+
1898
+ ### Data Models
1899
+
1900
+ ```python
1901
+ class OriginatorType(str, Enum):
1902
+ user = 'USER' # originator is a person
1903
+ trunk = 'TRUNK' # originator is a trunk
1904
+
1905
+ class DestinationType(str, Enum):
1906
+ hosted_agent = 'HOSTED_AGENT' # person or workspace
1907
+ hosted_feature = 'HOSTED_FEATURE' # auto-attendant, hunt group, call queue, etc.
1908
+ pbx_user = 'PBX_USER' # on-premises PBX user
1909
+ pstn_number = 'PSTN_NUMBER' # PSTN phone number
1910
+ virtual_extension = 'VIRTUAL_EXTENSION'
1911
+ virtual_extension_range = 'VIRTUAL_EXTENSION_RANGE'
1912
+ route_list = 'ROUTE_LIST'
1913
+ fac = 'FAC' # feature access code
1914
+ emergency = 'EMERGENCY'
1915
+ repair = 'REPAIR' # route in repair state
1916
+ unknown_extension = 'UNKNOWN_EXTENSION'
1917
+ unknown_number = 'UNKNOWN_NUMBER'
1918
+
1919
+ class CallSourceType(str, Enum):
1920
+ route_list = 'ROUTE_LIST'
1921
+ dial_pattern = 'DIAL_PATTERN'
1922
+ unknown_extension = 'UNKNOWN_EXTENSION'
1923
+ unknown_number = 'UNKNOWN_NUMBER'
1924
+
1925
+ class TestCallRoutingResult(ApiModel):
1926
+ language: Optional[str]
1927
+ time_zone: Optional[str]
1928
+ call_source_info: Optional[CallSourceInfo]
1929
+ destination_type: DestinationType
1930
+ routing_address: str # FAC code or routing address
1931
+ outside_access_code: Optional[str]
1932
+ is_rejected: bool
1933
+ hosted_user: Optional[HostedUserDestination] # alias 'hostedAgent'
1934
+ hosted_feature: Optional[HostedFeatureDestination]
1935
+ pbx_user: Optional[PbxUserDestination]
1936
+ pstn_number: Optional[PstnNumberDestination]
1937
+ virtual_extension: Optional[VirtualExtensionDestination]
1938
+ virtual_extension_range: Optional[VirtualExtensionRange]
1939
+ route_list: Optional[RouteListDestination]
1940
+ feature_access_code: Optional[FeatureAccessCodeDestination]
1941
+ emergency: Optional[EmergencyDestination]
1942
+ repair: Optional[TrunkDestination]
1943
+ unknown_extension: Optional[TrunkDestination]
1944
+ unknown_number: Optional[TrunkDestination]
1945
+ applied_services: Optional[list[AppliedService]]
1946
+ ```
1947
+
1948
+ #### `CallSourceInfo`
1949
+
1950
+ | Field | Type | Notes |
1951
+ |-------|------|-------|
1952
+ | `call_source_type` | `CallSourceType` | `ROUTE_LIST`, `DIAL_PATTERN`, `UNKNOWN_EXTENSION`, or `UNKNOWN_NUMBER` |
1953
+ | `route_list_name` | `str` | Name of the matched route list (when type is ROUTE_LIST) |
1954
+ | `route_list_id` | `str` | ID of the matched route list |
1955
+ | `dial_plan_name` | `str` | Name of the matched dial plan (when type is DIAL_PATTERN) |
1956
+ | `dial_plan_id` | `str` | ID of the matched dial plan |
1957
+ | `dial_pattern` | `str` | The specific pattern that matched |
1958
+
1959
+ #### `AppliedService`
1960
+
1961
+ | Field | Type | Notes |
1962
+ |-------|------|-------|
1963
+ | `translation_pattern` | `object` | Translation pattern details if applied (see below) |
1964
+
1965
+ Translation pattern sub-object fields:
1966
+ - `matching_pattern`: The pattern that matched the dialed number
1967
+ - `replacement_pattern`: The replacement pattern applied
1968
+ - `matched_number`: The original number that was matched
1969
+ - `translated_number`: The resulting number after translation
1970
+
1971
+ #### Destination-Specific Models
1972
+
1973
+ These models populate the corresponding field on `TestCallRoutingResult` based on the `destination_type`:
1974
+
1975
+ | Model | Key Fields | Notes |
1976
+ |-------|------------|-------|
1977
+ | `HostedUserDestination` | `hosted_user_id`, `last_name`, `first_name`, `extension`, `phone_number`, `location_name` | Person or workspace destination |
1978
+ | `HostedFeatureDestination` | `hosted_feature_id`, `name`, `feature_type`, `extension`, `phone_number`, `location_name` | AA, CQ, HG, or other hosted feature |
1979
+ | `PbxUserDestination` | `dial_plan_name`, `dial_pattern`, `trunk_name`, `route_group_name` | On-premises PBX user routed via trunk |
1980
+ | `PstnNumberDestination` | `trunk_name`, `route_group_name`, `trunk_id`, `route_group_id`, `outside_access_code` | PSTN number routed via trunk/route group |
1981
+ | `VirtualExtensionDestination` | `extension`, `first_name`, `last_name`, `phone_number` | Virtual extension destination |
1982
+ | `VirtualExtensionRange` | `extension`, `prefix`, `range_name` | Virtual extension range destination |
1983
+ | `RouteListDestination` | `route_list_id`, `route_list_name`, `route_group_name`, `trunk_name` | Route list destination |
1984
+ | `FeatureAccessCodeDestination` | `code`, `name` | Feature access code destination |
1985
+ | `EmergencyDestination` | `is_emergency_callback_number` | Emergency services destination |
1986
+ | `TrunkDestination` | `trunk_name`, `trunk_id`, `route_group_name`, `route_group_id` | Used for repair, unknown_extension, unknown_number |
1987
+
1988
+ The `applied_services` field returns details about any translation patterns, call intercept rules, or outgoing calling plan permissions that were applied during routing.
1989
+
1990
+ ### API Method
1991
+
1992
+ ```python
1993
+ TelephonyApi.test_call_routing(
1994
+ originator_id: str, # person ID or trunk ID
1995
+ originator_type: OriginatorType, # USER or TRUNK
1996
+ destination: str, # any dialable string (E.164, extension, URL, FAC)
1997
+ originator_number: str = None, # phone number/URI -- only when originator_type is TRUNK
1998
+ include_applied_services: bool = None, # include translation patterns, intercept, permissions
1999
+ org_id: str = None
2000
+ ) -> TestCallRoutingResult
2001
+ ```
2002
+
2003
+ ### Usage Example
2004
+
2005
+ ```python
2006
+ from wxc_sdk.telephony import OriginatorType
2007
+
2008
+ # Test routing from a user to an external number
2009
+ result = api.telephony.test_call_routing(
2010
+ originator_id=person_id,
2011
+ originator_type=OriginatorType.user,
2012
+ destination='+19195551234',
2013
+ include_applied_services=True
2014
+ )
2015
+
2016
+ print(f"Destination type: {result.destination_type}")
2017
+ print(f"Routing address: {result.routing_address}")
2018
+ print(f"Rejected: {result.is_rejected}")
2019
+
2020
+ if result.destination_type == DestinationType.pstn_number:
2021
+ print(f"Trunk: {result.pstn_number.trunk_name}")
2022
+ print(f"Route Group: {result.pstn_number.route_group_name}")
2023
+
2024
+ if result.applied_services:
2025
+ for svc in result.applied_services:
2026
+ if svc.translation_pattern:
2027
+ tp = svc.translation_pattern
2028
+ print(f"Translation: {tp.matching_pattern} -> {tp.replacement_pattern}")
2029
+ print(f" {tp.matched_number} -> {tp.translated_number}")
2030
+
2031
+ # Test routing from a trunk (inbound from PSTN)
2032
+ result = api.telephony.test_call_routing(
2033
+ originator_id=trunk_id,
2034
+ originator_type=OriginatorType.trunk,
2035
+ destination='+19195551234',
2036
+ originator_number='+14085559999'
2037
+ )
2038
+
2039
+ if result.call_source_info:
2040
+ csi = result.call_source_info
2041
+ print(f"Call source type: {csi.call_source_type}")
2042
+ if csi.route_list_name:
2043
+ print(f"Route list: {csi.route_list_name}")
2044
+ if csi.dial_plan_name:
2045
+ print(f"Dial plan: {csi.dial_plan_name}, Pattern: {csi.dial_pattern}")
2046
+ ```
2047
+
2048
+ ### CLI Examples
2049
+
2050
+ ```bash
2051
+ # Test call routing from a user to an external number
2052
+ wxcli call-routing test-call-routing \
2053
+ --originator-id Y2lzY29zcGFyazovL_PERSON_ID \
2054
+ --destination "+19195551234"
2055
+
2056
+ # Test with applied services (shows translation patterns, intercept, permissions)
2057
+ wxcli call-routing test-call-routing \
2058
+ --originator-id Y2lzY29zcGFyazovL_PERSON_ID \
2059
+ --destination "+19195551234" \
2060
+ --include-applied-services true
2061
+
2062
+ # Test routing from a trunk (inbound from PSTN)
2063
+ wxcli call-routing test-call-routing \
2064
+ --originator-id Y2lzY29zcGFyazovL_TRUNK_ID \
2065
+ --originator-number "+14085559999" \
2066
+ --destination "+19195551234"
2067
+
2068
+ # Test using full JSON body (originatorType defaults to PEOPLE per OpenAPI spec)
2069
+ wxcli call-routing test-call-routing --json-body '{
2070
+ "originatorId": "Y2lzY29zcGFyazovL_PERSON_ID",
2071
+ "originatorType": "PEOPLE",
2072
+ "destination": "+19195551234",
2073
+ "includeAppliedServices": true
2074
+ }'
2075
+
2076
+ # Test inbound from trunk via JSON body
2077
+ wxcli call-routing test-call-routing --json-body '{
2078
+ "originatorId": "Y2lzY29zcGFyazovL_TRUNK_ID",
2079
+ "originatorType": "TRUNK",
2080
+ "destination": "+19195551234",
2081
+ "originatorNumber": "+14085559999"
2082
+ }'
2083
+ ```
2084
+
2085
+ ### Raw HTTP
2086
+ <!-- Updated by playbook session 2026-03-18 -->
2087
+
2088
+ Test call routing uses an action endpoint under `/telephony/config/` (NOT `/premisePstn/`).
2089
+
2090
+ ```python
2091
+ BASE = "https://webexapis.com/v1"
2092
+
2093
+ # Test call routing (POST, not GET)
2094
+ result = api.session.rest_post(
2095
+ f"{BASE}/telephony/config/actions/testCallRouting/invoke", json={
2096
+ "originatorId": person_id,
2097
+ "originatorType": "USER",
2098
+ "destination": "+19195551234",
2099
+ "includeAppliedServices": True
2100
+ })
2101
+ # result["destinationType"] -> "HOSTED_AGENT", "PSTN_NUMBER", etc.
2102
+ # result["routingAddress"] -> the resolved routing address
2103
+ # result["isRejected"] -> boolean
2104
+
2105
+ # Test from trunk (inbound from PSTN)
2106
+ result = api.session.rest_post(
2107
+ f"{BASE}/telephony/config/actions/testCallRouting/invoke", json={
2108
+ "originatorId": trunk_id,
2109
+ "originatorType": "TRUNK",
2110
+ "destination": "+19195551234",
2111
+ "originatorNumber": "+14085559999"
2112
+ })
2113
+ ```
2114
+
2115
+ **Note:** The `originatorType` value differs between sources. The OpenAPI spec defines `OriginatorType` as `["PEOPLE", "TRUNK"]`, while wxc_sdk uses `"USER"` and `"TRUNK"`. The live API accepts **both** `"PEOPLE"` and `"USER"` as valid values for `originatorType` and returns identical results. Use `"PEOPLE"` for new code (matches OpenAPI spec), but `"USER"` (wxc_sdk convention) also works.
2116
+
2117
+ | Operation | Method | URL |
2118
+ |-----------|--------|-----|
2119
+ | Test call routing | POST | `{BASE}/telephony/config/actions/testCallRouting/invoke` |
2120
+
2121
+ ---
2122
+
2123
+ ## Phone Number Management
2124
+
2125
+ ### List Phone Numbers
2126
+
2127
+ ```python
2128
+ TelephonyApi.phone_numbers(
2129
+ location_id: str = None,
2130
+ phone_number: str = None,
2131
+ available: bool = None,
2132
+ order: str = None, # sort: lastName, dn, extension
2133
+ owner_name: str = None,
2134
+ owner_id: str = None,
2135
+ owner_type: OwnerType = None,
2136
+ extension: str = None,
2137
+ number_type: NumberType = None, # EXTENSION | NUMBER
2138
+ phone_number_type: NumberListPhoneNumberType = None, # PRIMARY | ALTERNATE | FAX | DNIS
2139
+ state: NumberState = None, # ACTIVE | INACTIVE
2140
+ details: bool = None,
2141
+ toll_free_numbers: bool = None,
2142
+ restricted_non_geo_numbers: bool = None,
2143
+ included_telephony_type: TelephonyType = None, # PSTN_NUMBER | MOBILE_NUMBER
2144
+ service_number: bool = None,
2145
+ org_id: str = None
2146
+ ) -> Generator[NumberListPhoneNumber, None, None]
2147
+ ```
2148
+
2149
+ Numbers can be standard, service, or mobile. Both standard and service numbers are PSTN numbers. Service numbers are high-utilization numbers assignable to features (auto-attendants, call queues, hunt groups).
2150
+
2151
+ ### Validate Phone Numbers
2152
+
2153
+ ```python
2154
+ TelephonyApi.validate_phone_numbers(
2155
+ phone_numbers: list[str],
2156
+ org_id: str = None
2157
+ ) -> ValidatePhoneNumbersResponse
2158
+ ```
2159
+
2160
+ ```python
2161
+ class ValidatePhoneNumbersResponse(ApiModel):
2162
+ status: ValidationStatus # 'OK' | 'ERRORS'
2163
+ phone_numbers: Optional[list[ValidatePhoneNumberStatus]]
2164
+ # .ok property: True if status == 'OK'
2165
+
2166
+ class ValidatePhoneNumberStatus(ApiModel):
2167
+ phone_number: str
2168
+ state: ValidatePhoneNumberStatusState # Available | Duplicate | Duplicate In List | Invalid | Unavailable
2169
+ toll_free_number: bool
2170
+ detail: list[str]
2171
+ # .ok property: True if state == Available
2172
+ ```
2173
+
2174
+ Phone numbers must follow **E.164 format** for all countries, except for the United States which can also use National format.
2175
+
2176
+ ### Usage Example
2177
+
2178
+ ```python
2179
+ # Validate numbers before provisioning
2180
+ result = api.telephony.validate_phone_numbers(
2181
+ phone_numbers=['+19195551234', '+19195555678']
2182
+ )
2183
+ if result.ok:
2184
+ print("All numbers available")
2185
+ else:
2186
+ for pn in result.phone_numbers:
2187
+ if not pn.ok:
2188
+ print(f"{pn.phone_number}: {pn.state} - {pn.detail}")
2189
+
2190
+ # List all assigned numbers at a location
2191
+ for num in api.telephony.phone_numbers(location_id=location_id, available=False):
2192
+ owner_name = num.owner.last_name if num.owner else 'Unassigned'
2193
+ print(f"{num.phone_number} ext:{num.extension} -> {owner_name}")
2194
+ ```
2195
+
2196
+ ### Raw HTTP
2197
+ <!-- Updated by playbook session 2026-03-18 -->
2198
+
2199
+ ```python
2200
+ BASE = "https://webexapis.com/v1"
2201
+
2202
+ # List phone numbers (no auto-pagination -- use max=1000)
2203
+ numbers = api.session.rest_get(f"{BASE}/telephony/config/numbers",
2204
+ params={"max": 1000, "locationId": location_id})
2205
+ # numbers["phoneNumbers"] -> list of dicts
2206
+
2207
+ # Validate phone numbers
2208
+ result = api.session.rest_post(f"{BASE}/telephony/config/actions/validateNumbers/invoke",
2209
+ json={"phoneNumbers": ["+19195551234", "+19195555678"]})
2210
+ # result["status"] -> "OK" or "ERRORS"
2211
+ # result["phoneNumbers"] -> list of validation status dicts
2212
+ ```
2213
+
2214
+ | Operation | Method | URL |
2215
+ |-----------|--------|-----|
2216
+ | List numbers | GET | `{BASE}/telephony/config/numbers` |
2217
+ | Validate numbers | POST | `{BASE}/telephony/config/actions/validateNumbers/invoke` |
2218
+
2219
+ ---
2220
+
2221
+ ## Data Models Quick Reference
2222
+
2223
+ ### Enums
2224
+
2225
+ | Enum | Values | Used In |
2226
+ |------|--------|---------|
2227
+ | `RouteType` | `ROUTE_GROUP`, `TRUNK`, `CISCO_PSTN`, `CLOUD_CONNECTED_PSTN` | Dial plans, PSTN config |
2228
+ | `TrunkType` | `REGISTERING`, `CERTIFICATE_BASED` | Trunk creation |
2229
+ | `PatternAction` | `ADD`, `DELETE` | Dial pattern and route list number modifications |
2230
+ | `ValidationStatus` | `OK`, `ERRORS` | Pattern and number validation results |
2231
+ | `DialPatternStatus` | `INVALID`, `DUPLICATE`, `DUPLICATE_IN_LIST` | Pattern validation errors |
2232
+ | `DeviceStatus` | `online`, `offline`, `unknown` | Trunk detail status |
2233
+ | `PSTNType` | `LOCAL_GATEWAY`, `NON_INTEGRATED_CCP`, `INTEGRATED_CCP`, `CISCO_PSTN` | PSTN connection config |
2234
+ | `NetworkConnectionType` | `PUBLIC_INTERNET`, `PRIVATE_NETWORK` | PNC settings |
2235
+ | `OriginatorType` | `USER`, `TRUNK` | Call routing test |
2236
+ | `DestinationType` | `HOSTED_AGENT`, `HOSTED_FEATURE`, `PBX_USER`, `PSTN_NUMBER`, `VIRTUAL_EXTENSION`, `VIRTUAL_EXTENSION_RANGE`, `ROUTE_LIST`, `FAC`, `EMERGENCY`, `REPAIR`, `UNKNOWN_EXTENSION`, `UNKNOWN_NUMBER` | Call routing test result |
2237
+ | `TranslationPatternLevel` | `Location`, `Organization` | Translation patterns |
2238
+ | `NumberState` | `ACTIVE`, `INACTIVE` | Phone number listing |
2239
+ | `PChargeInfoSupportPolicy` | `DISABLED`, `ASSERTED_IDENTITY`, `CONFIGURABLE_CHARGE_NUMBER` | Trunk config |
2240
+
2241
+ ### Complete End-to-End Setup Flow
2242
+
2243
+ The typical order for setting up premises-based PSTN routing:
2244
+
2245
+ 1. **Create Trunk(s)** -- establish SIP connections to on-premises gateways
2246
+ 2. **Create Route Group** (optional) -- bundle trunks for failover
2247
+ 3. **Create Dial Plan** -- define patterns and associate with trunk or route group
2248
+ 4. **Create Translation Patterns** (optional) -- digit manipulation before routing
2249
+ 5. **Configure PSTN Connection** -- point location to trunk or route group
2250
+ 6. **Create Route List** (optional) -- for Dedicated Instance cloud PSTN
2251
+ 7. **Validate** -- use `test_call_routing()` to verify the configuration
2252
+
2253
+ ```
2254
+ Step 1: Trunk (SBC/LGW)
2255
+ |
2256
+ Step 2: Route Group (optional, for failover)
2257
+ |
2258
+ Step 3: Dial Plan (pattern matching → route choice)
2259
+ |
2260
+ Step 4: Translation Pattern (optional, digit rewrite)
2261
+ |
2262
+ Step 5: PSTN Connection (location → trunk/route group)
2263
+ |
2264
+ Step 6: Route List (optional, Dedicated Instance)
2265
+ |
2266
+ Step 7: test_call_routing() to validate
2267
+ ```
2268
+
2269
+ ---
2270
+
2271
+ ## Common Gotchas
2272
+
2273
+ ### 0. Raw HTTP URLs require the `/premisePstn/` prefix for routing resources
2274
+ <!-- Updated by playbook session 2026-03-18 -->
2275
+
2276
+ Dial plans, trunks, route groups, and route lists all live under `telephony/config/premisePstn/` -- NOT under `telephony/config/dialPlans` or similar. Translation patterns use a different prefix: `telephony/config/callRouting/translationPatterns`. PSTN connection uses yet another: `telephony/pstn/locations/`. Test call routing uses `telephony/config/actions/testCallRouting/invoke`. Getting any of these prefixes wrong returns 404.
2277
+
2278
+ ### 1. Translation pattern replacement must use fully specified digits
2279
+
2280
+ Translation pattern replacement strings cannot contain `X` wildcards in any format. For example, `+1919666XXXX` is rejected, and so is `XXX` (non-E.164). Use fully specified literal digits: `+19196660000`. The matching pattern accepts `X` wildcards normally (`9XXXXXXX`, `+1512555XXXX`), but the replacement must be all literal digits. Confirmed via live API testing 2026-04-18 — error 28043 "Invalid Translation Replacement Pattern" for any replacement containing `X`.
2281
+
2282
+ ### 2. Dial plans require an existing trunk or route group
2283
+
2284
+ You cannot create a standalone dial plan without a route choice. The dial plan must reference an existing trunk or route group as its route choice at creation time.
2285
+
2286
+ ### 3. Test Call Routing requires a calling-enabled user's originatorId
2287
+
2288
+ The `test_call_routing` API requires the `originatorId` to be a valid calling-enabled user. Passing a non-calling user's ID returns `404 "Originator not found"`. Always verify the user has a Webex Calling license and location assigned before using them as an originator.
2289
+
2290
+ ### 4. Trunk passwords reject `?` and `!` characters
2291
+
2292
+ When creating a REGISTERING trunk, the password field rejects `?` and `!` — error 25015 "Invalid characters ? or ! in password." Use alphanumeric + other special characters (e.g., `@`, `#`, `$`). Confirmed via live API testing 2026-04-18.
2293
+
2294
+ ### 5. Number porting and ordering are Control Hub only
2295
+
2296
+ The API manages PSTN connections, trunks, and routing for numbers that already exist in the org. To port in new numbers or order from Cisco Calling Plan, use Control Hub or contact Cisco PTS.
2297
+
2298
+ ---
2299
+
2300
+ ## See Also
2301
+
2302
+ - [Major Call Features](call-features-major.md) -- Auto Attendants, Call Queues, and Hunt Groups (the `HOSTED_FEATURE` destination type in call routing test results covers these features)
2303
+ - [Provisioning Reference](provisioning.md) -- creating locations and users (trunks, route lists, and PSTN connections are all location-scoped)
2304
+ - [Devices Reference](devices-core.md) -- device types and device management (relevant to trunk device type selection)