Commit 983d1f20bd for asterisk.org

commit 983d1f20bdc71d9e60e59afd89358ce99b726230
Author: Ben Ford <bford@sangoma.com>
Date:   Wed Apr 15 12:09:29 2026 -0500

    ARI: Added paths to get and set multiple channel variables.

    Two new paths exist for ARI to get and set multiple channel variables at
    the same time. This is done via GET and POST like the single get and set
    variable equivalents. Leading and trailing whitespace will be stripped
    from the variable names for both paths. When setting variables, the
    values will be read as-is, whitespace included. GET takes in a single
    string with comma-separated values, while POST takes in a dictionary of
    key value pairs. The code follows the same paths as when setting
    multiple variables when originating a channel via ARI.

    UserNote: Added new ARI paths for getting and setting multiple channel
    variables at a time. For GET, this takes in a single string of
    comma-separated variable names, while POST takes in a dictionary of key
    value pairs. The behavior is the same as passing in variables when
    originating a channel.

diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index f41b3b072e..c80f8bc740 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -818,6 +818,44 @@ ari_validator ast_ari_validate_variable_fn(void)
 	return ast_ari_validate_variable;
 }

+int ast_ari_validate_variables(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_variables = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("variables", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_variables = 1;
+			prop_is_valid = ast_ari_validate_object(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Variables field variables failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI Variables has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_variables) {
+		ast_log(LOG_ERROR, "ARI Variables missing required field variables\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+ari_validator ast_ari_validate_variables_fn(void)
+{
+	return ast_ari_validate_variables;
+}
+
 int ast_ari_validate_endpoint(struct ast_json *json)
 {
 	int res = 1;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index e17dee9d52..fd25e0b9e2 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -327,6 +327,22 @@ int ast_ari_validate_variable(struct ast_json *json);
  */
 ari_validator ast_ari_validate_variable_fn(void);

+/*!
+ * \brief Validator for Variables.
+ *
+ * A dictionary of channel variables
+ *
+ * \param json JSON object to validate.
+ * \retval True (non-zero) if valid.
+ * \retval False (zero) if invalid.
+ */
+int ast_ari_validate_variables(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_variables().
+ */
+ari_validator ast_ari_validate_variables_fn(void);
+
 /*!
  * \brief Validator for Endpoint.
  *
@@ -1554,6 +1570,8 @@ ari_validator ast_ari_validate_application_fn(void);
  * - version: string (required)
  * Variable
  * - value: string (required)
+ * Variables
+ * - variables: object (required)
  * Endpoint
  * - channel_ids: List[string] (required)
  * - resource: string (required)
diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c
index 85351daf5d..ac4a33f092 100644
--- a/res/ari/resource_channels.c
+++ b/res/ari/resource_channels.c
@@ -1588,6 +1588,98 @@ void ast_ari_channels_get_channel_var(struct ast_variable *headers,
 	ast_ari_response_ok(response, ast_json_ref(json));
 }

+void ast_ari_channels_get_channel_vars(struct ast_variable *headers,
+	struct ast_ari_channels_get_channel_vars_args *args,
+	struct ast_ari_response *response)
+{
+	int res;
+	RAII_VAR(struct ast_json *, json, ast_json_object_create(), ast_json_unref);
+	RAII_VAR(struct ast_json *, inner_json, ast_json_object_create(), ast_json_unref);
+	RAII_VAR(struct ast_str *, value, ast_str_create(32), ast_free);
+	RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup);
+
+	ast_assert(response != NULL);
+
+	if (!json || !inner_json || !value) {
+		ast_ari_response_alloc_failed(response);
+		return;
+	}
+
+	if (args->variables_count == 0) {
+		ast_ari_response_error(
+			response, 400, "Bad Request",
+			"At least one variable name is required");
+		return;
+	}
+
+	if (ast_strlen_zero(args->channel_id)) {
+		ast_ari_response_error(
+			response, 400, "Bad Request",
+			"Channel ID is required");
+		return;
+	}
+
+	channel = ast_channel_get_by_name(args->channel_id);
+	if (!channel) {
+		ast_ari_response_error(
+			response, 404, "Channel Not Found",
+			"Provided channel was not found");
+		return;
+	}
+
+	for (int i = 0; i < args->variables_count; i++) {
+		struct ast_json *json_str;
+		char buf[strlen(args->variables[i]) + 1];
+		char *variable;
+
+		strcpy(buf, args->variables[i]);
+		variable = ast_strip(buf);
+		if (ast_strlen_zero(variable)) {
+			ast_ari_response_error(
+				response, 400, "Bad Request",
+				"Variable names are required");
+			return;
+		}
+
+		if (variable[strlen(variable) - 1] == ')') {
+			if (ast_func_read2(channel, variable, &value, 0)) {
+				ast_ari_response_error(
+					response, 500, "Error With Function",
+					"Unable to read provided function");
+				return;
+			}
+		} else {
+			if (!ast_str_retrieve_variable(&value, 0, channel, NULL, variable)) {
+				ast_ari_response_error(
+					response, 404, "Variable Not Found",
+					"Provided variable was not found");
+				return;
+			}
+		}
+
+		json_str = ast_json_string_create(ast_str_buffer(value));
+		if (!json_str) {
+			ast_ari_response_alloc_failed(response);
+			return;
+		}
+
+		res = ast_json_object_set(inner_json, variable, json_str);
+		if (res) {
+			ast_ari_response_alloc_failed(response);
+			ast_json_unref(json_str);
+			return;
+		}
+	}
+
+	res = ast_json_object_set(json, "variables", ast_json_ref(inner_json));
+	if (res) {
+		ast_ari_response_alloc_failed(response);
+		return;
+	}
+
+	ast_ari_response_ok(response, ast_json_ref(json));
+}
+
 void ast_ari_channels_set_channel_var(struct ast_variable *headers,
 	struct ast_ari_channels_set_channel_var_args *args,
 	struct ast_ari_response *response)
@@ -1619,6 +1711,102 @@ void ast_ari_channels_set_channel_var(struct ast_variable *headers,
 	ast_ari_response_no_content(response);
 }

+void ast_ari_channels_set_channel_vars(struct ast_variable *headers,
+	struct ast_ari_channels_set_channel_vars_args *args,
+	struct ast_ari_response *response)
+{
+	struct ast_json *json_variables;
+	struct ast_json_iter *it_json_var;
+	struct ast_variable *var = NULL;
+	RAII_VAR(struct ast_variable *, variables, NULL, ast_variables_destroy);
+	RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup);
+	RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+
+	ast_assert(response != NULL);
+
+	if (!args->variables) {
+		ast_ari_response_error(
+			response, 400, "Bad Request",
+			"The 'variables' field is required");
+		return;
+	}
+
+	channel = ast_channel_get_by_name(args->channel_id);
+	if (!channel) {
+		ast_ari_response_error(
+			response, 404, "Channel Not Found",
+			"Provided channel was not found");
+		return;
+	}
+
+	control = find_control(response, args->channel_id);
+	if (control == NULL) {
+		/* response filled in by find_control */
+		return;
+	}
+
+	json_variables = ast_json_object_get(args->variables, "variables");
+	for (it_json_var = ast_json_object_iter(json_variables); it_json_var;
+		it_json_var = ast_json_object_iter_next(json_variables, it_json_var)) {
+		const char *key = ast_json_object_iter_key(it_json_var);
+		char buf[strlen(key) + 1];
+		char *stripped_key;
+		struct ast_json *json_value;
+		const char *value;
+		struct ast_variable *new_var;
+
+		strcpy(buf, key);
+		stripped_key = ast_strip(buf);
+		if (ast_strlen_zero(stripped_key)) {
+			ast_ari_response_error(
+				response, 400, "Bad Request",
+				"Variable names are required");
+			return;
+		}
+
+		json_value = ast_json_object_iter_value(it_json_var);
+		if (ast_json_typeof(json_value) != AST_JSON_STRING) {
+			ast_ari_response_error(
+				response, 400, "Bad Request",
+				"Variable values must be strings");
+			return;
+		}
+
+		value = ast_json_string_get(json_value);
+		if (!value) {
+			ast_ari_response_error(
+				response, 500, "Internal Server Error",
+				"Could not get string value from JSON string");
+			return;
+		}
+
+		new_var = ast_variable_new(stripped_key, value, "");
+		if (!new_var) {
+			ast_ari_response_error(
+				response, 500, "Internal Server Error",
+				"Could not create internal variable");
+			return;
+		}
+
+		/* Append to the tail */
+		var = ast_variable_list_append_hint(&variables, var, new_var);
+	}
+
+	/* We loop twice to preserve variable state if something goes wrong. If something
+	 * goes wrong in this loop, something went VERY wrong.
+	 */
+	for (var = variables; var; var = var->next) {
+		if (stasis_app_control_set_channel_var(control, var->name, var->value)) {
+			ast_ari_response_error(
+				response, 400, "Bad Request",
+				"Failed to execute function");
+			return;
+		}
+	}
+
+	ast_ari_response_no_content(response);
+}
+
 static void ari_channels_handle_snoop_channel(
 	const char *args_channel_id,
 	const char *args_spy,
diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h
index e090dc204e..5d4bf5833a 100644
--- a/res/ari/resource_channels.h
+++ b/res/ari/resource_channels.h
@@ -726,6 +726,62 @@ int ast_ari_channels_set_channel_var_parse_body(
  * \param[out] response HTTP response
  */
 void ast_ari_channels_set_channel_var(struct ast_variable *headers, struct ast_ari_channels_set_channel_var_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_channels_get_channel_vars() */
+struct ast_ari_channels_get_channel_vars_args {
+	/*! Channel's id */
+	const char *channel_id;
+	/*! Array of The channel variables or functions to get */
+	const char **variables;
+	/*! Length of variables array. */
+	size_t variables_count;
+	/*! Parsing context for variables. */
+	char *variables_parse;
+};
+/*!
+ * \brief Body parsing function for /channels/{channelId}/variables.
+ * \param body The JSON body from which to parse parameters.
+ * \param[out] args The args structure to parse into.
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int ast_ari_channels_get_channel_vars_parse_body(
+	struct ast_json *body,
+	struct ast_ari_channels_get_channel_vars_args *args);
+
+/*!
+ * \brief Get the value of multiple channel variables or functions.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_channels_get_channel_vars(struct ast_variable *headers, struct ast_ari_channels_get_channel_vars_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_channels_set_channel_vars() */
+struct ast_ari_channels_set_channel_vars_args {
+	/*! Channel's id */
+	const char *channel_id;
+	/*! The "variables" key in the body object holds variable key/value pairs to set on the channel. Ex. { "variables": { "CALLERID(name)": "Alice" } } */
+	struct ast_json *variables;
+};
+/*!
+ * \brief Body parsing function for /channels/{channelId}/variables.
+ * \param body The JSON body from which to parse parameters.
+ * \param[out] args The args structure to parse into.
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int ast_ari_channels_set_channel_vars_parse_body(
+	struct ast_json *body,
+	struct ast_ari_channels_set_channel_vars_args *args);
+
+/*!
+ * \brief Set the values of multiple channel variables or functions.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_channels_set_channel_vars(struct ast_variable *headers, struct ast_ari_channels_set_channel_vars_args *args, struct ast_ari_response *response);
 /*! Argument struct for ast_ari_channels_snoop_channel() */
 struct ast_ari_channels_snoop_channel_args {
 	/*! Channel's id */
diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c
index 5474b60f42..3d109e101c 100644
--- a/res/res_ari_channels.c
+++ b/res/res_ari_channels.c
@@ -2559,6 +2559,229 @@ static void ast_ari_channels_set_channel_var_cb(
 	}
 #endif /* AST_DEVMODE */

+fin: __attribute__((unused))
+	return;
+}
+int ast_ari_channels_get_channel_vars_parse_body(
+	struct ast_json *body,
+	struct ast_ari_channels_get_channel_vars_args *args)
+{
+	struct ast_json *field;
+	/* Parse query parameters out of it */
+	field = ast_json_object_get(body, "variables");
+	if (field) {
+		/* If they were silly enough to both pass in a query param and a
+		 * JSON body, free up the query value.
+		 */
+		ast_free(args->variables);
+		if (ast_json_typeof(field) == AST_JSON_ARRAY) {
+			/* Multiple param passed as array */
+			size_t i;
+			args->variables_count = ast_json_array_size(field);
+			args->variables = ast_malloc(sizeof(*args->variables) * args->variables_count);
+
+			if (!args->variables) {
+				return -1;
+			}
+
+			for (i = 0; i < args->variables_count; ++i) {
+				args->variables[i] = ast_json_string_get(ast_json_array_get(field, i));
+			}
+		} else {
+			/* Multiple param passed as single value */
+			args->variables_count = 1;
+			args->variables = ast_malloc(sizeof(*args->variables) * args->variables_count);
+			if (!args->variables) {
+				return -1;
+			}
+			args->variables[0] = ast_json_string_get(field);
+		}
+	}
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/variables.
+ * \param ser TCP/TLS session object
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param body
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_channels_get_channel_vars_cb(
+	struct ast_tcptls_session_instance *ser,
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
+{
+	struct ast_ari_channels_get_channel_vars_args args = {};
+	struct ast_variable *i;
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
+	for (i = get_params; i; i = i->next) {
+		if (strcmp(i->name, "variables") == 0) {
+			/* Parse comma separated list */
+			char *vals[MAX_VALS];
+			size_t j;
+
+			args.variables_parse = ast_strdup(i->value);
+			if (!args.variables_parse) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (strlen(args.variables_parse) == 0) {
+				/* ast_app_separate_args can't handle "" */
+				args.variables_count = 1;
+				vals[0] = args.variables_parse;
+			} else {
+				args.variables_count = ast_app_separate_args(
+					args.variables_parse, ',', vals,
+					ARRAY_LEN(vals));
+			}
+
+			if (args.variables_count == 0) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (args.variables_count >= MAX_VALS) {
+				ast_ari_response_error(response, 400,
+					"Bad Request",
+					"Too many values for variables");
+				goto fin;
+			}
+
+			args.variables = ast_malloc(sizeof(*args.variables) * args.variables_count);
+			if (!args.variables) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			for (j = 0; j < args.variables_count; ++j) {
+				args.variables[j] = (vals[j]);
+			}
+		} else
+		{}
+	}
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "channelId") == 0) {
+			args.channel_id = (i->value);
+		} else
+		{}
+	}
+	if (ast_ari_channels_get_channel_vars_parse_body(body, &args)) {
+		ast_ari_response_alloc_failed(response);
+		goto fin;
+	}
+	ast_ari_channels_get_channel_vars(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 0: /* Implementation is still a stub, or the code wasn't set */
+		is_valid = response->message == NULL;
+		break;
+	case 500: /* Internal Server Error */
+	case 501: /* Not Implemented */
+	case 400: /* Missing variables parameter. */
+	case 404: /* Channel or variable not found */
+	case 409: /* Channel not in a Stasis application */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ast_ari_validate_variables(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/variables\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/variables\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+	ast_free(args.variables_parse);
+	ast_free(args.variables);
+	return;
+}
+int ast_ari_channels_set_channel_vars_parse_body(
+	struct ast_json *body,
+	struct ast_ari_channels_set_channel_vars_args *args)
+{
+	/* Parse query parameters out of it */
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /channels/{channelId}/variables.
+ * \param ser TCP/TLS session object
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param body
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_channels_set_channel_vars_cb(
+	struct ast_tcptls_session_instance *ser,
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
+{
+	struct ast_ari_channels_set_channel_vars_args args = {};
+	struct ast_variable *i;
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "channelId") == 0) {
+			args.channel_id = (i->value);
+		} else
+		{}
+	}
+	args.variables = body;
+	ast_ari_channels_set_channel_vars(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 0: /* Implementation is still a stub, or the code wasn't set */
+		is_valid = response->message == NULL;
+		break;
+	case 500: /* Internal Server Error */
+	case 501: /* Not Implemented */
+	case 400: /* Missing variables parameter. */
+	case 404: /* Channel not found */
+	case 409: /* Channel not in a Stasis application */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ast_ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/variables\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/variables\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
 fin: __attribute__((unused))
 	return;
 }
@@ -3310,6 +3533,16 @@ static struct stasis_rest_handlers channels_channelId_variable = {
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/channels.json */
+static struct stasis_rest_handlers channels_channelId_variables = {
+	.path_segment = "variables",
+	.callbacks = {
+		[AST_HTTP_GET] = ast_ari_channels_get_channel_vars_cb,
+		[AST_HTTP_POST] = ast_ari_channels_set_channel_vars_cb,
+	},
+	.num_children = 0,
+	.children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.json */
 static struct stasis_rest_handlers channels_channelId_snoop_snoopId = {
 	.path_segment = "snoopId",
 	.is_wildcard = 1,
@@ -3364,8 +3597,8 @@ static struct stasis_rest_handlers channels_channelId = {
 		[AST_HTTP_POST] = ast_ari_channels_originate_with_id_cb,
 		[AST_HTTP_DELETE] = ast_ari_channels_hangup_cb,
 	},
-	.num_children = 18,
-	.children = { &channels_channelId_continue,&channels_channelId_move,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_progress,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop,&channels_channelId_dial,&channels_channelId_rtp_statistics,&channels_channelId_transfer_progress, }
+	.num_children = 19,
+	.children = { &channels_channelId_continue,&channels_channelId_move,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_progress,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_variables,&channels_channelId_snoop,&channels_channelId_dial,&channels_channelId_rtp_statistics,&channels_channelId_transfer_progress, }
 };
 /*! \brief REST handler for /api-docs/channels.json */
 static struct stasis_rest_handlers channels_externalMedia = {
diff --git a/rest-api/api-docs/asterisk.json b/rest-api/api-docs/asterisk.json
index 83fef8b74a..9b0ab5237a 100644
--- a/rest-api/api-docs/asterisk.json
+++ b/rest-api/api-docs/asterisk.json
@@ -757,6 +757,17 @@
 				}
 			}
 		},
+		"Variables": {
+			"id": "Variables",
+			"description": "A dictionary of channel variables",
+			"properties": {
+				"variables": {
+					"required": true,
+					"type": "object",
+					"description": "A dictionary of channel variables"
+				}
+			}
+		},
 		"ConfigTuple": {
 			"id": "ConfigTuple",
 			"description": "A key/value pair that makes up part of a configuration object.",
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index f7ea634d07..0d7c245c78 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -1620,6 +1620,99 @@
 				}
 			]
 		},
+		{
+			"path": "/channels/{channelId}/variables",
+			"description": "Multiple variables on a channel",
+			"operations": [
+				{
+					"httpMethod": "GET",
+					"since": [
+						"20.20.0",
+						"22.10.0",
+						"23.4.0"
+					],
+					"summary": "Get the value of multiple channel variables or functions.",
+					"nickname": "getChannelVars",
+					"responseClass": "Variables",
+					"parameters": [
+						{
+							"name": "channelId",
+							"description": "Channel's id",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "variables",
+							"description": "The channel variables or functions to get",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": true,
+							"dataType": "string"
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 400,
+							"reason": "Missing variables parameter."
+						},
+						{
+							"code": 404,
+							"reason": "Channel or variable not found"
+						},
+						{
+							"code": 409,
+							"reason": "Channel not in a Stasis application"
+						}
+					]
+				},
+				{
+					"httpMethod": "POST",
+					"since": [
+						"20.20.0",
+						"22.10.0",
+						"23.4.0"
+					],
+					"summary": "Set the values of multiple channel variables or functions.",
+					"nickname": "setChannelVars",
+					"responseClass": "void",
+					"parameters": [
+						{
+							"name": "channelId",
+							"description": "Channel's id",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "variables",
+							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel. Ex. { \"variables\": { \"CALLERID(name)\": \"Alice\" } }",
+							"paramType": "body",
+							"required": false,
+							"dataType": "containers",
+							"allowMultiple": false
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 400,
+							"reason": "Missing variables parameter."
+						},
+						{
+							"code": 404,
+							"reason": "Channel not found"
+						},
+						{
+							"code": 409,
+							"reason": "Channel not in a Stasis application"
+						}
+					]
+
+				}
+			]
+		},
 		{
 			"path": "/channels/{channelId}/snoop",
 			"description": "Snoop (spy/whisper) on a channel",