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.
- wxcli/__init__.py +4 -0
- wxcli/_playbook/.claude/agents/migration-advisor.md +239 -0
- wxcli/_playbook/.claude/agents/wxc-calling-builder.md +946 -0
- wxcli/_playbook/.claude/rules/cleanup.md +29 -0
- wxcli/_playbook/.claude/rules/cucm-migration.md +58 -0
- wxcli/_playbook/.claude/rules/org-health.md +14 -0
- wxcli/_playbook/.claude/settings.json +8 -0
- wxcli/_playbook/.claude/skills/audit-compliance/SKILL.md +515 -0
- wxcli/_playbook/.claude/skills/call-control/SKILL.md +747 -0
- wxcli/_playbook/.claude/skills/configure-features/SKILL.md +623 -0
- wxcli/_playbook/.claude/skills/configure-routing/SKILL.md +627 -0
- wxcli/_playbook/.claude/skills/contact-center/SKILL.md +1181 -0
- wxcli/_playbook/.claude/skills/cucm-migrate/SKILL.md +820 -0
- wxcli/_playbook/.claude/skills/customer-assist/SKILL.md +443 -0
- wxcli/_playbook/.claude/skills/device-platform/SKILL.md +407 -0
- wxcli/_playbook/.claude/skills/manage-call-settings/SKILL.md +757 -0
- wxcli/_playbook/.claude/skills/manage-devices/SKILL.md +790 -0
- wxcli/_playbook/.claude/skills/manage-identity/SKILL.md +638 -0
- wxcli/_playbook/.claude/skills/manage-licensing/SKILL.md +528 -0
- wxcli/_playbook/.claude/skills/manage-meetings/SKILL.md +708 -0
- wxcli/_playbook/.claude/skills/messaging-bots/SKILL.md +365 -0
- wxcli/_playbook/.claude/skills/messaging-spaces/SKILL.md +520 -0
- wxcli/_playbook/.claude/skills/org-health/SKILL.md +159 -0
- wxcli/_playbook/.claude/skills/provision-calling/SKILL.md +421 -0
- wxcli/_playbook/.claude/skills/query-live/SKILL.md +183 -0
- wxcli/_playbook/.claude/skills/query-live/domains/call-flow-trace.md +223 -0
- wxcli/_playbook/.claude/skills/query-live/domains/call-history.md +44 -0
- wxcli/_playbook/.claude/skills/query-live/domains/features.md +169 -0
- wxcli/_playbook/.claude/skills/query-live/domains/numbers.md +93 -0
- wxcli/_playbook/.claude/skills/query-live/domains/people-and-settings.md +146 -0
- wxcli/_playbook/.claude/skills/query-live/domains/routing.md +124 -0
- wxcli/_playbook/.claude/skills/reporting/SKILL.md +501 -0
- wxcli/_playbook/.claude/skills/reporting/references/cdr-recipes.md +1628 -0
- wxcli/_playbook/.claude/skills/reporting-cc/SKILL.md +251 -0
- wxcli/_playbook/.claude/skills/reporting-meetings/SKILL.md +290 -0
- wxcli/_playbook/.claude/skills/teardown/SKILL.md +214 -0
- wxcli/_playbook/.claude/skills/video-mesh/SKILL.md +379 -0
- wxcli/_playbook/.claude/skills/wxc-calling-debug/SKILL.md +475 -0
- wxcli/_playbook/CLAUDE.md +373 -0
- wxcli/_playbook/docs/reference/CLAUDE.md +25 -0
- wxcli/_playbook/docs/reference/admin-apps-data.md +643 -0
- wxcli/_playbook/docs/reference/admin-audit-security.md +322 -0
- wxcli/_playbook/docs/reference/admin-hybrid.md +383 -0
- wxcli/_playbook/docs/reference/admin-identity-scim.md +1007 -0
- wxcli/_playbook/docs/reference/admin-licensing.md +414 -0
- wxcli/_playbook/docs/reference/admin-org-management.md +586 -0
- wxcli/_playbook/docs/reference/admin-partner.md +357 -0
- wxcli/_playbook/docs/reference/archive/wxc-sdk-patterns.md +1229 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-advanced.md +835 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-core.md +743 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-devices-workspaces.md +1305 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-features.md +849 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-locations.md +938 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-person.md +1255 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-routing.md +1262 -0
- wxcli/_playbook/docs/reference/authentication.md +965 -0
- wxcli/_playbook/docs/reference/call-control.md +1205 -0
- wxcli/_playbook/docs/reference/call-features-additional.md +2541 -0
- wxcli/_playbook/docs/reference/call-features-major.md +1320 -0
- wxcli/_playbook/docs/reference/call-routing.md +2304 -0
- wxcli/_playbook/docs/reference/contact-center-analytics.md +1049 -0
- wxcli/_playbook/docs/reference/contact-center-core.md +1614 -0
- wxcli/_playbook/docs/reference/contact-center-journey.md +457 -0
- wxcli/_playbook/docs/reference/contact-center-routing.md +1138 -0
- wxcli/_playbook/docs/reference/devices-core.md +1869 -0
- wxcli/_playbook/docs/reference/devices-dect.md +1150 -0
- wxcli/_playbook/docs/reference/devices-platform.md +467 -0
- wxcli/_playbook/docs/reference/devices-workspaces.md +1426 -0
- wxcli/_playbook/docs/reference/emergency-services.md +1122 -0
- wxcli/_playbook/docs/reference/location-calling-core.md +2037 -0
- wxcli/_playbook/docs/reference/location-calling-media.md +1334 -0
- wxcli/_playbook/docs/reference/location-recording-advanced.md +1482 -0
- wxcli/_playbook/docs/reference/meetings-content.md +473 -0
- wxcli/_playbook/docs/reference/meetings-core.md +903 -0
- wxcli/_playbook/docs/reference/meetings-infrastructure.md +899 -0
- wxcli/_playbook/docs/reference/meetings-settings.md +850 -0
- wxcli/_playbook/docs/reference/messaging-bots.md +1091 -0
- wxcli/_playbook/docs/reference/messaging-spaces.md +695 -0
- wxcli/_playbook/docs/reference/person-call-settings-behavior.md +1639 -0
- wxcli/_playbook/docs/reference/person-call-settings-handling.md +1394 -0
- wxcli/_playbook/docs/reference/person-call-settings-media.md +1898 -0
- wxcli/_playbook/docs/reference/person-call-settings-permissions.md +1329 -0
- wxcli/_playbook/docs/reference/provisioning.md +1366 -0
- wxcli/_playbook/docs/reference/reporting-analytics.md +1318 -0
- wxcli/_playbook/docs/reference/self-service-call-settings.md +1020 -0
- wxcli/_playbook/docs/reference/virtual-lines.md +1132 -0
- wxcli/_playbook/docs/reference/webhooks-events.md +1444 -0
- wxcli/_playbook/docs/reference/wxcadm-xsi-realtime.md +953 -0
- wxcli/_version.py +24 -0
- wxcli/auth.py +137 -0
- wxcli/commands/__init__.py +0 -0
- wxcli/commands/_registry.py +181 -0
- wxcli/commands/activation_email.py +103 -0
- wxcli/commands/admin_recordings.py +585 -0
- wxcli/commands/analytics.py +105 -0
- wxcli/commands/announcement_playlists.py +219 -0
- wxcli/commands/announcements.py +519 -0
- wxcli/commands/archive_users.py +36 -0
- wxcli/commands/attachment_actions.py +60 -0
- wxcli/commands/audit_events.py +83 -0
- wxcli/commands/authorizations.py +113 -0
- wxcli/commands/auto_attendant.py +642 -0
- wxcli/commands/broadworks_billing_reports.py +126 -0
- wxcli/commands/broadworks_enterprises.py +160 -0
- wxcli/commands/broadworks_subscribers.py +319 -0
- wxcli/commands/broadworks_workspaces.py +108 -0
- wxcli/commands/call_controls.py +1304 -0
- wxcli/commands/call_park.py +494 -0
- wxcli/commands/call_pickup.py +229 -0
- wxcli/commands/call_queue.py +1878 -0
- wxcli/commands/call_recording.py +707 -0
- wxcli/commands/call_routing.py +1610 -0
- wxcli/commands/call_settings_for_me_phase_5.py +212 -0
- wxcli/commands/caller_reputation.py +169 -0
- wxcli/commands/calling_service.py +305 -0
- wxcli/commands/cc_address_book.py +770 -0
- wxcli/commands/cc_agent_greetings.py +242 -0
- wxcli/commands/cc_agent_summaries.py +39 -0
- wxcli/commands/cc_agent_wellbeing.py +216 -0
- wxcli/commands/cc_agents.py +425 -0
- wxcli/commands/cc_ai_assistant.py +55 -0
- wxcli/commands/cc_ai_feature.py +294 -0
- wxcli/commands/cc_audio_files.py +149 -0
- wxcli/commands/cc_auto_csat.py +311 -0
- wxcli/commands/cc_aux_code.py +458 -0
- wxcli/commands/cc_business_hour.py +353 -0
- wxcli/commands/cc_call_monitoring.py +228 -0
- wxcli/commands/cc_callbacks.py +194 -0
- wxcli/commands/cc_campaign.py +212 -0
- wxcli/commands/cc_captures.py +39 -0
- wxcli/commands/cc_contact_list.py +169 -0
- wxcli/commands/cc_contact_number.py +295 -0
- wxcli/commands/cc_data_sources.py +235 -0
- wxcli/commands/cc_desktop_layout.py +380 -0
- wxcli/commands/cc_desktop_profile.py +322 -0
- wxcli/commands/cc_dial_number.py +511 -0
- wxcli/commands/cc_dial_plan.py +347 -0
- wxcli/commands/cc_dnc.py +101 -0
- wxcli/commands/cc_entry_point.py +545 -0
- wxcli/commands/cc_ewt.py +51 -0
- wxcli/commands/cc_flow.py +477 -0
- wxcli/commands/cc_global_vars.py +430 -0
- wxcli/commands/cc_holiday_list.py +335 -0
- wxcli/commands/cc_journey.py +1261 -0
- wxcli/commands/cc_legacy_flows.py +49 -0
- wxcli/commands/cc_multimedia_profile.py +436 -0
- wxcli/commands/cc_notification.py +51 -0
- wxcli/commands/cc_outdial_ani.py +604 -0
- wxcli/commands/cc_overrides.py +341 -0
- wxcli/commands/cc_queue.py +1083 -0
- wxcli/commands/cc_queue_stats.py +55 -0
- wxcli/commands/cc_realtime.py +51 -0
- wxcli/commands/cc_resource_collection.py +312 -0
- wxcli/commands/cc_search.py +46 -0
- wxcli/commands/cc_site.py +400 -0
- wxcli/commands/cc_skill.py +350 -0
- wxcli/commands/cc_skill_profile.py +289 -0
- wxcli/commands/cc_subscriptions.py +426 -0
- wxcli/commands/cc_summaries.py +130 -0
- wxcli/commands/cc_tasks.py +844 -0
- wxcli/commands/cc_team.py +325 -0
- wxcli/commands/cc_user_profiles.py +349 -0
- wxcli/commands/cc_users.py +701 -0
- wxcli/commands/cc_work_types.py +363 -0
- wxcli/commands/cdr.py +89 -0
- wxcli/commands/classifications.py +38 -0
- wxcli/commands/cleanup.py +1427 -0
- wxcli/commands/client_settings.py +72 -0
- wxcli/commands/conference.py +328 -0
- wxcli/commands/configure.py +72 -0
- wxcli/commands/converged_recordings.py +347 -0
- wxcli/commands/converged_recordings_export.py +310 -0
- wxcli/commands/cq_playlists.py +42 -0
- wxcli/commands/cucm.py +2794 -0
- wxcli/commands/cucm_config.py +129 -0
- wxcli/commands/customer_assist.py +373 -0
- wxcli/commands/data_sources.py +227 -0
- wxcli/commands/dect_devices.py +787 -0
- wxcli/commands/device_configurations.py +77 -0
- wxcli/commands/device_dynamic_settings.py +320 -0
- wxcli/commands/device_settings.py +1489 -0
- wxcli/commands/devices.py +280 -0
- wxcli/commands/domains.py +142 -0
- wxcli/commands/ecm.py +178 -0
- wxcli/commands/emergency_services.py +827 -0
- wxcli/commands/events.py +83 -0
- wxcli/commands/external_voicemail.py +52 -0
- wxcli/commands/groups.py +206 -0
- wxcli/commands/guest_management.py +76 -0
- wxcli/commands/hds.py +340 -0
- wxcli/commands/hot_desk.py +67 -0
- wxcli/commands/hot_desking_members.py +118 -0
- wxcli/commands/hot_desking_portal.py +124 -0
- wxcli/commands/hunt_group.py +577 -0
- wxcli/commands/hybrid_clusters.py +71 -0
- wxcli/commands/hybrid_connectors.py +67 -0
- wxcli/commands/identity_org.py +159 -0
- wxcli/commands/init_playbook.py +155 -0
- wxcli/commands/licenses.py +106 -0
- wxcli/commands/live_monitoring.py +40 -0
- wxcli/commands/location_call_handling.py +577 -0
- wxcli/commands/location_schedules.py +341 -0
- wxcli/commands/location_settings.py +1297 -0
- wxcli/commands/location_voicemail.py +494 -0
- wxcli/commands/locations.py +336 -0
- wxcli/commands/meeting_captions.py +110 -0
- wxcli/commands/meeting_chats.py +63 -0
- wxcli/commands/meeting_invitees.py +234 -0
- wxcli/commands/meeting_messages.py +28 -0
- wxcli/commands/meeting_participants.py +278 -0
- wxcli/commands/meeting_polls.py +111 -0
- wxcli/commands/meeting_preferences.py +515 -0
- wxcli/commands/meeting_qa.py +78 -0
- wxcli/commands/meeting_qualities.py +43 -0
- wxcli/commands/meeting_reports.py +101 -0
- wxcli/commands/meeting_session_types.py +105 -0
- wxcli/commands/meeting_site.py +57 -0
- wxcli/commands/meeting_slido.py +44 -0
- wxcli/commands/meeting_summaries.py +92 -0
- wxcli/commands/meeting_tracking_codes.py +233 -0
- wxcli/commands/meeting_transcripts.py +230 -0
- wxcli/commands/meetings.py +1997 -0
- wxcli/commands/memberships.py +164 -0
- wxcli/commands/messages.py +213 -0
- wxcli/commands/mode_management.py +244 -0
- wxcli/commands/my_call_settings.py +3577 -0
- wxcli/commands/numbers.py +405 -0
- wxcli/commands/operating_modes.py +410 -0
- wxcli/commands/org_contacts.py +284 -0
- wxcli/commands/org_health_cli.py +55 -0
- wxcli/commands/org_settings.py +71 -0
- wxcli/commands/organizations.py +83 -0
- wxcli/commands/paging_group.py +257 -0
- wxcli/commands/partner_admins.py +141 -0
- wxcli/commands/partner_reports.py +188 -0
- wxcli/commands/partner_tags.py +216 -0
- wxcli/commands/people.py +279 -0
- wxcli/commands/person_call_settings.py +71 -0
- wxcli/commands/pstn.py +280 -0
- wxcli/commands/recording_report.py +154 -0
- wxcli/commands/report_templates.py +38 -0
- wxcli/commands/reports.py +144 -0
- wxcli/commands/resource_group_memberships.py +160 -0
- wxcli/commands/resource_groups.py +67 -0
- wxcli/commands/roles.py +63 -0
- wxcli/commands/room_tabs.py +160 -0
- wxcli/commands/rooms.py +225 -0
- wxcli/commands/scim_bulk.py +46 -0
- wxcli/commands/scim_groups.py +246 -0
- wxcli/commands/scim_schemas.py +82 -0
- wxcli/commands/scim_users.py +283 -0
- wxcli/commands/security_audit.py +54 -0
- wxcli/commands/service_apps.py +51 -0
- wxcli/commands/single_number_reach.py +209 -0
- wxcli/commands/team_memberships.py +155 -0
- wxcli/commands/teams.py +153 -0
- wxcli/commands/ucm_profile.py +42 -0
- wxcli/commands/update.py +219 -0
- wxcli/commands/user_settings.py +5116 -0
- wxcli/commands/video_mesh.py +1090 -0
- wxcli/commands/virtual_extensions.py +502 -0
- wxcli/commands/virtual_line_settings.py +2072 -0
- wxcli/commands/webhooks.py +176 -0
- wxcli/commands/wholesale_billing_reports.py +147 -0
- wxcli/commands/wholesale_provisioning.py +523 -0
- wxcli/commands/workspace_locations.py +339 -0
- wxcli/commands/workspace_metrics.py +103 -0
- wxcli/commands/workspace_personalization.py +70 -0
- wxcli/commands/workspace_settings.py +3347 -0
- wxcli/commands/workspaces.py +279 -0
- wxcli/commands/xapi.py +114 -0
- wxcli/config.py +139 -0
- wxcli/errors.py +77 -0
- wxcli/main.py +210 -0
- wxcli/migration/CLAUDE.md +106 -0
- wxcli/migration/__init__.py +1 -0
- wxcli/migration/advisory/CLAUDE.md +196 -0
- wxcli/migration/advisory/__init__.py +46 -0
- wxcli/migration/advisory/advisor.py +107 -0
- wxcli/migration/advisory/advisory_patterns.py +2552 -0
- wxcli/migration/advisory/recommendation_rules.py +835 -0
- wxcli/migration/cucm/CLAUDE.md +74 -0
- wxcli/migration/cucm/__init__.py +5 -0
- wxcli/migration/cucm/connection.py +265 -0
- wxcli/migration/cucm/discovery.py +225 -0
- wxcli/migration/cucm/extractors/__init__.py +1 -0
- wxcli/migration/cucm/extractors/announcements.py +92 -0
- wxcli/migration/cucm/extractors/base.py +90 -0
- wxcli/migration/cucm/extractors/device_profiles.py +98 -0
- wxcli/migration/cucm/extractors/devices.py +193 -0
- wxcli/migration/cucm/extractors/e911.py +103 -0
- wxcli/migration/cucm/extractors/features.py +309 -0
- wxcli/migration/cucm/extractors/helpers.py +73 -0
- wxcli/migration/cucm/extractors/informational.py +295 -0
- wxcli/migration/cucm/extractors/locations.py +221 -0
- wxcli/migration/cucm/extractors/moh.py +100 -0
- wxcli/migration/cucm/extractors/remote_destinations.py +96 -0
- wxcli/migration/cucm/extractors/routing.py +493 -0
- wxcli/migration/cucm/extractors/shared_lines.py +146 -0
- wxcli/migration/cucm/extractors/templates.py +229 -0
- wxcli/migration/cucm/extractors/tier4.py +173 -0
- wxcli/migration/cucm/extractors/users.py +240 -0
- wxcli/migration/cucm/extractors/voicemail.py +243 -0
- wxcli/migration/cucm/extractors/workspaces.py +88 -0
- wxcli/migration/cucm/unity_connection.py +304 -0
- wxcli/migration/execute/CLAUDE.md +372 -0
- wxcli/migration/execute/__init__.py +303 -0
- wxcli/migration/execute/batch.py +277 -0
- wxcli/migration/execute/dependency.py +483 -0
- wxcli/migration/execute/engine.py +964 -0
- wxcli/migration/execute/handlers.py +2232 -0
- wxcli/migration/execute/planner.py +1939 -0
- wxcli/migration/execute/runtime.py +571 -0
- wxcli/migration/export/CLAUDE.md +49 -0
- wxcli/migration/export/__init__.py +8 -0
- wxcli/migration/export/csv_export.py +126 -0
- wxcli/migration/export/deployment_plan.py +479 -0
- wxcli/migration/export/json_export.py +60 -0
- wxcli/migration/models.py +941 -0
- wxcli/migration/phone_models.py +50 -0
- wxcli/migration/preflight/CLAUDE.md +52 -0
- wxcli/migration/preflight/__init__.py +89 -0
- wxcli/migration/preflight/checks.py +911 -0
- wxcli/migration/preflight/runner.py +293 -0
- wxcli/migration/rate_limiter.py +144 -0
- wxcli/migration/report/CLAUDE.md +245 -0
- wxcli/migration/report/__init__.py +7 -0
- wxcli/migration/report/appendix.py +2437 -0
- wxcli/migration/report/assembler.py +183 -0
- wxcli/migration/report/charts.py +263 -0
- wxcli/migration/report/executive.py +776 -0
- wxcli/migration/report/explainer.py +704 -0
- wxcli/migration/report/helpers.py +74 -0
- wxcli/migration/report/ingest.py +152 -0
- wxcli/migration/report/notice_templates.py +192 -0
- wxcli/migration/report/score.py +375 -0
- wxcli/migration/report/styles.py +1052 -0
- wxcli/migration/report/user_diff.py +1052 -0
- wxcli/migration/report/user_notice.py +425 -0
- wxcli/migration/state.py +139 -0
- wxcli/migration/store.py +860 -0
- wxcli/migration/transform/CLAUDE.md +107 -0
- wxcli/migration/transform/__init__.py +12 -0
- wxcli/migration/transform/analysis_pipeline.py +453 -0
- wxcli/migration/transform/analyzers/__init__.py +142 -0
- wxcli/migration/transform/analyzers/css_permission.py +189 -0
- wxcli/migration/transform/analyzers/css_routing.py +335 -0
- wxcli/migration/transform/analyzers/device_compatibility.py +122 -0
- wxcli/migration/transform/analyzers/dn_ambiguity.py +131 -0
- wxcli/migration/transform/analyzers/duplicate_user.py +221 -0
- wxcli/migration/transform/analyzers/extension_conflict.py +173 -0
- wxcli/migration/transform/analyzers/feature_approximation.py +227 -0
- wxcli/migration/transform/analyzers/layout_overflow.py +174 -0
- wxcli/migration/transform/analyzers/location_ambiguity.py +184 -0
- wxcli/migration/transform/analyzers/missing_data.py +300 -0
- wxcli/migration/transform/analyzers/selective_call_handling.py +394 -0
- wxcli/migration/transform/analyzers/shared_line.py +170 -0
- wxcli/migration/transform/analyzers/voicemail_compatibility.py +267 -0
- wxcli/migration/transform/analyzers/workspace_license.py +212 -0
- wxcli/migration/transform/cross_reference.py +1136 -0
- wxcli/migration/transform/cucm_pattern.py +246 -0
- wxcli/migration/transform/decisions.py +301 -0
- wxcli/migration/transform/e164.py +145 -0
- wxcli/migration/transform/engine.py +256 -0
- wxcli/migration/transform/mappers/CLAUDE.md +123 -0
- wxcli/migration/transform/mappers/__init__.py +76 -0
- wxcli/migration/transform/mappers/announcement_mapper.py +122 -0
- wxcli/migration/transform/mappers/base.py +190 -0
- wxcli/migration/transform/mappers/button_template_mapper.py +186 -0
- wxcli/migration/transform/mappers/call_forwarding_mapper.py +228 -0
- wxcli/migration/transform/mappers/call_settings_mapper.py +144 -0
- wxcli/migration/transform/mappers/css_mapper.py +868 -0
- wxcli/migration/transform/mappers/dect_mapper.py +367 -0
- wxcli/migration/transform/mappers/device_layout_mapper.py +289 -0
- wxcli/migration/transform/mappers/device_mapper.py +227 -0
- wxcli/migration/transform/mappers/device_profile_mapper.py +190 -0
- wxcli/migration/transform/mappers/device_settings_mapper.py +464 -0
- wxcli/migration/transform/mappers/e911_mapper.py +129 -0
- wxcli/migration/transform/mappers/ecbn_mapper.py +263 -0
- wxcli/migration/transform/mappers/executive_assistant_mapper.py +156 -0
- wxcli/migration/transform/mappers/feature_mapper.py +1179 -0
- wxcli/migration/transform/mappers/line_mapper.py +310 -0
- wxcli/migration/transform/mappers/location_mapper.py +235 -0
- wxcli/migration/transform/mappers/moh_mapper.py +114 -0
- wxcli/migration/transform/mappers/monitoring_mapper.py +221 -0
- wxcli/migration/transform/mappers/receptionist_mapper.py +245 -0
- wxcli/migration/transform/mappers/routing_mapper.py +821 -0
- wxcli/migration/transform/mappers/snr_mapper.py +201 -0
- wxcli/migration/transform/mappers/softkey_mapper.py +205 -0
- wxcli/migration/transform/mappers/user_mapper.py +264 -0
- wxcli/migration/transform/mappers/voicemail_group_mapper.py +286 -0
- wxcli/migration/transform/mappers/voicemail_mapper.py +514 -0
- wxcli/migration/transform/mappers/workspace_mapper.py +464 -0
- wxcli/migration/transform/normalizers.py +2008 -0
- wxcli/migration/transform/pattern_converter.py +106 -0
- wxcli/migration/transform/pipeline.py +271 -0
- wxcli/migration/transform/rules.py +255 -0
- wxcli/org_health/CLAUDE.md +63 -0
- wxcli/org_health/__init__.py +7 -0
- wxcli/org_health/analyze.py +93 -0
- wxcli/org_health/checks.py +458 -0
- wxcli/org_health/collector.py +62 -0
- wxcli/org_health/models.py +78 -0
- wxcli/org_health/report.py +300 -0
- wxcli/output.py +101 -0
- wxcli-1.2.0.dist-info/METADATA +505 -0
- wxcli-1.2.0.dist-info/RECORD +413 -0
- wxcli-1.2.0.dist-info/WHEEL +5 -0
- wxcli-1.2.0.dist-info/entry_points.txt +2 -0
- wxcli-1.2.0.dist-info/licenses/LICENSE +191 -0
- wxcli-1.2.0.dist-info/scm_file_list.json +751 -0
- wxcli-1.2.0.dist-info/scm_version.json +8 -0
- wxcli-1.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,965 @@
|
|
|
1
|
+
# Authentication Reference — Webex Calling APIs
|
|
2
|
+
|
|
3
|
+
This document covers every authentication method available for the Webex Calling APIs, including token types, OAuth flows, scope requirements, and the `wxc_sdk` Python SDK patterns for each.
|
|
4
|
+
|
|
5
|
+
## Sources
|
|
6
|
+
|
|
7
|
+
- wxc_sdk v1.30.0
|
|
8
|
+
- OpenAPI specs: specs/webex-cloud-calling.json, specs/webex-admin.json
|
|
9
|
+
- developer.webex.com Authentication APIs
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
1. [Authentication Methods Overview](#authentication-methods-overview)
|
|
16
|
+
2. [Personal Access Tokens](#personal-access-tokens)
|
|
17
|
+
3. [OAuth Integrations](#oauth-integrations)
|
|
18
|
+
4. [Service Apps](#service-apps)
|
|
19
|
+
5. [Partner/Multi-Org Tokens](#partnerulti-org-tokens)
|
|
20
|
+
6. [Bot Tokens](#bot-tokens)
|
|
21
|
+
7. [Guest Issuer Tokens](#guest-issuer-tokens)
|
|
22
|
+
8. [Calling-Related Scopes](#calling-related-scopes)
|
|
23
|
+
8. [wxc_sdk Auth Setup](#wxc_sdk-auth-setup)
|
|
24
|
+
9. [Raw HTTP via api.session](#raw-http-via-apisession)
|
|
25
|
+
10. [Token Refresh Flow](#token-refresh-flow)
|
|
26
|
+
11. [Common Auth Errors](#common-auth-errors)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Authentication Methods Overview
|
|
31
|
+
|
|
32
|
+
| Method | Lifetime | Refresh? | Use Case | Calling API Access |
|
|
33
|
+
|--------|----------|----------|----------|-------------------|
|
|
34
|
+
| Personal Access Token | 12 hours | No | Dev/testing only | Full (your own scopes) |
|
|
35
|
+
| OAuth Integration | 14-day access / 90-day refresh | Yes | Production apps, user-delegated | Full (requested scopes) |
|
|
36
|
+
| Service App | Access token via refresh | Yes | Machine-to-machine, no user present | Full (admin-authorized scopes) |
|
|
37
|
+
| Bot Token | Does not expire | No | Automation, messaging-focused | Limited to bot scopes |
|
|
38
|
+
| Guest Issuer | Short-lived | No | Anonymous guest users | Not applicable to Calling |
|
|
39
|
+
|
|
40
|
+
All methods authenticate against the same base URL:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
https://webexapis.com/v1
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Every API request requires an `Authorization` header:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Authorization: Bearer <ACCESS_TOKEN>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Personal Access Tokens
|
|
55
|
+
|
|
56
|
+
**What they are:** A quick-start token tied to your own Webex identity. Available from [developer.webex.com](https://developer.webex.com) by clicking your avatar and copying the token.
|
|
57
|
+
|
|
58
|
+
**Key facts:**
|
|
59
|
+
- Expires after **12 hours** from the time it is displayed
|
|
60
|
+
- Cannot be refreshed — you must generate a new one manually
|
|
61
|
+
- Carries most scopes your account has access to (including `spark-admin:*` scopes if you are an admin), but does NOT include Contact Center scopes (`cjp:config_read`, `cjp:config_write`). CC config operations require an OAuth integration with CC scopes explicitly selected.
|
|
62
|
+
- Intended strictly for development and testing — never embed in production code
|
|
63
|
+
- **Contact Center limitation:** Even full admins on CC-provisioned orgs get 403 on CC config endpoints (`api.wxcc-{region}.cisco.com`) with a PAT. Create an OAuth integration at developer.webex.com with `cjp:config_read` and `cjp:config_write` scopes, complete the OAuth flow, and use that token instead.
|
|
64
|
+
|
|
65
|
+
**wxc_sdk usage:**
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from wxc_sdk import WebexSimpleApi
|
|
69
|
+
|
|
70
|
+
# Pass token directly as a string
|
|
71
|
+
api = WebexSimpleApi(tokens='YOUR_PERSONAL_ACCESS_TOKEN')
|
|
72
|
+
|
|
73
|
+
# Or set the environment variable and pass nothing
|
|
74
|
+
# export WEBEX_ACCESS_TOKEN=YOUR_PERSONAL_ACCESS_TOKEN
|
|
75
|
+
api = WebexSimpleApi()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## OAuth Integrations
|
|
81
|
+
|
|
82
|
+
OAuth integrations use the **Authorization Code Grant** flow (OAuth 2.0). This is the standard method for production applications that act on behalf of a user.
|
|
83
|
+
|
|
84
|
+
### Creating an Integration
|
|
85
|
+
|
|
86
|
+
1. Log into [developer.webex.com](https://developer.webex.com)
|
|
87
|
+
2. Click your avatar > **My Webex Apps** > **Create a New App** > **Create an Integration**
|
|
88
|
+
3. Provide app name, description, logo, redirect URI, and select scopes
|
|
89
|
+
4. Save the **Client ID** and **Client Secret** (secret shown only once)
|
|
90
|
+
|
|
91
|
+
### OAuth Flow (4 Steps)
|
|
92
|
+
|
|
93
|
+
**Step 1 — Authorization Request:**
|
|
94
|
+
Redirect the user to:
|
|
95
|
+
```
|
|
96
|
+
https://webexapis.com/v1/authorize?
|
|
97
|
+
client_id=YOUR_CLIENT_ID&
|
|
98
|
+
response_type=code&
|
|
99
|
+
redirect_uri=http://localhost:6001/redirect&
|
|
100
|
+
scope=spark:calls_read spark:calls_write spark:people_read spark-admin:telephony_config_read&
|
|
101
|
+
state=RANDOM_STATE_STRING
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Step 2 — User Authenticates:**
|
|
105
|
+
User logs into Webex and approves the requested scopes.
|
|
106
|
+
|
|
107
|
+
**Step 3 — Receive Authorization Code:**
|
|
108
|
+
Webex redirects to your `redirect_uri` with a `code` parameter:
|
|
109
|
+
```
|
|
110
|
+
http://localhost:6001/redirect?code=AUTH_CODE&state=RANDOM_STATE_STRING
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Step 4 — Exchange Code for Tokens:**
|
|
114
|
+
POST to `https://webexapis.com/v1/access_token`:
|
|
115
|
+
```
|
|
116
|
+
grant_type=authorization_code
|
|
117
|
+
client_id=YOUR_CLIENT_ID
|
|
118
|
+
client_secret=YOUR_CLIENT_SECRET
|
|
119
|
+
code=AUTH_CODE
|
|
120
|
+
redirect_uri=http://localhost:6001/redirect
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Response:**
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"access_token": "...",
|
|
127
|
+
"expires_in": 1209600,
|
|
128
|
+
"refresh_token": "...",
|
|
129
|
+
"refresh_token_expires_in": 7776000,
|
|
130
|
+
"token_type": "Bearer",
|
|
131
|
+
"scope": "spark:calls_read spark:calls_write ..."
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Token Lifetimes
|
|
136
|
+
|
|
137
|
+
| Token | Lifetime | Notes |
|
|
138
|
+
|-------|----------|-------|
|
|
139
|
+
| Access token | **14 days** (1,209,600 seconds) | Must refresh before expiry |
|
|
140
|
+
| Refresh token | **90 days** (7,776,000 seconds) | Refreshing the access token also renews the refresh token |
|
|
141
|
+
|
|
142
|
+
### PKCE Support
|
|
143
|
+
|
|
144
|
+
Webex supports **Proof Key for Code Exchange (PKCE)** for enhanced security in the Authorization Code flow.
|
|
145
|
+
|
|
146
|
+
### OpenID Connect Discovery
|
|
147
|
+
|
|
148
|
+
Endpoint locations and server capabilities are available at:
|
|
149
|
+
```
|
|
150
|
+
https://idbroker.webex.com/idb/.well-known/openid-configuration
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
This returns a standard OpenID Connect discovery document including `authorization_endpoint`, `token_endpoint`, `userinfo_endpoint`, `jwks_uri`, supported scopes (`openid`, `email`, `profile`, `phone`, `address`), and `code_challenge_methods_supported` (`plain`, `S256`).
|
|
154
|
+
|
|
155
|
+
### Gotchas
|
|
156
|
+
|
|
157
|
+
- **wxc_sdk does not support PKCE natively.** The `Integration.auth_url()` method builds the authorization URL with only `client_id`, `response_type`, `redirect_uri`, `scope`, and `state` — no `code_challenge` or `code_challenge_method` parameters. The `tokens_from_code()` method does not send a `code_verifier`. To use PKCE with Webex, you would need to construct the authorization URL and token exchange manually.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Service Apps
|
|
162
|
+
|
|
163
|
+
Service apps are designed for **machine-to-machine** scenarios where no interactive user login is possible (background jobs, server daemons, scheduled automation).
|
|
164
|
+
|
|
165
|
+
### How They Differ from Integrations
|
|
166
|
+
|
|
167
|
+
| Aspect | Integration | Service App |
|
|
168
|
+
|--------|-------------|-------------|
|
|
169
|
+
| User presence | Requires user to authorize | No user interaction after admin approval |
|
|
170
|
+
| Token source | OAuth code flow | Refresh token provided at creation |
|
|
171
|
+
| Admin approval | User grants scopes | Org admin authorizes the app |
|
|
172
|
+
| Use case | User-facing apps | Backend automation, scheduled jobs |
|
|
173
|
+
|
|
174
|
+
### Creating & Registering a Service App
|
|
175
|
+
|
|
176
|
+
Service app creation is a 3-step process spanning two portals: the Developer Portal (registration) and Control Hub (authorization).
|
|
177
|
+
|
|
178
|
+
#### Step 1: Register on developer.webex.com
|
|
179
|
+
|
|
180
|
+
1. Log into [developer.webex.com](https://developer.webex.com)
|
|
181
|
+
2. Click your avatar (top right) → **My Webex Apps**
|
|
182
|
+
3. Click **Create a New App**
|
|
183
|
+
4. Select **Create a Service App**
|
|
184
|
+
5. Fill in the registration form:
|
|
185
|
+
- **App Name** — displayed to admins during authorization
|
|
186
|
+
- **Description** — what the app does
|
|
187
|
+
- **Logo** — appears in Control Hub when admins review the app
|
|
188
|
+
- **Scopes** — select the permissions your app needs (see [Calling-Related Scopes](#calling-related-scopes) below)
|
|
189
|
+
6. Click **Create** (or **Add Service App**)
|
|
190
|
+
7. **Immediately copy and save the Client Secret** — it is shown only once and cannot be retrieved later
|
|
191
|
+
|
|
192
|
+
You now have a **Client ID** and **Client Secret**. The app is registered but not yet authorized for any org.
|
|
193
|
+
|
|
194
|
+
#### Step 2: Authorize in Control Hub
|
|
195
|
+
|
|
196
|
+
An org Full Admin must authorize the service app before it can access that org's data.
|
|
197
|
+
|
|
198
|
+
1. Log into [admin.webex.com](https://admin.webex.com)
|
|
199
|
+
2. Navigate to **Management → Apps → Service Apps** tab
|
|
200
|
+
3. Find your service app in the list
|
|
201
|
+
4. Click it and select **Authorize**
|
|
202
|
+
5. Review the requested scopes
|
|
203
|
+
6. Click **Save**
|
|
204
|
+
|
|
205
|
+
The authorization is recorded in Admin Audit events. Any admin can later enable/disable the app from this same page.
|
|
206
|
+
|
|
207
|
+
#### Step 3: Generate Tokens
|
|
208
|
+
|
|
209
|
+
1. Return to [developer.webex.com](https://developer.webex.com) → **My Webex Apps** → your service app
|
|
210
|
+
2. In the **Org Authorizations** section, select your organization
|
|
211
|
+
3. Enter your **Client Secret**
|
|
212
|
+
4. Click **Generate Tokens**
|
|
213
|
+
5. You receive:
|
|
214
|
+
- **Access Token** (valid 14 days)
|
|
215
|
+
- **Refresh Token** (valid 90 days)
|
|
216
|
+
6. **Immediately copy and save the Refresh Token** — it is shown only once
|
|
217
|
+
|
|
218
|
+
Your service app is now ready to make API calls. Use the refresh token to obtain new access tokens programmatically (see below).
|
|
219
|
+
|
|
220
|
+
#### Scope Restrictions for Service Apps
|
|
221
|
+
|
|
222
|
+
Not all scopes work with service apps:
|
|
223
|
+
|
|
224
|
+
| Restriction | Detail |
|
|
225
|
+
|-------------|--------|
|
|
226
|
+
| XSI scopes | Not supported |
|
|
227
|
+
| Analytics scopes | Not supported |
|
|
228
|
+
| Organization contacts | Cannot manage |
|
|
229
|
+
| CDR records | Cannot query |
|
|
230
|
+
| Meeting scopes | Limited to `adminOnBehalf` functions (require `hostEmail` parameter) |
|
|
231
|
+
| Compliance scopes (`spark-compliance:*`) | Require Full Admin with Compliance Officer role |
|
|
232
|
+
| CJP scopes (`cjp:config_read`, `cjp:config_write`) | **Supported.** Select them at app creation in the developer portal. Confirmed by Cisco's "Introducing Service Apps for Webex Contact Center" blog post. `spark:applications_token` and `spark:kms` are NOT available for CC service apps — those require a separate OAuth integration. |
|
|
233
|
+
| CJDS scopes (`cjds:admin_org_read`, `cjds:admin_org_write`) | **Required for JDS admin APIs** (`/admin/v1/api/...` — workspace, person, template management). Standard `cjp:` scopes alone do NOT grant JDS admin access. The runtime profile view and event stream endpoints (`/v1/api/...`) may differ — verify per endpoint. Select these in addition to `cjp:` scopes when building JDS integrations. |
|
|
234
|
+
| Scope string length | Limited to ~880 characters total — only request what you need |
|
|
235
|
+
|
|
236
|
+
### Authentication Flow
|
|
237
|
+
|
|
238
|
+
Service apps receive a **refresh token**, **client ID**, and **client secret** upon creation. They obtain access tokens by calling the same token endpoint used for integration refresh:
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
POST https://webexapis.com/v1/access_token
|
|
242
|
+
|
|
243
|
+
grant_type=refresh_token
|
|
244
|
+
client_id=SERVICE_APP_CLIENT_ID
|
|
245
|
+
client_secret=SERVICE_APP_CLIENT_SECRET
|
|
246
|
+
refresh_token=SERVICE_APP_REFRESH_TOKEN
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The response includes a new `access_token` (and potentially a renewed `refresh_token`).
|
|
250
|
+
|
|
251
|
+
### Token Lifecycle
|
|
252
|
+
|
|
253
|
+
| Token | Lifetime | Renewal |
|
|
254
|
+
|-------|----------|---------|
|
|
255
|
+
| Access token | 14 days | Refresh using refresh token |
|
|
256
|
+
| Refresh token | 90 days | 90-day expiry clock resets each time you make a refresh call (use the refresh token to generate a new access token). Simply using the access token does NOT reset it. |
|
|
257
|
+
| Client secret | Does not expire | Regenerate via developer portal if compromised |
|
|
258
|
+
|
|
259
|
+
**For long-lived automations**, Webex recommends a 3-tier pattern:
|
|
260
|
+
|
|
261
|
+
1. **Tier 1:** Use the service app's refresh token to get new access tokens (normal operation)
|
|
262
|
+
2. **Tier 2:** If the refresh token expires (90 days unused), use the Applications API with a separate OAuth integration to regenerate it
|
|
263
|
+
3. **Tier 3:** The OAuth integration's own refresh token, refreshed by your token manager
|
|
264
|
+
|
|
265
|
+
This requires two Webex apps: your working service app and a token-manager integration. See the [Service App Token Management blog post](https://developer.webex.com/blog/service-app-token-management-a-developer-s-guide-to-automation) for details.
|
|
266
|
+
|
|
267
|
+
### Environment Variables (Convention from wxc_sdk examples)
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
SERVICE_APP_REFRESH_TOKEN=<refresh_token>
|
|
271
|
+
SERVICE_APP_CLIENT_ID=<client_id>
|
|
272
|
+
SERVICE_APP_CLIENT_SECRET=<client_secret>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### wxc_sdk Service App Pattern
|
|
276
|
+
|
|
277
|
+
From `examples/service_app.py` in the SDK:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
from wxc_sdk import WebexSimpleApi
|
|
281
|
+
from wxc_sdk.integration import Integration
|
|
282
|
+
from wxc_sdk.tokens import Tokens
|
|
283
|
+
|
|
284
|
+
# Build an Integration object (used only for the refresh call)
|
|
285
|
+
integration = Integration(
|
|
286
|
+
client_id=client_id,
|
|
287
|
+
client_secret=client_secret,
|
|
288
|
+
scopes=[], # scopes not needed for refresh
|
|
289
|
+
redirect_url=None # no redirect needed for service apps
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Create a Tokens object with just the refresh token
|
|
293
|
+
tokens = Tokens(refresh_token=refresh_token)
|
|
294
|
+
|
|
295
|
+
# Refresh to get a valid access token
|
|
296
|
+
integration.refresh(tokens=tokens)
|
|
297
|
+
|
|
298
|
+
# Use the tokens
|
|
299
|
+
with WebexSimpleApi(tokens=tokens) as api:
|
|
300
|
+
users = list(api.people.list())
|
|
301
|
+
queues = list(api.telephony.callqueue.list())
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Token Caching
|
|
305
|
+
|
|
306
|
+
The SDK example caches tokens to a YML file keyed by `client_id` to avoid unnecessary refresh calls. It checks `tokens.remaining` and refreshes when remaining lifetime drops below 24 hours:
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
if tokens.expires_in is not None and tokens.remaining < 24 * 60 * 60:
|
|
310
|
+
tokens = get_access_token(client_id=client_id, client_secret=client_secret, refresh=refresh)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Region Extraction from Token
|
|
314
|
+
|
|
315
|
+
Webex access tokens encode the CI (Common Identity) cluster and org ID directly in the token string. A production app can extract both without a separate API call:
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
parts = access_token.split('_')
|
|
319
|
+
# parts[0] = the access token value
|
|
320
|
+
# parts[1] = ciCluster (e.g. "us1", "eu1", "us2")
|
|
321
|
+
# parts[2] = orgId (base64-encoded Spark ID)
|
|
322
|
+
ci_cluster = parts[1]
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
This is especially important for **Contact Center APIs**, which use a regional base URL rather than `webexapis.com`:
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
https://api.wxcc-{ciCluster}.cisco.com
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
For example, a token from a US org with `ciCluster = "us1"` hits `https://api.wxcc-us1.cisco.com`. The `get_cc_org_id()` helper in `wxcli/config.py` performs both the cluster extraction and the org ID decoding.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Partner/Multi-Org Tokens
|
|
336
|
+
|
|
337
|
+
Partner/VAR/MSP admins hold tokens that have access to multiple customer organizations. Most Webex API endpoints accept an `orgId` query parameter to target a specific customer org; without it, the API defaults to the partner's own org (usually not what you want).
|
|
338
|
+
|
|
339
|
+
### How wxcli handles partner tokens
|
|
340
|
+
|
|
341
|
+
wxcli detects multi-org tokens automatically and manages `orgId` injection transparently:
|
|
342
|
+
|
|
343
|
+
| Command | Purpose |
|
|
344
|
+
|---------|---------|
|
|
345
|
+
| `wxcli configure` | Detects whether the token has multi-org access. If so, lists available customer orgs and prompts you to select one. The chosen `orgId` is saved to the config file. |
|
|
346
|
+
| `wxcli switch-org` | Change the active target org at any time without re-running `configure`. |
|
|
347
|
+
| `wxcli clear-org` | Remove the saved `orgId` to revert to single-org (partner-org) behavior. |
|
|
348
|
+
| `wxcli whoami` | Shows a "Target: <org name>" line when a target org is set. |
|
|
349
|
+
|
|
350
|
+
Once a target org is configured, 668 of 804 generated commands automatically inject `orgId` from config on every API call that accepts the parameter — no `--org-id` flag required. The four hand-coded command files (users, licenses, locations, numbers) also inject `orgId` the same way.
|
|
351
|
+
|
|
352
|
+
### Builder agent behavior
|
|
353
|
+
|
|
354
|
+
When the builder agent detects a partner token (section 2b of its workflow), it pauses and requires explicit org confirmation before proceeding. This prevents accidentally configuring the wrong customer org.
|
|
355
|
+
|
|
356
|
+
### Scopes for partner tokens
|
|
357
|
+
|
|
358
|
+
Partner tokens use the same `spark-admin:` scopes as regular admin tokens. No additional scopes are required to access customer org data — the token type (partner) is what grants cross-org access, not a special scope.
|
|
359
|
+
|
|
360
|
+
### Gotchas
|
|
361
|
+
|
|
362
|
+
- **`organizations list` returns multiple orgs for partner tokens.** wxcli uses this call to detect partner tokens: if the response contains more than one org, it treats the token as multi-org and prompts for selection. Single-org admins always see exactly one result.
|
|
363
|
+
- **Some endpoints do not accept `orgId`.** The 136 of 804 endpoints that do not accept `orgId` operate in the context of the token's own org. These are typically endpoints that are inherently org-scoped (e.g., `/v1/organizations/{orgId}/...` where the org is a path param, not a query param).
|
|
364
|
+
- **Service app tokens scoped to a single customer org** behave like single-org tokens and do not trigger multi-org detection.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Bot Tokens
|
|
369
|
+
|
|
370
|
+
Bots are special Webex identities with their own access token.
|
|
371
|
+
|
|
372
|
+
**Key facts:**
|
|
373
|
+
- Bot tokens **do not expire** — they remain valid until the bot is deleted or regenerated
|
|
374
|
+
- No refresh token is provided (none needed)
|
|
375
|
+
- Bots have their own identity (separate from any user)
|
|
376
|
+
- Bots can interact with messaging, spaces, and webhooks
|
|
377
|
+
- Bot tokens have **limited scope for Calling APIs** — bots cannot place or manage calls on behalf of users
|
|
378
|
+
|
|
379
|
+
**wxc_sdk usage:**
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
from wxc_sdk import WebexSimpleApi
|
|
383
|
+
|
|
384
|
+
api = WebexSimpleApi(tokens='BOT_ACCESS_TOKEN')
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Since bot tokens never expire, no refresh logic is needed.
|
|
388
|
+
|
|
389
|
+
### Gotchas
|
|
390
|
+
|
|
391
|
+
- **Bot calling scopes unverified.** The exact list of calling-related scopes available to bots (if any) has not been confirmed. The developer.webex.com docs list scopes with a `show_for_app_type` property for "integration" and "serviceApp" but do not enumerate bot-specific scopes. Calling scopes like `spark:calls_read` and `spark:calls_write` appear to be user-level scopes for integrations. Bots likely cannot use calling scopes since they don't act on behalf of a user, but this has not been confirmed with a live bot token. *(Unverified — requires live bot token testing. Checked 2026-03-19.)*
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Guest Issuer Tokens
|
|
396
|
+
|
|
397
|
+
> **⚠️ End of Life — December 31, 2025**
|
|
398
|
+
> The Guest Issuer API has been deprecated and reached End of Life on December 31, 2025. New Guest Issuer applications can no longer be created. Existing applications should migrate to **Service Apps** with guest management functionality. The section below is retained for reference only.
|
|
399
|
+
|
|
400
|
+
Guest Issuer tokens create temporary, anonymous guest users for scenarios like customer-facing meetings or support sessions.
|
|
401
|
+
|
|
402
|
+
**Key facts:**
|
|
403
|
+
- Managed via Service Apps with `guest-issuer:read` and `guest-issuer:write` scopes
|
|
404
|
+
- Guest tokens are short-lived
|
|
405
|
+
- **Not applicable to Webex Calling APIs** — guests cannot access telephony features
|
|
406
|
+
|
|
407
|
+
### Gotchas
|
|
408
|
+
|
|
409
|
+
- **Guest token lifetime is variable, set by `expiresIn` in the response.** The OpenAPI spec example shows `expiresIn: 64799` (~6 hours), but the actual lifetime is returned per-token at creation time via the `expiresIn` field. The SDK `Guest` model exposes this as `expires_in` and computes `expires_at` from it. There is no single fixed lifetime — it depends on org/service-app configuration.
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## Calling-Related Scopes
|
|
414
|
+
|
|
415
|
+
### User-Level Scopes
|
|
416
|
+
|
|
417
|
+
These scopes operate in the context of the authenticated user. Any Webex Calling-licensed user can authorize these.
|
|
418
|
+
|
|
419
|
+
| Scope | Description |
|
|
420
|
+
|-------|-------------|
|
|
421
|
+
| `spark:calls_read` | List all active calls the user is part of; list call history from Webex Calling |
|
|
422
|
+
| `spark:calls_write` | Invoke call commands on the authenticated user (answer, hold, transfer, etc.) |
|
|
423
|
+
| `spark:xsi` | Access Webex Calling resources via XSI (calls and call settings) |
|
|
424
|
+
| `spark:webrtc_calling` | Access WebRTC services for Webex Calling |
|
|
425
|
+
| `spark:people_read` | Read people/user information (commonly needed alongside calling scopes) |
|
|
426
|
+
| `spark:kms` | Key Management Service — required for end-to-end encryption operations |
|
|
427
|
+
|
|
428
|
+
### Admin-Level Scopes
|
|
429
|
+
|
|
430
|
+
These scopes require the authenticated user to be a **full org administrator**. They provide organization-wide access.
|
|
431
|
+
|
|
432
|
+
| Scope | Description |
|
|
433
|
+
|-------|-------------|
|
|
434
|
+
| `spark-admin:telephony_config_read` | Read and list telephony configuration (locations, numbers, call routing, features) |
|
|
435
|
+
| `spark-admin:telephony_config_write` | Create, edit, and delete telephony configuration |
|
|
436
|
+
| `spark-admin:calls_read` | List all calls across the organization |
|
|
437
|
+
| `spark-admin:calls_write` | Invoke call commands on any user in the organization |
|
|
438
|
+
| `spark-admin:calling_cdr_read` | Access comprehensive Call Detail Records, including PII-protected phone numbers |
|
|
439
|
+
| `spark-admin:people_read` | Read people across the organization |
|
|
440
|
+
| `spark-admin:people_write` | Create, update, delete people in the organization |
|
|
441
|
+
|
|
442
|
+
### Scope Categories by API Function
|
|
443
|
+
|
|
444
|
+
**Administrator / Provisioning APIs** (require `spark-admin:` scopes):
|
|
445
|
+
- Telephony configuration (locations, numbers, call routing, auto attendants, call queues, hunt groups)
|
|
446
|
+
- CDR / reporting
|
|
447
|
+
- User/workspace provisioning
|
|
448
|
+
|
|
449
|
+
**End-User / Call Control APIs** (require `spark:` scopes):
|
|
450
|
+
- Call commands (dial, answer, hold, resume, transfer, park)
|
|
451
|
+
- Call history
|
|
452
|
+
- Voicemail
|
|
453
|
+
- Call settings (forwarding, DND, etc.)
|
|
454
|
+
- XSI-based operations
|
|
455
|
+
|
|
456
|
+
### The `spark:all` Scope
|
|
457
|
+
|
|
458
|
+
The `spark:all` scope grants full access to a Webex account and allows applications to behave as native Webex clients, including calling features when using Webex SDKs. Use this scope sparingly — prefer requesting only the scopes your application needs.
|
|
459
|
+
|
|
460
|
+
### Scope Parsing in wxc_sdk
|
|
461
|
+
|
|
462
|
+
The SDK includes a `parse_scopes` utility that handles multiple input formats — full authorization URLs, query strings, URL-encoded strings, or plain space-separated scope lists:
|
|
463
|
+
|
|
464
|
+
```python
|
|
465
|
+
from wxc_sdk.scopes import parse_scopes
|
|
466
|
+
|
|
467
|
+
# All of these work:
|
|
468
|
+
scopes = parse_scopes('spark:calls_read spark:calls_write spark:people_read')
|
|
469
|
+
scopes = parse_scopes('spark%3Acalls_read%20spark%3Acalls_write')
|
|
470
|
+
scopes = parse_scopes('https://webexapis.com/v1/authorize?...&scope=spark%3Acalls_read%20...')
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## wxc_sdk Auth Setup
|
|
476
|
+
|
|
477
|
+
> **Note:** The async variant `AsWebexSimpleApi` (from `wxc_sdk.as_api`) accepts identical token arguments and initialization patterns. See `archive/wxc-sdk-patterns.md` section 4 for async usage details.
|
|
478
|
+
|
|
479
|
+
### Initialization Patterns
|
|
480
|
+
|
|
481
|
+
The `WebexSimpleApi` class accepts tokens in three forms:
|
|
482
|
+
|
|
483
|
+
```python
|
|
484
|
+
from wxc_sdk import WebexSimpleApi
|
|
485
|
+
from wxc_sdk.tokens import Tokens
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
**Pattern 1 — String token (simplest, for dev/testing):**
|
|
489
|
+
|
|
490
|
+
```python
|
|
491
|
+
api = WebexSimpleApi(tokens='YOUR_ACCESS_TOKEN')
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Internally, this wraps the string in a `Tokens(access_token='...')` object.
|
|
495
|
+
|
|
496
|
+
**Pattern 2 — Environment variable (no arguments):**
|
|
497
|
+
|
|
498
|
+
```python
|
|
499
|
+
import os
|
|
500
|
+
os.environ['WEBEX_ACCESS_TOKEN'] = 'YOUR_ACCESS_TOKEN'
|
|
501
|
+
|
|
502
|
+
api = WebexSimpleApi() # reads from WEBEX_ACCESS_TOKEN
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
If no `tokens` argument is provided and `WEBEX_ACCESS_TOKEN` is not set, a `ValueError` is raised:
|
|
506
|
+
```
|
|
507
|
+
ValueError: if no access token is passed, then a valid access token has to be present in
|
|
508
|
+
WEBEX_ACCESS_TOKEN environment variable
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**Pattern 3 — Tokens object (for OAuth/service app flows):**
|
|
512
|
+
|
|
513
|
+
```python
|
|
514
|
+
tokens = Tokens(
|
|
515
|
+
access_token='...',
|
|
516
|
+
refresh_token='...',
|
|
517
|
+
expires_in=1209600,
|
|
518
|
+
refresh_token_expires_in=7776000,
|
|
519
|
+
token_type='Bearer',
|
|
520
|
+
scope='spark:calls_read spark:calls_write'
|
|
521
|
+
)
|
|
522
|
+
tokens.set_expiration() # calculate expires_at from expires_in
|
|
523
|
+
|
|
524
|
+
api = WebexSimpleApi(tokens=tokens)
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
**Pattern 4 — Pre-built RestSession:**
|
|
528
|
+
|
|
529
|
+
```python
|
|
530
|
+
from wxc_sdk.rest import RestSession
|
|
531
|
+
|
|
532
|
+
session = RestSession(tokens=tokens, concurrent_requests=10, retry_429=True)
|
|
533
|
+
api = WebexSimpleApi(session=session)
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Constructor Parameters
|
|
537
|
+
|
|
538
|
+
```python
|
|
539
|
+
WebexSimpleApi(
|
|
540
|
+
tokens: Union[str, Tokens] = None, # access token or Tokens object
|
|
541
|
+
concurrent_requests: int = 10, # max parallel requests (semaphore)
|
|
542
|
+
retry_429: bool = True, # auto-retry on rate limiting
|
|
543
|
+
session: RestSession = None, # pre-built session (overrides above)
|
|
544
|
+
)
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Context Manager Support
|
|
548
|
+
|
|
549
|
+
`WebexSimpleApi` supports the context manager protocol, which closes the underlying session on exit:
|
|
550
|
+
|
|
551
|
+
```python
|
|
552
|
+
with WebexSimpleApi(tokens=tokens) as api:
|
|
553
|
+
me = api.people.me()
|
|
554
|
+
# session is automatically closed at the end of the block
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Full OAuth Integration Example
|
|
558
|
+
|
|
559
|
+
From `examples/get_tokens.py` — obtains tokens via OAuth flow, caches to YML, and initializes the API:
|
|
560
|
+
|
|
561
|
+
```python
|
|
562
|
+
from dotenv import load_dotenv
|
|
563
|
+
from wxc_sdk import WebexSimpleApi
|
|
564
|
+
from wxc_sdk.integration import Integration
|
|
565
|
+
from wxc_sdk.scopes import parse_scopes
|
|
566
|
+
from wxc_sdk.tokens import Tokens
|
|
567
|
+
|
|
568
|
+
# Load environment variables
|
|
569
|
+
load_dotenv('get_tokens.env')
|
|
570
|
+
|
|
571
|
+
# Build integration from env vars
|
|
572
|
+
integration = Integration(
|
|
573
|
+
client_id=os.getenv('TOKEN_INTEGRATION_CLIENT_ID'),
|
|
574
|
+
client_secret=os.getenv('TOKEN_INTEGRATION_CLIENT_SECRET'),
|
|
575
|
+
scopes=parse_scopes(os.getenv('TOKEN_INTEGRATION_CLIENT_SCOPES')),
|
|
576
|
+
redirect_url='http://localhost:6001/redirect'
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
# Get tokens (reads from cache or initiates OAuth flow)
|
|
580
|
+
tokens = integration.get_cached_tokens_from_yml(yml_path='get_tokens.yml')
|
|
581
|
+
|
|
582
|
+
# Use the API
|
|
583
|
+
api = WebexSimpleApi(tokens=tokens)
|
|
584
|
+
me = api.people.me()
|
|
585
|
+
print(f'Authenticated as {me.display_name} ({me.emails[0]})')
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
**Required `.env` file:**
|
|
589
|
+
```bash
|
|
590
|
+
TOKEN_INTEGRATION_CLIENT_ID=Ce429631...
|
|
591
|
+
TOKEN_INTEGRATION_CLIENT_SECRET=a1b2c3d4...
|
|
592
|
+
TOKEN_INTEGRATION_CLIENT_SCOPES=spark:calls_read spark:calls_write spark:people_read spark-admin:telephony_config_read
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## Raw HTTP via api.session
|
|
598
|
+
|
|
599
|
+
<!-- Added by playbook session 2026-03-18 -->
|
|
600
|
+
|
|
601
|
+
The wxc_sdk `WebexSimpleApi` object is not just an SDK client — it also provides a pre-authenticated HTTP session you can use to call **any** Webex API endpoint directly, without going through typed SDK methods. This is the pattern used by the wxcli auto-generated commands.
|
|
602
|
+
|
|
603
|
+
### Why Use Raw HTTP
|
|
604
|
+
|
|
605
|
+
- **Coverage gaps:** The SDK may not yet wrap every Webex Calling endpoint. Raw HTTP lets you call any documented (or undocumented) API.
|
|
606
|
+
- **Exact control:** You send the exact JSON body and query params the API expects, with no SDK data model translation.
|
|
607
|
+
- **Same auth:** The session inherits all authentication, token refresh, rate-limit retry, and concurrency control from the `WebexSimpleApi` you already set up.
|
|
608
|
+
|
|
609
|
+
### How It Works
|
|
610
|
+
|
|
611
|
+
Initialize `WebexSimpleApi` using any auth method from the sections above. Then use `api.session.rest_*()` methods for direct HTTP calls:
|
|
612
|
+
|
|
613
|
+
```python
|
|
614
|
+
from wxc_sdk import WebexSimpleApi
|
|
615
|
+
|
|
616
|
+
# Auth via environment variable (same as SDK usage)
|
|
617
|
+
# export WEBEX_ACCESS_TOKEN=YOUR_TOKEN
|
|
618
|
+
api = WebexSimpleApi()
|
|
619
|
+
|
|
620
|
+
BASE = "https://webexapis.com/v1"
|
|
621
|
+
|
|
622
|
+
# GET — list people
|
|
623
|
+
result = api.session.rest_get(f"{BASE}/people", params={"max": 100})
|
|
624
|
+
# result is a parsed JSON dict, e.g. {"items": [...]}
|
|
625
|
+
|
|
626
|
+
# POST — create a resource
|
|
627
|
+
body = {"displayName": "Test User", "emails": ["test@example.com"]}
|
|
628
|
+
result = api.session.rest_post(f"{BASE}/people", json=body)
|
|
629
|
+
|
|
630
|
+
# PUT — update a resource
|
|
631
|
+
api.session.rest_put(f"{BASE}/people/{person_id}", json=updated_body)
|
|
632
|
+
|
|
633
|
+
# DELETE — remove a resource
|
|
634
|
+
api.session.rest_delete(f"{BASE}/people/{person_id}")
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Available Session Methods
|
|
638
|
+
|
|
639
|
+
| Method | HTTP Verb | Returns | Notes |
|
|
640
|
+
|--------|-----------|---------|-------|
|
|
641
|
+
| `api.session.rest_get(url, params=...)` | GET | Parsed JSON dict | Use `params` for query string |
|
|
642
|
+
| `api.session.rest_post(url, json=...)` | POST | Parsed JSON dict | Use `json` for request body |
|
|
643
|
+
| `api.session.rest_put(url, json=...)` | PUT | Parsed JSON dict or `None` | Use `json` for request body |
|
|
644
|
+
| `api.session.rest_delete(url)` | DELETE | `None` | No response body |
|
|
645
|
+
|
|
646
|
+
### Key Constraints
|
|
647
|
+
|
|
648
|
+
- **Full URLs required:** You must provide the complete URL including `https://webexapis.com/v1/...`. The session does not prepend a base URL.
|
|
649
|
+
- **No auto-pagination:** Unlike typed SDK methods (e.g., `api.people.list()`), raw HTTP calls return a single page. To paginate, pass `max=1000` and handle `next` links yourself.
|
|
650
|
+
- **Responses are plain dicts:** Results are parsed JSON dictionaries, not SDK model objects. Access fields with bracket notation (`result["items"]`), not dot notation.
|
|
651
|
+
- **Errors raise `RestError`:** All HTTP errors (401, 403, 404, 429, etc.) raise `wxc_sdk.rest.RestError`, just like typed SDK calls.
|
|
652
|
+
|
|
653
|
+
### Auth Inheritance
|
|
654
|
+
|
|
655
|
+
The session inherits every auth behavior from `WebexSimpleApi`:
|
|
656
|
+
|
|
657
|
+
| Feature | Behavior with raw HTTP |
|
|
658
|
+
|---------|----------------------|
|
|
659
|
+
| `WEBEX_ACCESS_TOKEN` env var | Works — session reads the token automatically |
|
|
660
|
+
| `Tokens` object with refresh | Works — session refreshes transparently before expired requests |
|
|
661
|
+
| Service app tokens | Works — same `Integration.refresh()` flow, then pass `Tokens` to `WebexSimpleApi` |
|
|
662
|
+
| `retry_429=True` | Works — session retries rate-limited requests automatically |
|
|
663
|
+
| `concurrent_requests=10` | Works — session enforces the semaphore on raw HTTP calls too |
|
|
664
|
+
| Debug logging | Works — `Authorization` headers are masked as `Bearer ***` |
|
|
665
|
+
|
|
666
|
+
### Complete Example: Service App + Raw HTTP
|
|
667
|
+
|
|
668
|
+
```python
|
|
669
|
+
import os
|
|
670
|
+
from wxc_sdk import WebexSimpleApi
|
|
671
|
+
from wxc_sdk.integration import Integration
|
|
672
|
+
from wxc_sdk.tokens import Tokens
|
|
673
|
+
|
|
674
|
+
# Set up service app auth (identical to SDK pattern)
|
|
675
|
+
tokens = Tokens(refresh_token=os.getenv('SERVICE_APP_REFRESH_TOKEN'))
|
|
676
|
+
integration = Integration(
|
|
677
|
+
client_id=os.getenv('SERVICE_APP_CLIENT_ID'),
|
|
678
|
+
client_secret=os.getenv('SERVICE_APP_CLIENT_SECRET'),
|
|
679
|
+
scopes=[],
|
|
680
|
+
redirect_url=None
|
|
681
|
+
)
|
|
682
|
+
integration.refresh(tokens=tokens)
|
|
683
|
+
|
|
684
|
+
# Use the authenticated session for raw HTTP calls
|
|
685
|
+
BASE = "https://webexapis.com/v1"
|
|
686
|
+
|
|
687
|
+
with WebexSimpleApi(tokens=tokens) as api:
|
|
688
|
+
# List all locations
|
|
689
|
+
locations = api.session.rest_get(
|
|
690
|
+
f"{BASE}/locations", params={"max": 1000}
|
|
691
|
+
)
|
|
692
|
+
for loc in locations.get("items", []):
|
|
693
|
+
print(f"{loc['name']} ({loc['id']})")
|
|
694
|
+
|
|
695
|
+
# Read telephony config for a location
|
|
696
|
+
loc_id = locations["items"][0]["id"]
|
|
697
|
+
tele = api.session.rest_get(
|
|
698
|
+
f"{BASE}/telephony/config/locations/{loc_id}"
|
|
699
|
+
)
|
|
700
|
+
print(f"Calling line ID: {tele.get('callingLineId')}")
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### When to Use SDK Methods vs Raw HTTP
|
|
704
|
+
|
|
705
|
+
| Situation | Use |
|
|
706
|
+
|-----------|-----|
|
|
707
|
+
| Endpoint is wrapped by wxc_sdk (e.g., `api.people.list()`) | SDK method — typed, paginated, validated |
|
|
708
|
+
| Endpoint is not in wxc_sdk yet | Raw HTTP via `api.session.rest_*()` |
|
|
709
|
+
| You need exact control over request body/params | Raw HTTP |
|
|
710
|
+
| You need auto-pagination over large result sets | SDK method (handles `next` links automatically) |
|
|
711
|
+
| Building CLI commands from Postman collections | Raw HTTP (the wxcli auto-gen pattern) |
|
|
712
|
+
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
## Token Refresh Flow
|
|
716
|
+
|
|
717
|
+
### How the SDK Handles Refresh
|
|
718
|
+
|
|
719
|
+
The `Integration` class provides the refresh logic. Here is the flow:
|
|
720
|
+
|
|
721
|
+
1. **Check remaining lifetime** via `tokens.remaining` (returns seconds until expiry)
|
|
722
|
+
2. **If below threshold**, call `integration.refresh(tokens=tokens)` which POSTs to `https://webexapis.com/v1/access_token`
|
|
723
|
+
3. **Tokens are updated in place** — the `access_token`, `expires_in`, `expires_at`, `refresh_token`, and `refresh_token_expires_at` fields are all refreshed
|
|
724
|
+
|
|
725
|
+
```python
|
|
726
|
+
# Manual refresh check
|
|
727
|
+
if tokens.remaining < 300: # less than 5 minutes
|
|
728
|
+
integration.refresh(tokens=tokens)
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
### Automatic Token Validation
|
|
732
|
+
|
|
733
|
+
The `Integration.validate_tokens()` method encapsulates the check-and-refresh pattern:
|
|
734
|
+
|
|
735
|
+
```python
|
|
736
|
+
changed = integration.validate_tokens(tokens=tokens, min_lifetime_seconds=300)
|
|
737
|
+
# changed=True means a refresh was attempted
|
|
738
|
+
# If refresh fails, tokens.access_token is set to None
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### Cached Token Flow (get_cached_tokens)
|
|
742
|
+
|
|
743
|
+
The `Integration.get_cached_tokens()` method implements the full lifecycle:
|
|
744
|
+
|
|
745
|
+
1. Read tokens from cache (callback-based — YML, database, etc.)
|
|
746
|
+
2. Validate tokens (refresh if needed)
|
|
747
|
+
3. If no valid token exists, initiate a full OAuth flow
|
|
748
|
+
4. Write updated tokens back to cache
|
|
749
|
+
|
|
750
|
+
```python
|
|
751
|
+
tokens = integration.get_cached_tokens_from_yml(
|
|
752
|
+
yml_path='tokens.yml',
|
|
753
|
+
force_new=False # set True to skip cache and force new OAuth flow
|
|
754
|
+
)
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
### The Tokens Model
|
|
758
|
+
|
|
759
|
+
```python
|
|
760
|
+
class Tokens(BaseModel):
|
|
761
|
+
access_token: Optional[str] # the bearer token
|
|
762
|
+
expires_in: Optional[int] # lifetime in seconds at creation time
|
|
763
|
+
expires_at: Optional[datetime] # computed absolute expiry (UTC)
|
|
764
|
+
refresh_token: Optional[str] # refresh token
|
|
765
|
+
refresh_token_expires_in: Optional[int] # refresh token lifetime at creation
|
|
766
|
+
refresh_token_expires_at: Optional[datetime] # computed absolute expiry (UTC)
|
|
767
|
+
token_type: Optional[Literal['Bearer']] # always 'Bearer'
|
|
768
|
+
scope: Optional[str] # space-separated scope list
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
Key methods:
|
|
772
|
+
- `set_expiration()` — computes `expires_at` and `refresh_token_expires_at` from current time + `expires_in`
|
|
773
|
+
- `remaining` — property returning seconds until access token expiry
|
|
774
|
+
- `update(new_tokens)` — copies all fields from another `Tokens` instance (used during refresh)
|
|
775
|
+
|
|
776
|
+
### Service App Refresh (No OAuth Flow)
|
|
777
|
+
|
|
778
|
+
Service apps skip the authorization code flow entirely. They use only the refresh step:
|
|
779
|
+
|
|
780
|
+
```python
|
|
781
|
+
# Service app environment variables
|
|
782
|
+
# SERVICE_APP_REFRESH_TOKEN, SERVICE_APP_CLIENT_ID, SERVICE_APP_CLIENT_SECRET
|
|
783
|
+
|
|
784
|
+
tokens = Tokens(refresh_token=os.getenv('SERVICE_APP_REFRESH_TOKEN'))
|
|
785
|
+
integration = Integration(
|
|
786
|
+
client_id=os.getenv('SERVICE_APP_CLIENT_ID'),
|
|
787
|
+
client_secret=os.getenv('SERVICE_APP_CLIENT_SECRET'),
|
|
788
|
+
scopes=[],
|
|
789
|
+
redirect_url=None
|
|
790
|
+
)
|
|
791
|
+
integration.refresh(tokens=tokens)
|
|
792
|
+
|
|
793
|
+
# tokens now has a valid access_token
|
|
794
|
+
api = WebexSimpleApi(tokens=tokens)
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
---
|
|
798
|
+
|
|
799
|
+
## Common Auth Errors
|
|
800
|
+
|
|
801
|
+
### HTTP 401 Unauthorized
|
|
802
|
+
|
|
803
|
+
**Causes:**
|
|
804
|
+
- Access token has expired (personal access token after 12 hours, integration token after 14 days)
|
|
805
|
+
- Token is malformed or has been revoked
|
|
806
|
+
- Missing `Authorization` header entirely
|
|
807
|
+
- Wrong token type (e.g., using a refresh token as an access token)
|
|
808
|
+
|
|
809
|
+
**SDK behavior:** Raises `RestError` with `response.status_code == 401`.
|
|
810
|
+
|
|
811
|
+
**Fix:** Refresh the token (for integrations/service apps) or generate a new personal access token.
|
|
812
|
+
|
|
813
|
+
### HTTP 403 Forbidden
|
|
814
|
+
|
|
815
|
+
**Causes:**
|
|
816
|
+
- Token is valid but lacks the required scope for the endpoint
|
|
817
|
+
- Non-admin user trying to access `spark-admin:` endpoints
|
|
818
|
+
- Bot token trying to access calling endpoints it does not have permission for
|
|
819
|
+
- Service app not authorized by the org admin
|
|
820
|
+
|
|
821
|
+
**Common scope mismatches:**
|
|
822
|
+
|
|
823
|
+
| Attempted Action | Missing Scope |
|
|
824
|
+
|-----------------|---------------|
|
|
825
|
+
| Read telephony config | `spark-admin:telephony_config_read` |
|
|
826
|
+
| Modify call queue | `spark-admin:telephony_config_write` |
|
|
827
|
+
| Read call history | `spark:calls_read` |
|
|
828
|
+
| Control a call | `spark:calls_write` |
|
|
829
|
+
| Read CDR records | `spark-admin:calling_cdr_read` |
|
|
830
|
+
|
|
831
|
+
**Fix:** Verify the scopes on your integration/service app include what the endpoint requires. For admin scopes, confirm the authorizing user is a full org admin.
|
|
832
|
+
|
|
833
|
+
### HTTP 429 Too Many Requests
|
|
834
|
+
|
|
835
|
+
**Cause:** Rate limiting. Webex APIs enforce per-token request limits.
|
|
836
|
+
|
|
837
|
+
**SDK behavior:** When `retry_429=True` (the default), the SDK automatically retries after the duration specified in the `Retry-After` response header, up to a maximum wait of 60 seconds (`RETRY_429_MAX_WAIT`).
|
|
838
|
+
|
|
839
|
+
**Response header:**
|
|
840
|
+
```
|
|
841
|
+
Retry-After: 5
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
### Token Expiry Symptoms
|
|
845
|
+
|
|
846
|
+
| Symptom | Likely Cause |
|
|
847
|
+
|---------|-------------|
|
|
848
|
+
| 401 after exactly 12 hours | Personal access token expired |
|
|
849
|
+
| 401 after ~14 days | Integration access token expired, refresh needed |
|
|
850
|
+
| 401 immediately after refresh attempt | Refresh token also expired (>90 days) — full re-auth required |
|
|
851
|
+
| `tokens.remaining` returns 0 | Access token is not set or has expired |
|
|
852
|
+
| `ValueError` on `WebexSimpleApi()` | No token provided and `WEBEX_ACCESS_TOKEN` env var not set |
|
|
853
|
+
|
|
854
|
+
### Error Response Format
|
|
855
|
+
|
|
856
|
+
Webex API errors return JSON with a tracking ID useful for support:
|
|
857
|
+
|
|
858
|
+
```json
|
|
859
|
+
{
|
|
860
|
+
"message": "The request requires a valid access token set in the Authorization request header.",
|
|
861
|
+
"errors": [
|
|
862
|
+
{
|
|
863
|
+
"description": "The request requires a valid access token set in the Authorization request header."
|
|
864
|
+
}
|
|
865
|
+
],
|
|
866
|
+
"trackingId": "ROUTER_6542a1b2-..."
|
|
867
|
+
}
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
The SDK parses this into a `RestError` with `.detail` containing an `ErrorDetail` object:
|
|
871
|
+
- `error.detail.message` — the error message
|
|
872
|
+
- `error.detail.tracking_id` — the tracking ID for Webex TAC support
|
|
873
|
+
- `error.detail.description` — specific error description (from nested `errors` array)
|
|
874
|
+
- `error.detail.code` — numeric error code (when present)
|
|
875
|
+
|
|
876
|
+
### Debugging Auth Issues
|
|
877
|
+
|
|
878
|
+
Enable SDK debug logging to see full request/response details (tokens are masked automatically):
|
|
879
|
+
|
|
880
|
+
```python
|
|
881
|
+
import logging
|
|
882
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
The SDK masks `Authorization` headers as `Bearer ***` and redacts `access_token`, `refresh_token`, and `client_secret` values in logged output.
|
|
886
|
+
|
|
887
|
+
---
|
|
888
|
+
|
|
889
|
+
## Quick Reference: Which Auth Method to Use
|
|
890
|
+
|
|
891
|
+
| Scenario | Method | Notes |
|
|
892
|
+
|----------|--------|-------|
|
|
893
|
+
| Quick API test in terminal | Personal Access Token | Fastest to start, expires in 12h |
|
|
894
|
+
| Production app acting as a user | OAuth Integration | Standard OAuth 2.0 code flow |
|
|
895
|
+
| Nightly automation / cron job | Service App | No user interaction needed |
|
|
896
|
+
| Chatbot responding to messages | Bot Token | Does not expire, but limited calling access |
|
|
897
|
+
| One-off script during development | Personal Access Token or `WEBEX_ACCESS_TOKEN` env var | Use env var to avoid token in source code |
|
|
898
|
+
| CI/CD pipeline | Service App | Store credentials in secrets manager |
|
|
899
|
+
| Production CC app | Service App with CJP scopes | Select `cjp:config_read`/`cjp:config_write` at creation; regional base URL (`api.wxcc-{ciCluster}.cisco.com`) |
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
## Gotchas (Cross-Cutting)
|
|
904
|
+
|
|
905
|
+
- **call-controls requires user-level OAuth.** Admin tokens and service-app tokens get HTTP 400 "Target user not authorized" on `/telephony/calls` endpoints. Use a calling-licensed user's OAuth token for call control operations.
|
|
906
|
+
- **`spark-admin:` scopes require full org admin.** If the authorizing user is a read-only admin or compliance officer, requests to admin endpoints will return 403 even with the correct scopes listed on the integration.
|
|
907
|
+
- **Personal access tokens carry all scopes silently.** A personal access token for an org admin includes all `spark-admin:` scopes without requesting them, which can mask scope-related bugs that appear only in production integrations.
|
|
908
|
+
- **Service app refresh tokens can expire.** Although the initial refresh token is long-lived, if it is not used **to generate a new access token** within 90 days, it expires and the service app must be re-authorized by an org admin. Simply using the access token does not reset the 90-day clock — only making a refresh call does.
|
|
909
|
+
|
|
910
|
+
---
|
|
911
|
+
|
|
912
|
+
## Webex for Government (FedRAMP)
|
|
913
|
+
|
|
914
|
+
Webex for Government is a parallel deployment with separate URLs and feature restrictions.
|
|
915
|
+
|
|
916
|
+
### Base URLs
|
|
917
|
+
|
|
918
|
+
| Service | Standard | FedRAMP |
|
|
919
|
+
|---------|----------|---------|
|
|
920
|
+
| API | `webexapis.com/v1` | `api-usgov.webex.com/v1` |
|
|
921
|
+
| Control Hub | `admin.webex.com` | `admin-usgov.webex.com` |
|
|
922
|
+
| Developer Portal | `developer.webex.com` | `developer-usgov.webex.com` |
|
|
923
|
+
| CDR/Analytics | `analytics.webexapis.com` | `analytics-calling-gov.webexapis.com` |
|
|
924
|
+
|
|
925
|
+
### Feature Restrictions
|
|
926
|
+
|
|
927
|
+
These features/APIs are **not supported** in FedRAMP deployments:
|
|
928
|
+
|
|
929
|
+
| Feature | Reference Doc | Notes |
|
|
930
|
+
|---------|---------------|-------|
|
|
931
|
+
| DECT Devices | [devices-dect.md](devices-dect.md) | Entire DECT API excluded |
|
|
932
|
+
| Announcements & Playlists | [location-calling-media.md](location-calling-media.md) | Upload and playlist APIs excluded |
|
|
933
|
+
| Call Recording (location-level) | [location-recording-advanced.md](location-recording-advanced.md) | Recording vendor config excluded |
|
|
934
|
+
| Caller Reputation | [location-recording-advanced.md](location-recording-advanced.md) | Provider config excluded |
|
|
935
|
+
| Operating Modes | [call-features-additional.md](call-features-additional.md) | Mode management excluded |
|
|
936
|
+
| Hot Desking | [devices-dect.md](devices-dect.md) | Hot desk portal excluded |
|
|
937
|
+
| AA `directLineCallerIdName` | [call-features-major.md](call-features-major.md) | Use `firstName`/`lastName` instead |
|
|
938
|
+
| AA `dialByName` | [call-features-major.md](call-features-major.md) | Not available |
|
|
939
|
+
| 3rd-party device SIP mgmt | [devices-core.md](devices-core.md) | `line_port`, `sip_user_name` retrieval and SIP password modification |
|
|
940
|
+
| UC-One settings | [person-call-settings-behavior.md](person-call-settings-behavior.md) | UC Manager Profile config |
|
|
941
|
+
| MS Teams integration | [person-call-settings-behavior.md](person-call-settings-behavior.md) | MS Teams calling settings |
|
|
942
|
+
|
|
943
|
+
### Authentication Differences
|
|
944
|
+
|
|
945
|
+
- **Service App tokens** (`spark:applications_token` scope): NOT supported in FedRAMP
|
|
946
|
+
- **Bot/Integration creation**: Must use REST API (`POST /applications`), not the developer portal UI
|
|
947
|
+
- **Application webhooks** (`application:webhooks_write/read`): NOT supported
|
|
948
|
+
- **OAuth integration refresh token lifetime:** 60 days in FedRAMP (vs. 90 days in commercial). The 60-day clock resets each time a refresh call is made.
|
|
949
|
+
- **Guest Issuer tokens:** Not supported in FedRAMP (and globally EOL'd December 31, 2025 — see Guest Issuer section).
|
|
950
|
+
|
|
951
|
+
### wxcli Usage
|
|
952
|
+
|
|
953
|
+
Set the base URL before running commands:
|
|
954
|
+
|
|
955
|
+
```bash
|
|
956
|
+
# Configure wxcli for FedRAMP
|
|
957
|
+
wxcli configure --base-url https://api-usgov.webex.com/v1
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
---
|
|
961
|
+
|
|
962
|
+
## See Also
|
|
963
|
+
|
|
964
|
+
- **`provisioning.md`** — Provisioning-specific scope requirements and end-to-end user/license/location provisioning workflows.
|
|
965
|
+
- **`archive/wxc-sdk-patterns.md`** — SDK code recipes, async auth patterns, and the service app token caching pattern (section 3, Pattern D). Archived: historical SDK doc.
|