diff --git a/.bandwidth/catalog-info.yaml b/.bandwidth/catalog-info.yaml index 4c414dd..862f4b7 100644 --- a/.bandwidth/catalog-info.yaml +++ b/.bandwidth/catalog-info.yaml @@ -2,10 +2,10 @@ apiVersion: backstage.io/v1alpha1 kind: Location metadata: schemaVersion: v1.0.0 - name: bandwidth-mcp-server-location - description: Links to additional entities in the bandwidth-mcp-server repository. + name: mcp-server-location + description: Links to additional entities in the mcp-server repository. annotations: - github.com/project-slug: Bandwidth/bandwidth-mcp-server + github.com/project-slug: Bandwidth/mcp-server spec: targets: - ./component.yaml diff --git a/.bandwidth/component.yaml b/.bandwidth/component.yaml index 295a747..b5efbd7 100644 --- a/.bandwidth/component.yaml +++ b/.bandwidth/component.yaml @@ -2,10 +2,10 @@ apiVersion: backstage.io/v1alpha1 kind: Component metadata: schemaVersion: v1.0.0 - name: bandwidth-mcp-server + name: mcp-server description: Official Bandwidth MCP Server annotations: - github.com/project-slug: Bandwidth/bandwidth-mcp-server + github.com/project-slug: Bandwidth/mcp-server organization: BW costCenter: Development - Software Infra platformType: diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml new file mode 100644 index 0000000..484f046 --- /dev/null +++ b/.github/workflows/test-pr.yml @@ -0,0 +1,38 @@ +name: Test PR + +on: + pull_request: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref }} + cancel-in-progress: true + +jobs: + test: + name: Test PR + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-2025, ubuntu-24.04] + python-version: ['3.10', '3.11', '3.12', '3.13'] + fail-fast: false + env: + PYTHON_VERSION: ${{ matrix.python-version }} + OPERATING_SYSTEM: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Packages and Test + run: | + pip install -r requirements.txt + pip install -r dev-requirements.txt + python -m pytest -v diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8de3fe --- /dev/null +++ b/.gitignore @@ -0,0 +1,93 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# uv +uv.lock + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c7ef0b --- /dev/null +++ b/README.md @@ -0,0 +1,301 @@ +# Bandwidth Official MCP Server +Source code for the official Bandwidth Model Context Protocol (MCP) Server. +This server can be used to interact with different Bandwidth APIs via an AI agent. +The server is provided as a python package and may be cloned directly from this repo. + +## Installation + +Clone directly from this git repository using: + +```shell +git clone https://github.com/Bandwidth/mcp-server.git +cd mcp-server +``` + +## Getting Started + +### Prerequisites + +In order to use the Bandwidth MCP Server, you'll need the following things, set as environment variables. +- Valid Bandwidth API Credentials + - This will be the username and password of your Bandwidth API user + - For more info on creating API credentials, see our [Credentials](https://dev.bandwidth.com/docs/credentials) page +- Bandwidth Account ID + - The ID of the account you'd like to make API calls on behalf of + +### Configuration + +#### Environment Variables + +Environment variables are used to configure the Bandwidth MCP Server. +The server will respect both system environment variables and those configured via your AI agent. + +The following variables will be required to use the server: + +```sh +BW_USERNAME # Your Bandwidth API User Username +BW_PASSWORD # Your Bandwidth API User Password +``` + +The following variables are optional or conditionally required: + +```sh +BW_ACCOUNT_ID # Your Bandwidth Account ID. Required for most API operations. +BW_NUMBER # A valid phone number on your Bandwidth account. Used with our Messaging and MFA APIs. Must be in E164 format. +BW_MESSAGING_APPLICATION_ID # A Bandwidth Messaging Application ID. Used with our Messaging and MFA APIs. +BW_VOICE_APPLICATION_ID # A Bandwidth Voice Application ID. Used with our MFA API. +BW_MCP_TOOLS # The list of MCP tools you'd like to enable. If not set, all tools are enabled. +BW_MCP_EXCLUDE_TOOLS # The list of MCP tools you'd like to exclude. Takes priority over BW_MCP_TOOLS. +``` + +#### Including or Excluding Tools + +By default, the server provides and enables all tools listed in the [Tools List](#tools-list). +Enabling all of these tools may cause context window size issues for certain AI agents or lead to slower agent response times. +To work around this and for a better experience, we recommend enabling only the certain subset of tools you plan on using. + +This can be accomplished by supplying a list of tool names to specifically enable or exclude to the server. +This list must be comma separated, with the tool names matching their names in the [Tools List](#tools-list). +The `BW_MCP_TOOLS` and `BW_MCP_EXCLUDE_TOOLS` mentioned in the [Environment Variables](#environment-variables) +section allow for enabling and excluding tools by name. You can also use the CLI flags `--tools` and `--exclude-tools`. +Using the CLI flags will take priority over the environment variables, and providing tools to exclude will take priority over the list of enabled tools. + +For a more comprehensive list of common use cases when which tools are required for each, check out our +[Common Use Cases Guide](common_use_cases.md). + +##### Tool Filtering Examples + +**Including only our Messaging tools** + +```sh +# Environment Variable +BW_MCP_TOOLS=listMessages,createMessage,createMultiChannelMessage + +# CLI Flag +--tools listMessages,createMessage,createMultiChannelMessage +``` + +**Excluding our Phone Number Lookup Tools** + +```sh +# Environment Variable +BW_MCP_EXCLUDE_TOOLS=createLookup,getLookupStatus + +# CLI Flag +--exclude-tools createLookup,getLookupStatus +``` + +## Using the Server + +Below you'll find instructions for using our MCP server with different common AI agents, as well as instructions for running the server locally. For usage with AI agents, it is recommended to use a combination of [uv](https://github.com/astral-sh/uv?tab=readme-ov-file#uv) and environment variables to start and configure the server respectively. + +### Goose CLI + +1. Install [Goose CLI](https://block.github.io/goose/docs/getting-started/installation/) + - We recommend configuring Goose to use `Allow Mode`. This will require user approval before Goose calls tools, which could prevent Goose from accidentally taking unwanted actions. +2. Add the Bandwidth MCP Server as a Command-line Extension + +```shell +goose configure +``` + +Then follow the prompts like the example below. + +```shell +┌ goose-configure +│ +◇ What would you like to configure? +│ Add Extension +│ +◇ What type of extension would you like to add? +│ Command-line Extension +│ +◇ What would you like to call this extension? +│ bw-mcp-server +│ +◇ What command should be run? +│ uvx --from /path/to/mcp-server start +``` + +> **_NOTE:_** If you configure environment variables with Goose, it will prioritize those over your system environment variables. + +### Cursor + +1. Install [Cursor](https://cursor.com/downloads) +2. Update your `.cursor/mcp.json` file to include the following object + +```json +{ + "mcpServers": { + "bw-mcp-server": { + "command":"uvx", + "args": ["--from", "/path/to/mcp-server", "start"], + "env": { + "BW_USERNAME": "", + "BW_PASSWORD": "", + "BW_MCP_TOOLS": "tools,to,enable", + "BW_MCP_EXCLUDE_TOOLS": "tools,to,exclude", + } + } + } +} +``` + +### VSCode (Copilot) + +1. Within VSCode, open the Command Palette and search for `MCP: Add Server`. +2. Choose `Command (stdio)`, then enter the full command to start the server. (Example Below) + +```shell +uvx --from /path/to/mcp-server start +``` + +3. Choose a name for the server (ie. `bw-mcp-server`) and select if you'd like it to be enabled Globally or only in the current workspace. +4. You can configure environment variables by opening the `mcp.json` file VSCode provides like the example below. + +```json +{ + "servers": { + "bw-mcp-server": { + "type": "stdio", + "command": "uvx", + "args": ["--from", "/path/to/mcp-server", "start"], + "env": { + "BW_USERNAME": "", + "BW_PASSWORD": "", + "BW_MCP_TOOLS": "tools,to,enable", + "BW_MCP_EXCLUDE_TOOLS": "tools,to,exclude", + } + } + }, + "inputs": [] +} +``` + +> **_NOTE:_** You may need to make sure MCP servers are enabled in VSCode to begin using the server. See the [official guide](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) for more info. + +### Claude Desktop + +1. Install [Claude Desktop](https://claude.ai/download) +2. Edit your `claude_desktop_config.json` to include the following object + +```json +{ + "mcpServers": { + "Bandwidth": { + "command": "uvx", + "args": ["--from", "/path/to/mcp-server", "start"], + "env": { + "BW_USERNAME": "", + "BW_PASSWORD": "", + "BW_MCP_TOOLS": "tools,to,enable", + "BW_MCP_EXCLUDE_TOOLS": "tools,to,exclude", + } + } + } +} +``` + +> **_NOTE:_** We've noticed some issues with Claude not being able to see MCP resources. This could require you to manually enter some tool parameters normally included in our config resource. + +### Running the Server Standalone + +The MCP server can be run locally using either native python or uv. +When running this way, all environment variables MUST be set in your system environment. + +#### Run Using Native Python + +Running the server locally with a python [virtual environment](https://docs.python.org/3/library/venv.html) requires both [python](https://www.python.org/downloads/) and [pip](https://pip.pypa.io/en/stable/getting-started/). +Once these are installed, create a virtual environment using: + +```sh +python -m venv .venv +``` + +Then activate and install the required packaged from the `requirements.txt` file. + +```sh +source .venv/bin/activate +pip install -r requirements.txt +``` + +After all packages are installed in the virtual environment, you can run the server locally using: + +```sh +python src/app.py +``` + +#### Run Using uv + +Make sure you have [uv installed](https://github.com/astral-sh/uv?tab=readme-ov-file#installation), +then you can start the server by running the following command from the root directory of this repository. + +```sh +uvx --from ./ start +``` + +## Tools List + +## **Multi-Factor Authentication (MFA)** +- `generateMessagingCode` - Send MFA code via SMS +- `generateVoiceCode` - Send MFA code via voice call +- `verifyCode` - Verify a previously sent MFA code + +## **Phone Number Lookup** +- `createLookup` - Create a phone number lookup request +- `getLookupStatus` - Get status of an existing lookup request + +## **Voice & Call Management** +- `listCalls` - Returns a list of call events with filtering options +- `listCall` - Returns details for a single call event + +## **Reporting & Analytics** +- `getReports` - Get history of created reports +- `createReport` - Create a new report instance +- `getReportStatus` - Get status of a report +- `getReportFile` - Download report file +- `getReportDefinitions` - Get available report definitions + +## **Media Management** +- `listMedia` - List your media files +- `getMedia` - Download a specific media file +- `uploadMedia` - Upload a media file +- `deleteMedia` - Delete a media file + +## **Messaging** +- `listMessages` - List messages with filtering options +- `createMessage` - Send SMS/MMS messages +- `createMultiChannelMessage` - Send multi-channel messages (RBM, SMS, MMS) + +## **Address Management** +- `getAddressFields` - Get supported address fields by country +- `validateAddress` - Validate an address and get excluded features +- `listAddresses` - List all addresses +- `createAddress` - Create an address +- `getAddress` - Get an address by ID +- `updateAddress` - Update an address +- `listCityInfo` - List city info search results + +## **Compliance** +- `listDocumentTypes` - List all accepted document types and metadata requirements +- `listEndUserTypes` - List all End user types and accepted metadata +- `listEndUserActivationRequirements` - List requirements for End user activation +- `getComplianceDocumentMetadata` - Get metadata of uploaded documents +- `updateComplianceDocument` - Modify document data and file +- `downloadComplianceDocuments` - Download document using document ID +- `createComplianceDocument` - Upload a document with metadata +- `listComplianceEndUsers` - List all End users of an account +- `createComplianceEndUser` - Create an End user +- `getComplianceEndUser` - Retrieve an End User by ID +- `updateComplianceEndUser` - Update End user details + +## **Requirements Packages** +- `listRequirementsPackages` - List all requirements packages +- `createRequirementsPackage` - Create a requirements package +- `getRequirementsPackage` - Retrieve a requirements package +- `patchRequirementsPackage` - Update Requirements package +- `getRequirementsPackageAssets` - Get assets attached to requirements package +- `attachRequirementsPackageAsset` - Attach an asset to requirements package +- `detachRequirementsPackageAsset` - Detach an asset from requirements package +- `validateNumberActivation` - Validate number activation requirements +- `getRequirementsPackageHistory` - Get history of a requirements package diff --git a/common_use_cases.md b/common_use_cases.md new file mode 100644 index 0000000..5ee6d06 --- /dev/null +++ b/common_use_cases.md @@ -0,0 +1,90 @@ +# Common Use Cases + +This guide outlines some common use cases for the MCP Server, as well as the tools required for these cases. +For more information on how to include the tools mentioned in this guide, please see the +[Including or Excluding Tools](README.md#including-or-excluding-tools) section in the README. + +## Sending Text Messages + +If you're looking to send messages using the MCP server, we recommend enabling the following tools: +- `listMessages` - Get info about messages you just sent or other messages on your account. +- `createMessage` - Send SMS or MMS messages +- `createMultiChannelMessage` - Send multi-channel messages (mostly for RBM messaging) + +Sending messages requires `BW_ACCOUNT_ID`, `BW_MESSAGING_APPLICATION_ID`, and `BW_NUMBER` to be set in your environment variables. + +**Enabling these tools** +```sh +# Environment Variable +BW_MCP_TOOLS=listMessages,createMessage,createMultiChannelMessage + +# CLI Flag +--tools listMessages,createMessage,createMultiChannelMessage +``` + +## Looking up Telephone Numbers + +If you'd like to get info about a specific telephone number or list of numbers, +you'll need both our `createLookup` and `getLookupStatus` tools. +Most agents we've experimented with have been smart enough to figure out that you +need to both create a lookup request and then get its' status to actually get the TN info, +and enabling only these two tools is a good way to help your agent remember that! + +Phone Number Lookup requires the `BW_ACCOUNT_ID` environment variable to be set. + +**Enabling these tools** +```sh +# Environment Variable +BW_MCP_TOOLS=createLookup,getLookupStatus + +# CLI Flag +--tools createLookup,getLookupStatus +``` + +## Utilizing our MFA Service + +To create and verify multi-factor authentication codes, you'll need our three MFA tools. +- `generateMessagingCode` - Used to generate and send an MFA code via SMS +- `generateVoiceCode` - Use to generate and send an MFA code via a phone call +- `verifyCode` - Verify an MFA code sent with one of the previous tools + +Generating Messaging and Voice codes requires the `BW_MESSAGING_APPLICATION_ID` and `BW_VOICE_APPLICATION_ID` environment variables respectively. +Both of these tools also require `BW_ACCOUNT_ID` and `BW_NUMBER`. + +**Enabling these tools** +```sh +# Environment Variable +BW_MCP_TOOLS=generateMessagingCode,generateVoiceCode,verifyCode + +# CLI Flag +--tools generateMessagingCode,generateVoiceCode,verifyCode +``` + +## Adding a Business End User + +To add an end user, you'll need three specific Compliance endpoints. +- `listEndUserTypes` - Used to list all End user types and their required fields +- `listEndUserActivationRequirements` - Required if the end user will be used for requirements packages +- `createComplianceEndUser` - Used to create the end user + +These tools will require the `BW_ACCOUNT_ID` environment variable. + +**Enabling these tools** +```sh +# Environment Variable +BW_MCP_TOOLS=listEndUserTypes,listEndUserActivationRequirements,createComplianceEndUser +# CLI Flag +--tools listEndUserTypes,listEndUserActivationRequirements,createComplianceEndUser +``` + +## Address Validation + +Validating an address can be done with just the `validateAddress` tool and the `BW_ACCOUNT_ID` environment variable! + +**Enabling this tool** +```sh +# Environment Variable +BW_MCP_TOOLS=validateAddress +# CLI Flag +--tools validateAddress +``` diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..7dac1cb --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,4 @@ +pytest>=8.4.1 +pytest-asyncio>=1.1.0 +pytest-httpx>=0.35.0 +black>=25.1.0 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0c9356f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "bw-mcp-server" +version = "0.1.0" +description = "Bandwidth MCP Server" +authors = [ + {name = "Bandwidth", email = "dx@bandwidth.com"} +] +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "httpx~=0.28.0", + "fastmcp~=2.11.0", + "pyyaml~=6.0.0", +] + +[project.scripts] +start = "app:main" + +[dependency-groups] +dev = [ + "black>=25.1.0", + "pytest>=8.4.1", + "pytest-asyncio>=1.1.0", + "pytest-httpx>=0.35.0", +] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4adbbdd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +httpx~=0.28.0 +fastmcp~=2.11.0 +pyyaml~=6.0.0 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/app.py b/src/app.py new file mode 100644 index 0000000..63eb7d6 --- /dev/null +++ b/src/app.py @@ -0,0 +1,26 @@ +import asyncio +from fastmcp import FastMCP +from servers import create_bandwidth_mcp +from config import load_config, get_enabled_tools, get_excluded_tools + +mcp = FastMCP(name="Bandwidth MCP") + + +async def setup(mcp: FastMCP = mcp): + """Setup the Bandwidth MCP server with tools and resources.""" + enabled_tools = get_enabled_tools() + excluded_tools = get_excluded_tools() + config = load_config() + + print("Setting up Bandwidth MCP server...") + await create_bandwidth_mcp(mcp, enabled_tools, excluded_tools, config) + + +def main(): + """Main function to run the Bandwidth MCP server.""" + asyncio.run(setup()) + mcp.run() + + +if __name__ == "__main__": + main() diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..ed01d7c --- /dev/null +++ b/src/config.py @@ -0,0 +1,78 @@ +import os +from typing import Dict, List, Optional +from argparse import ArgumentParser, Namespace + + +def load_config() -> Dict[str, str]: + """Load Bandwidth configuration from environment variables.""" + config = {} + required_vars = ["BW_USERNAME", "BW_PASSWORD"] + optional_vars = [ + "BW_ACCOUNT_ID", + "BW_NUMBER", + "BW_MESSAGING_APPLICATION_ID", + "BW_VOICE_APPLICATION_ID", + ] + + # Add all variables that exist + for var in required_vars + optional_vars: + value = os.getenv(var) + if value: + config[var] = value + + # Required variables + for var in required_vars: + if var not in config.keys(): + raise ValueError(f"Missing required environment variable: {var}") + + return config + + +def _parse_cli_args(args: Optional[List[str]] = None) -> Namespace: + """Parse command line arguments with proper type hints.""" + parser = ArgumentParser(description="Bandwidth MCP Server") + + # Tools + parser.add_argument( + "--tools", + help="Comma-separated list of tool names to enable. If not specified, all tools are enabled.", + type=str, + ) + parser.add_argument( + "--exclude-tools", + help="Comma-separated list of tool names to disable.", + type=str, + ) + + return parser.parse_known_args(args)[0] + + +def _parse_arg_list(arg_string: str) -> List[str]: + """Parse a comma-separated argument string into a list.""" + return [item.strip() for item in arg_string.split(",") if item.strip()] + + +def _parse_flags(cli_arg: Optional[str], env_var: str) -> Optional[List[str]]: + """Get flag values from CLI argument or environment variable.""" + # Try CLI argument first + if cli_arg: + return _parse_arg_list(cli_arg) + + # Fall back to environment variable + env_value = os.getenv(env_var) + if env_value: + return _parse_arg_list(env_value) + + return None + + +def get_enabled_tools() -> Optional[List[str]]: + """Get the list of enabled tools from CLI args or environment variable.""" + args = _parse_cli_args() + return _parse_flags(args.tools, "BW_MCP_TOOLS") + + +def get_excluded_tools() -> Optional[List[str]]: + """Get the list of excluded tools from CLI args or environment variable.""" + args = _parse_cli_args() + return _parse_flags(args.exclude_tools, "BW_MCP_EXCLUDE_TOOLS") diff --git a/src/resources.py b/src/resources.py new file mode 100644 index 0000000..80be9ec --- /dev/null +++ b/src/resources.py @@ -0,0 +1,17 @@ +from typing import List +from fastmcp.resources import HttpResource, Resource + + +number_order_guide_resource = HttpResource( + name="Bandwidth Number Order Guide", + description="Bandwidth Number Order Guide", + tags={"bandwidth", "number", "order", "guide"}, + uri="resource://number_order_guide", + mime_type="text/markdown", + url="https://dev.bandwidth.com/docs/numbers/guides/searchingForNumbers.md", +) + + +def get_bandwidth_resources() -> List[Resource]: + """Get all Bandwidth resources.""" + return [number_order_guide_resource] diff --git a/src/server_utils.py b/src/server_utils.py new file mode 100644 index 0000000..2aa5fb5 --- /dev/null +++ b/src/server_utils.py @@ -0,0 +1,145 @@ +import copy +import yaml +import httpx +import base64 + +from fastmcp import FastMCP +from fastmcp.resources import FunctionResource +from fastmcp.server.openapi import MCPType, HTTPRoute +from typing import Dict, List, Optional, Any, Callable + +from resources import get_bandwidth_resources + + +async def print_server_info(mcp: FastMCP) -> None: + """Print concise server information.""" + + all_tools = await mcp.get_tools() + all_resources = await mcp.get_resources() + + tool_names = list(all_tools.keys()) + resource_names = [resource.name for resource in all_resources.values()] + + print("Bandwidth MCP Server Started") + print( + f"Tools ({len(tool_names)}): {', '.join(sorted(tool_names)) if tool_names else 'None'}" + ) + print( + f"Resources ({len(resource_names)}): {', '.join(sorted(resource_names)) if resource_names else 'None'}" + ) + + +def create_route_map_fn( + enabled_tools: Optional[List[str]], excluded_tools: Optional[List[str]] +) -> Callable[[HTTPRoute, MCPType], MCPType]: + """Create a route map function based on enabled and excluded tools. + + Args: + enabled_tools: List of tools to enable. If None, all tools are enabled. + excluded_tools: List of tools to exclude. Takes priority over enabled_tools. + + Returns: + A function that maps routes to MCP types based on the tool configuration. + """ + + def route_map_fn(route: HTTPRoute, mcp_type: MCPType) -> MCPType: + # Excluded tools have priority - if provided, ignore enabled tools + if excluded_tools: + return ( + mcp_type + if route.operation_id not in excluded_tools + else MCPType.EXCLUDE + ) + if enabled_tools: + return mcp_type if route.operation_id in enabled_tools else MCPType.EXCLUDE + + return mcp_type + + return route_map_fn + + +def _clean_openapi_spec(spec: Dict[str, Any]) -> Dict[str, Any]: + """Recursively clean OpenAPI spec: + - Remove all callbacks + - Remove all 4xx/5xx responses + - Remove any field starting with 'x-' + - Remove all path resources that start with 'x-' + """ + cleaned_spec = copy.deepcopy(spec) + + def _clean(obj: Any) -> Any: + if isinstance(obj, dict): + # Remove 'callbacks' and 'x-' fields + keys_to_remove = [k for k in obj if k == "callbacks" or k.startswith("x-")] + for k in keys_to_remove: + del obj[k] + # Remove 4xx/5xx responses + if "responses" in obj: + codes_to_remove = [ + code + for code in obj["responses"] + if str(code).startswith(("4", "5")) + ] + for code in codes_to_remove: + del obj["responses"][code] + # Special handling for paths + if "paths" in obj: + paths_to_remove = [p for p in obj["paths"] if p.startswith("x-")] + for p in paths_to_remove: + del obj["paths"][p] + # Recurse into all values + for v in obj.values(): + _clean(v) + elif isinstance(obj, list): + for item in obj: + _clean(item) + return obj + + return _clean(cleaned_spec) + + +async def fetch_openapi_spec(url: str) -> Dict[str, Any]: + """Fetch and parse OpenAPI spec from URL.""" + try: + async with httpx.AsyncClient() as client: + response = await client.get(url) + response.raise_for_status() + spec_text = response.text + + spec_object = yaml.safe_load(spec_text) + if not spec_object: + raise ValueError(f"Empty or invalid YAML spec from {url}") + + return _clean_openapi_spec(spec_object) + except httpx.HTTPError as e: + raise RuntimeError(f"Failed to fetch OpenAPI spec from {url}: {e}") from e + except yaml.YAMLError as e: + raise RuntimeError(f"Failed to parse YAML spec from {url}: {e}") from e + + +def create_auth_header(username: str, password: str) -> str: + """Create a basic authentication header.""" + auth_bytes = f"{username}:{password}".encode("utf-8") + return base64.b64encode(auth_bytes).decode("utf-8") + + +def add_resources(mcp: FastMCP, config: Dict[str, Any]) -> FastMCP: + """Add configuration and other resources to the MCP server.""" + config_resource = FunctionResource( + name="Bandwidth API Configuration", + description="Object containing API credentials, application IDs, and account ID.", + tags={"bandwidth", "config", "credentials"}, + uri="resource://config", + mime_type="application/json", + fn=lambda: config, + ) + + mcp.add_resource(config_resource) + + for resource in get_bandwidth_resources(): + try: + mcp.add_resource(resource) + except Exception as e: + print(f"Warning: Failed to import resource {resource.name}: {e}") + + return mcp diff --git a/src/servers.py b/src/servers.py new file mode 100644 index 0000000..4a5a6a8 --- /dev/null +++ b/src/servers.py @@ -0,0 +1,94 @@ +from fastmcp import FastMCP +from httpx import AsyncClient +from typing import Dict, List, Optional, Callable, Any + +from server_utils import ( + add_resources, + create_route_map_fn, + create_auth_header, + fetch_openapi_spec, + print_server_info, +) + +api_server_info: Dict[str, Dict[str, Any]] = { + "messaging": {"url": "https://dev.bandwidth.com/spec/messaging.yml"}, + "multi-factor-auth": { + "url": "https://dev.bandwidth.com/spec/multi-factor-auth.yml" + }, + "phone-number-lookup": { + "url": "https://dev.bandwidth.com/spec/phone-number-lookup.yml" + }, + "insights": {"url": "https://dev.bandwidth.com/spec/insights.yml"}, + "end-user-management": { + "url": "https://dev.bandwidth.com/spec/end-user-management.yml" + }, +} + + +async def _create_server( + url: str, route_map_fn: Optional[Callable] = None, config: Dict[str, Any] = {} +) -> FastMCP: + """Create an MCP server from the provided spec URL and credentials.""" + # Fetch and clean the OpenAPI spec + spec_object = await fetch_openapi_spec(url) + + # Validate spec structure + if "servers" not in spec_object or not spec_object["servers"]: + raise ValueError(f"OpenAPI spec from {url} has no servers defined") + + base_url = spec_object["servers"][0]["url"] + auth_b64 = create_auth_header(config["BW_USERNAME"], config["BW_PASSWORD"]) + + client = AsyncClient( + base_url=base_url, + headers={ + "Authorization": f"Basic {auth_b64}", + "User-Agent": "Bandwidth MCP Server", + }, + ) + + mcp = FastMCP.from_openapi( + openapi_spec=spec_object, + client=client, + name="Bandwidth", + route_map_fn=route_map_fn, + ) + + return mcp + + +async def create_bandwidth_mcp( + mcp: FastMCP, + enabled_tools: Optional[List[str]], + excluded_tools: Optional[List[str]], + config: Dict[str, Any] = {}, +) -> FastMCP: + """Create the Bandwidth MCP server from all supplied APIs, taking into account enabled and excluded APIs. + + Args: + mcp: The FastMCP instance to import servers into + enabled_tools: List of tools to enable. If None, all tools are enabled. + excluded_tools: List of tools to exclude. Takes priority over enabled_tools. + config: Configuration dictionary containing API credentials and other variables. + + Returns: + The FastMCP instance with all API servers imported and resources added. + + Raises: + RuntimeError: If any API server fails to create or import + """ + route_map_fn = create_route_map_fn(enabled_tools, excluded_tools) + + for api_name, api_info in api_server_info.items(): + try: + server = await _create_server( + api_info["url"], route_map_fn=route_map_fn, config=config + ) + await mcp.import_server(server) + except Exception as e: + print(f"Warning: Failed to create server for {api_name}: {e}") + + add_resources(mcp, config) + await print_server_info(mcp) + + return mcp diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..f11816f --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,6 @@ +import sys +from pathlib import Path + +# Add src directory to Python path +src_path = Path(__file__).parent.parent / "src" +sys.path.insert(0, str(src_path)) diff --git a/test/fixtures/empty.yml b/test/fixtures/empty.yml new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/test/fixtures/empty.yml @@ -0,0 +1 @@ + diff --git a/test/fixtures/end-user-management.yml b/test/fixtures/end-user-management.yml new file mode 100644 index 0000000..1070049 --- /dev/null +++ b/test/fixtures/end-user-management.yml @@ -0,0 +1,3079 @@ +openapi: 3.1.0 +info: + title: End User Management + description: |- + Bandwidth's End User Management API allows you to manage addresses, + validate them, and handle compliance requirements for end users. + + - Addresses Management + - Compliance + - Requirements Packages + version: 1.0.0 + contact: + name: Bandwidth + url: https://support.bandwidth.com + email: support@bandwidth.com + termsOfService: https://www.bandwidth.com/legal/terms-of-use-bandwidthcom-web-sites/ +servers: + - url: https://api.bandwidth.com/api/v2 + description: Production + +paths: + /addresses/fields/{countryCodeA3}: + get: + summary: Get Address Fields + operationId: getAddressFields + description: Get a list of address fields that is supported as per the country and feature specific requirements. + tags: + - Addresses + parameters: + - $ref: "#/components/parameters/countryCodeA3PathParam" + responses: + "200": + $ref: "#/components/responses/getAddressFieldsResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/addresses/validator: + post: + summary: Validate Address + operationId: validateAddress + description: Returns features that will NOT work for the entered address. Please note that independent features/services may have additional validation and absence from excludedFeatures does not guarantee that the address will be valid for the feature/service. + tags: + - Addresses + parameters: + - $ref: "#/components/parameters/accountId" + requestBody: + $ref: "#/components/requestBodies/validateAddressRequest" + responses: + "200": + $ref: "#/components/responses/validateAddressResponse" + "400": + $ref: "#/components/responses/validateAddressBadRequestResponse" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/addresses: + get: + summary: List Addresses + operationId: listAddresses + description: List all addresses. The results are sorted by last updated time in reverse chronological order by default without any query filters. + tags: + - Addresses + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/addressCustomReferenceQueryParamEqStartsWithContains" + - $ref: "#/components/parameters/countryCodeA3QueryParamEq" + - $ref: "#/components/parameters/cityQueryParamEqStartsWith" + - $ref: "#/components/parameters/postalCodeQueryParamEqStartsWithContains" + - $ref: "#/components/parameters/geoValidationStatusQueryParamEq" + - $ref: "#/components/parameters/addressFieldsQueryParamContains" + - $ref: "#/components/parameters/afterCursorQueryParam" + - $ref: "#/components/parameters/limitQueryParam" + responses: + "200": + $ref: "#/components/responses/listAddressesResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + post: + summary: Create Address + operationId: createAddress + description: Create an address. Use GET /addresses/fields for full list of country specific address fields. + tags: + - Addresses + parameters: + - $ref: "#/components/parameters/accountId" + requestBody: + $ref: "#/components/requestBodies/createAddressRequest" + responses: + "201": + $ref: "#/components/responses/createAddressResponse" + "400": + $ref: "#/components/responses/createAddressBadRequestResponse" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/addresses/{addressId}: + get: + summary: Get Address + operationId: getAddress + description: Get an address. + tags: + - Addresses + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/addressIdPathParam" + responses: + "200": + $ref: "#/components/responses/getAddressResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + patch: + summary: Update Address + operationId: updateAddress + description: Update an address. Use GET /addresses/fields for full list of country specific address fields that can be updated. + tags: + - Addresses + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/addressIdPathParam" + requestBody: + $ref: "#/components/requestBodies/updateAddressRequest" + responses: + "200": + $ref: "#/components/responses/updateAddressResponse" + "400": + $ref: "#/components/responses/updateAddressBadRequestResponse" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /addresses/{countryCodeA3}/cityInfo: + get: + summary: List City Info + operationId: listCityInfo + description: | + List city info search results. + + Allowed search parameter combinations: + * city[startsWith]=Aalen + * postalCode[startsWith]=734 + * city[eq]=Aalen&postalCode[startsWith]=734 + tags: + - Addresses + parameters: + - $ref: "#/components/parameters/cityInfoCountryCodeA3PathParam" + - $ref: "#/components/parameters/cityInfoCityQueryParamEqStartsWith" + - $ref: "#/components/parameters/cityInfoPostalCodeQueryParamStartsWith" + responses: + "200": + $ref: "#/components/responses/listCityInfoResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /compliance/documentTypes: + get: + summary: List Document Types + operationId: listDocumentTypes + description: | + List of all accepted document types and their metadata requirements. + When adding a document, + the 'fields' properties indicated as required are mandatory. + tags: + - Compliance + responses: + "200": + $ref: "#/components/responses/listDocumentTypesResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /compliance/endUserTypes: + get: + summary: List End User Types + operationId: listEndUserTypes + description: | + List of all End user types and the accepted metadata information. + When creating an End user, + the 'fields' properties indicated as required are mandatory. + tags: + - Compliance + responses: + "200": + $ref: "#/components/responses/listEndUserTypesResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/compliance/requirements: + get: + summary: List End User Activation Requirements + operationId: listEndUserActivationRequirements + description: | + Lists all requirements required to validate an End user and activate services. + Requirements are set by - country, phone number type and End user type. + + The fields mentioned above can also be used in a hierarchical manner to search for requirements. + This means that the country field is mandatory when filtering based on phone number type, and similarly, + both country and phone number type fields are mandatory when filtering for End user type. + + Note: The 'fields' properties in End user requirements and accepted documents indicated as + required are mandatory to provide in the attached End user and document type assets before + submitting a requirement package. + + Explore all available fields for End user using + list End user types + and for document using list document types. + tags: + - Compliance + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/countryCodeA3QueryParamEq" + - $ref: "#/components/parameters/phoneNumberTypeQueryParamEq" + - $ref: "#/components/parameters/rpEndUserTypeQueryParamEq" + responses: + "200": + $ref: "#/components/responses/listRequirementsResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/compliance/documents/{documentId}: + get: + summary: Get Compliance Document Metadata + operationId: getComplianceDocumentMetadata + description: Get all the metadata details of the uploaded document. + tags: + - Compliance + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/documentIdPathParam" + responses: + "200": + $ref: "#/components/responses/getComplianceDocumentResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + patch: + summary: Update Compliance Document Metadata and Files + operationId: updateComplianceDocument + description: Modify document data and file. Updates are only allowed in `DRAFT` and `VERIFICATION_FAILED` state. + tags: + - Compliance + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/documentIdPathParam" + requestBody: + $ref: "#/components/requestBodies/updateComplianceDocumentRequest" + responses: + "200": + $ref: "#/components/responses/getComplianceDocumentResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/compliance/documents/{documentId}/fileContent: + get: + summary: Download Compliance Document + operationId: downloadComplianceDocuments + description: | + Download document using the document id. + Download will fail if 'fileContentExists' from get document metadata is false. + tags: + - Compliance + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/documentIdPathParam" + responses: + "200": + $ref: "#/components/responses/downloadDocumentResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/compliance/documents: + post: + summary: Create Compliance Document + operationId: createComplianceDocument + description: | + Upload a document with metadata as part of any compliance requirements. + One document can be used only in one requirement package. + To successfully create a document, please provide all the mandatory fields + marked in list document types. + To successfully submit a requirement package with this document, find all required fields + using list End user activation requirements. + The document can be created with minimum fields mentioned in list document types and attached to the requirement package. + After this, metadata can be updated using update document + before submitting the requirement package. + + Supported file formats: pdf, jpeg, png, doc, docx. + The maximum file size currently supported is 5 MB. + tags: + - Compliance + parameters: + - $ref: "#/components/parameters/accountId" + requestBody: + $ref: "#/components/requestBodies/addComplianceDocumentRequest" + responses: + "201": + $ref: "#/components/responses/addComplianceDocumentResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/compliance/endUsers: + get: + summary: List Compliance End Users + operationId: listComplianceEndUsers + description: | + List all End users of an account. + The results are sorted by last updated time in reverse chronological order by default without any query filters. + + The following filters can be used to filter the result + * End User Type + * End User Type and End User Name + * Custom Reference + + Also, the results can be sorted by following fields + * End User Name + * Custom Reference + tags: + - Compliance + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/endUserTypeQueryParamEq" + - $ref: "#/components/parameters/endUserNameQueryParam" + - $ref: "#/components/parameters/customReferenceQueryParam" + - $ref: "#/components/parameters/rpLimitQueryParam" + responses: + "200": + $ref: "#/components/responses/listEndUsersResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + post: + summary: Create Compliance End User + operationId: createComplianceEndUser + description: | + Create an End user. + This newly created End user can be attached to multiple requirement packages. + An End user resource can be created with minimum required fields. + Please find all minimum required fields for an End user type + using list End user types. + If this End user is to be attached to multiple requirement packages, + please provide all mandatory fields common to all the requirement packages where this End + user needs to be attached before submitting one of the associated requirement packages. + Find all required fields for End user for all the requirement packages where this End user needs to be + attached using list End user activation requirements. + End user metadata can be updated with extra required fields after creation + using update End user. + tags: + - Compliance + parameters: + - $ref: "#/components/parameters/accountId" + requestBody: + $ref: "#/components/requestBodies/postRequirementsPackageEndUserBody" + responses: + "201": + $ref: "#/components/responses/createEndUserResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/compliance/endUsers/{endUserId}: + get: + summary: Get Compliance End User + operationId: getComplianceEndUser + description: Retrieve an End User by ID. + tags: + - Compliance + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/endUserIdPathParam" + responses: + "200": + $ref: "#/components/responses/getEndUserResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + patch: + summary: Update Compliance End User + operationId: updateComplianceEndUser + description: | + Update End user's details except `type`. + Updates are only allowed in `DRAFT` and `VERIFICATION_FAILED` state. + Please note that once an End user's fields have been provided, it can be edited, but can't be cleared or removed. + tags: + - Compliance + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/endUserIdPathParam" + requestBody: + $ref: "#/components/requestBodies/patchRequirementsPackageEndUserBody" + responses: + "200": + $ref: "#/components/responses/updateEndUserResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/compliance/requirementsPackages: + get: + summary: List Requirements Packages + operationId: listRequirementsPackages + description: | + List all requirements packages. + The results are sorted by last updated time in reverse chronological order by default without any query filters. + + Following filters can be used to filter the result + * Country Code : List requirements packages based on the country code. + * Phone Number Type : List requirements packages based on the phone number type. + * End user Type : List requirements packages based on the End user type. + * Custom Reference : List requirements packages based on the custom reference. + * Status : List requirements packages based on the status. + + tags: + - Requirements Packages + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/rpCountryCodeA3QueryParamEq" + - $ref: "#/components/parameters/rpPhoneNumberTypeQueryParamEq" + - $ref: "#/components/parameters/rpEndUserTypeQueryParamEq" + - $ref: "#/components/parameters/statusQueryParamEq" + - $ref: "#/components/parameters/rpCustomReferenceQueryParamStartsWith" + - $ref: "#/components/parameters/rpLimitQueryParam" + responses: + "200": + $ref: "#/components/responses/listRequirementsPackageResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + post: + summary: Create Requirements Package + operationId: createRequirementsPackage + description: | + Create a requirements package with a set of requirements. + Note: Country, Phone Number Type and End User Type cannot be changed once created. + tags: + - Requirements Packages + parameters: + - $ref: "#/components/parameters/accountId" + requestBody: + $ref: "#/components/requestBodies/postRequirementsPackageBody" + responses: + "201": + $ref: "#/components/responses/createRequirementsPackageResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/compliance/requirementsPackages/{requirementsPackageId}: + get: + summary: Get Requirements Package + operationId: getRequirementsPackage + description: Retrieve a requirements package using the id. + tags: + - Requirements Packages + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/requirementsPackageIdPathParam" + responses: + "200": + $ref: "#/components/responses/getRequirementsPackageResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + patch: + summary: Update Requirements Package + operationId: patchRequirementsPackage + description: | + Update Requirements package status to `SUBMITTED` to submit a package. + * 'acknowledgements' is required for submitting the package with 'allDetailsAccurate' set to true. + * Once submitted, all associated assets will be locked and cannot be modified. + Update custom reference, email, or callback. + tags: + - Requirements Packages + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/requirementsPackageIdPathParam" + requestBody: + $ref: "#/components/requestBodies/patchRequirementsPackageBody" + responses: + "200": + $ref: "#/components/responses/updateRequirementsPackageResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/compliance/requirementsPackages/{requirementsPackageId}/assets: + get: + summary: Get Requirements Package Assets + operationId: getRequirementsPackageAssets + description: Retrieve all assets attached to the requirements package with their details + tags: + - Requirements Packages + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/requirementsPackageIdPathParam" + responses: + "200": + $ref: "#/components/responses/getRequirementsPackageAssetsResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + post: + summary: Attach Requirements Package Asset + operationId: attachRequirementsPackageAsset + description: | + Attach an asset to the requirements package. + tags: + - Requirements Packages + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/requirementsPackageIdPathParam" + requestBody: + $ref: "#/components/requestBodies/postRequirementsPackageAssetsBody" + responses: + "201": + $ref: "#/components/responses/createRequirementsPackageAssetResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/compliance/requirementsPackages/{requirementsPackageId}/assets/{assetId}: + delete: + summary: Detach Requirements Package Asset + operationId: detachRequirementsPackageAsset + description: Detach an asset from the requirements package. + tags: + - Requirements Packages + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/requirementsPackageIdPathParam" + - $ref: '#/components/parameters/requirementsPackageAssetIdPathParam' + responses: + "204": + description: No Content + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/compliance/numberActivation/validator: + post: + summary: Validate Number Activation + operationId: validateNumberActivation + description: Validate number activation requirements. + tags: + - Requirements Packages + parameters: + - $ref: "#/components/parameters/accountId" + requestBody: + $ref: "#/components/requestBodies/postNumberActivationValidatorBody" + responses: + "200": + $ref: "#/components/responses/validateNumberActivationResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /accounts/{accountId}/compliance/requirementsPackages/{requirementsPackageId}/history: + get: + tags: + - Requirements Packages + summary: Get Requirements Package History + operationId: getRequirementsPackageHistory + description: Get the history of a requirements package. + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/requirementsPackageIdPathParam" + responses: + "200": + $ref: "#/components/responses/getRequirementsPackageHistoryResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedEUMSError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotAllowedError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" +components: + parameters: + accountId: + in: path + name: accountId + required: true + schema: + type: string + description: Your Bandwidth Account ID. + example: "9900000" + addressIdPathParam: + in: path + name: addressId + required: true + schema: + type: string + example: daa9dd0f-de97-4103-8530-b31bf4be8fc0 + description: The Address ID. + cityInfoCountryCodeA3PathParam: + name: countryCodeA3 + description: Country Code A3. + in: path + required: true + schema: + type: string + example: DEU + countryCodeA3PathParam: + name: countryCodeA3 + description: Country Code A3 + in: path + required: true + schema: + type: string + example: FRA + cityInfoCityQueryParamEqStartsWith: + in: query + name: city + required: false + style: deepObject + explode: true + schema: + $ref: "#/components/schemas/queryParamStringEqStartsWith" + examples: + eq: + value: + eq: Aalen + startsWith: + value: + startsWith: Aal + description: The name of the city. The system defaults to return all cities + when the query parameter is not passed. The countryCodeA3 is a required parameter + with city. Some phone number types like TOLL_FREE may not have any associated + cities. + cityInfoPostalCodeQueryParamStartsWith: + in: query + name: postalCode + required: false + style: deepObject + explode: true + schema: + type: object + properties: + startsWith: + type: string + example: "734" + description: | + The postal code of the address. The system defaults to return all + postal codes when the query parameter is not passed. The countryCodeA3 is + a required parameter with postalCode. + addressCustomReferenceQueryParamEqStartsWithContains: + in: query + name: customReference + required: false + style: deepObject + explode: true + schema: + $ref: "#/components/schemas/queryParamStringEqStartsWithContains" + examples: + eq: + value: + eq: home_office + startsWith: + value: + startsWith: home + contains: + value: + contains: office + description: | + Custom reference enables flexible search operations including exact matches (eq), + prefix-based searches (startsWith), and substring-based searches (contains). + geoValidationStatusQueryParamEq: + in: query + name: geoValidationStatus + required: false + style: deepObject + explode: true + schema: + type: object + properties: + eq: + $ref: "#/components/schemas/geoValidationStatusEnum" + example: + eq: GEO_VALID + description: | + Geo validation status which can be one of: + GEO_VALID, NOT_GEO_VALID, or NOT_GEO_VALIDATED. + countryCodeA3QueryParamEq: + in: query + name: countryCodeA3 + required: false + style: deepObject + explode: true + schema: + $ref: "#/components/schemas/queryParamStringEq" + example: + eq: USA + description: Country code of the address in ISO 3166-1 alpha-3 format. + cityQueryParamEqStartsWith: + in: query + name: city + required: false + style: deepObject + explode: true + schema: + $ref: "#/components/schemas/queryParamStringEqStartsWithContains" + examples: + eq: + value: + eq: Seattle + startsWith: + value: + startsWith: Seat + contains: + value: + contains: eattl + description: The name of the city. The system defaults to return all cities + when the query parameter is not passed. The countryCodeA3 is a required parameter + with city. Some phone number types like TOLL_FREE may not have any associated + cities. + postalCodeQueryParamEqStartsWithContains: + in: query + name: postalCode + required: false + style: deepObject + explode: true + schema: + $ref: "#/components/schemas/queryParamStringEqStartsWithContains" + examples: + eq: + value: + eq: "98072" + startsWith: + value: + startsWith: "9807" + contains: + value: + contains: "807" + description: The postal code of the address. The system defaults to return all + postal codes when the query parameter is not passed. The countryCodeA3 is + a required parameter with postalCode. + addressFieldsQueryParamContains: + description: Any of the location-related fields in the address can be used. + examples: + contains: + value: + contains: 123 Main + explode: true + in: query + name: addressFields + required: false + schema: + $ref: '#/components/schemas/queryParamStringContains' + afterCursorQueryParam: + in: query + name: afterCursor + required: false + schema: + type: string + example: ArAnD0m1d + description: Returns the page after the last record on the current page. + limitQueryParam: + in: query + name: limit + required: false + schema: + type: integer + default: 10 + minimum: 1 + maximum: 100 + example: 50 + description: Limit the number of returned records. + endUserIdPathParam: + name: endUserId + description: EndUser ID + in: path + required: true + schema: + type: string + example: "878345" + endUserTypeQueryParamEq: + in: query + name: type + required: false + style: deepObject + explode: true + schema: + type: object + properties: + eq: + $ref: "#/components/schemas/endUserTypeEnum" + example: + eq: BUSINESS + description: The type of end user. + rpEndUserTypeQueryParamEq: + in: query + name: endUserType + required: false + style: deepObject + explode: true + schema: + type: object + properties: + eq: + $ref: "#/components/schemas/endUserTypeEnum" + example: + eq: RESIDENTIAL + description: The type of end user. + endUserNameQueryParam: + in: query + name: endUserName + required: false + style: deepObject + explode: true + schema: + type: object + allOf: + - $ref: "#/components/schemas/queryParamStringStartsWith" + - type: object + properties: + sort: + $ref: "#/components/schemas/sortEnum" + examples: + RESIDENTIAL: + value: + startsWith: Adam + BUSINESS: + value: + startsWith: Alphabet + description: The End user name in order 'FirstName LastName' or 'Business Name' + rpLimitQueryParam: + in: query + name: limit + required: false + schema: + type: integer + default: 10 + minimum: 1 + maximum: 100 + example: 50 + description: Limit the number of returned records. + customReferenceQueryParam: + in: query + name: customReference + required: false + style: deepObject + explode: true + schema: + type: object + allOf: + - $ref: "#/components/schemas/queryParamStringStartsWith" + - type: object + properties: + sort: + $ref: "#/components/schemas/sortEnum" + example: + startsWith: home + description: Custom reference filter, and can be combined with 'customReference[sort]' + documentIdPathParam: + in: path + name: documentId + required: true + schema: + type: string + example: ArAnD0m1d + description: Document ID. + phoneNumberTypeQueryParamEq: + in: query + name: phoneNumberType + required: true + style: deepObject + explode: true + schema: + type: object + properties: + eq: + $ref: "#/components/schemas/phoneNumberTypeEnum" + example: + eq: GEOGRAPHIC + description: The type of phone number. + requirementsPackageAssetIdPathParam: + name: assetId + description: Requirements package asset Id + in: path + required: true + schema: + type: string + example: 925e38bb-6d09-4b25-9828-afcbe9417e53 + statusQueryParamEq: + in: query + name: status + description: The status of the item + required: false + style: deepObject + explode: true + schema: + $ref: "#/components/schemas/queryParamStringEq" + example: + eq: VERIFIED + rpCustomReferenceQueryParamStartsWith: + in: query + name: customReference + description: Filter by customReference(startsWith). + required: false + style: deepObject + explode: true + schema: + type: object + properties: + startsWith: + minLength: 1 + maxLength: 100 + example: + startsWith: home + rpCountryCodeA3QueryParamEq: + in: query + name: countryCodeA3 + description: Country code of the address in ISO 3166-1 alpha-3 format. + required: false + style: deepObject + explode: true + schema: + $ref: "#/components/schemas/queryParamStringEq" + example: + eq: USA + rpPhoneNumberTypeQueryParamEq: + in: query + name: phoneNumberType + description: The type of phone number. + required: false + style: deepObject + explode: true + schema: + type: object + properties: + eq: + $ref: "#/components/schemas/phoneNumberTypeEnum" + example: + eq: GEOGRAPHIC + requirementsPackageIdPathParam: + name: requirementsPackageId + description: Requirements Package ID + in: path + required: true + schema: + type: string + example: 815e38bb-6d09-4b25-9828-afcbe9417e53 + requestBodies: + createAddressRequest: + content: + application/json: + schema: + $ref: "#/components/schemas/createAddressRequestData" + updateAddressRequest: + content: + application/json: + schema: + $ref: "#/components/schemas/updateAddressData" + validateAddressRequest: + content: + application/json: + schema: + $ref: "#/components/schemas/validateAddressRequestData" + patchRequirementsPackageEndUserBody: + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/endUserData" + properties: + type: + readOnly: true + examples: + patchEndUserBodyBasicExample: + $ref: "#/components/examples/patchEndUserBodyBasicExample" + postRequirementsPackageEndUserBody: + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/endUserData" + required: + - type + addComplianceDocumentRequest: + description: Add a document with any metadata + content: + multipart/form-data: + schema: + type: object + properties: + metadata: + $ref: "#/components/schemas/complianceDocumentData" + file: + type: string + format: binary + encoding: + file: + contentType: application/pdf, image/jpeg, image/jpg, image/png, application/msword, + application/vnd.openxmlformats-officedocument.wordprocessingml.document + style: deepObject + explode: true + metadata: + contentType: application/json + updateComplianceDocumentRequest: + description: Update document data along with any new file change. Type cannot + be modified + content: + multipart/form-data: + schema: + type: object + properties: + metadata: + $ref: "#/components/schemas/complianceDocumentUpdateData" + file: + type: string + format: binary + postNumberActivationValidatorBody: + content: + application/json: + schema: + $ref: "#/components/schemas/numberActivationRequirementsSet" + postRequirementsPackageAssetsBody: + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/assetData" + required: + - assetReferenceId + - assetType + examples: + postRequirementsPackageAssetBodyBasicExample: + $ref: "#/components/examples/postRequirementsPackageAssetBodyBasicExample" + patchRequirementsPackageBody: + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/requirementsPackageData" + - type: object + properties: + countryCodeA3: + readOnly: true + phoneNumberType: + readOnly: true + endUserType: + readOnly: true + status: + description: Status change to requirement package. Only allowed value is SUBMITTED + type: string + example: SUBMITTED + example: + customReference: home_office + email: foo@bar.com + status: SUBMITTED + postRequirementsPackageBody: + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/requirementsPackageData" + required: + - countryCodeA3 + - phoneNumberType + - endUserType + responses: + createAddressResponse: + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/createUpdateAddressResponse" + listCityInfoResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/listCityInfoResponse" + updateAddressResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/createUpdateAddressResponse" + getAddressResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/genericAddressResponse" + listAddressesResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/listAddressesResponse" + validateAddressResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/validateAddressResponse" + getAddressFieldsResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/getAddressFieldsResponse" + createAddressBadRequestResponse: + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/addressBadRequest" + examples: + badRequestAddressSuggestionExample: + $ref: "#/components/examples/geoValidationAddressSuggestionExample" + badRequestAddressMissingGeoValidationFeatureExample: + $ref: "#/components/examples/geoValidationDisabledExample" + validateAddressBadRequestResponse: + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + badRequestErrorExample: + $ref: "#/components/examples/geoValidationAddressSuggestionExample" + badRequestError: + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + badRequestErrorExample: + $ref: "#/components/examples/badRequestErrorExample" + notFoundError: + description: Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + notFoundErrorExample: + $ref: "#/components/examples/notFoundErrorExample" + methodNotAllowedError: + description: Method Not Allowed + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + methodNotAllowedErrorExample: + $ref: "#/components/examples/methodNotAllowedErrorExample" + tooManyRequestsError: + description: Too Many Requests + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + tooManyRequestsErrorExample: + $ref: "#/components/examples/tooManyRequestsErrorExample" + internalServerError: + description: Internal Server Error + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + internalServerErrorExample: + $ref: "#/components/examples/internalServerErrorExample" + forbiddenError: + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + forbiddenErrorExample: + $ref: "#/components/examples/forbiddenErrorExample" + unauthorizedError: + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + unauthorizedErrorExample: + $ref: "#/components/examples/unauthorizedErrorExample" + updateAddressBadRequestResponse: + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/updateGeoValidationStatusSuggestionAddressResponse" + updateEndUserResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/endUserResponse" + getEndUserResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/endUserResponse" + createEndUserResponse: + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/endUserResponse" + listEndUsersResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/endUserListResponse" + addComplianceDocumentResponse: + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/createUpdateComplianceDocumentResponse" + downloadDocumentResponse: + description: OK + content: + application/octet-stream: + schema: + type: string + format: binary + listRequirementsResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/requirementsListResponse" + getComplianceDocumentResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/getComplianceDocumentResponse" + listEndUserTypesResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/endUserTypesListResponse" + listDocumentTypesResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/documentTypesListResponse" + unauthorizedEUMSError: + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/unauthorizedComplianceResponse" + validateNumberActivationResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/numberActivationValidatorResponse" + getRequirementsPackageHistoryResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/requirementsPackageHistoryResponse" + createRequirementsPackageAssetResponse: + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/requirementsPackageAssetsPostResponse" + listRequirementsPackageResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/requirementsPackageListResponse" + getRequirementsPackageAssetsResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/requirementsPackageAssetsGetResponse" + updateRequirementsPackageResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/requirementsPackageCreateUpdateResponse" + examples: + requirementsPackageResponseExample: + $ref: "#/components/examples/requirementsPackageResponseExample" + createRequirementsPackageResponse: + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/requirementsPackageCreateUpdateResponse" + examples: + requirementsPackageResponseExample: + $ref: "#/components/examples/requirementsPackageResponseExample" + getRequirementsPackageResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/requirementsPackageGetResponse" + examples: + requirementsPackageResponseExample: + $ref: "#/components/examples/requirementsPackageResponseExample" + schemas: + sortEnum: + type: string + enum: + - ASC + - DESC + geoValidationStatusEnum: + type: string + enum: + - GEO_VALID + - NOT_GEO_VALID + - NOT_GEO_VALIDATED + description: | + The geo validation status of the address. + - `GEO_VALID`: The address is valid and geo validated. + - `NOT_GEO_VALID`: The address is not valid or geo validated. + - `NOT_GEO_VALIDATED`: The address has not been geo validated yet. + endUserTypeEnum: + type: string + description: The type of end user + enum: + - BUSINESS + - RESIDENTIAL + - SOLE_PROPRIETOR + example: BUSINESS + phoneNumberTypeEnum: + type: string + description: The type of phone number. + enum: + - GEOGRAPHIC + - NATIONAL + - MOBILE + - TOLL_FREE + - SHARED_COST + example: GEOGRAPHIC + createAddressRequestData: + allOf: + - $ref: "#/components/schemas/validateAddressRequestData" + - type: object + properties: + geoValidation: + $ref: "#/components/schemas/geoValidation" + addressRequestData: + type: object + properties: + customReference: + $ref: "#/components/schemas/customReference" + countryCodeA3: + $ref: "#/components/schemas/countryCodeA3" + addressLine1: + $ref: "#/components/schemas/addressLine1" + addressLine2: + $ref: "#/components/schemas/addressLine2" + city: + $ref: "#/components/schemas/addressCity" + stateAbbreviation: + $ref: "#/components/schemas/stateAbbreviation" + postalCode: + $ref: "#/components/schemas/postalCode" + addressId: + type: string + description: Address ID + example: daa9dd0f-de97-4103-8530-b31bf4be8fc0 + customReference: + type: string + minLength: 1 + maxLength: 100 + description: | + A custom reference name. + It can be used for your own reference to the item to easily identify one of your customers, + requirements package, enduser etc. + example: home_office + countryCodeA3: + type: string + description: Country code of the address in ISO 3166-1 alpha-3 format. + example: USA + addressLine1: + type: string + description: The first line of the address. + example: 123 Main St + addressLine2: + type: string + description: The second line of the address. + example: Suite 200 + stateAbbreviation: + type: string + description: The state abbreviation of the address. + example: WA + geoValidation: + type: object + properties: + required: + type: boolean + description: | + Flag to indicate if the address should be geo validated or + not. By default the value is set to true. + example: true + createOnFailure: + type: boolean + description: | + Flag to indicate if the address should be created even if geo + validation fails. 'false' indicates that the creation request will fail + and “suggestedAddresses” will be provided in the response. By default + the value is set to false. + example: false + unsupportedFeatures: + description: Excluded Features + type: array + items: + type: object + properties: + featureName: + $ref: "#/components/schemas/featureNameEnum" + addressCriteria: + type: array + items: + type: object + properties: + description: + type: string + description: | + A human-readable explanation that SHOULD be specific + to this occurrence of the problem + example: | + Field 'province' is optional, but is required for the feature 'REQUIREMENTS_PACKAGE' + featureNameEnum: + type: string + description: A unique identifier for feature. + enum: + - REQUIREMENTS_PACKAGE + - EMERGENCY + - PORTING + addressFieldSchema: + type: object + properties: + fieldName: + type: string + description: The address field to be provided. + example: postalCode + friendlyName: + type: string + description: The friendly name of the address field. + example: Postal code + description: + type: string + description: The description of the address field. + example: | + Enter the 5-digit ZIP code for your location. You can also enter + a 9-digit ZIP+4 code. For example, 27607 or 27607-6789 are valid ZIP codes. + type: + $ref: "#/components/schemas/fieldType" + maxLength: + $ref: "#/components/schemas/fieldMaxLength" + required: + type: boolean + description: The address field is required or optional + example: true + changeable: + type: boolean + description: The address field value can be updated or not + example: false + usedBy: + type: array + items: + type: object + properties: + feature: + type: string + description: Feature name + example: REQUIREMENTS_PACKAGE + required: + type: boolean + description: The address field is required or optional for the feature + example: true + validateAddressRequestData: + type: object + allOf: + - $ref: "#/components/schemas/addressRequestData" + - type: object + properties: + geoValidation: + $ref: "#/components/schemas/geoValidation" + updateAddressData: + type: object + properties: + customReference: + $ref: "#/components/schemas/customReference" + geoValidation: + $ref: "#/components/schemas/geoValidation" + validateAddressResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: object + properties: + address: + $ref: "#/components/schemas/validateAddressResponseData" + geoValidationStatus: + $ref: "#/components/schemas/geoValidationStatusEnum" + excludedFeatures: + $ref: "#/components/schemas/unsupportedFeatures" + errors: + type: array + items: + $ref: "#/components/schemas/error" + getAddressFieldsResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: object + properties: + addressFields: + type: array + items: + $ref: "#/components/schemas/addressFieldSchema" + errors: + type: array + items: + $ref: "#/components/schemas/error" + addressCity: + type: string + description: The city of the address. + example: Seattle + fieldType: + type: string + description: The data type of the field + example: string + fieldMaxLength: + type: integer + description: The length of the field + example: 256 + postalCode: + type: string + description: The postal code of the address. + example: "98104" + createdDateTime: + type: string + description: The date and time this object was created in ISO 8601 format + example: "2024-03-11T04:09:25.399Z" + updatedDateTime: + type: string + description: The date and time this object was last updated in ISO 8601 format + example: "2024-03-11T04:09:25.399Z" + errorCode: + type: integer + format: int32 + description: | + An application-specific error code for services with extensive + error scenarios to supplement `description` + minimum: 4 + example: 51130 + errorDescription: + type: string + description: | + A human-readable explanation that SHOULD be specific to this occurrence + of the problem + example: There was an issue with a field in your request body + errorType: + type: string + description: | + A short, human-readable summary of the problem that SHOULD NOT + change from occurrence to occurrence of the problem + example: REQUEST_ERROR + errorSource: + type: object + properties: + parameter: + type: string + description: A string indicating which URI query parameter caused the error + example: someParameter + field: + type: string + description: A string indicating which request body field caused the error + example: someField + header: + type: string + description: A string indicating which header field caused the error + example: someHeader + reference: + type: string + description: | + A string that references a resource ID or path to the resource + (or non-existent resource) causing the error + example: /some/reference + queryParamStringEqStartsWith: + type: object + properties: + eq: + type: string + startsWith: + type: string + queryParamStringContains: + type: object + properties: + contains: + type: string + queryParamStringEqStartsWithContains: + type: object + properties: + contains: + type: string + eq: + type: string + startsWith: + type: string + queryParamStringEq: + type: object + properties: + eq: + type: string + genericError: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: object + nullable: true + errors: + type: array + items: + $ref: "#/components/schemas/error" + required: + - links + - data + - errors + updateGeoValidationStatusSuggestionAddressResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: object + properties: + address: + $ref: "#/components/schemas/validateAddressResponseData" + errors: + type: array + items: + $ref: "#/components/schemas/error" + example: + - code: 205617 + description: | + Address provided is not geo-valid. + Please refer suggestedAddresses for a list of geo valid addresses + meta: + suggestedAddresses: + - city: "SEATTLE" + countryCodeA3: USA + stateAbbreviation: WA + postalCode: "98104" + addressLine1: 123 S MAIN ST STE 200 + createUpdateAddressResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: object + properties: + address: + $ref: "#/components/schemas/addressResponseData" + errors: + type: array + items: + $ref: "#/components/schemas/error" + listAddressesResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: array + items: + $ref: "#/components/schemas/listAddressesResponseData" + errors: + type: array + items: + $ref: "#/components/schemas/error" + genericAddressResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: object + properties: + address: + $ref: "#/components/schemas/addressResponseData" + errors: + type: array + items: + $ref: "#/components/schemas/error" + listCityInfoResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: array + items: + $ref: "#/components/schemas/cityInfoData" + errors: + type: array + items: + $ref: "#/components/schemas/error" + addressResponseData: + allOf: + - $ref: "#/components/schemas/validateAddressResponseData" + - type: object + properties: + geoValidationStatus: + $ref: "#/components/schemas/geoValidationStatusEnum" + addressId: + $ref: "#/components/schemas/addressId" + createdDateTime: + $ref: "#/components/schemas/createdDateTime" + updatedDateTime: + $ref: "#/components/schemas/updatedDateTime" + listAddressesResponseData: + type: object + properties: + address: + $ref: "#/components/schemas/addressResponseData" + validateAddressResponseData: + type: object + properties: + customReference: + $ref: "#/components/schemas/customReference" + countryCodeA3: + $ref: "#/components/schemas/countryCodeA3" + addressLine1: + $ref: "#/components/schemas/addressLine1" + addressLine2: + $ref: "#/components/schemas/addressLine2" + city: + $ref: "#/components/schemas/addressCity" + stateAbbreviation: + $ref: "#/components/schemas/stateAbbreviation" + postalCode: + $ref: "#/components/schemas/postalCode" + cityInfoData: + type: array + items: + type: object + properties: + city: + type: string + description: The name of the city. + example: Seattle + postalCode: + type: string + description: The postal code of the address. + example: "98104" + link: + description: The URI link details to self or a related resource with the relation + type: object + properties: + href: + type: string + description: URI of the link + example: /resource/uri + rel: + type: string + description: Specifies the relationship between this link and the resource + example: self + method: + type: string + description: HTTP method to be used + example: GET + required: + - href + - rel + - method + error: + type: object + properties: + id: + type: string + description: A unique identifier for this particular instance of this problem + example: optional-error-id + type: + $ref: "#/components/schemas/errorType" + description: + $ref: "#/components/schemas/errorDescription" + code: + $ref: "#/components/schemas/errorCode" + meta: + type: object + description: A meta object containing application-specific information or + non-standard metadata about the error + source: + $ref: "#/components/schemas/errorSource" + required: + - code + - description + addressBadRequest: + description: addressBadRequest + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: object + nullable: true + errors: + type: array + items: + $ref: "#/components/schemas/error" + complianceDocumentUpdateData: + description: Metadata of updated document + type: object + properties: + customReference: + $ref: "#/components/schemas/customReference" + description: + $ref: "#/components/schemas/documentDescription" + fields: + $ref: "#/components/schemas/fields" + endUserResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + $ref: "#/components/schemas/endUserData" + errors: + $ref: "#/components/schemas/errors" + endUserListResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: array + items: + $ref: "#/components/schemas/endUserData" + errors: + $ref: "#/components/schemas/errors" + createUpdateComplianceDocumentResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + $ref: "#/components/schemas/complianceDocumentResponseData" + errors: + $ref: "#/components/schemas/errors" + requirementsListResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: array + items: + $ref: "#/components/schemas/requirementData" + errors: + $ref: "#/components/schemas/errors" + getComplianceDocumentResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + $ref: "#/components/schemas/complianceDocumentResponseData" + errors: + $ref: "#/components/schemas/errors" + endUserTypesListResponse: + description: Response with all End user type details + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: array + items: + $ref: "#/components/schemas/endUserTypeData" + errors: + $ref: "#/components/schemas/errors" + documentTypesListResponse: + description: Response with all document type details + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: array + items: + $ref: "#/components/schemas/documentTypeData" + errors: + $ref: "#/components/schemas/errors" + complianceDocumentResponseData: + allOf: + - $ref: "#/components/schemas/complianceDocumentData" + - type: object + properties: + documentId: + $ref: "#/components/schemas/documentId" + status: + $ref: "#/components/schemas/documentStatusEnum" + fileContentExists: + $ref: "#/components/schemas/fileContentExists" + createdDateTime: + $ref: "#/components/schemas/createdDateTime" + updatedDateTime: + $ref: "#/components/schemas/updatedDateTime" + fileName: + $ref: "#/components/schemas/fileName" + complianceDocumentData: + type: object + properties: + customReference: + $ref: "#/components/schemas/customReference" + type: + $ref: "#/components/schemas/complianceDocumentType" + description: + $ref: "#/components/schemas/complianceDocumentDescription" + fields: + $ref: "#/components/schemas/fields" + fileContentExists: + type: boolean + description: Indicates if file exists on system + example: false + documentId: + type: string + description: Unique identifier of the document + format: uuid + example: daa9dd0f-de97-4103-8530-b31bf4be8fc0 + documentStatusEnum: + type: string + description: Document Statuses + enum: + - DRAFT + - SUBMITTED + - PROCESSING + - VERIFIED + - VERIFICATION_FAILED + example: SUBMITTED + complianceDocumentType: + type: string + description: Unique name representing the type of document + example: tradeLicense + documentDescription: + type: string + description: Description about the document + example: Letter of Authorization + complianceDocumentDescription: + type: string + description: Description about the document + maxLength: 1000 + example: Trade License + fileName: + type: string + queryParamStringStartsWith: + type: object + properties: + startsWith: + type: string + unauthorizedComplianceResponse: + type: object + properties: + timestamp: + type: integer + format: int64 + description: Epoch timestamp of the error occurrence. + example: 1742186541633 + status: + type: integer + example: 401 + description: HTTP status code. + error: + type: string + description: Error message. + example: Unauthorized + path: + type: string + description: The request path that caused the error. + example: /api/v2/self-resource + required: + - timestamp + - status + - error + - path + requirementData: + type: object + properties: + countryCodeA3: + $ref: "#/components/schemas/countryCodeA3String" + phoneNumberType: + $ref: "#/components/schemas/phoneNumberTypeEnum" + endUserType: + $ref: "#/components/schemas/endUserTypeEnum" + requirement: + $ref: "#/components/schemas/requirement" + endUserTypeData: + description: EndUser type specification + type: object + properties: + type: + $ref: "#/components/schemas/endUserTypeEnum" + fields: + description: List of all accepted End user fields + type: array + items: + $ref: "#/components/schemas/endUserField" + example: + - fieldName: salutation + friendlyName: Salutation + type: String + values: + - MR + - MRS + required: true + - fieldName: firstName + friendlyName: First Name + type: String + maxLength: 300 + required: true + - friendlyName: Tax ID Number + fieldName: taxIdNumber + type: String + maxLength: 300 + required: false + endUserData: + type: object + properties: + type: + $ref: "#/components/schemas/endUserTypeEnum" + customReference: + $ref: "#/components/schemas/customReference" + fields: + $ref: "#/components/schemas/fields" + endUserId: + $ref: "#/components/schemas/endUserId" + status: + $ref: "#/components/schemas/endUserStatusEnum" + createdDateTime: + $ref: "#/components/schemas/createdDateTime" + updatedDateTime: + $ref: "#/components/schemas/updatedDateTime" + endUserId: + type: string + description: Unique identifier of the endUser + format: uuid + readOnly: true + example: dcb9dd0f-de97-4103-8530-b31bf4be8fc0 + endUserStatusEnum: + type: string + description: End user status + enum: + - DRAFT + - SUBMITTED + - VERIFIED + - VERIFICATION_FAILED + example: DRAFT + countryCodeA3String: + type: string + description: Country code in ISO 3166-1 alpha-3 format + example: FRA + errors: + type: array + items: + $ref: "#/components/schemas/error" + nullable: true + requirement: + description: The details of different requirements + type: object + properties: + endUser: + $ref: "#/components/schemas/endUserRequirement" + address: + $ref: "#/components/schemas/addressRequirement" + supportingDocuments: + description: List of all document requirements + type: array + items: + $ref: "#/components/schemas/supportingDocumentsRequirement" + endUserRequirement: + description: EndUser requirement specification + type: object + properties: + type: + $ref: "#/components/schemas/endUserTypeEnum" + fields: + description: List of all accepted end user fields + type: array + items: + $ref: "#/components/schemas/endUserField" + addressRequirement: + description: Address requirement specification + type: object + properties: + location: + $ref: "#/components/schemas/addressLocationEnum" + addressLocationEnum: + type: string + description: The address location + enum: + - LOCAL + - NATIONAL + - WORLDWIDE + example: NATIONAL + supportingDocumentsRequirement: + description: Document requirement specification + type: object + properties: + name: + description: The name of the document + type: string + example: Business registration + type: + $ref: "#/components/schemas/supportingDocumentTypeEnum" + description: + description: A small description about the document + type: string + example: The business registration document + acceptedDocuments: + description: List of document types accepted against the supporting document + type: array + items: + $ref: "#/components/schemas/documentTypeData" + supportingDocumentTypeEnum: + type: string + description: The type of the supporting document + enum: + - ADDRESS + - IDENTITY + example: ADDRESS + documentTypeData: + description: Document type specification + type: object + properties: + type: + description: Unique name indicating the type of the document + type: string + example: businessRegistration + friendlyName: + description: Human readable description of the document + type: string + example: Business registration + fields: + description: List of all accepted document fields + type: array + items: + $ref: "#/components/schemas/endUserField" + example: + - fieldName: companyName + friendlyName: Company Name + type: String + maxLength: 500 + - fieldName: issueDate + friendlyName: Issue Date + type: Date + fields: + type: object + description: Field is a key value pair of attribute name and value. All Date + or Number type values should be provided as a 'string'. Format for the Date + type is YYYY-MM-DD. + additionalProperties: + type: string + example: + firstName: First Name + issueDate: "2020-11-23" + validity: "120" + endUserField: + description: Field information + type: object + properties: + friendlyName: + type: string + description: Human readable description of the field + example: Salutation + fieldName: + type: string + description: Unique name of the field + example: salutation + type: + type: string + description: The data type of the field + example: String + values: + type: array + description: Provides accepted values for the field. Not Applicable when type value is 'Date'. + items: + type: string + example: + - Mr. + - Mrs. + required: + description: Specifies if the field is mandatory + type: boolean + example: true + maxLength: + description: Maximum allowed character length of the field. Not Applicable + when type value is 'Date' or values are provided. + type: integer + example: 10 + + + + numberActivationValidatorResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + $ref: "#/components/schemas/numberActivationValidatorResult" + errors: + $ref: "#/components/schemas/errors" + requirementsPackageHistoryResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: array + items: + $ref: "#/components/schemas/requirementsPackageHistoryData" + errors: + $ref: "#/components/schemas/errors" + requirementsPackageAssetsPostResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + $ref: "#/components/schemas/assetData" + errors: + $ref: "#/components/schemas/errors" + requirementsPackageListResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: array + items: + $ref: "#/components/schemas/requirementsPackageData" + errors: + $ref: "#/components/schemas/errors" + requirementsPackageAssetsGetResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + type: array + items: + $ref: "#/components/schemas/assetGetData" + errors: + $ref: "#/components/schemas/errors" + requirementsPackageCreateUpdateResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + $ref: "#/components/schemas/requirementsPackageData" + errors: + $ref: "#/components/schemas/errors" + requirementsPackageGetResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + data: + $ref: "#/components/schemas/requirementsPackageData" + errors: + $ref: "#/components/schemas/errors" + numberActivationValidatorResult: + description: | + Details of requirements for number activation validation. + Activatable indicates the TN is valid and can be provisioned or linked to a service. + Not-Activatable indicates the TN cannot be linked for activation. + type: object + properties: + requirementsSets: + type: array + items: + allOf: + - $ref: "#/components/schemas/numberActivationRequirementsData" + - type: object + properties: + status: + $ref: '#/components/schemas/requirementsPackageStatusEnum' + result: + description: Details of results of number activation validation + type: object + properties: + activatable: + $ref: "#/components/schemas/numberActivationResultData" + notActivatable: + $ref: "#/components/schemas/numberActivationResultData" + assetData: + description: Asset information with associated asset data + type: object + properties: + assetId: + description: Unique id of the asset + readOnly: true + type: string + example: 93ac73e8-20a7-95ec-bd32-4e8270e66c31 + assetType: + $ref: "#/components/schemas/assetTypeEnum" + assetReferenceId: + description: Id of the referenced asset object + type: string + example: 05ee3231-ad80-4fc5-9c13-68ffc26846a5 + assetReferenceData: + description: Details of the referenced asset object. It can corresponds + to an address, endUser or document + readOnly: true + oneOf: + - $ref: "#/components/schemas/createAddressRequestData" + - $ref: "#/components/schemas/complianceDocumentResponseData" + - $ref: "#/components/schemas/endUserData" + createdDateTime: + allOf: + - $ref: "#/components/schemas/createdDateTime" + readOnly: true + updatedDateTime: + allOf: + - $ref: "#/components/schemas/updatedDateTime" + readOnly: true + assetTypeEnum: + type: string + description: Type of the asset + enum: + - ADDRESS + - END_USER + - DOCUMENT + example: ADDRESS + assetGetData: + description: Asset information with associated asset data + type: object + properties: + assetId: + description: Unique id of the asset + readOnly: true + type: string + example: 93ac73e8-20a7-95ec-bd32-4e8270e66c31 + assetReferenceId: + description: Id of the referenced asset object + type: string + example: 05ee3231-ad80-4fc5-9c13-68ffc26846a5 + assetType: + $ref: "#/components/schemas/assetTypeEnum" + assetReferenceData: + description: Details of the referenced asset object. It can corresponds + to an address, endUser or document + readOnly: true + oneOf: + - $ref: "#/components/schemas/addressResponseAssetData" + - $ref: "#/components/schemas/endUserResponseAssetData" + - $ref: "#/components/schemas/complianceDocumentResponseData" + createdDateTime: + allOf: + - $ref: "#/components/schemas/createdDateTime" + readOnly: true + updatedDateTime: + allOf: + - $ref: "#/components/schemas/updatedDateTime" + readOnly: true + requirementsPackageHistoryData: + description: History of the Requirements package + type: object + properties: + status: + $ref: "#/components/schemas/requirementsPackageStatusEnum" + customReference: + type: string + description: The customReference of the requirements package + email: + type: string + format: email + description: The email provided as part of the requirements + callbacks: + type: string + format: uri + description: The callback provided as part of the requirements + dateTime: + type: string + format: date-time + description: The date and time of the requirements package event in ISO 8601 format + example: "2015-03-11T04:09:25.399Z" + author: + type: string + description: The name of the user who performed the requirements package event + example: Bandwidth User + remarks: + type: string + description: The remarks, if any, provided by the author along with the requirements package event + maxLength: 5000 + example: Remarks provided by user + allDetailsAccurate: + type: boolean + description: The confirmation on accuracy of data submitted at the time of the requirements package event + example: true + requirementsPackageStatusEnum: + type: string + description: Status of the Requirements package + enum: + - DRAFT + - SUBMITTED + - VERIFIED + - VERIFICATION_FAILED + - DISABLED + - AUTO_VALIDATED + example: SUBMITTED + requirementsPackageData: + description: Details of the Requirements package + type: object + properties: + requirementsPackageId: + allOf: + - $ref: "#/components/schemas/requirementsPackageId" + readOnly: true + countryCodeA3: + $ref: "#/components/schemas/countryCodeA3String" + phoneNumberType: + $ref: "#/components/schemas/phoneNumberTypeEnum" + endUserType: + $ref: "#/components/schemas/endUserTypeEnum" + customReference: + $ref: "#/components/schemas/customReference" + email: + $ref: "#/components/schemas/email" + callback: + $ref: "#/components/schemas/callbackUrl" + acknowledgements: + $ref: "#/components/schemas/requirementsPackageAcknowledgements" + status: + allOf: + - $ref: '#/components/schemas/requirementsPackageStatusEnum' + readOnly: true + createdDateTime: + allOf: + - $ref: "#/components/schemas/createdDateTime" + readOnly: true + updatedDateTime: + allOf: + - $ref: "#/components/schemas/updatedDateTime" + readOnly: true + remarks: + $ref: "#/components/schemas/remarks" + numberActivationResultData: + description: Details of result of a number activation + type: object + properties: + areaCodes: + type: array + items: + $ref: "#/components/schemas/areaCode" + reasons: + type: array + items: + type: string + remarks: + type: string + description: Remarks provided by the user or admin + maxLength: 5000 + readOnly: true + example: Remarks provided by Admin + callbackUrl: + description: Callback URL + type: string + example: http://somehost.com/new-callback + email: + type: string + format: email + minLength: 0 + maxLength: 500 + pattern: + ^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$ + example: foo@bar.com + requirementsPackageAcknowledgements: + description: Captures the acknowledgments on requirements package submission. Mandatory when status is provided + type: object + properties: + allDetailsAccurate: + type: boolean + description: Confirms that all provided information is accurate and valid + for the end user attached to the requirements package. + example: true + addressResponseAssetData: + type: object + properties: + addressId: + $ref: "#/components/schemas/addressId" + city: + $ref: "#/components/schemas/addressCity" + countryCodeA3: + $ref: "#/components/schemas/countryCodeA3" + postalCode: + $ref: "#/components/schemas/postalCode" + addressLine1: + $ref: "#/components/schemas/addressLine1" + addressLine2: + $ref: "#/components/schemas/addressLine2" + stateAbbreviation: + $ref: "#/components/schemas/stateAbbreviation" + customReference: + $ref: "#/components/schemas/customReference" + geoValidationStatus: + $ref: "#/components/schemas/geoValidationStatusEnum" + createdDateTime: + $ref: "#/components/schemas/createdDateTime" + updatedDateTime: + $ref: "#/components/schemas/updatedDateTime" + endUserResponseAssetData: + type: object + properties: + type: + $ref: "#/components/schemas/endUserTypeEnum" + customReference: + $ref: "#/components/schemas/customReference" + fields: + $ref: "#/components/schemas/fields" + endUserId: + $ref: "#/components/schemas/endUserId" + status: + $ref: "#/components/schemas/endUserStatusEnum" + createdDateTime: + $ref: "#/components/schemas/createdDateTime" + updatedDateTime: + $ref: "#/components/schemas/updatedDateTime" + numberActivationRequirementsSet: + description: A set of number activation requirements + type: object + properties: + requirementsSets: + type: array + items: + $ref: "#/components/schemas/numberActivationRequirementsData" + required: + - requirementsSets + numberActivationRequirementsData: + description: Details of requirements for number activation + type: object + properties: + requirementsPackageId: + $ref: "#/components/schemas/requirementsPackageId" + countryCodeA3: + $ref: "#/components/schemas/countryCodeA3String" + phoneNumberType: + $ref: "#/components/schemas/phoneNumberTypeEnum" + referenceId: + type: string + description: A unique reference id to the requirement set + example: requirementsSetReference + areaCodes: + type: array + items: + $ref: "#/components/schemas/areaCode" + example: + - '454' + - '456' + - '794' + - '796' + required: + - requirementsPackageId + - countryCodeA3 + - phoneNumberType + - areaCodes + areaCode: + type: string + description: The 1 to 6 digit area code associated with the phone number. + example: "435" + requirementsPackageId: + type: string + description: Unique identifier for requirements package + example: 93ac73e8-20a7-95ec-bd32-4e8270e66c31 + examples: + geoValidationAddressSuggestionExample: + summary: Example of Geo-validation suggestion address + value: + links: [] + data: null + errors: + - description: | + Address provided is not geo-valid. + Please refer suggestedAddresses for a list of geo valid addresses + code: 205617 + meta: + suggestedAddresses: + - countryCodeA3: USA + addressLine1: Main Street, 123 + city: Seattle + stateAbbreviation: CA + postalCode: "67890" + - countryCodeA3: USA + addressLine1: 456 Elm Street + city: Seattle + stateAbbreviation: NC + postalCode: 67890 + geoValidationDisabledExample: + summary: Example of Geo-validation not enabled on account + value: + links: [] + data: null + errors: + - type: bad-request + description: Geo-validation is not enabled for this account. Please contact customer support. + code: 20011 + badRequestErrorExample: + summary: Bad Request Error Example + value: + links: [] + data: null + errors: + - type: bad-request + description: There was an issue with your request. + code: 205617 + unauthorizedErrorExample: + summary: Unauthorized Error Example + value: + links: [] + data: null + errors: + - type: authentication-credentials + description: Invalid or missing credentials. + code: 206401 + forbiddenErrorExample: + summary: Unauthorized Error Example + value: + links: [] + data: null + errors: + - type: resource-permissions + description: User does not have permissions to access this resource. + code: 109107 + notFoundErrorExample: + summary: Not Found Error Example + value: + links: [] + data: null + errors: + - type: resource-not-found + description: The resource specified cannot be found. + code: 50420 + methodNotAllowedErrorExample: + summary: An example of a generic Not Allowed Error Example + value: + links: [] + data: null + errors: + - type: http-method-not-supported + description: The HTTP method used is not supported by this resource. + code: 205621 + meta: + method: TRACE + tooManyRequestsErrorExample: + summary: Too Many Requests Error Example + value: + links: [] + data: null + errors: + - type: rate-limiting + description: | + Rate limit exceeded. Wait for the time specified in the "Retry-After" + header before sending another request. + code: 32002 + internalServerErrorExample: + summary: Internal Server Error Example + value: + links: [] + data: null + errors: + - type: internal-server-error + description: | + Unexpected internal server error. Contact Bandwidth Customer + Support if this problem persists. + code: 32030 + patchEndUserBodyBasicExample: + summary: Update customReference, add or modify fields + value: + customReference: "New custom reference" + fields: + companyVat: "AB123456" + dateOfBirth: "1992-11-23" + firstName: "First Name" + postRequirementsPackageAssetBodyBasicExample: + summary: Attach an asset to requirement package + value: + assetReferenceId: 05ee3231-ad80-4fc5-9c13-68ffc26846a5 + assetType: ADDRESS + requirementsPackageResponseExample: + summary: Requirements Package Response Example + value: + links: + - href: https://api.bandwidth.com/api/v2/accounts/1/compliance/requirementsPackages/46bd4a91-582d-442f-bd1b-bcf17ca7fc6a + rel: self + method: GET + data: + requirementsPackageId: 93ac73e8-20a7-95ec-bd32-4e8270e66c31 + countryCodeA3: FRA + phoneNumberType: GEOGRAPHIC + endUserType: BUSINESS + customReference: home_office + email: foo@bar.com + callback: http://somehost.com/new-callback + acknowledgements: + allDetailsAccurate: true + status: SUBMITTED + createdDateTime: "2024-03-11T04:09:25.399Z" + updatedDateTime: "2024-03-11T04:09:25.399Z" + remarks: Remarks provided by Admin + errors: [] + securitySchemes: + Basic: + type: http + scheme: basic + description: |- + Basic authentication is a simple authentication scheme built into the HTTP protocol. To use it, send your HTTP requests with an `Authorization` header that contains the word `Basic` followed by a space and a Base64-encoded string `username:password`. + + - Example: `Authorization: Basic ZGVtbZpwQDU1dzByZA==` + Bearer: + type: http + scheme: bearer + bearerFormat: JWT + description: |- + Bearer authentication (also called token authentication) is an HTTP authentication scheme that involves security tokens called bearer tokens. The name “Bearer authentication” can be understood as “give access to the bearer of this token.” The client must send this token in the `Authorization` header when making requests to protected resources. + + - Example: `Authorization: Bearer ` + + Where `` should be replaced with the token string without angle brackets. + + For more information, see the following guide: [Credentials](https://dev.bandwidth.com/docs/credentials) +tags: + - name: Addresses + - name: Compliance + - name: Requirements Packages +security: + - Basic: [] + - Bearer: [] diff --git a/test/fixtures/insights.yml b/test/fixtures/insights.yml new file mode 100644 index 0000000..6e73731 --- /dev/null +++ b/test/fixtures/insights.yml @@ -0,0 +1,1244 @@ +openapi: 3.0.1 +info: + title: Insights + contact: + name: Bandwidth + version: 0.1.2 + termsOfService: https://www.bandwidth.com/legal/terms-of-use-bandwidthcom-web-sites/ + description: |- + The API specification for Bandwidth's Insights platform. + + ## Base URL + `https://insights.bandwidth.com/api` +servers: + - url: https://insights.bandwidth.com/api +tags: + - name: Voice + description: Call Logs + - name: Reporting + description: Reporting Framework +paths: + /v1/voice/calls: + get: + tags: + - Voice + summary: List Calls + description: Returns a list of call events. + operationId: listCalls + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/callId" + - $ref: "#/components/parameters/startTime" + - $ref: "#/components/parameters/endTime" + - $ref: "#/components/parameters/callingNumber" + - $ref: "#/components/parameters/calledNumber" + - $ref: "#/components/parameters/callDirection" + - $ref: "#/components/parameters/callType" + - $ref: "#/components/parameters/callResult" + - $ref: "#/components/parameters/hangUpSource" + - $ref: "#/components/parameters/sipResponseCode" + - $ref: "#/components/parameters/subAccount" + - $ref: "#/components/parameters/locationId" + - $ref: "#/components/parameters/sourceCountryCodeA3" + - $ref: "#/components/parameters/destinationCountryCodeA3" + - $ref: "#/components/parameters/sourceIp" + - $ref: "#/components/parameters/destinationIp" + - $ref: "#/components/parameters/qualityStatus" + - $ref: "#/components/parameters/sort" + - $ref: "#/components/parameters/offset" + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/region" + responses: + "200": + $ref: "#/components/responses/listCallsResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotFoundError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /v1/voice/calls/{callId}: + get: + tags: + - Voice + summary: Get Call + description: Returns a single call event. + operationId: listCall + parameters: + - $ref: "#/components/parameters/callIdPath" + - $ref: "#/components/parameters/region" + responses: + "200": + $ref: "#/components/responses/getCallResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotFoundError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /v1/reports: + post: + tags: + - Reporting + description: Create a new report instance. + operationId: createReport + summary: Create Report + requestBody: + $ref: "#/components/requestBodies/createReportBody" + responses: + "200": + $ref: "#/components/responses/reportStatusResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotFoundError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + get: + tags: + - Reporting + description: Get a history of created reports. + operationId: getReports + summary: Get Reports + parameters: + - $ref: "#/components/parameters/accountIds" + - $ref: "#/components/parameters/categoryRequired" + responses: + "200": + $ref: "#/components/responses/reportHistoryResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotFoundError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /v1/reports/{reportId}: + get: + tags: + - Reporting + description: Get the status of a report. + operationId: getReportStatus + summary: Get Report Status + parameters: + - $ref: "#/components/parameters/reportIdPath" + responses: + "200": + $ref: "#/components/responses/reportStatusResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotFoundError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /v1/reports/{reportId}/file: + get: + tags: + - Reporting + description: Get the file associated with a report. + operationId: getReportFile + summary: Get Report File + parameters: + - $ref: "#/components/parameters/reportIdPath" + responses: + "200": + $ref: "#/components/responses/reportFileResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotFoundError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" + /v1/reportDefinitions: + get: + tags: + - Reporting + description: Get a list of report definitions. + operationId: getReportDefinitions + summary: Get Report Definitions + parameters: + - $ref: "#/components/parameters/category" + - $ref: "#/components/parameters/domain" + - $ref: "#/components/parameters/reportName" + responses: + "200": + $ref: "#/components/responses/reportDefinitionsResponse" + "400": + $ref: "#/components/responses/badRequestError" + "401": + $ref: "#/components/responses/unauthorizedError" + "403": + $ref: "#/components/responses/forbiddenError" + "404": + $ref: "#/components/responses/notFoundError" + "405": + $ref: "#/components/responses/methodNotFoundError" + "429": + $ref: "#/components/responses/tooManyRequestsError" + "500": + $ref: "#/components/responses/internalServerError" +components: + schemas: + reportStatusObject: + title: Create Report Request + type: object + properties: + category: + $ref: "#/components/schemas/category" + domain: + $ref: "#/components/schemas/domain" + reportName: + type: string + example: MDRs + accountIds: + type: array + items: + type: string + example: "123456" + example: [ "123456", "654321" ] + filters: + $ref: "#/components/schemas/filters" + region: + $ref: "#/components/schemas/region" + breakdown: + type: string + example: campaignId + description: The filter name to breakdown the report by. + reportStatus: + type: object + properties: + reportId: + type: string + example: 67a95525-4618-418b-ad8b-4ddd8562ad37 + format: uuid + requestedAt: + type: string + format: date-time + example: 2024-01-18T17:38:37.041444Z + completedAt: + type: string + format: date-time + example: 2024-01-18T17:38:37.041444Z + user: + type: string + example: Username + report: + $ref: "#/components/schemas/reportStatusObject" + status: + $ref: "#/components/schemas/status" + fileName: + type: string + example: report123.zip + reportDefinitionObject: + type: object + properties: + category: + $ref: "#/components/schemas/category" + domain: + $ref: "#/components/schemas/domain" + reportName: + type: string + example: MDRs + description: + type: string + example: Messaging details for particular snapshot date + regions: + type: array + items: + $ref: "#/components/schemas/region" + example: [ "US", "EU" ] + maxMonthsBack: + type: integer + example: 12 + minimum: 1 + maximum: 24 + filters: + type: array + items: + $ref: "#/components/schemas/rdoFilter" + breakdowns: + type: array + items: + type: object + properties: + value: + type: string + example: column_name + category: + type: string + enum: + - CHARGES + - NUMBER_DETAILS + - USAGE + example: USAGE + dropdownValues: + type: object + properties: + values: + type: array + items: + type: object + properties: + key: + type: string + example: column_name + domain: + type: string + enum: + - EMERGENCY + - MESSAGING + - VOICE + example: MESSAGING + operator: + type: string + enum: + - EQUAL + - GREATER + - LESSER + - LESSER_OR_EQUAL + - GREATER_OR_EQUAL + - LIKE + - IN + - STARTS_WITH + - BETWEEN + example: EQUAL + status: + type: string + enum: + - COMPLETED + - FAILED + - NO_RESULTS + - PROCESSING + example: COMPLETED + validationType: + type: string + enum: + - E164 + - REGEX + - ISO8601 + - ISO8601_RANGE + - STRING + - INTEGER + - ACCOUNT + example: E164 + filters: + description: Use the Get Report Definitions endpoint to see the filters available for a report. + type: object + additionalProperties: + type: array + items: + type: string + rdoFilter: + type: object + properties: + filterName: + type: string + example: My Filter + validationType: + $ref: "#/components/schemas/validationType" + dropdownValues: + type: array + items: + $ref: "#/components/schemas/dropdownValues" + operator: + $ref: "#/components/schemas/operator" + validationRegex: + type: string + example: '^\\d{10}$' + required: + type: boolean + example: true + call: + title: Call + type: object + properties: + accountId: + type: string + description: CRM ID of the account to get calls from. + example: "1234567" + callId: + type: string + description: Unique call identifier. + example: 1234567890abcdefghijklmnopqrstuvwxyz + startTime: + type: string + description: |- + Timestamp of when the call started, conforming to RFC 3339 format. + example: "2022-05-26T17:05:48.960Z" + endTime: + type: string + description: |- + Timestamp of when the call ended, conforming to RFC 3339 format. + example: "2022-05-26T17:27:44.400Z" + duration: + type: integer + nullable: true + description: Length of the call, measured in milliseconds. + example: 500000 + callingNumber: + type: string + nullable: true + description: |- + Phone number of the caller who initiated the call. + + Format: E.164 with '+' prefix + example: "+18185559876" + calledNumber: + type: string + nullable: true + description: |- + Phone number of the caller who received the call. + + Format: E.164 with '+' prefix + example: "+18185551234" + callDirection: + $ref: "#/components/schemas/callDirection" + callType: + $ref: "#/components/schemas/callType" + callResult: + $ref: "#/components/schemas/callResult" + sipResponseCode: + type: integer + description: Message generated by a user agent server (UAS) or SIP server to reply to a request generated by a client. A value of negative one (-1) indicates an unknown SIP response. + example: 200 + sipResponseDescription: + type: string + description: A short description of the SIP response. + example: "OK" + cost: + format: double + type: number + nullable: true + description: Estimated cost associated with the call. This may not match directly to your invoice as this is an estimate. If this value is null it is because the cost has not yet been calculated. Cost is usually available within a day of call completion. + example: 0.0001230 + subAccount: + type: string + nullable: true + description: Sub-Account ID. + example: "1234" + subAccountName: + type: string + nullable: true + description: Sub-Account Name. + example: "Account 1234" + locationId: + type: string + nullable: true + description: Location ID. + example: "1234" + locationName: + type: string + nullable: true + description: Location Name. + example: "Location 1234" + sourceCountryCodeA3: + type: string + nullable: true + description: Source Country in A3 format. + example: USA + destinationCountryCodeA3: + type: string + nullable: true + description: Destination Country in A3 format. + example: + USA + sourceIp: + type: string + nullable: true + description: Source ip in ipv4 or ipv6 format. + example: + 168.212.226.204 + destinationIp: + type: string + nullable: true + description: Destination ip in ipv4 or ipv6 format. + example: + 168.212.226.204 + customerJitter: + type: integer + nullable: true + description: Jitter experienced by the customer during the call, measured in milliseconds. + example: + 500 + customerLatency: + type: integer + nullable: true + description: Latency experienced by the customer during the call, measured in milliseconds. + example: + 500 + customerPacketLossPercentage: + type: number + nullable: true + description: Packet loss percentage experienced by the customer during the call. + example: + 50.05 + carrierJitter: + type: integer + nullable: true + description: Jitter experienced by the carrrier during the call, measured in milliseconds. + example: + 500 + carrierLatency: + type: integer + nullable: true + description: Latency experienced by the carrier during the call, measured in milliseconds. + example: + 500 + carrierPacketLossPercentage: + type: number + nullable: true + description: Packet loss percentage experienced by the carrier during the call. + example: + 50.05 + attestationIndicator: + $ref: "#/components/schemas/attestationIndicator" + x5u: + type: string + nullable: true + description: X5U URL of the certificate’s location. + example: "https://example.com/x5u" + hangUpSource: + $ref: "#/components/schemas/hangUpSource" + postDialDelay: + type: integer + description: How long it takes for a calling party to hear a ringback tone after initiating a call, measured in milliseconds. + nullable: true + example: 0 + packetsSent: + type: integer + description: Count of packets sent during transmission. + nullable: true + example: 0 + packetsReceived: + type: integer + description: Count of packets received during transmission. + nullable: true + example: 0 + genericError: + title: Generic Error + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + nullable: true + example: null + data: + type: object + nullable: true + example: null + errors: + type: array + items: + $ref: "#/components/schemas/error" + nullable: true + required: + - links + - data + - errors + callDirection: + type: string + description: Direction of call. + enum: + - INBOUND + - INBOUND-FORWARDED + - OUTBOUND + - OUTBOUND-FORWARDED + example: INBOUND + callType: + type: string + description: Type of call. + enum: + - EMERGENCY + - INBOUND-TFOOS + - INFORMATION + - INTERNATIONAL + - INTERNATIONAL-INTERNAL + - INTERSTATE + - INTRASTATE + - INTL-BLOCK + - LOCAL + - OPERATOR + - OTHER-N11 + - SIPURI-EXT + - TOLLFREE-IN + - TOLLFREE-OUT + - UNDETERMINED + example: LOCAL + callResult: + type: string + description: Call completion status. + enum: + - COMPLETE + - INCOMPLETE + example: COMPLETE + attestationIndicator: + type: string + description: STIR/SHAKEN attestation. + nullable: true + enum: + - A + - B + - C + example: A + hangUpSource: + type: string + description: Hang up source. + enum: + - BANDWIDTH_INTERNAL + - CALLED_PARTY + - CALLING_PARTY + example: CALLED_PARTY + countryCodeA3: + type: string + description: |- + Filter Type: Exact Match, Multimatch. + + Example: + + * Exact Match: USA + + * Multimatch: USA,IRL + + Format: two-letter (A3) country code . + ipAddress: + type: string + description: |- + Filter Type: Exact Match. + + Example: + + * Exact Match: 168.212.226.204 + * Exact Match: 2001%3Adb8%3A3333%3A4444%3A5555%3A6666%3A7777%3A8888 + + Format: ipv4, ipv6 format ( : is url encoded as %3A) + example: 168.212.226.204 + qualityStatus: + type: string + description: A combination of Jitter, Latency, and Packet Loss Percentage to determine the overall quality of the call. Quality Status filter is only available for accounts with Advanced Quality Metrics feature. + enum: + - GOOD + - AVERAGE + - BAD + example: GOOD + region: + type: string + enum: + - US + - EU + example: US + default: US + link: + title: Link + type: object + properties: + href: + type: string + description: URI of the link. + example: self + rel: + type: string + description: Specifies the relationship between this link and the resource. + example: https://insights.bandwidth.com/api/v1/resource + error: + title: Error + type: object + properties: + description: + type: string + description: A human-readable explanation that SHOULD be specific to this occurrence of the problem. + example: There was an issue with a field in your request body. + required: + - description + parameters: + accountIds: + name: accountIds + in: query + schema: + type: array + items: + type: string + categoryRequired: + name: category + in: query + required: true + schema: + type: string + enum: + - CHARGES + - NUMBER_DETAILS + - USAGE + category: + name: category + in: query + schema: + type: string + enum: + - CHARGES + - NUMBER_DETAILS + - USAGE + domain: + name: domain + in: query + schema: + type: string + enum: + - EMERGENCY + - MESSAGING + - VOICE + reportIdPath: + name: reportId + in: path + description: The ID of the report. + required: true + schema: + type: string + reportName: + name: reportName + in: query + schema: + type: string + accountId: + name: accountId + in: query + description: CRM ID of the account to get calls from. + schema: + type: string + example: "1234567" + callId: + name: callId + in: query + description: | + Exact Match, any valid call ID. + schema: + type: string + example: 12345678980abcdefghijklmnopqrstuvwxyz + callIdPath: + name: callId + in: path + required: true + description: |- + Filter Type: Exact Match, any valid call ID. + schema: + type: string + example: 12345678980abcdefghijklmnopqrstuvwxyz + startTime: + name: startTime + in: query + description: | + Filter Type: Range using gt, gte, lt, and lte. + + Note: If no startTime or endTime is specified, startTime will default to the last 24 hours. + schema: + type: string + example: | + gte:2022-05-26T12:00:00Z + endTime: + name: endTime + in: query + description: | + Filter Type: Range using gt, gte, lt, and lte. + schema: + type: string + example: |- + lte:2022-05-26T12:00:50Z + callingNumber: + name: callingNumber + in: query + description: |- + Filter Type: Exact Match, Multimatch, Prefix Match. + + Example: + + * Exact Match: 15555551234 + + * Multimatch: 15555551234,15555554321 + + * Prefix: 155555512* + + Format: E.164 with an optional leading '+' (URL encoded as %2B) + schema: + type: string + example: "%2B18185559876" + calledNumber: + name: calledNumber + in: query + description: |- + Filter Type: Exact Match, Multimatch, Prefix Match. + + Example: + + * Exact Match: 15555551234 + + * Multimatch: 15555551234,15555554321 + + * Prefix: 155555512* + + Format: E.164 with an optional leading '+' (URL encoded as %2B) + schema: + type: string + example: "%2B18185551234" + callDirection: + name: callDirection + in: query + description: |- + Filter Type: Exact Match, any valid call direction type. + schema: + $ref: "#/components/schemas/callDirection" + callType: + name: callType + in: query + description: |- + Filter Type: Exact Match, any valid call type. + schema: + $ref: "#/components/schemas/callType" + callResult: + name: callResult + in: query + description: |- + Filter Type: Exact Match, any valid call result type. + schema: + $ref: "#/components/schemas/callResult" + hangUpSource: + name: hangUpSource + in: query + description: |- + Filter Type: Exact Match, any valid hang up source. + schema: + $ref: "#/components/schemas/hangUpSource" + sipResponseCode: + name: sipResponseCode + in: query + description: |- + Filter Type: Exact Match. Examples: 200, 300, 400, 500. + schema: + type: integer + example: 200 + subAccount: + name: subAccount + in: query + description: |- + Filter Type: Exact Match. Examples: 1234,2345 + schema: + type: string + example: "1234" + locationId: + name: locationId + in: query + description: |- + Filter Type: Exact Match. Examples: 1234,2345 + schema: + type: string + example: "1234" + sourceCountryCodeA3: + name: sourceCountryCodeA3 + in: query + schema: + $ref: "#/components/schemas/countryCodeA3" + destinationCountryCodeA3: + name: destinationCountryCodeA3 + in: query + schema: + $ref: "#/components/schemas/countryCodeA3" + sourceIp: + name: sourceIp + in: query + schema: + $ref: "#/components/schemas/ipAddress" + destinationIp: + name: destinationIp + in: query + schema: + $ref: "#/components/schemas/ipAddress" + qualityStatus: + name: qualityStatus + in: query + schema: + $ref: "#/components/schemas/qualityStatus" + sort: + name: sort + in: query + description: |- + The field and direction to sort by, combined with a colon. Direction is one of asc, desc. + schema: + type: string + example: startTime:desc + offset: + name: offset + in: query + description: |- + Return records starting at the nth record. + schema: + type: integer + example: 1 + limit: + name: limit + in: query + description: |- + The maximum records to return per page. + schema: + type: integer + example: 50 + default: 100 + minimum: 1 + maximum: 10000 + region: + name: region + in: query + description: |- + Filter Type: Exact Match, any valid region. + schema: + $ref: "#/components/schemas/region" + requestBodies: + createReportBody: + content: + application/json: + schema: + $ref: "#/components/schemas/reportStatusObject" + responses: + reportStatusResponse: + description: OK + content: + application/json: + schema: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + example: + - rel: "reportDownload" + href: "https://insights.bandwidth.com/api/v1/reports/67a95525-4618-418b-ad8b-4ddd8562ad37/file" + data: + $ref: "#/components/schemas/reportStatus" + errors: + type: array + items: + $ref: "#/components/schemas/error" + example: [ ] + required: + - links + - data + - errors + reportFileResponse: + description: OK + content: + application/gzip: + schema: + type: string + format: binary + headers: + Content-Disposition: + schema: + type: string + example: attachment; filename="report123.zip" + Content-Length: + schema: + type: integer + example: 12345 + reportHistoryResponse: + description: OK + content: + application/json: + schema: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + example: [ ] + data: + type: object + properties: + totalCount: + type: integer + example: 1 + reports: + type: array + items: + $ref: "#/components/schemas/reportStatus" + errors: + type: array + items: + $ref: "#/components/schemas/error" + example: [ ] + required: + - links + - data + - errors + reportDefinitionsResponse: + description: OK + content: + application/json: + schema: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + example: [ ] + data: + type: object + properties: + reportDefinitions: + type: array + items: + $ref: "#/components/schemas/reportDefinitionObject" + errors: + type: array + items: + $ref: "#/components/schemas/error" + example: [ ] + required: + - links + - data + - errors + listCallsResponse: + description: OK + content: + application/json: + schema: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + example: + - rel: "next" + href: "https://insights.bandwidth.com/api/v1/voice/calls?callResult=completed&offset=1&limit=1&sort=startTime:desc" + nullable: true + data: + type: object + properties: + totalCount: + type: integer + description: The total count of objects returned in the response. + example: 1 + calls: + type: array + items: + $ref: "#/components/schemas/call" + nullable: true + errors: + type: array + items: + $ref: "#/components/schemas/error" + nullable: true + example: null + required: + - links + - data + - errors + getCallResponse: + description: OK + content: + application/json: + schema: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + nullable: false + example: [] + data: + $ref: "#/components/schemas/call" + errors: + type: array + items: + $ref: "#/components/schemas/error" + nullable: false + example: [] + required: + - links + - data + - errors + badRequestError: + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + badRequestErrorExample: + $ref: "#/components/examples/badRequestErrorExample" + unauthorizedError: + description: Unauthorized Account + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + unknownAccountErrorExample: + $ref: "#/components/examples/unauthorizedErrorExample" + forbiddenError: + description: Unknown Account + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + unknownAccountErrorExample: + $ref: "#/components/examples/forbiddenErrorExample" + notFoundError: + description: Method Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + unknownAccountErrorExample: + $ref: "#/components/examples/notFoundErrorExample" + methodNotFoundError: + description: Method Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + unknownAccountErrorExample: + $ref: "#/components/examples/methodNotFoundErrorExample" + tooManyRequestsError: + description: Too Many Requests Error + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + unknownAccountErrorExample: + $ref: "#/components/examples/tooManyRequestsErrorExample" + internalServerError: + description: Internal Server Error + content: + application/json: + schema: + $ref: "#/components/schemas/genericError" + examples: + unknownAccountErrorExample: + $ref: "#/components/examples/internalServerErrorExample" + examples: + badRequestErrorExample: + summary: An example of a generic Bad Request Error + value: + links: null + data: null + errors: + - description: "There was an issue with your request." + unauthorizedErrorExample: + summary: An example of a generic Unauthorized Account Error + value: + links: null + data: null + errors: + - description: "This is an Unauthorized Account." + forbiddenErrorExample: + summary: An example of a generic Unknown Account Error + value: + links: null + data: null + errors: + - description: "This is an Unknown Account." + notFoundErrorExample: + summary: An example of a generic Not Found Error + value: + links: null + data: null + errors: + - description: "The origin server did not find a current representation for the target resource or is not willing to disclose that one exists." + methodNotFoundErrorExample: + summary: An example of a generic Method Not Found Error + value: + links: null + data: null + errors: + - description: "POST is not an allowed method. Method must be GET" + tooManyRequestsErrorExample: + summary: An example of a generic Too Many Requests Error + value: + links: null + data: null + errors: + - description: "The user has sent too many requests in a given amount of time." + internalServerErrorExample: + summary: An example of a generic Internal Server Error + value: + links: null + data: null + errors: + - description: "The server encountered an unexpected condition that prevented it from fulfilling the request." + securitySchemes: + Basic: + type: http + scheme: basic + description: |- + Basic authentication is a simple authentication scheme built into the HTTP protocol. To use it, send your HTTP requests with an `Authorization` header that contains the word `Basic` followed by a space and a Base64-encoded string `username:password`. + + - Example: `Authorization: Basic ZGVtbZpwQDU1dzByZA==` + Bearer: + type: http + scheme: bearer + bearerFormat: JWT + description: |- + Bearer authentication (also called token authentication) is an HTTP authentication scheme that involves security tokens called bearer tokens. The name “Bearer authentication” can be understood as “give access to the bearer of this token.” The client must send this token in the `Authorization` header when making requests to protected resources. + + - Example: `Authorization: Bearer ` + + Where `` should be replaced with the token string without angle brackets. + + For more information, see the following guide: [Credentials](https://dev.bandwidth.com/docs/credentials) +security: + - Basic: [] + - Bearer: [] diff --git a/test/fixtures/messaging.yml b/test/fixtures/messaging.yml new file mode 100644 index 0000000..d97db28 --- /dev/null +++ b/test/fixtures/messaging.yml @@ -0,0 +1,2267 @@ +openapi: 3.0.3 +info: + title: Messaging + version: 4.3.0 + contact: + name: Bandwidth + url: https://support.bandwidth.com + email: support@bandwidth.com + termsOfService: https://www.bandwidth.com/legal/terms-of-use-bandwidthcom-web-sites/ + description: |- + The API Specification for Bandwidth's Messaging Platform + + ## Base URL + + `https://messaging.bandwidth.com/api/v2` +servers: + - url: https://messaging.bandwidth.com/api/v2 + description: Production +paths: + /users/{accountId}/media: + get: + summary: List Media + description: |- + Gets a list of your media files. No query parameters are supported. + operationId: listMedia + tags: + - Media + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/continuationToken" + responses: + "200": + $ref: "#/components/responses/listMediaResponse" + "400": + $ref: "#/components/responses/messagingBadRequestError" + "401": + $ref: "#/components/responses/messagingUnauthorizedError" + "403": + $ref: "#/components/responses/messagingForbiddenError" + "404": + $ref: "#/components/responses/messagingNotFoundError" + "406": + $ref: '#/components/responses/messagingNotAcceptableError' + "415": + $ref: "#/components/responses/messagingInvalidMediaTypeError" + "429": + $ref: "#/components/responses/messagingTooManyRequestsError" + "500": + $ref: "#/components/responses/messagingInternalServerError" + /users/{accountId}/media/{mediaId}: + get: + summary: Get Media + description: Downloads a media file you previously uploaded. + operationId: getMedia + tags: + - Media + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/mediaId" + responses: + "200": + $ref: "#/components/responses/getMediaResponse" + "400": + $ref: "#/components/responses/messagingBadRequestError" + "401": + $ref: "#/components/responses/messagingUnauthorizedError" + "403": + $ref: "#/components/responses/messagingForbiddenError" + "404": + $ref: "#/components/responses/messagingNotFoundError" + "406": + $ref: '#/components/responses/messagingNotAcceptableError' + "415": + $ref: "#/components/responses/messagingInvalidMediaTypeError" + "429": + $ref: "#/components/responses/messagingTooManyRequestsError" + "500": + $ref: "#/components/responses/messagingInternalServerError" + put: + summary: Upload Media + description: |- + Upload a file. You may add headers to the request in order to provide some control to your media file. + + If a file is uploaded with the same name as a file that already exists under this account, the previous file will be overwritten. + + A list of supported media types can be found [here](https://support.bandwidth.com/hc/en-us/articles/360014128994-What-MMS-file-types-are-supported-). + operationId: uploadMedia + tags: + - Media + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/mediaId" + - $ref: "#/components/parameters/contentType" + - $ref: "#/components/parameters/cacheControl" + requestBody: + $ref: "#/components/requestBodies/uploadMediaRequest" + responses: + "204": + description: No Content + "400": + $ref: "#/components/responses/messagingBadRequestError" + "401": + $ref: "#/components/responses/messagingUnauthorizedError" + "403": + $ref: "#/components/responses/messagingForbiddenError" + "404": + $ref: "#/components/responses/messagingNotFoundError" + "406": + $ref: '#/components/responses/messagingNotAcceptableError' + "415": + $ref: "#/components/responses/messagingInvalidMediaTypeError" + "429": + $ref: "#/components/responses/messagingTooManyRequestsError" + "500": + $ref: "#/components/responses/messagingInternalServerError" + delete: + summary: Delete Media + description: |- + Deletes a media file from Bandwidth API server. Make sure you don't have + any application scripts still using the media before you delete. + + If you accidentally delete a media file you can immediately upload a new + file with the same name. + operationId: deleteMedia + tags: + - Media + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/mediaId" + responses: + "204": + description: No Content + "400": + $ref: "#/components/responses/messagingBadRequestError" + "401": + $ref: "#/components/responses/messagingUnauthorizedError" + "403": + $ref: "#/components/responses/messagingForbiddenError" + "404": + $ref: "#/components/responses/messagingNotFoundError" + "406": + $ref: '#/components/responses/messagingNotAcceptableError' + "415": + $ref: "#/components/responses/messagingInvalidMediaTypeError" + "429": + $ref: "#/components/responses/messagingTooManyRequestsError" + "500": + $ref: "#/components/responses/messagingInternalServerError" + /users/{accountId}/messages: + get: + summary: List Messages + description: Returns a list of messages based on query parameters. + operationId: listMessages + tags: + - Messages + parameters: + - $ref: "#/components/parameters/accountId" + - $ref: "#/components/parameters/messageId" + - $ref: "#/components/parameters/sourceTn" + - $ref: "#/components/parameters/destinationTn" + - $ref: "#/components/parameters/messageStatus" + - $ref: "#/components/parameters/messageDirection" + - $ref: "#/components/parameters/carrierName" + - $ref: "#/components/parameters/messageType" + - $ref: "#/components/parameters/errorCode" + - $ref: "#/components/parameters/fromDateTime" + - $ref: "#/components/parameters/toDateTime" + - $ref: "#/components/parameters/campaignId" + - $ref: "#/components/parameters/fromBwLatency" + - $ref: "#/components/parameters/bwQueued" + - $ref: "#/components/parameters/product" + - $ref: "#/components/parameters/location" + - $ref: "#/components/parameters/callingNumberCountryA3" + - $ref: "#/components/parameters/calledNumberCountryA3" + - $ref: "#/components/parameters/fromSegmentCount" + - $ref: "#/components/parameters/toSegmentCount" + - $ref: "#/components/parameters/fromMessageSize" + - $ref: "#/components/parameters/toMessageSize" + - $ref: "#/components/parameters/sort" + - $ref: "#/components/parameters/pageToken" + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/limitTotalCount" + responses: + "200": + $ref: "#/components/responses/listMessagesResponse" + "400": + $ref: "#/components/responses/messagingBadRequestError" + "401": + $ref: "#/components/responses/messagingUnauthorizedError" + "403": + $ref: "#/components/responses/messagingForbiddenError" + "404": + $ref: "#/components/responses/messagingNotFoundError" + "415": + $ref: "#/components/responses/messagingInvalidMediaTypeError" + "429": + $ref: "#/components/responses/messagingTooManyRequestsError" + "500": + $ref: "#/components/responses/messagingInternalServerError" + post: + summary: Create Message + description: Endpoint for sending text messages and picture messages using V2 messaging. + operationId: createMessage + tags: + - Messages + parameters: + - $ref: "#/components/parameters/accountId" + requestBody: + $ref: "#/components/requestBodies/createMessageRequest" + responses: + "202": + $ref: "#/components/responses/createMessageResponse" + "400": + $ref: "#/components/responses/createMessageBadRequestError" + "401": + $ref: "#/components/responses/messagingUnauthorizedError" + "403": + $ref: "#/components/responses/messagingForbiddenError" + "404": + $ref: "#/components/responses/messagingNotFoundError" + "405": + $ref: "#/components/responses/messagingMethodNotAllowedError" + "406": + $ref: '#/components/responses/messagingNotAcceptableError' + "415": + $ref: "#/components/responses/messagingInvalidMediaTypeError" + "429": + $ref: "#/components/responses/messagingTooManyRequestsError" + "500": + $ref: "#/components/responses/messagingInternalServerError" + callbacks: + statusCallback: + $ref: "#/components/callbacks/statusCallback" + /users/{accountId}/messages/multiChannel: + post: + summary: Create Multi-Channel Message + description: Endpoint for sending Multi-Channel messages. + operationId: createMultiChannelMessage + parameters: + - $ref: "#/components/parameters/accountId" + tags: + - Multi-Channel + requestBody: + $ref: "#/components/requestBodies/createMultiChannelMessageRequest" + responses: + "202": + $ref: "#/components/responses/createMultiChannelMessageResponse" + "400": + $ref: "#/components/responses/multiChannelBadRequestError" + "401": + $ref: "#/components/responses/multiChannelUnauthorizedError" + "403": + $ref: "#/components/responses/multiChannelForbiddenError" + "404": + $ref: "#/components/responses/multiChannelNotFoundError" + "405": + $ref: "#/components/responses/multiChannelMethodNotAllowedError" + "406": + $ref: '#/components/responses/multiChannelNotAcceptableError' + "415": + $ref: "#/components/responses/multiChannelInvalidMediaTypeError" + "429": + $ref: "#/components/responses/multiChannelTooManyRequestsError" + "500": + $ref: "#/components/responses/multiChannelInternalServerError" + callbacks: + statusCallback: + $ref: "#/components/callbacks/statusCallback" + x-badges: + - name: Beta + color: '#076EA8' +components: + parameters: + accountId: + in: path + name: accountId + required: true + schema: + type: string + description: Your Bandwidth Account ID. + example: "9900000" + mediaId: + in: path + name: mediaId + required: true + description: Media ID to retrieve. + example: 14762070468292kw2fuqty55yp2b2/0/bw.png + schema: + type: string + contentType: + in: header + name: Content-Type + style: simple + explode: false + description: The media type of the entity-body. + example: audio/wav + schema: + type: string + cacheControl: + in: header + name: Cache-Control + style: simple + explode: false + description: >- + General-header field is used to specify directives that MUST be obeyed by + all caching mechanisms along the request/response chain. + example: no-cache + schema: + type: string + continuationToken: + in: header + name: Continuation-Token + description: Continuation token used to retrieve subsequent media. + example: "1XEi2tsFtLo1JbtLwETnM1ZJ+PqAa8w6ENvC5QKvwyrCDYII663Gy5M4s40owR1tjkuWUif6qbWvFtQJR5/ipqbUnfAqL254LKNlPy6tATCzioKSuHuOqgzloDkSwRtX0LtcL2otHS69hK343m+SjdL+vlj71tT39" + schema: + type: string + messageId: + in: query + name: messageId + required: false + description: >- + The ID of the message to search for. Special characters need to be + encoded using URL encoding. Message IDs could come in different formats, + e.g., 9e0df4ca-b18d-40d7-a59f-82fcdf5ae8e6 and + 1589228074636lm4k2je7j7jklbn2 are valid message ID formats. Note that you + must include at least one query parameter. + example: 9e0df4ca-b18d-40d7-a59f-82fcdf5ae8e6 + schema: + type: string + sourceTn: + in: query + name: sourceTn + required: false + description: >- + The phone number that sent the message. Accepted values are: a single + full phone number a comma separated list of full phone numbers (maximum + of 10) or a single partial phone number (minimum of 5 characters e.g. + '%2B1919'). + example: "%2B15554443333" + schema: + type: string + destinationTn: + in: query + name: destinationTn + required: false + description: >- + The phone number that received the message. Accepted values are: a single + full phone number a comma separated list of full phone numbers (maximum + of 10) or a single partial phone number (minimum of 5 characters e.g. + '%2B1919'). + example: "%2B15554443333" + schema: + type: string + messageStatus: + in: query + name: messageStatus + required: false + description: >- + The status of the message. One of RECEIVED QUEUED SENDING SENT FAILED + DELIVERED ACCEPTED UNDELIVERED. + schema: + $ref: "#/components/schemas/messageStatusEnum" + messageDirection: + in: query + name: messageDirection + required: false + description: The direction of the message. One of INBOUND OUTBOUND. + schema: + $ref: "#/components/schemas/listMessageDirectionEnum" + carrierName: + in: query + name: carrierName + required: false + description: >- + The name of the carrier used for this message. Possible values include + but are not limited to Verizon and TMobile. Special characters need to + be encoded using URL encoding (i.e. AT&T should be passed as AT%26T). + example: Verizon + schema: + type: string + messageType: + in: query + name: messageType + required: false + description: The type of message. Either sms or mms. + schema: + $ref: "#/components/schemas/messageTypeEnum" + errorCode: + in: query + name: errorCode + required: false + description: The error code of the message. + example: 9902 + schema: + type: integer + fromDateTime: + in: query + name: fromDateTime + required: false + description: >- + The start of the date range to search in ISO 8601 format. Uses the + message receive time. The date range to search in is currently 14 days. + example: 2022-09-14T18:20:16.000Z + schema: + type: string + toDateTime: + in: query + name: toDateTime + required: false + description: >- + The end of the date range to search in ISO 8601 format. Uses the message + receive time. The date range to search in is currently 14 days. + example: 2022-09-14T18:20:16.000Z + schema: + type: string + campaignId: + in: query + name: campaignId + required: false + description: The campaign ID of the message. + example: CJEUMDK + schema: + type: string + fromBwLatency: + in: query + name: fromBwLatency + required: false + description: >- + The minimum Bandwidth latency of the message in seconds. Only available for accounts with the Advanced Quality Metrics feature enabled. + example: 5 + schema: + type: integer + bwQueued: + in: query + name: bwQueued + required: false + description: >- + A boolean value indicating whether the message is queued in the Bandwidth network. + example: true + schema: + type: boolean + product: + in: query + name: product + required: false + description: Messaging product associated with the message. + example: 'P2P' + schema: + $ref: "#/components/schemas/productTypeEnum" + location: + in: query + name: location + required: false + description: Location Id associated with the message. + example: '123ABC' + schema: + type: string + callingNumberCountryA3: + in: query + name: callingNumberCountryA3 + required: false + description: Calling number country in A3 format. + example: 'USA' + schema: + type: string + calledNumberCountryA3: + in: query + name: calledNumberCountryA3 + required: false + description: Called number country in A3 format. + example: 'USA' + schema: + type: string + fromSegmentCount: + in: query + name: fromSegmentCount + required: false + description: Segment count (start range). + example: 1 + schema: + type: integer + toSegmentCount: + in: query + name: toSegmentCount + required: false + description: Segment count (end range). + example: 3 + schema: + type: integer + fromMessageSize: + in: query + name: fromMessageSize + required: false + description: Message size (start range). + example: 100 + schema: + type: integer + toMessageSize: + in: query + name: toMessageSize + required: false + description: Message size (end range). + example: 120 + schema: + type: integer + sort: + in: query + name: sort + required: false + description: The field and direction to sort by combined with a colon. Direction is either asc or desc. + example: sourceTn:desc + schema: + type: string + pageToken: + in: query + name: pageToken + required: false + description: A base64 encoded value used for pagination of results. + example: gdEewhcJLQRB5 + schema: + type: string + limit: + in: query + name: limit + required: false + description: >- + The maximum records requested in search result. Default 100. The sum of + limit and after cannot be more than 10000. + schema: + type: integer + example: 50 + limitTotalCount: + in: query + name: limitTotalCount + required: false + description: When set to true, the response's totalCount field will have a maximum value of + 10,000. When set to false, or excluded, this will give an accurate totalCount of all + messages that match the provided filters. If you are experiencing latency, try using this + parameter to limit your results. + example: true + schema: + type: boolean + schemas: + applicationId: + type: string + description: The ID of the Application your from number or senderId is associated with in the Bandwidth Phone Number Dashboard. + example: 93de2206-9669-4e07-948d-329f4b722ee2 + priorityEnum: + type: string + description: >- + Specifies the message's sending priority with respect to other messages in your account. + For best results and optimal throughput, reserve the 'high' priority setting for critical messages only. + enum: + - default + - high + example: default + messageStatusEnum: + type: string + description: >- + The status of the message. One of RECEIVED QUEUED SENDING SENT FAILED + DELIVERED ACCEPTED UNDELIVERED. + enum: + - RECEIVED + - QUEUED + - SENDING + - SENT + - FAILED + - DELIVERED + - ACCEPTED + - UNDELIVERED + example: "RECEIVED" + listMessageDirectionEnum: + type: string + description: The direction of the message. One of INBOUND OUTBOUND. + enum: + - INBOUND + - OUTBOUND + example: INBOUND + messageDirectionEnum: + type: string + description: The direction of the message. One of in out. + enum: + - in + - out + example: in + messageTypeEnum: + type: string + description: The type of message. Either SMS or MMS. + enum: + - sms + - mms + - rcs + example: 'sms' + productTypeEnum: + type: string + description: The type of product associated with the message. + enum: + - LOCAL_A2P + - P2P + - SHORT_CODE_REACH + - TOLL_FREE + - HOSTED_SHORT_CODE + - ALPHA_NUMERIC + - RBM_MEDIA + - RBM_RICH + - RBM_CONVERSATIONAL + example: 'P2P' + fieldError: + type: object + properties: + fieldName: + type: string + description: The name of the field that contains the error + example: from + description: + type: string + description: The error associated with the field + example: "'+invalid' must be replaced with a valid E164 formatted telephone number" + messagesList: + title: MessagesList + type: object + properties: + totalCount: + type: integer + description: The total number of messages matched by the search. When the request has limitTotalCount set to true this value is limited to 10,000. + example: 100 + pageInfo: + $ref: "#/components/schemas/pageInfo" + messages: + type: array + items: + $ref: "#/components/schemas/listMessageItem" + listMessageItem: + title: listMessageItem + type: object + properties: + messageId: + type: string + description: The message id + example: 1589228074636lm4k2je7j7jklbn2 + accountId: + type: string + description: The account id associated with this message. + example: "9900000" + sourceTn: + type: string + description: The source phone number of the message. + example: "+15554443333" + destinationTn: + type: string + description: The recipient phone number of the message. + example: "+15554442222" + messageStatus: + $ref: "#/components/schemas/messageStatusEnum" + messageDirection: + $ref: "#/components/schemas/listMessageDirectionEnum" + messageType: + $ref: "#/components/schemas/messageTypeEnum" + segmentCount: + $ref: "#/components/schemas/segmentCount" + errorCode: + type: integer + description: The numeric error code of the message. + example: 9902 + receiveTime: + type: string + format: date-time + description: The ISO 8601 datetime of the message. + example: 2020-04-07T14:03:07.000Z + carrierName: + type: string + nullable: true + description: The name of the carrier. Not currently supported for MMS coming soon. + example: other + messageSize: + type: integer + description: The size of the message including message content and headers. + nullable: true + example: 27 + messageLength: + type: integer + description: The length of the message content. + example: 18 + attachmentCount: + type: integer + description: The number of attachments the message has. + nullable: true + example: 1 + recipientCount: + type: integer + description: The number of recipients the message has. + nullable: true + example: 1 + campaignClass: + type: string + description: The campaign class of the message if it has one. + nullable: true + example: T + campaignId: + type: string + description: The campaign ID of the message if it has one. + nullable: true + example: CJEUMDK + bwLatency: + type: integer + description: The Bandwidth latency of the message in seconds. Only available for accounts with the Advanced Quality Metrics feature enabled. + nullable: true + example: 20 + callingNumberCountryA3: + type: string + description: The A3 country code of the calling number. + nullable: true + example: 'USA' + calledNumberCountryA3: + type: string + description: The A3 country code of the called number. + nullable: true + example: 'USA' + product: + type: string + description: The messaging product associated with the message. + nullable: true + example: 'P2P' + location: + type: string + description: The location ID associated with this message. + nullable: true + example: '123ID' + pageInfo: + title: PageInfo + type: object + properties: + prevPage: + type: string + description: The link to the previous page for pagination. + example: https://messaging.bandwidth.com/api/v2/users/accountId/messages?messageStatus=DLR_EXPIRED&nextPage=DLAPE902 + nextPage: + type: string + description: The link to the next page for pagination. + example: https://messaging.bandwidth.com/api/v2/users/accountId/messages?messageStatus=DLR_EXPIRED&prevPage=GL83PD3C + prevPageToken: + type: string + description: The isolated pagination token for the previous page. + example: DLAPE902 + nextPageToken: + type: string + description: The isolated pagination token for the next page. + example: GL83PD3C + messagingRequestError: + title: MessagingRequestError + type: object + properties: + type: + type: string + description: + type: string + required: + - type + - description + createMessageRequestError: + title: CreateMessageRequestError + type: object + properties: + type: + type: string + description: + type: string + fieldErrors: + type: array + items: + $ref: "#/components/schemas/fieldError" + required: + - type + - description + messageId: + type: string + description: The ID of the message. + example: 1589228074636lm4k2je7j7jklbn2 + media: + title: Media + type: object + properties: + content: + type: string + contentLength: + type: integer + mediaName: + type: string + segmentCount: + type: integer + description: The number of segments the user's message is broken into before sending over carrier networks. + example: 1 + tag: + title: Tag + type: string + description: A custom string that will be included in callback events of the message. Max 1024 characters. + example: custom string + expiration: + type: string + format: date-time + description: >- + A string with the date/time value that the message will automatically + expire by. This must be a valid RFC-3339 value, e.g., + 2021-03-14T01:59:26Z or 2021-03-13T20:59:26-05:00. Must be a date-time in the future. + example: "2021-02-01T11:29:18-05:00" + carrierName: + type: string + description: |- + The name of the Authorized Message Provider (AMP) that handled this message. + In the US, this is the carrier that the message was sent to. + This field is present only when this account feature has been enabled. + example: AT&T + message: + title: Message + type: object + properties: + id: + type: string + description: The id of the message. + example: 1589228074636lm4k2je7j7jklbn2 + owner: + type: string + description: The Bandwidth phone number associated with the message. + example: "+15554443333" + applicationId: + $ref: "#/components/schemas/applicationId" + time: + type: string + format: date-time + description: The datetime stamp of the message in ISO 8601 + example: 2024-12-02T20:15:57.278201Z + segmentCount: + $ref: "#/components/schemas/segmentCount" + direction: + $ref: "#/components/schemas/messageDirectionEnum" + to: + uniqueItems: true + type: array + items: + type: string + description: The phone number recipients of the message. + example: + - "+15552223333" + from: + type: string + description: The phone number the message was sent from. + example: "+15553332222" + media: + uniqueItems: true + type: array + items: + type: string + description: >- + The list of media URLs sent in the message. Including a `filename` + field in the `Content-Disposition` header of the media linked with + a URL will set the displayed file name. This is a best practice to + ensure that your media has a readable file name. + example: + - https://dev.bandwidth.com/images/bandwidth-logo.png + text: + type: string + description: The contents of the message. + example: Hello world + tag: + $ref: "#/components/schemas/tag" + priority: + $ref: "#/components/schemas/priorityEnum" + expiration: + $ref: "#/components/schemas/expiration" + messageRequest: + title: MessageRequest + type: object + required: + - applicationId + - to + - from + properties: + applicationId: + $ref: "#/components/schemas/applicationId" + to: + uniqueItems: true + type: array + description: The phone number(s) the message should be sent to in E164 format. + example: + - "+15554443333" + - "+15552223333" + items: + type: string + from: + type: string + description: >- + Either an alphanumeric sender ID or the sender's Bandwidth phone number in E.164 format, which must be hosted within Bandwidth and linked to the account that is generating the message. + + Alphanumeric Sender IDs can contain up to 11 characters, upper-case letters A-Z, lower-case letters a-z, numbers 0-9, space, hyphen -, plus +, underscore _ and ampersand &. + Alphanumeric Sender IDs must contain at least one letter. + example: + "+15551113333" + text: + $ref: "#/components/schemas/messageText" + media: + $ref: "#/components/schemas/messageMedia" + tag: + $ref: "#/components/schemas/tag" + priority: + $ref: "#/components/schemas/priorityEnum" + expiration: + $ref: "#/components/schemas/expiration" + messageText: + type: string + description: The contents of the text message. Must be 2048 characters or less. + maxLength: 2048 + example: Hello world + messageMedia: + type: array + items: + type: string + format: uri + maxLength: 4096 + description: >- + A list of URLs to include as media attachments as part of the message. + + Each URL can be at most 4096 characters. + example: + - https://dev.bandwidth.com/images/bandwidth-logo.png + - https://dev.bandwidth.com/images/github_logo.png + createMultiChannelMessageResponse: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + example: [] + data: + $ref: "#/components/schemas/multiChannelMessageResponseData" + errors: + type: array + items: + $ref: "#/components/schemas/errorObject" + example: [] + multiChannelError: + type: object + properties: + links: + type: array + items: + $ref: "#/components/schemas/link" + example: [] + data: + type: object + nullable: true + example: null + errors: + type: array + items: + $ref: "#/components/schemas/errorObject" + link: + type: object + properties: + rel: + type: string + href: + type: string + errorObject: + type: object + properties: + type: + description: A concise summary of the error used for categorization. + type: string + description: + description: A detailed explanation of the error. + type: string + source: + $ref: "#/components/schemas/errorSource" + required: + - type + - description + - source + errorSource: + title: Error Source + type: object + description: Specifies relevant sources of the error, if any. + properties: + parameter: + type: string + description: The relevant URI query parameter causing the error + field: + type: string + description: The request body field that led to the error + header: + type: string + description: The header field that contributed to the error + reference: + type: string + description: A resource ID or path linked to the error + multiChannelMessageChannelEnum: + description: The channel of the multi-channel message. + type: string + enum: + - RBM + - SMS + - MMS + example: RBM + multiChannelSenderId: + type: string + description: The sender ID of the message. This could be an alphanumeric sender ID. + example: "BandwidthRBM" + multiChannelDestination: + type: string + description: The phone number the message should be sent to in E164 format. + example: "+15552223333" + multiChannelDestinations: + uniqueItems: true + type: array + description: The destination phone number(s) of the message, in E164 format. + example: + - "+15554443333" + items: + type: string + rbmMessageContentText: + title: RBM Text + type: object + properties: + text: + type: string + description: The text associated with the message. Must be 3270 characters or less + maxLength: 3270 + example: Hello world + suggestions: + $ref: "#/components/schemas/multiChannelFullActions" + required: + - text + rbmMediaHeightEnum: + type: string + description: The height of the media. + enum: + - SHORT + - MEDIUM + - TALL + example: SHORT + rbmMessageContentFile: + title: RBM Rich Media File + type: object + properties: + fileUrl: + type: string + format: uri + description: The URL of the media file. 100MB is the maximum file size. + example: https://dev.bandwidth.com/images/bandwidth-logo.png + maxLength: 1000 + thumbnailUrl: + type: string + format: uri + description: The URL of the thumbnail image. Applies only to video file media. + example: https://dev.bandwidth.com/images/bandwidth-logo.png + maxLength: 1000 + required: + - fileUrl + mmsMessageContentFile: + title: MMS Media File + type: object + properties: + fileUrl: + type: string + format: uri + description: >- + The URL of a media attachment. + + + For MMS, the API limits file size to 3.5MB. + Specific carriers and channels may have a smaller limit that could cause a large file to fail, see + [here](https://support.bandwidth.com/hc/en-us/articles/360014235473-What-are-the-MMS-file-size-limits) + for more details. + example: https://dev.bandwidth.com/images/bandwidth-logo.png + maxLength: 1000 + required: + - fileUrl + rbmMessageMedia: + title: RBM Media + type: object + properties: + media: + $ref: "#/components/schemas/rbmMessageContentFile" + suggestions: + $ref: "#/components/schemas/multiChannelFullActions" + required: + - media + rbmCardContent: + type: object + properties: + title: + type: string + description: The title of the card. Must be 200 characters or less. + maxLength: 200 + example: "Bandwidth" + description: + type: string + description: The description of the card. Must be 2000 characters or less. + maxLength: 2000 + example: "Bandwidth is a communications platform as a service (CPaaS) company." + media: + allOf: + - $ref: "#/components/schemas/rbmMessageContentFile" + - type: object + properties: + height: + $ref: "#/components/schemas/rbmMediaHeightEnum" + required: + - height + suggestions: + description: An array of suggested actions for the recipient that will be displayed on the rich card. + type: array + items: + $ref: "#/components/schemas/multiChannelAction" + maxItems: 4 + rbmStandaloneCard: + title: Standalone Card + type: object + properties: + orientation: + $ref: "#/components/schemas/standaloneCardOrientationEnum" + thumbnailImageAlignment: + $ref: "#/components/schemas/thumbnailAlignmentEnum" + cardContent: + $ref: "#/components/schemas/rbmCardContent" + suggestions: + $ref: "#/components/schemas/multiChannelFullActions" + required: + - orientation + - thumbnailImageAlignment + - cardContent + standaloneCardOrientationEnum: + type: string + enum: + - HORIZONTAL + - VERTICAL + example: VERTICAL + thumbnailAlignmentEnum: + type: string + description: >- + The alignment of the thumbnail image in the card. Only applicable if + the card using horizontal orientation. + enum: + - LEFT + - RIGHT + example: LEFT + rbmMessageCarouselCard: + title: Carousel + type: object + properties: + cardWidth: + $ref: "#/components/schemas/cardWidthEnum" + cardContents: + type: array + items: + $ref: "#/components/schemas/rbmCardContent" + maxItems: 10 + suggestions: + $ref: "#/components/schemas/multiChannelFullActions" + required: + - cardContents + - cardWidth + cardWidthEnum: + type: string + enum: + - SMALL + - MEDIUM + example: SMALL + smsMessageContent: + title: SMS Text + type: object + properties: + text: + $ref: "#/components/schemas/messageText" + required: + - text + mmsMessageContent: + title: MMS Message + type: object + properties: + text: + $ref: "#/components/schemas/messageText" + media: + type: array + items: + $ref: "#/components/schemas/mmsMessageContentFile" + rbmMessageContentRichCard: + title: RBM Rich Card + oneOf: + - $ref: "#/components/schemas/rbmStandaloneCard" + - $ref: "#/components/schemas/rbmMessageCarouselCard" + rbmActionTypeEnum: + type: string + enum: + - REPLY + - DIAL_PHONE + - SHOW_LOCATION + - CREATE_CALENDAR_EVENT + - OPEN_URL + - REQUEST_LOCATION + example: REPLY + rbmActionText: + title: Text + type: string + description: Displayed text for user to click + maxLength: 25 + example: Hello world + rbmActionPostbackData: + title: Post Back Data + type: string + format: byte + description: Base64 payload the customer receives when the reply is clicked. + maxLength: 2048 + example: SGVsbG8gd29ybGQ= + rbmActionBase: + type: object + properties: + type: + $ref: '#/components/schemas/rbmActionTypeEnum' + text: + $ref: "#/components/schemas/rbmActionText" + postbackData: + $ref: "#/components/schemas/rbmActionPostbackData" + required: + - text + - postbackData + - type + rbmActionDial: + allOf: + - $ref: "#/components/schemas/rbmActionBase" + - title: Dial Phone + type: object + properties: + phoneNumber: + type: string + description: The phone number to dial. Must be E164 format. + example: "+15552223333" + required: + - phoneNumber + rbmActionViewLocation: + allOf: + - $ref: "#/components/schemas/rbmActionBase" + - title: Show Location + type: object + properties: + latitude: + type: string + format: double + description: The latitude of the location. + example: "37.7749" + longitude: + type: string + format: double + description: The longitude of the location. + example: "-122.4194" + label: + type: string + description: The label of the location. + example: "San Francisco" + maxLength: 100 + required: + - latitude + - longitude + multiChannelActionCalendarEvent: + allOf: + - $ref: "#/components/schemas/rbmActionBase" + - title: Calendar Event + type: object + properties: + title: + type: string + description: The title of the event. + example: "Meeting with John" + maxLength: 100 + startTime: + type: string + format: date-time + description: The start time of the event. + example: 2022-09-14T18:20:16.000Z + endTime: + type: string + format: date-time + description: The end time of the event. + example: 2022-09-14T18:20:16.000Z + description: + type: string + description: The description of the event. + example: "Discuss the new project" + maxLength: 500 + required: + - title + - startTime + - endTime + rbmActionOpenUrl: + allOf: + - $ref: "#/components/schemas/rbmActionBase" + - title: Open URL + type: object + properties: + url: + type: string + format: uri + description: The URL to open in browser. + example: https://dev.bandwidth.com + maxLength: 2048 + required: + - url + multiChannelFullActions: + type: array + description: An array of suggested actions for the recipient. + items: + $ref: "#/components/schemas/multiChannelAction" + maxItems: 11 + multiChannelAction: + oneOf: + - $ref: "#/components/schemas/rbmActionBase" + - $ref: "#/components/schemas/rbmActionDial" + - $ref: "#/components/schemas/rbmActionViewLocation" + - $ref: "#/components/schemas/multiChannelActionCalendarEvent" + - $ref: "#/components/schemas/rbmActionOpenUrl" + discriminator: + propertyName: type + mapping: + REPLY: "#/components/schemas/rbmActionBase" + DIAL_PHONE: "#/components/schemas/rbmActionDial" + SHOW_LOCATION: "#/components/schemas/rbmActionViewLocation" + CREATE_CALENDAR_EVENT: "#/components/schemas/multiChannelActionCalendarEvent" + OPEN_URL: "#/components/schemas/rbmActionOpenUrl" + REQUEST_LOCATION: "#/components/schemas/rbmActionBase" + multiChannelChannelListObject: + type: object + properties: + from: + $ref: "#/components/schemas/multiChannelSenderId" + applicationId: + $ref: "#/components/schemas/applicationId" + channel: + $ref: "#/components/schemas/multiChannelMessageChannelEnum" + content: + description: The content of the message. + oneOf: + - $ref: "#/components/schemas/rbmMessageContentText" + - $ref: "#/components/schemas/rbmMessageMedia" + - $ref: "#/components/schemas/rbmMessageContentRichCard" + - $ref: '#/components/schemas/smsMessageContent' + - $ref: '#/components/schemas/mmsMessageContent' + required: + - from + - applicationId + - channel + - content + multiChannelMessageRequest: + description: Multi-Channel Message Request + type: object + properties: + to: + $ref: "#/components/schemas/multiChannelDestination" + channelList: + type: array + description: A list of message bodies. The messages will be attempted in the order they are listed. Once a message sends successfully, the others will be ignored. + items: + $ref: "#/components/schemas/multiChannelChannelListObject" + maxItems: 4 + tag: + $ref: "#/components/schemas/tag" + priority: + $ref: "#/components/schemas/priorityEnum" + expiration: + $ref: "#/components/schemas/expiration" + required: + - to + - channelList + multiChannelMessageResponseData: + description: The data returned in a multichannel message response. + type: object + properties: + messageId: + $ref: "#/components/schemas/messageId" + time: + description: The time the message was received by the Bandwidth API. + type: string + format: date-time + example: 2025-01-01T18:20:16.000000Z + direction: + $ref: "#/components/schemas/messageDirectionEnum" + to: + $ref: "#/components/schemas/multiChannelDestinations" + channelList: + type: array + description: A list of message bodies. The messages will be attempted in the order they are listed. Once a message sends successfully, the others will be ignored. + items: + allOf: + - $ref: "#/components/schemas/multiChannelChannelListObject" + - type: object + properties: + owner: + type: string + description: The Bandwidth senderId associated with the message. Identical to 'from'. + required: + - owner + maxItems: 4 + tag: + $ref: "#/components/schemas/tag" + priority: + $ref: "#/components/schemas/priorityEnum" + expiration: + $ref: "#/components/schemas/expiration" + required: + - messageId + - time + - direction + - to + - channelList + multiChannelMessageContent: + description: The structure of the content field of a multichannel message. + type: object + properties: + text: + type: string + media: + $ref: '#/components/schemas/rbmMessageContentFile' + rbmSuggestionResponse: + type: object + properties: + text: + type: string + description: The text associated with the suggestion response. + example: "Yes, I would like to proceed" + postbackData: + $ref: "#/components/schemas/rbmActionPostbackData" + rbmLocationResponse: + type: object + properties: + latitude: + type: string + format: double + description: The latitude of the client's location. + example: "37.7749" + longitude: + type: string + format: double + description: The longitude of the client's location. + example: "-122.4194" + callback: + description: |- + Callbacks are divided into two types based on direction of the related message: + - `statusCallback` indicates status of an outbound MT SMS, MMS, or RBM message. + - `inboundCallback` indicates an inbound MO message or a multichannel message client's response to a suggestion or location request. + type: object + oneOf: + - $ref: '#/components/schemas/statusCallback' + - $ref: '#/components/schemas/inboundCallback' + discriminator: + propertyName: type + mapping: + message-sent: '#/components/schemas/statusCallback' + message-delivered: '#/components/schemas/statusCallback' + message-failed: '#/components/schemas/statusCallback' + message-read: '#/components/schemas/statusCallback' + message-received: '#/components/schemas/inboundCallback' + request-location-response: '#/components/schemas/inboundCallback' + suggestion-response: '#/components/schemas/inboundCallback' + statusCallback: + type: object + description: Represents a status callback for an outbound MT SMS or MMS or RBM message. + properties: + time: + type: string + format: date-time + example: 2024-12-02T20:15:57.278201Z + eventTime: + type: string + description: Represents the time at which the message was read, for `message-read` callbacks. + format: date-time + example: 2024-12-02T20:15:58.278205Z + type: + $ref: "#/components/schemas/statusCallbackTypeEnum" + to: + type: string + description: |- + The destination phone number the message was sent to. + For status callbacks, this the the Bandwidth user's client phone number. + example: "+15552223333" + description: + type: string + description: A detailed description of the event described by the callback. + example: Message delivered to carrier. + message: + $ref: '#/components/schemas/statusCallbackMessage' + errorCode: + type: integer + description: Optional error code, applicable only when type is `message-failed`. + example: 4405 + carrierName: + $ref: "#/components/schemas/carrierName" + required: + - time + - type + - to + - description + - message + inboundCallback: + type: object + description: Represents an inbound callback. + properties: + time: + type: string + format: date-time + example: 2024-12-02T20:15:57.278201Z + type: + $ref: "#/components/schemas/inboundCallbackTypeEnum" + to: + type: string + description: | + The destination phone number the message was sent to. + For inbound callbacks, this is the Bandwidth number or alphanumeric identifier that received the message. + example: "+15552223333" + description: + type: string + description: A detailed description of the event described by the callback. + example: Incoming message received + message: + $ref: '#/components/schemas/inboundCallbackMessage' + carrierName: + $ref: "#/components/schemas/carrierName" + required: + - time + - type + - to + - description + - message + statusCallbackTypeEnum: + type: string + description: |- + The possible status callbacks when sending an MT SMS or MMS or RBM message: + - `message-sending` indicates that Bandwidth is sending the message to the upstream provider. + - `message-delivered` indicates that the message was successfully sent. + - `message-failed` indicates that the message could not be sent to the intended recipient. + - `message-read` indicates that the RBM message was read by the recipient. + enum: + - message-sending + - message-delivered + - message-failed + - message-read + example: message-delivered + inboundCallbackTypeEnum: + type: string + description: |- + The possible inbound callback types originating from MO messages or multichannel message client responses: + - `message-received` indicates an MO message from a Bandwidth user's client to a Bandwidth number. + - `request-location-response` indicates a response to a location request sent by the Bandwidth user's client after receiving an RBM message. + - `suggestion-response` indicates a response to a suggestion sent by the Bandwidth user's client after receiving an RBM message. + enum: + - message-received + - request-location-response + - suggestion-response + example: message-received + statusCallbackMessage: + description: Message payload schema within a callback + type: object + properties: + id: + type: string + description: A unique identifier of the message. + example: 1661365814859loidf7mcwd4qacn7 + owner: + type: string + description: The Bandwidth phone number or alphanumeric identifier associated with the message. + example: "+15553332222" + applicationId: + $ref: "#/components/schemas/applicationId" + time: + type: string + format: date-time + example: 2024-12-02T20:15:57.666000Z + segmentCount: + $ref: "#/components/schemas/segmentCount" + direction: + $ref: "#/components/schemas/messageDirectionEnum" + to: + description: The phone number recipients of the message. + uniqueItems: true + type: array + items: + type: string + example: + - "+15552223333" + from: + type: string + description: The Bandwidth phone number or alphanumeric identifier the message was sent from. + example: "+15553332222" + text: + type: string + example: Hello world + tag: + $ref: "#/components/schemas/tag" + media: + type: array + description: Optional media, not applicable for sms + items: + type: string + format: uri + example: + - "https://dev.bandwidth.com/images/bandwidth-logo.png" + - "https://dev.bandwidth.com/images/github_logo.png" + priority: + $ref: "#/components/schemas/priorityEnum" + channel: + $ref: "#/components/schemas/multiChannelMessageChannelEnum" + required: + - id + - owner + - applicationId + - time + - segmentCount + - direction + - to + - from + inboundCallbackMessage: + allOf: + - $ref: "#/components/schemas/statusCallbackMessage" + - type: object + properties: + content: + $ref: "#/components/schemas/multiChannelMessageContent" + suggestionResponse: + $ref: "#/components/schemas/rbmSuggestionResponse" + locationResponse: + $ref: "#/components/schemas/rbmLocationResponse" + required: + - id + - owner + - applicationId + - time + - segmentCount + - direction + - to + - from + requestBodies: + createMessageRequest: + content: + application/json: + schema: + $ref: "#/components/schemas/messageRequest" + required: true + createMultiChannelMessageRequest: + content: + application/json: + schema: + $ref: "#/components/schemas/multiChannelMessageRequest" + required: true + uploadMediaRequest: + content: + application/json: + schema: + type: string + format: binary + application/ogg: + schema: + type: string + format: binary + application/pdf: + schema: + type: string + format: binary + application/rtf: + schema: + type: string + format: binary + application/zip: + schema: + type: string + format: binary + application/x-tar: + schema: + type: string + format: binary + application/xml: + schema: + type: string + format: binary + application/gzip: + schema: + type: string + format: binary + application/x-bzip2: + schema: + type: string + format: binary + application/x-gzip: + schema: + type: string + format: binary + application/smil: + schema: + type: string + format: binary + application/javascript: + schema: + type: string + format: binary + audio/mp4: + schema: + type: string + format: binary + audio/mpeg: + schema: + type: string + format: binary + audio/ogg: + schema: + type: string + format: binary + audio/flac: + schema: + type: string + format: binary + audio/webm: + schema: + type: string + format: binary + audio/wav: + schema: + type: string + format: binary + audio/amr: + schema: + type: string + format: binary + audio/3gpp: + schema: + type: string + format: binary + image/bmp: + schema: + type: string + format: binary + image/gif: + schema: + type: string + format: binary + image/heic: + schema: + type: string + format: binary + image/heif: + schema: + type: string + format: binary + image/jpeg: + schema: + type: string + format: binary + image/pjpeg: + schema: + type: string + format: binary + image/png: + schema: + type: string + format: binary + image/svg+xml: + schema: + type: string + format: binary + image/tiff: + schema: + type: string + format: binary + image/webp: + schema: + type: string + format: binary + image/x-icon: + schema: + type: string + format: binary + text/css: + schema: + type: string + format: binary + text/csv: + schema: + type: string + format: binary + text/calendar: + schema: + type: string + format: binary + text/html: + schema: + type: string + format: binary + text/plain: + schema: + type: string + format: binary + text/javascript: + schema: + type: string + format: binary + text/vcard: + schema: + type: string + format: binary + text/vnd.wap.wml: + schema: + type: string + format: binary + text/xml: + schema: + type: string + format: binary + video/avi: + schema: + type: string + format: binary + video/mp4: + schema: + type: string + format: binary + video/mpeg: + schema: + type: string + format: binary + video/ogg: + schema: + type: string + format: binary + video/quicktime: + schema: + type: string + format: binary + video/webm: + schema: + type: string + format: binary + video/x-ms-wmv: + schema: + type: string + format: binary + video/x-flv: + schema: + type: string + format: binary + required: true + responses: + createMessageResponse: + description: Accepted + content: + application/json: + schema: + $ref: "#/components/schemas/message" + createMultiChannelMessageResponse: + description: Accepted + content: + application/json: + schema: + $ref: "#/components/schemas/createMultiChannelMessageResponse" + listMessagesResponse: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/messagesList" + getMediaResponse: + description: OK + content: + application/octet-stream: + schema: + type: string + description: Successful Operation + format: binary + listMediaResponse: + description: OK + headers: + Continuation-Token: + description: Continuation token used to retrieve subsequent media. + schema: + type: string + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/media" + messagingBadRequestError: + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/messagingRequestError" + messagingNotAcceptableError: + description: Not Acceptable + content: + application/json: + schema: + $ref: "#/components/schemas/messagingRequestError" + createMessageBadRequestError: + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/createMessageRequestError" + messagingUnauthorizedError: + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/messagingRequestError" + messagingForbiddenError: + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/messagingRequestError" + messagingNotFoundError: + description: Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/messagingRequestError" + messagingMethodNotAllowedError: + description: Method Not Allowed + content: + application/json: + schema: + $ref: "#/components/schemas/messagingRequestError" + messagingInvalidMediaTypeError: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: "#/components/schemas/messagingRequestError" + messagingTooManyRequestsError: + description: Too Many Requests + content: + application/json: + schema: + $ref: "#/components/schemas/messagingRequestError" + messagingInternalServerError: + description: Internal Server Error + content: + application/json: + schema: + $ref: "#/components/schemas/messagingRequestError" + multiChannelBadRequestError: + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/multiChannelError" + example: + links: [ ] + data: null + errors: + - type: "request-validation" + description: "The 'channelList[0].from' field must contain exactly one telephone number" + source: + field: "channelList[0].from" + multiChannelNotAcceptableError: + description: Not Acceptable + # Unsurprisingly, if the accept header is bad spring has trouble generating a response; could be fixed. + multiChannelUnauthorizedError: + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/multiChannelError" + example: + links: [ ] + data: null + errors: + - type: "unauthorized" + description: "Authentication Failed" + source: { } + multiChannelForbiddenError: + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/multiChannelError" + example: + links: [ ] + data: null + errors: + - type: "forbidden" + description: "Access Denied" + source: { } + multiChannelNotFoundError: + description: Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/multiChannelError" + example: + links: [ ] + data: null + errors: + - type: "forbidden" + description: "Resource not found." + source: { } + multiChannelMethodNotAllowedError: + description: Method Not Allowed + content: + application/json: + schema: + $ref: "#/components/schemas/multiChannelError" + example: + links: [ ] + data: null + errors: + - type: "method-not-allowed" + description: "Method 'PUT' not supported for this resource." + source: { } + multiChannelInvalidMediaTypeError: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: "#/components/schemas/multiChannelError" + example: + links: [ ] + data: null + errors: + - type: "unsupported-content-type" + description: "Content-Type 'application/xml;charset=UTF-8' is not supported. Please use 'application/json'" + source: + header: "Content-Type" + multiChannelTooManyRequestsError: + description: Too Many Requests + content: + application/json: + schema: + $ref: "#/components/schemas/multiChannelError" + example: + links: [ ] + data: null + errors: + - type: "rate-limit-exceeded" + description: "You have exceeded your rate limit for this endpoint. Please retry later." + source: { } + multiChannelInternalServerError: + description: Internal Server Error + content: + application/json: + schema: + $ref: "#/components/schemas/multiChannelError" + example: + links: [ ] + data: null + errors: + - type: "internal-server-error" + description: "Internal server error. No further information available" + source: { } + callbacks: + inboundCallback: + "{inboundCallbackUrl}": + post: + requestBody: + required: true + description: |- +

This Inbound Message Webhook is an envelope containing either a received (MO) message to your + message-enabled Bandwidth telephone number or a multichannel client's response to a suggestion response + or location request. +

The payload type will be one of message-received, suggestion-response, or location-request-response. +

Note that suggestion-response and location-request-response callback types are pertinent only for RBM messages sent from the /messages/multiChannel endpoint. +

Please visit Webhooks

+ content: + application/json: + schema: + $ref: "#/components/schemas/inboundCallback" + examples: + smsMessageReceivedCallback: + $ref: '#/components/examples/smsMessageReceivedCallbackExample' + mmsMessageReceivedCallback: + $ref: '#/components/examples/mmsMessageReceivedCallbackExample' + responses: + "200": + description: OK + "202": + description: Accepted + statusCallback: + "{statusCallbackUrl}": + post: + requestBody: + required: true + description: |- +

This Outbound Message Webhook is an envelope containing status information regarding a message sent (MT) + from your message-enabled Bandwidth telephone number. +

The payload type will be one of message-sending, message-delivered, message-failed or message-read. +

Note that message-read callbacks are pertinent only for RBM messages sent from the /messages/multiChannel endpoint. +

Please visit Webhooks

+ content: + application/json: + schema: + $ref: "#/components/schemas/statusCallback" + examples: + messageSendingCallback: + $ref: '#/components/examples/messageSendingCallbackExample' + smsMessageDeliveredCallback: + $ref: '#/components/examples/smsMessageDeliveredCallbackExample' + mmsMessageDeliveredCallback: + $ref: '#/components/examples/mmsMessageDeliveredCallbackExample' + groupMmsMessageDeliveredCallback: + $ref: '#/components/examples/groupMmsMessageDeliveredCallbackExample' + messageFailedCallback: + $ref: '#/components/examples/messageFailedCallbackExample' + responses: + "200": + description: OK + "202": + description: Accepted + securitySchemes: + Basic: + type: http + scheme: basic + description: |- + Basic authentication is a simple authentication scheme built into the HTTP protocol. To use it, send your HTTP requests with an `Authorization` header that contains the word `Basic` followed by a space and a Base64-encoded string `username:password`. + + - Example: `Authorization: Basic ZGVtbZpwQDU1dzByZA==` + examples: + smsMessageReceivedCallbackExample: + summary: An example of a sms message-received callback body. + value: + time: "2025-01-06T15:43:35.502180Z" + type: message-received + to: "+12345678902" + description: Incoming message received + message: + id: "14762070468292kw2fuqty55yp2b2" + owner: "+12345678902" + applicationId: "93de2206-9669-4e07-948d-329f4b722ee2" + time: "2025-01-06T15:43:34.000000Z" + segmentCount: 1 + direction: in + to: + - "+12345678902" + from: "+12345678901" + text: "Hey, check out this SMS!" + mmsMessageReceivedCallbackExample: + summary: An example of a mms message-received callback body. + value: + time: "2024-09-14T18:20:45.160744Z" + type: message-received + to: "+12345678902" + description: Incoming message received + message: + id: "14762070468292kw2fuqty55yp2b2" + owner: "+12345678902" + applicationId: "93de2206-9669-4e07-948d-329f4b722ee2" + time: "2024-09-14T18:20:45.160744Z" + segmentCount: 1 + direction: in + to: + - "+12345678902" + - "+12345678903" + from: "+12345678901" + text: "Hey, check out the MMS!" + media: + - "https://messaging.bandwidth.com/api/v2/users/9900902/media/14762070468292kw2fuqty55yp2b2/0/bw.png" + messageSendingCallbackExample: + summary: An example of a message-sending callback body. + value: + time: "2024-06-25T18:42:36.979456Z" + type: message-sending + to: "+15554443333" + description: Message is sending to carrier. + message: + id: "1593110555875xo7watq5px6rbe5d" + owner: "+15552221111" + applicationId: "cfd4fb83-7531-4acc-b471-42d0bb76a65c" + time: "2024-06-25T18:42:35.876906Z" + segmentCount: 1 + direction: out + to: + - "+15554443333" + from: "+15552221111" + text: "" + media: + - "https://dev.bandwidth.com/images/bandwidth-logo.png" + tag: your tag here + smsMessageDeliveredCallbackExample: + summary: An example of a sms message-delivered callback body. + value: + type: message-delivered + time: "2024-09-14T18:20:11.160744Z" + description: Message delivered to carrier. + to: "+12345678902" + message: + id: "14762070468292kw2fuqty55yp2b2" + time: "2024-09-14T18:20:11.160744Z" + to: + - "+12345678902" + from: "+12345678901" + text: "" + applicationId: "93de2206-9669-4e07-948d-329f4b722ee2" + owner: "+12345678902" + direction: out + segmentCount: 1 + mmsMessageDeliveredCallbackExample: + summary: An example of a mms message-delivered callback body. + value: + type: message-delivered + time: "2024-09-14T18:20:24.160544Z" + description: Message delivered to carrier. + to: "+12345678902" + message: + id: "14762070468292kw2fuqty55yp2b2" + time: "2024-09-14T18:20:24.160544Z" + to: + - "+12345678902" + from: "+12345678901" + text: "" + applicationId: "93de2206-9669-4e07-948d-329f4b722ee2" + owner: "+12345678902" + direction: out + segmentCount: 1 + media: + - "https://dev.bandwidth.com/images/bandwidth-logo.png" + groupMmsMessageDeliveredCallbackExample: + summary: An example of a group mms message-delivered callback body. + value: + type: message-delivered + time: "2024-09-14T18:20:17.160544Z" + description: Message delivered to carrier. + to: "+12345678902" + message: + id: "14762070468292kw2fuqty55yp2b2" + time: "2024-09-14T18:20:17.160544Z" + to: + - "+12345678902" + - "+12345678903" + from: "+12345678901" + text: "" + applicationId: "93de2206-9669-4e07-948d-329f4b722ee2" + owner: "+12345678902" + direction: out + segmentCount: 1 + messageFailedCallbackExample: + summary: An example of a message-failed callback body. + value: + type: message-failed + time: "2024-12-18T16:51:27.704450Z" + description: forbidden to country + to: "+52345678903" + errorCode: 4432 + message: + id: "14762070468292kw2fuqty55yp2b2" + time: "2024-12-18T16:51:27.704450Z" + to: + - "+12345678902" + - "+52345678903" + from: "+12345678901" + text: "" + applicationId: "93de2206-9669-4e07-948d-329f4b722ee2" + media: + - "https://dev.bandwidth.com/images/bandwidth-logo.png" + owner: "+12345678901" + direction: out + segmentCount: 1 +security: + - Basic: [] +tags: + - name: Messages + - name: Media + - name: Multi-Channel diff --git a/test/fixtures/multi-factor-auth.yml b/test/fixtures/multi-factor-auth.yml new file mode 100644 index 0000000..acc50f2 --- /dev/null +++ b/test/fixtures/multi-factor-auth.yml @@ -0,0 +1,311 @@ +openapi: 3.0.3 +info: + title: Multi-Factor Authentication + description: |- + Bandwidth's Two-Factor Authentication service + + ## Base Path + + https://mfa.bandwidth.com/api/v1 + contact: + name: Bandwidth Support + email: support@bandwidth.com + url: https://support.bandwidth.com + termsOfService: https://www.bandwidth.com/legal/terms-of-use-bandwidthcom-web-sites/ + version: 3.1.0 +servers: + - url: https://mfa.bandwidth.com/api/v1 + description: Production +paths: + /accounts/{accountId}/code/voice: + post: + tags: + - MFA + summary: Voice Authentication Code + description: Send an MFA Code via a phone call. + operationId: generateVoiceCode + parameters: + - $ref: '#/components/parameters/accountId' + requestBody: + $ref: '#/components/requestBodies/codeRequest' + responses: + '200': + $ref: '#/components/responses/voiceCodeResponse' + '400': + $ref: '#/components/responses/mfaBadRequestError' + '401': + $ref: '#/components/responses/mfaUnauthorizedError' + '403': + $ref: '#/components/responses/mfaForbiddenError' + '500': + $ref: '#/components/responses/mfaInternalServerError' + /accounts/{accountId}/code/messaging: + post: + tags: + - MFA + summary: Messaging Authentication Code + description: Send an MFA code via text message (SMS). + operationId: generateMessagingCode + parameters: + - $ref: '#/components/parameters/accountId' + requestBody: + $ref: '#/components/requestBodies/codeRequest' + responses: + '200': + $ref: '#/components/responses/messagingCodeResponse' + '400': + $ref: '#/components/responses/mfaBadRequestError' + '401': + $ref: '#/components/responses/mfaUnauthorizedError' + '403': + $ref: '#/components/responses/mfaForbiddenError' + '500': + $ref: '#/components/responses/mfaInternalServerError' + /accounts/{accountId}/code/verify: + post: + tags: + - MFA + summary: Verify Authentication Code + description: Verify a previously sent MFA code. + operationId: verifyCode + parameters: + - $ref: '#/components/parameters/accountId' + requestBody: + $ref: '#/components/requestBodies/codeVerify' + responses: + '200': + $ref: '#/components/responses/verifyCodeResponse' + '400': + $ref: '#/components/responses/mfaBadRequestError' + '401': + $ref: '#/components/responses/mfaUnauthorizedError' + '403': + $ref: '#/components/responses/mfaForbiddenError' + '429': + $ref: '#/components/responses/mfaTooManyRequestsError' + '500': + $ref: '#/components/responses/mfaInternalServerError' +components: + schemas: + codeRequest: + type: object + properties: + to: + type: string + description: The phone number to send the mfa code to. + pattern: '^\+[1-9]\d{1,14}$' + example: '+19195551234' + from: + type: string + description: The application phone number, the sender of the mfa code. + pattern: '^\+[1-9]\d{1,14}$' + maxLength: 32 + example: '+19195554321' + applicationId: + type: string + description: The application unique ID, obtained from Bandwidth. + maxLength: 50 + example: 66fd98ae-ac8d-a00f-7fcd-ba3280aeb9b1 + scope: + type: string + description: >- + An optional field to denote what scope or action the mfa code is + addressing. If not supplied, defaults to "2FA". + maxLength: 25 + example: 2FA + message: + type: string + description: >- + The message format of the mfa code. There are three values that the + system will replace "{CODE}", "{NAME}", "{SCOPE}". The "{SCOPE}" + and "{NAME} value template are optional, while "{CODE}" must be + supplied. As the name would suggest, code will be replace with the + actual mfa code. Name is replaced with the application name, + configured during provisioning of mfa. The scope value is the same + value sent during the call and partitioned by the server. + maxLength: 2048 + example: 'Your temporary {NAME} {SCOPE} code is {CODE}' + digits: + type: integer + description: >- + The number of digits for your mfa code. The valid number ranges + from 2 to 8, inclusively. + minimum: 4 + maximum: 8 + example: 6 + required: + - to + - from + - applicationId + - message + - digits + voiceCodeResponse: + type: object + properties: + callId: + type: string + description: Programmable Voice API Call ID. + example: c-15ac29a2-1331029c-2cb0-4a07-b215-b22865662d85 + messagingCodeResponse: + type: object + properties: + messageId: + type: string + description: Messaging API Message ID. + example: 1589228074636lm4k2je7j7jklbn2 + verifyCodeRequest: + type: object + properties: + to: + type: string + description: The phone number to send the mfa code to. + pattern: '^\+[1-9]\d{1,14}$' + example: '+19195551234' + scope: + type: string + description: >- + An optional field to denote what scope or action the mfa code is + addressing. If not supplied, defaults to "2FA". + example: 2FA + expirationTimeInMinutes: + type: number + description: >- + The time period, in minutes, to validate the mfa code. By setting + this to 3 minutes, it will mean any code generated within the last 3 + minutes are still valid. The valid range for expiration time is + between 0 and 15 minutes, exclusively and inclusively, respectively. + minimum: 1 + maximum: 15 + example: 3 + code: + type: string + description: The generated mfa code to check if valid. + minLength: 4 + maxLength: 8 + example: '123456' + required: + - to + - expirationTimeInMinutes + - code + verifyCodeResponse: + type: object + properties: + valid: + type: boolean + description: Whether or not the supplied code is valid. + example: true + mfaRequestError: + type: object + properties: + error: + type: string + description: A message describing the error with your request. + example: 400 Request is malformed or invalid + requestId: + type: string + description: The associated requestId from AWS. + example: 354cc8a3-6701-461e-8fa7-8671703dd898 + mfaUnauthorizedRequestError: + type: object + properties: + message: + type: string + description: Unauthorized + example: Unauthorized + mfaForbiddenRequestError: + type: object + properties: + message: + type: string + description: The message containing the reason behind the request being forbidden. + example: Missing Authentication Token + responses: + voiceCodeResponse: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/voiceCodeResponse' + messagingCodeResponse: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/messagingCodeResponse' + verifyCodeResponse: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/verifyCodeResponse' + mfaBadRequestError: + description: Bad Request + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/mfaRequestError' + mfaUnauthorizedError: + description: Unauthorized + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/mfaUnauthorizedRequestError' + mfaForbiddenError: + description: Forbidden + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/mfaForbiddenRequestError' + mfaTooManyRequestsError: + description: Too Many Requests + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/mfaRequestError' + mfaInternalServerError: + description: Internal Server Error + headers: {} + content: + application/json: + schema: + $ref: '#/components/schemas/mfaRequestError' + parameters: + accountId: + in: path + name: accountId + required: true + schema: + type: string + description: Your Bandwidth Account ID. + example: "9900000" + requestBodies: + codeRequest: + description: MFA code request body. + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/codeRequest' + codeVerify: + description: MFA code verify request body. + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/verifyCodeRequest' + securitySchemes: + Basic: + type: http + scheme: basic + description: >- + Basic authentication is a simple authentication scheme built into the HTTP protocol. To use it, send your HTTP requests with an Authorization header that contains the word Basic followed by a space and a base64-encoded string `username:password`. + + Example: `Authorization: Basic ZGVtbZpwQDU1dzByZA==` +security: + - Basic: [] +tags: + - name: MFA diff --git a/test/fixtures/no-servers.yml b/test/fixtures/no-servers.yml new file mode 100644 index 0000000..17e7054 --- /dev/null +++ b/test/fixtures/no-servers.yml @@ -0,0 +1,57 @@ +openapi: 3.0.3 +info: + title: Multi-Factor Authentication + description: Bandwidth's Two-Factor Authentication service + contact: + name: Bandwidth Support + email: support@bandwidth.com + url: https://support.bandwidth.com + termsOfService: https://www.bandwidth.com/legal/terms-of-use-bandwidthcom-web-sites/ + version: 3.1.0 +paths: + /code/verify: + post: + tags: + - MFA + summary: Verify Authentication Code + description: Verify a previously sent MFA code. + operationId: verifyCode + requestBody: + $ref: '#/components/requestBodies/codeVerify' + responses: + '200': + $ref: '#/components/responses/verifyCodeResponse' +components: + schemas: + verifyCodeRequest: + type: object + properties: + code: + type: string + description: The generated mfa code to check if valid. + example: '123456' + required: + - code + verifyCodeResponse: + type: object + properties: + valid: + type: boolean + description: Whether or not the supplied code is valid. + responses: + verifyCodeResponse: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/verifyCodeResponse' + requestBodies: + codeVerify: + description: MFA code verify request body. + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/verifyCodeRequest' +tags: + - name: MFA diff --git a/test/fixtures/phone-number-lookup.yml b/test/fixtures/phone-number-lookup.yml new file mode 100644 index 0000000..5a4018f --- /dev/null +++ b/test/fixtures/phone-number-lookup.yml @@ -0,0 +1,424 @@ +openapi: 3.0.3 +info: + title: Phone Number Lookup + version: 1.1.0 + contact: + name: Bandwidth Support + email: support@bandwidth.com + url: https://support.bandwidth.com + termsOfService: https://www.bandwidth.com/legal/terms-of-use-bandwidthcom-web-sites/ + description: >- + A Bandwidth API to provide carrier information for a telephone number or + batch of telephone numbers. Currently supports lookups of telephone numbers + in the mainland United States, Alaska, Hawaii, and the District of Columbia. Telephone numbers submitted must be in E.164 format to + be processed. + NPAC Data and data derived from User Data is confidential information and is restricted in use as defined by the Master Services Agreement for Number Portability Administration Center/Service Management System) between iconectiv and the North American Portability Management LLC (NAPM). + https://numberportability.com/about/lrn-contacts + + + Deprecation Note: This endpoint is deprecated and will be decommissioned on Dec 1, 2025. It has been replaced with: /v2/accounts/{accountId}/phoneNumberLookup/bulk + + ## Base Path + + `https://numbers.bandwidth.com/api/v1` +servers: + - url: https://numbers.bandwidth.com/api/v1 + description: Production +paths: + /accounts/{accountId}/tnlookup: + post: + summary: Create Lookup + description: Create a Phone Number Lookup Request. + operationId: createLookup + tags: + - Phone Number Lookup + parameters: + - $ref: '#/components/parameters/accountId' + requestBody: + $ref: '#/components/requestBodies/createLookupRequest' + responses: + '202': + $ref: '#/components/responses/createLookupResponse' + '400': + $ref: '#/components/responses/tnLookupBadRequestError' + '401': + $ref: '#/components/responses/tnLookupUnauthorizedError' + '403': + $ref: '#/components/responses/tnLookupForbiddenError' + '415': + $ref: '#/components/responses/tnLookupMediaTypeError' + '429': + $ref: '#/components/responses/tnLookupTooManyRequestsError' + '500': + $ref: '#/components/responses/tnLookupInternalServerError' + /accounts/{accountId}/tnlookup/{requestId}: + get: + summary: Get Lookup Request Status + description: Get an existing Phone Number Lookup Request. + operationId: getLookupStatus + tags: + - Phone Number Lookup + parameters: + - $ref: '#/components/parameters/accountId' + - $ref: '#/components/parameters/requestId' + responses: + '200': + $ref: '#/components/responses/getLookupResponse' + '400': + $ref: '#/components/responses/tnLookupBadRequestError' + '401': + $ref: '#/components/responses/tnLookupUnauthorizedError' + '403': + $ref: '#/components/responses/tnLookupForbiddenError' + '404': + $ref: '#/components/responses/tnLookupNotFoundError' + '429': + $ref: '#/components/responses/tnLookupTooManyRequestsError' + '500': + $ref: '#/components/responses/tnLookupInternalServerError' +components: + schemas: + lookupStatusEnum: + type: string + description: >- + The status of the request (IN_PROGRESS, COMPLETE, PARTIAL_COMPLETE, or + FAILED). + enum: + - IN_PROGRESS + - COMPLETE + - PARTIAL_COMPLETE + - FAILED + example: COMPLETE + lookupRequest: + type: object + description: Create phone number lookup request. + properties: + tns: + type: array + items: + type: string + required: + - tns + createLookupResponse: + type: object + description: >- + The request has been accepted for processing but not yet finished and in + a terminal state (COMPLETE, PARTIAL_COMPLETE, or FAILED). + properties: + requestId: + type: string + description: The phone number lookup request ID from Bandwidth. + status: + $ref: '#/components/schemas/lookupStatusEnum' + lookupStatus: + type: object + description: >- + If requestId exists, the result for that request is returned. See the + Examples for details on the various responses that you can receive. + Generally, if you see a Response Code of 0 in a result for a TN, + information will be available for it. Any other Response Code will + indicate no information was available for the TN. + properties: + requestId: + type: string + description: The requestId. + example: 004223a0-8b17-41b1-bf81-20732adf5590 + status: + $ref: '#/components/schemas/lookupStatusEnum' + result: + type: array + description: The carrier information results for the specified telephone number. + items: + $ref: '#/components/schemas/lookupResult' + failedTelephoneNumbers: + type: array + description: The telephone numbers whose lookup failed. + items: + type: string + example: ['+191955512345'] + lookupResult: + type: object + description: Carrier information results for the specified telephone number. + properties: + Response Code: + type: integer + description: Our vendor's response code. + example: 0 + Message: + type: string + description: Message associated with the response code. + example: NOERROR + E.164 Format: + type: string + description: The telephone number in E.164 format. + example: '+19195551234' + Formatted: + type: string + description: The formatted version of the telephone number. + example: '(919) 555-1234' + Country: + type: string + description: The country of the telephone number. + example: US + Line Type: + type: string + description: The line type of the telephone number. + example: Mobile + Line Provider: + type: string + description: The messaging service provider of the telephone number. + example: Verizon Wireless + Mobile Country Code: + type: string + description: The first half of the Home Network Identity (HNI). + example: '310' + Mobile Network Code: + type: string + description: The second half of the HNI. + example: '010' + tnLookupRequestError: + type: object + properties: + message: + type: string + description: A description of what validation error occurred. + example: example error message + responses: + createLookupResponse: + description: Accepted + content: + application/json: + schema: + $ref: '#/components/schemas/createLookupResponse' + examples: + lookupResponseExample: + $ref: '#/components/examples/lookupInProgressExample' + getLookupResponse: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/lookupStatus' + examples: + lookupInProgressExample: + $ref: '#/components/examples/lookupInProgressExample' + lookupFailedExample: + $ref: '#/components/examples/lookupFailedExample' + lookupSingleNumberCompleteExample: + $ref: '#/components/examples/lookupSingleNumberCompleteExample' + lookupMultipleNumbersCompleteExample: + $ref: '#/components/examples/lookupMultipleNumbersCompleteExample' + lookupMultipleNumbersPartialCompleteExample: + $ref: '#/components/examples/lookupMultipleNumbersPartialCompleteExample' + lookupSingleNumberCompleteNoInfoExample: + $ref: '#/components/examples/lookupSingleNumberCompleteNoInfoExample' + tnLookupBadRequestError: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/tnLookupRequestError' + examples: + badRequest: + summary: Example Bad Request Error + value: + message: 'Some tns do not match e164 format: 1234' + tnLookupUnauthorizedError: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/tnLookupRequestError' + examples: + unauthorized: + summary: Example Unauthorized Error + value: + message: Unauthorized + tnLookupForbiddenError: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/tnLookupRequestError' + examples: + forbidden: + summary: Example Forbidden Error + value: + message: >- + Authorization header requires 'Credential' parameter. + Authorization header requires 'Signature' parameter. + Authorization header requires 'SignedHeaders' parameter. + Authorization header requires existence of either a + 'X-Amz-Date' or a 'Date' header. Authorization=Basic + Y2tvZloPTGhHgywYIzGlcGVlcGvvcGovYTIGIt==' + tnLookupMediaTypeError: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/tnLookupRequestError' + examples: + mediaType: + summary: Example Unsupported Media Type Error + value: + message: Content-Type must be application/json. + tnLookupNotFoundError: + description: Not Found + tnLookupTooManyRequestsError: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/tnLookupRequestError' + examples: + mediaType: + summary: Example Too Many Requests Error + value: + message: Too many requests. + tnLookupInternalServerError: + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/tnLookupRequestError' + examples: + mediaType: + summary: Example Internal Server Error Error + value: + message: Request has not been passed further. + parameters: + accountId: + in: path + name: accountId + required: true + schema: + type: string + description: Your Bandwidth Account ID. + example: "9900000" + requestId: + name: requestId + in: path + required: true + schema: + type: string + description: The phone number lookup request ID from Bandwidth. + example: 004223a0-8b17-41b1-bf81-20732adf5590 + examples: + singleNumberRequestExample: + summary: Example Number Lookup Request for One Number + value: + tns: + - '+19195551234' + multipleNumberRequestExample: + summary: Example Number Lookup Request for Multiple Numbers + value: + tns: + - '+19195551234' + - '+19195554321' + lookupInProgressExample: + summary: Example Lookup In Progress Response + value: + requestId: 004223a0-8b17-41b1-bf81-20732adf5590 + status: IN_PROGRESS + lookupFailedExample: + summary: Example Lookup Failed Response + value: + requestId: 004223a0-8b17-41b1-bf81-20732adf5590 + status: FAILED + failedTelephoneNumbers: + - '+191955512345' + lookupSingleNumberCompleteExample: + summary: Example Single Number Lookup Complete Response + value: + requestId: 004223a0-8b17-41b1-bf81-20732adf5590 + status: COMPLETE + result: + - Response Code: 0 + Message: NOERROR + E.164 Format: '+19195551234' + Formatted: (919) 555-1234 + Country: US + Line Type: Mobile + Line Provider: Verizon Wireless + Mobile Country Code: '310' + Mobile Network Code: '010' + lookupMultipleNumbersCompleteExample: + summary: Example Multiple Numbers Lookup Complete Response + value: + requestId: 004223a0-8b17-41b1-bf81-20732adf5590 + status: COMPLETE + result: + - Response Code: 0 + Message: NOERROR + E.164 Format: '+19195551234' + Formatted: (919) 555-1234 + Country: US + Line Type: Mobile + Line Provider: Verizon Wireless + Mobile Country Code: '310' + Mobile Network Code: '010' + - Response Code: 0 + Message: NOERROR + E.164 Format: '+19195554321' + Formatted: (919) 555-4321 + Country: US + Line Type: Mobile + Line Provider: T-Mobile USA + Mobile Country Code: '310' + Mobile Network Code: '160' + lookupMultipleNumbersPartialCompleteExample: + summary: Example Multiple Numbers Lookup Partial Complete Response + value: + requestId: 004223a0-8b17-41b1-bf81-20732adf5590 + status: PARTIAL_COMPLETE + result: + - Response Code: 0 + Message: NOERROR + E.164 Format: '+19195551234' + Formatted: (919) 555-1234 + Country: US + Line Type: Mobile + Line Provider: Verizon Wireless + Mobile Country Code: '310' + Mobile Network Code: '010' + failedTelephoneNumbers: + - '+191955512345' + lookupSingleNumberCompleteNoInfoExample: + summary: Example Single Number Lookup Complete with No Information Response + value: + requestId: 004223a0-8b17-41b1-bf81-20732adf5590 + status: COMPLETE + result: + - Response Code: 3 + Message: NXDOMAIN + E.164 Format: '+19195550000' + Formatted: (919) 555-0000 + Country: US + requestBodies: + createLookupRequest: + description: Phone number lookup request. + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/lookupRequest' + examples: + singleNumberRequestExample: + $ref: '#/components/examples/singleNumberRequestExample' + multipleNumberRequestExample: + $ref: '#/components/examples/multipleNumberRequestExample' + securitySchemes: + Basic: + type: http + scheme: basic + description: >- + Basic authentication is a simple authentication scheme built into the + HTTP protocol. To use it, send your HTTP requests with an Authorization + header that contains the word Basic followed by a space and a + base64-encoded string `username:password`. + + Example: `Authorization: Basic ZGVtbZpwQDU1dzByZA==` +security: + - Basic: [] +tags: + - name: Phone Number Lookup diff --git a/test/test_openapi.py b/test/test_openapi.py new file mode 100644 index 0000000..b1fe478 --- /dev/null +++ b/test/test_openapi.py @@ -0,0 +1,40 @@ +import pytest +from pytest_httpx import HTTPXMock +from utils import create_mock +from src.server_utils import fetch_openapi_spec + + +@pytest.mark.asyncio +async def test_fetch_openapi_spec_valid(httpx_mock: HTTPXMock): + """Test that the OpenAPI spec can be fetched and parsed correctly.""" + + create_mock(httpx_mock, "insights") + + spec = await fetch_openapi_spec("https://dev.bandwidth.com/spec/insights.yml") + + assert isinstance(spec, dict), "Fetched spec should be a dictionary" + assert "openapi" in spec, "Spec should contain 'openapi' key" + assert "info" in spec, "Spec should contain 'info' key" + assert "paths" in spec, "Spec should contain 'paths' key" + + +@pytest.mark.asyncio +async def test_fetch_openapi_spec_empty_yaml(httpx_mock: HTTPXMock): + """Test that fetching an empty spec raises an error.""" + create_mock(httpx_mock, "empty") + with pytest.raises(ValueError): + await fetch_openapi_spec("https://dev.bandwidth.com/spec/empty.yml") + + +@pytest.mark.asyncio +async def test_fetch_openapi_spec_http_error(): + """Test that fetching an invalid URL raises an HTTP error.""" + with pytest.raises(RuntimeError): + await fetch_openapi_spec("https://dev.bandwidth.com/spec/nonexistent.yml") + + +@pytest.mark.asyncio +async def test_fetch_openapi_spec_invalid_yaml(): + """Test that fetching an invalid YAML file raises a YAMLError.""" + with pytest.raises(RuntimeError): + await fetch_openapi_spec("https://example.com") diff --git a/test/test_servers.py b/test/test_servers.py new file mode 100644 index 0000000..b84b021 --- /dev/null +++ b/test/test_servers.py @@ -0,0 +1,136 @@ +import pytest +from fastmcp import FastMCP +from pytest_httpx import HTTPXMock +from utils import create_mock +from src.servers import create_bandwidth_mcp, _create_server + + +async def create_mcp_server(name=None, tools=None, excluded_tools=None): + """Fixture to create and return a FastMCP instance.""" + mcp = FastMCP(name=name or "Test MCP") + config = {"BW_USERNAME": "test_user", "BW_PASSWORD": "test_pass"} + enabled_tools = tools if tools is not None else [] + excluded_tools = excluded_tools if excluded_tools is not None else [] + + await create_bandwidth_mcp(mcp, enabled_tools, excluded_tools, config) + + return mcp + + +def calculate_expected_tools(tools, excluded_tools, total_tools=46): + if tools and not excluded_tools: + return len(tools) + elif excluded_tools: + return total_tools - len(excluded_tools) + else: + return total_tools + + +server_configuration_list = [ + ([], []), + ([], ["getReports", "createReport"]), + (["getReports", "createReport"], []), + (["uploadMedia", "deleteMedia", "getMedia"], ["listMedia"]), + (["listMedia"], ["uploadMedia", "deleteMedia", "getMedia"]), +] + + +@pytest.mark.asyncio +@pytest.mark.parametrize("tools, excluded_tools", server_configuration_list) +async def test_full_mcp_server_creation(tools, excluded_tools, httpx_mock: HTTPXMock): + """Test that the MCP server is created correctly with included and excluded tools.""" + + expected_tools = calculate_expected_tools(tools, excluded_tools) + name = f"Test MCP with {expected_tools} Tools" + + for name in [ + "messaging", + "multi-factor-auth", + "phone-number-lookup", + "insights", + "end-user-management", + ]: + create_mock(httpx_mock, name) + + mcp = await create_mcp_server(name, tools, excluded_tools) + mcp_tools = await mcp.get_tools() + mcp_tool_names = list(mcp_tools.keys()) + mcp_resources = await mcp.get_resources() + + assert isinstance(mcp, FastMCP) + assert mcp.name == name, f"Expected MCP name '{name}', got '{mcp.name}'" + assert ( + len(mcp_tools) == expected_tools + ), f"Expected {expected_tools} tools, got {len(mcp_tools)}" + assert len(mcp_resources) == 2, f"Expected 2 resources, got {len(mcp_resources)}" + + if excluded_tools: + for tool in excluded_tools: + assert ( + tool not in mcp_tool_names + ), f"Excluded tool {tool} should not be present" + + if tools and not excluded_tools: + for tool in tools: + assert tool in mcp_tool_names, f"Enabled tool {tool} should be present" + + +spec_list = [ + ( + "https://dev.bandwidth.com/spec/multi-factor-auth.yml", + {"BW_USERNAME": "test_user_mfa", "BW_PASSWORD": "test_pass_mfa"}, + "https://mfa.bandwidth.com/api/v1/", + {"generateMessagingCode", "generateVoiceCode", "verifyCode"}, + "Basic dGVzdF91c2VyX21mYTp0ZXN0X3Bhc3NfbWZh", + ), + ( + "https://dev.bandwidth.com/spec/phone-number-lookup.yml", + {"BW_USERNAME": "test_user_tnlookup", "BW_PASSWORD": "test_pass_tnlookup"}, + "https://numbers.bandwidth.com/api/v1/", + {"createLookup", "getLookupStatus"}, + "Basic dGVzdF91c2VyX3RubG9va3VwOnRlc3RfcGFzc190bmxvb2t1cA==", + ), +] + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "url, config, expected_base_url, expected_tools, expected_auth_header", spec_list +) +async def test_individual_mcp_server_creation( + url, config, expected_base_url, expected_tools, expected_auth_header +): + """Test that individual MCP servers are created correctly.""" + + server = await _create_server(url, None, config) + server_client = server._client + + server_tools = await server.get_tools() + server_tool_names = set(server_tools.keys()) + + assert isinstance(server, FastMCP) + assert ( + server.name == "Bandwidth" + ), f"Expected server name to be 'Bandwidth', got '{server.name}'" + assert ( + server_tool_names == expected_tools + ), f"Expected tools {expected_tools}, got {server_tool_names}" + assert ( + server_client.headers["User-Agent"] == "Bandwidth MCP Server" + ), f"Expected User-Agent 'Bandwidth MCP Server', got '{server_client.headers['User-Agent']}'" + assert ( + server_client.base_url == expected_base_url + ), f"Expected base URL '{expected_base_url}', got '{server_client.base_url}'" + assert ( + server_client.headers["Authorization"] == expected_auth_header + ), f"Expected auth header '{expected_auth_header}', got '{server_client.headers['Authorization']}'" + + +@pytest.mark.asyncio +async def test_create_server_no_servers_defined(httpx_mock: HTTPXMock): + """Test that creating a server with no servers defined raises an error.""" + + create_mock(httpx_mock, "no-servers") + + with pytest.raises(ValueError, match="has no servers defined"): + await _create_server("https://dev.bandwidth.com/spec/no-servers.yml") diff --git a/test/utils.py b/test/utils.py new file mode 100644 index 0000000..b2ec6a9 --- /dev/null +++ b/test/utils.py @@ -0,0 +1,10 @@ +from pytest_httpx import HTTPXMock + + +def create_mock(httpx_mock: HTTPXMock, spec_name: str): + """Helper function to create a mock response for HTTPX.""" + with open(f"test/fixtures/{spec_name}.yml", "r", encoding="utf-8") as f: + response_text = f.read() + httpx_mock.add_response( + url=f"https://dev.bandwidth.com/spec/{spec_name}.yml", text=response_text + )