GenericAPICall
Integration version of the httpv2 automation used to send HTTP requests for API calls to endpoints not associated with an existing XSOAR integration.
Utilities · Generic API Call
Details
| ID | GenericAPICall |
|---|---|
| Provider | Open Source |
| Category | Utilities |
| From Version | 5.5.0 |
| Docker Image | demisto/python3:3.12.13.10404775 |
| Supported Modules | Agentix XSIAM Cloud Posture Security |
README
GenericAPICall Integration
This integration provides a method for executing HTTP API calls to specific API endpoints used in one-off scenarios not covered by other XSOAR integrations. This integration supports unauthenticated and authenticated API calls over HTTPS, using API key and basic HTTP authentication methods.
Configuration Parameters
- Name - Integration instance name
- Base Server URL - The base server URL for the API endpoint without the trailing slash (/)
- Username - Username for HTTP basic authentication.
- Leave blank when using an API key
- Password/API Key - The password (when using HTTP basic authentication) or API key to use for API calls with this integration instance
- API call is authenticated - Check this box if API calls to this endpoint require authentication (Default: False)
- API key supplied in header - Check this box if API key or authentication credentials are provided as part of the HTTP header (Default: False)
- Unchecking this box will pass the API key as a parameter in the URL (&apiKey=KEY_HERE)
- Authentication Header - The value to use to identify the API key field as part of the HTTP header or API Key parameter in the URL
- Header example: { ‘AUTHENICATION_HEADER_HERE’: ‘API_KEY_HERE’ }
- Parameterized example: &AUTHENTICATION_HEADER=API_KEY_HERE
- Use system proxy settings - Leverage the proxy settings configured on the XSOAR server
- Trust any certificate (not secure) - Bypass SSL certificate verification for these connections
Commands
generic-api-call
Performs a HTTP-based API call to the server configured server and url path.
NOTE: Because of the potentially large amount of data returned per specific API call, no content is output to the war room except a message confirming call execution.
Base Command
!generic-api-call
Input
| Argument Name | Description | Required |
|---|---|---|
| method | The HTTP method to use for the API call. (Default: POST) GET, POST, PUT, PATCH, DELETE, HEAD | Required |
| urlpath | The URL path to the API endpoint to be appended to the base_url parameter. (Default: /) | Required |
| parse_response_as | Specify the type of data expected in the API response Defined types: - json - raw_response - text - xml |
Required |
| body | The body to be used for the API call | Optional |
| request_content_type | Specify the Content-Type header for the request.Shorthands are provided for the following common content types:json (application/json)xml (text/xml)form (application/x-www-form-urlencoded)data (multipart/form-data). If you choose to define a different type, please include the full type name, e.g: application/pdf Predefined types: - application/json - application/pdf - application/x-www-form-urlencoded - application/xml - multipart/form-data - text/csv - text/plain - text/xml |
Optional |
| response_content_type | Specify the Accept header for the request.Shorthands are provided for the following common content types:json (application/json)xml (text/xml)form (application/x-www-form-urlencoded)data (multipart/form-data). If you choose to define a different type, please include the full type name, e.g: application/pdf Predefined types: - application/json - application/pdf - application/x-www-form-urlencoded - application/xml - multipart/form-data - text/csv - text/plain - text/xml |
Optional |
| params | User-defined parameters for the API call | Optional |
| save_as_file | Save the API response as a file to context in XSOAR (Default: False) | Optional |
| file_name | The filename to save the returned API response as in XSOAR (Default: http-file) | Optional |
| timeout | Specify the timeout of the HTTP request in seconds. (Default: 10 seconds) | Optional |
| timeout_between_retries | Specify the timeout between each retry in seconds. (Default: 5 seconds) | Optional |
| retry_count | Specify the number or retries to be made in case of a call failure. (Default: 3 attempts) | Optional |
| retry_on_status | Specify a list of HTTP status codes that a retry should be attempted on. For example: 301-303,400,402.” | Optional |
| enable_redirect | The request will be called again with the new URL (Default: false) | Optional |
Context Output
The response data
| Path | Type | Description |
|---|---|---|
| APICall.StatusCode | String | The HTTP status code returned from the request |
| APICall.StatusText | String | The text corresponding to the returned HTTP status code |
| APICall.URL | String | The URL of the API call |
| APICall.ParsedBody | String | The parsed response, formatted according to parse_response_as argument |
| APICall.Headers | String | The headers from the API call response |
| APICall.Body | String | The raw data returned from the API call |
Command examples
All examples in this documentation are based off the following endpoints:
JSON: https[:]//api[.]apis[.]guru/v2/metrics.json
XML: https[:]//boardgamegeek[.]com/xmlapi/search?search=civilizations
JSON Request Body: https[:]//reqres[.]in/
Command example - JSON
API call with output to context - JSON Return
!generic-api-call urlpath=/metrics.json request_content_type=application/json response_content_type=application/json method=GET parse_response_as=json
Context Example


Human Readable Output
Sent a GET request to https://api.apis.guru/v2
Command example - XML
API call with output to context - XML Return
!generic-api-call urlpath=/xmlapi/search?search=civilizations method=GET request_content_type=application/xml response_content_type=application/xml parse_response_as=xml
Context Example



Human Readable Output
Sent a GET request to https://boardgamegeek.com/xmlapi
Command example - Saving output to file in war room/context
Saving output to file in context/war room
!generic-api-call urlpath=/metrics.json request_content_type=application/json response_content_type=application/json method=GET parse_response_as=json save_as_file=true
Context Example

Human Readable Output

Command example - API Call with JSON Request Body
API call with output to context - JSON specified in request body
!generic-api-call response_content_type=application/json method=POST urlpath=/api/users parse_response_as=json body=`{"name": "xsoar", "job": "XSOAR Ninja"}` request_content_type=application/json
Output Example

Human Readable Output
Sent a POST request to https://reqres.in
Command example - ElasticSearch Cases API Call
As the current ElasticSearch integration does not support pulling case data, the Generic API Call integration can be used to perform such a data pull. In the below example, a generic API call is issued to pull back a list of cases in ElasticSearch tied to the Elastic Cases API. Below are screenshots showing how the integration is configured to authenticate to ElasticSearch.
Please make note of the way the API key and authorization header are specified. For the API key, Elastic uses the “Authorization” header with an API key formatted as “ApiKey ENCODED_KEY_HERE”. The “ApiKey ENCODED_KEY_HERE” text goes in the password field for the integration, while “Authorization” is specified in the “Authentication Header” integration instance configuration field. The USERNAME field is left blank in the integration configuration for a call to the ElasticSearch API endpoint.

API call with output to context - ElasticSearch Cases API
!generic-api-call method=GET parse_response_as=json enable_redirect=true urlpath=/_find"
Context Example

Human Readable Output
Sent a GET request to https://X.X.X.X/api/cases
Configuration parameters
base_url— Base API Endpoint URL (https:// with no trailing slash) (required)credentials— Username (Blank when using API key)is_auth— API call is authenticated (Default: False)apikey_in_header— API Key supplied in headerapi_call_key— Authentication Headerproxy— Use system proxy settingsinsecure— Trust any certificate (not secure)
Commands (1)
-
generic-api-callGeneric HTTPS request to a specified API endpoint.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 import urllib3 import requests import json from typing import Any # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # ISO8601 format with UTC, default in XSOAR """ CLIENT CLASS """ CONTENT_TYPE_MAPPER = { "json": "application/json", "xml": "text/xml", "form": "application/x-www-form-urlencoded", "data": "multipart/form-data", } RAW_RESPONSE = "raw_response" class Client(BaseClient): def __init__(self, base_url: str, auth, verify: bool, proxy: bool): super().__init__(base_url=base_url, auth=auth, verify=verify, proxy=proxy) def http_request( self, method: str, full_url: str = "", headers: dict = None, resp_type: str = RAW_RESPONSE, params: dict = None, data: str = None, timeout: int = 10, retries: int = 0, status_list_to_retry: list = None, raise_on_status: bool = False, allow_redirects: bool = True, backoff_factor: int = 5, ): try: res = self._http_request( method=method, full_url=full_url, headers=headers, params=params, timeout=timeout, resp_type=resp_type, status_list_to_retry=status_list_to_retry, raise_on_status=raise_on_status, retries=retries, data=data, error_handler=self._generic_error_handler, allow_redirects=allow_redirects, backoff_factor=backoff_factor, ) except Exception as e: return_error(f"Failed to execute API call. Error: {str(e)}") return res @staticmethod def _generic_error_handler(res: requests.Response): status_code = res.status_code if status_code == 400: raise DemistoException(f"Bad request. Status code: {status_code}. Origin response from server: {res.text}") if status_code == 401: raise DemistoException(f"Unauthorized. Status code: {status_code}. Origin response from server: {res.text}") if status_code == 403: raise DemistoException(f"Invalid permissions. Status code: {status_code}. Origin response from server: {res.text}") if status_code == 404: raise DemistoException( f"The server has not found anything matching the request URI. Status code:" f" {status_code}. Origin response from server: {res.text}" ) if status_code == 500: raise DemistoException(f"Internal server error. Status code: {status_code}. Origin response from server: {res.text}") if status_code == 502: raise DemistoException(f"Bad gateway. Status code: {status_code}. Origin response from server: {res.text}") def create_headers(headers: Dict, request_content_type_header: str, response_content_type_header: str) -> Dict[str, str]: """ Create a dictionary of headers. It will map the header if it exists in the CONTENT_TYPE_MAPPER. Args: headers: The headers the user insert. request_content_type_header: The content type header. response_content_type_header: The response type header. Returns: A dictionary of headers to send in the request. """ if request_content_type_header in CONTENT_TYPE_MAPPER: request_content_type_header = CONTENT_TYPE_MAPPER[request_content_type_header] if response_content_type_header in CONTENT_TYPE_MAPPER: response_content_type_header = CONTENT_TYPE_MAPPER[response_content_type_header] if request_content_type_header and not headers.get("Content-Type"): headers["Content-Type"] = request_content_type_header if response_content_type_header and not headers.get("Accept"): headers["Accept"] = response_content_type_header return headers def get_parsed_response(res, resp_type: str) -> Any: try: resp_type = resp_type.lower() if resp_type == "json": res = res.json() elif resp_type == "xml": res = json.loads(xml2json(res.content)) else: res = res.text return res except ValueError as e: raise DemistoException(f"Failed to parse json object from response: {res.content}\n\nError Message: {e}") def format_status_list(status_list: list) -> List[int]: """ Get a status list and format it to a range of status numbers. Example: given: ['400-404',500,501] return: [400,401,402,403,500,501] Args: status_list: The given status list. Returns: A list of statuses to retry. """ final_status_list = [] for status in status_list: # Checks if the status is a range of statuses if "-" in status: range_numbers = status.split("-") status_range = list(range(int(range_numbers[0]), int(range_numbers[1]) + 1)) final_status_list.extend(status_range) elif status.isdigit(): final_status_list.append(int(status)) return final_status_list def build_outputs(parsed_res, res: requests.Response) -> Dict: return { "ParsedBody": parsed_res, "Body": res.text, "StatusCode": res.status_code, "StatusText": res.reason, "URL": res.url, "Headers": dict(res.headers), } def parse_headers(headers: str) -> Dict: """ Parsing headers from str type to dict. The allowed format are: 1. {"key": "value"} 2. "key": "value" """ if not headers.startswith("{") and not headers.endswith("}"): headers = "{" + headers + "}" try: headers_dict = json.loads(headers) except json.decoder.JSONDecodeError: raise DemistoException("Make sure the headers are in one of the allowed formats.") return headers_dict def api_call_command(client: Client): dmst_params = demisto.params() apikey_in_header = dmst_params.get("apikey_in_header", True) api_call_key = dmst_params.get("api_call_key", "") is_auth = argToBoolean(dmst_params.get("is_auth", "False")) cmd_args = demisto.args() method = cmd_args.get("method", "") body = cmd_args.get("body", "") request_content_type = cmd_args.get("request_content_type", "") response_content_type = cmd_args.get("response_content_type", "") parse_response_as = cmd_args.get("parse_response_as", RAW_RESPONSE) params = cmd_args.get("params", {}) headers = cmd_args.get("headers", {}) if not api_call_key and is_auth: demisto.error("Parameter/Header key used for API call must be specified") elif is_auth: if apikey_in_header: headers.update({api_call_key: demisto.getParam("credentials")["password"]}) else: params.update({api_call_key: demisto.getParam("credentials")["password"]}) url_path = cmd_args.get("urlpath", "/") if isinstance(headers, str): headers = parse_headers(headers) headers = create_headers(headers, request_content_type, response_content_type) save_as_file = argToBoolean(cmd_args.get("save_as_file", False)) file_name = cmd_args.get("filename", "http-file") timeout = arg_to_number(cmd_args.get("timeout", 10)) timeout_between_retries = cmd_args.get("timeout_between_retries", 5) retry_count = arg_to_number(cmd_args.get("retry_count", 3)) kwargs = { "method": method, "full_url": client._base_url + url_path, "headers": headers, "data": body, "timeout": timeout, "params": params, "backoff_factor": timeout_between_retries, } retry_on_status = cmd_args.get("retry_on_status", None) raise_on_status = bool(retry_on_status) retry_status_list = format_status_list(argToList(retry_on_status)) if raise_on_status: kwargs.update({"retries": retry_count, "status_list_to_retry": retry_status_list, "raise_on_status": raise_on_status}) enable_redirect = argToBoolean(cmd_args.get("enable_redirect", True)) if not enable_redirect: kwargs.update({"allow_redirects": enable_redirect}) res = client.http_request(**kwargs) parsed_res = get_parsed_response(res, parse_response_as) if save_as_file: return fileResult(file_name, res.content) outputs = build_outputs(parsed_res, res) return CommandResults( readable_output=f"Sent a {method} request to {client._base_url}", outputs_prefix="APICall", outputs=outputs, raw_response={"data": parsed_res}, ) def test_module(client): # Basic test logic to validate the connection or configuration return """Test-module is not implemented in this integration due to the number of possible API endpoints that may be configured.""" """ MAIN FUNCTION """ def main(): try: params = demisto.params() results = "" auth: Optional[tuple[str, str]] = None apikey_in_header = argToBoolean(params.get("apikey_in_header", False)) base_url = params.get("base_url", "") is_auth = params.get("is_auth", True) creds = params.get("credentials", "") proxy = params.get("proxy", False) verify = not params.get("insecure", True) command = demisto.command() if command == "generic-api-call": if is_auth and not apikey_in_header: # Credential object - API Key or HTTP Basic Auth if "credentials" in creds and creds["credentials"]["name"]: auth = (creds["credentials"]["user"], creds["credentials"]["password"]) # Creds configured in integration instance else: auth = (creds["identifier"], creds["password"]) else: auth = None client = Client(base_url, auth=auth, verify=verify, proxy=proxy) demisto.debug(f"Command being called is {command}") results = api_call_command(client) elif command == "test-module": return_results(test_module(client)) return_results(results) except Exception as e: return_error(f"Failed to execute generic API call. Error: {str(e)}") if __name__ in ("__main__", "__builtin__", "builtins"): main()