diff --git a/CHANGES b/CHANGES
index a72c970f6dae4aaada1bb78abc780ac1a3409956..3d117b8bed03436c8249730069bfed372c153e1e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -196,6 +196,13 @@ ARI
    can be also be retrieved. Individual modules can be loaded to Asterisk, as
    well as unloaded and reloaded.
 
+* A new resource has been added to the 'asterisk' resource, 'config/dynamic'.
+   This resource allows for push configuration of sorcery derived objects
+   within Asterisk. The resource supports creation, retrieval, updating, and
+   deletion. Sorcery derived objects that are manipulated by this resource
+   must have a sorcery wizard that supports the desired operations.
+
+
 res_pjsip
 ------------------
 * A new 'g726_non_standard' endpoint option has been added that, when set to
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index ca72f93bd983c759b6939873ba303eb1465f14c2..fa16aea20409e0bdf145041fd530fa19817b8a48 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -308,6 +308,60 @@ ari_validator ast_ari_validate_config_info_fn(void)
 	return ast_ari_validate_config_info;
 }
 
+int ast_ari_validate_config_tuple(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_attribute = 0;
+	int has_value = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("attribute", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_attribute = 1;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ConfigTuple field attribute failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("value", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_value = 1;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ConfigTuple field value failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ConfigTuple has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_attribute) {
+		ast_log(LOG_ERROR, "ARI ConfigTuple missing required field attribute\n");
+		res = 0;
+	}
+
+	if (!has_value) {
+		ast_log(LOG_ERROR, "ARI ConfigTuple missing required field value\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+ari_validator ast_ari_validate_config_tuple_fn(void)
+{
+	return ast_ari_validate_config_tuple;
+}
+
 int ast_ari_validate_module(struct ast_json *json)
 {
 	int res = 1;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index 41b91791d3a121e85f356513529588e49813f6e6..e122ded345c06b20a411b3bbb034fb83e9a9898a 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -206,6 +206,24 @@ int ast_ari_validate_config_info(struct ast_json *json);
  */
 ari_validator ast_ari_validate_config_info_fn(void);
 
+/*!
+ * \brief Validator for ConfigTuple.
+ *
+ * A key/value pair that makes up part of a configuration object.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_config_tuple(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_config_tuple().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_config_tuple_fn(void);
+
 /*!
  * \brief Validator for Module.
  *
@@ -1262,6 +1280,9 @@ ari_validator ast_ari_validate_application_fn(void);
  * - max_open_files: int
  * - name: string (required)
  * - setid: SetId (required)
+ * ConfigTuple
+ * - attribute: string (required)
+ * - value: string (required)
  * Module
  * - description: string (required)
  * - name: string (required)
diff --git a/res/ari/resource_asterisk.c b/res/ari/resource_asterisk.c
index 569013f8520ad09668d7ad5e9db5400578fac261..8006862048e61c01575fefdd554ac3c44f5f0cb4 100644
--- a/res/ari/resource_asterisk.c
+++ b/res/ari/resource_asterisk.c
@@ -36,8 +36,262 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/module.h"
 #include "asterisk/paths.h"
 #include "asterisk/pbx.h"
+#include "asterisk/sorcery.h"
 #include "resource_asterisk.h"
 
+static void return_sorcery_object(struct ast_sorcery *sorcery, void *sorcery_obj,
+	struct ast_ari_response *response)
+{
+	RAII_VAR(struct ast_json *, return_set, NULL, ast_json_unref);
+	struct ast_variable *change_set;
+	struct ast_variable *it_change_set;
+
+	return_set = ast_json_array_create();
+	if (!return_set) {
+		ast_ari_response_alloc_failed(response);
+		return;
+	}
+
+	/* Note that we can't use the sorcery JSON change set directly,
+	 * as it will hand us back an Object (with fields), and we need
+	 * a more generic representation of whatever the API call asked
+	 * for, i.e., a list of tuples.
+	 */
+	change_set = ast_sorcery_objectset_create(sorcery, sorcery_obj);
+	if (!change_set) {
+		ast_ari_response_alloc_failed(response);
+		return;
+	}
+
+	for (it_change_set = change_set; it_change_set; it_change_set = it_change_set->next) {
+		struct ast_json *tuple;
+
+		tuple = ast_json_pack("{s: s, s: s}",
+			"attribute", it_change_set->name,
+			"value", it_change_set->value);
+		if (!tuple) {
+			ast_variables_destroy(change_set);
+			ast_ari_response_alloc_failed(response);
+			return;
+		}
+
+		if (ast_json_array_append(return_set, tuple)) {
+			ast_json_unref(tuple);
+			ast_variables_destroy(change_set);
+			ast_ari_response_alloc_failed(response);
+			return;
+		}
+	}
+	ast_variables_destroy(change_set);
+
+	ast_ari_response_ok(response, ast_json_ref(return_set));
+}
+
+void ast_ari_asterisk_get_object(struct ast_variable *headers,
+	struct ast_ari_asterisk_get_object_args *args,
+	struct ast_ari_response *response)
+{
+	RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
+	RAII_VAR(struct ast_sorcery_object_type *, object_type, NULL, ao2_cleanup);
+	RAII_VAR(void *, sorcery_obj, NULL, ao2_cleanup);
+
+
+	sorcery = ast_sorcery_retrieve_by_module_name(args->config_class);
+	if (!sorcery) {
+		ast_ari_response_error(
+			response, 404, "Not Found",
+			"configClass '%s' not found",
+			args->config_class);
+		return;
+	}
+
+	object_type = ast_sorcery_get_object_type(sorcery, args->object_type);
+	if (!object_type) {
+		ast_ari_response_error(
+			response, 404, "Not Found",
+			"objectType '%s' not found",
+			args->object_type);
+		return;
+	}
+
+	sorcery_obj = ast_sorcery_retrieve_by_id(sorcery, args->object_type, args->id);
+	if (!sorcery_obj) {
+		ast_ari_response_error(
+			response, 404, "Not Found",
+			"Object with id '%s' not found",
+			args->id);
+		return;
+	}
+
+	return_sorcery_object(sorcery, sorcery_obj, response);
+}
+
+void ast_ari_asterisk_update_object(struct ast_variable *headers, struct ast_ari_asterisk_update_object_args *args, struct ast_ari_response *response)
+{
+	RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
+	RAII_VAR(struct ast_sorcery_object_type *, object_type, NULL, ao2_cleanup);
+	RAII_VAR(void *, sorcery_obj, NULL, ao2_cleanup);
+	struct ast_json *fields;
+	struct ast_variable *update_set = NULL;
+	int created = 0;
+
+	sorcery = ast_sorcery_retrieve_by_module_name(args->config_class);
+	if (!sorcery) {
+		ast_ari_response_error(
+			response, 404, "Not Found",
+			"configClass '%s' not found",
+			args->config_class);
+		return;
+	}
+
+	object_type = ast_sorcery_get_object_type(sorcery, args->object_type);
+	if (!object_type) {
+		ast_ari_response_error(
+			response, 404, "Not Found",
+			"objectType '%s' not found",
+			args->object_type);
+		return;
+	}
+
+	sorcery_obj = ast_sorcery_retrieve_by_id(sorcery, args->object_type, args->id);
+	if (!sorcery_obj) {
+		ast_debug(5, "Sorcery object '%s' does not exist; creating it\n", args->id);
+		sorcery_obj = ast_sorcery_alloc(sorcery, args->object_type, args->id);
+		if (!sorcery_obj) {
+			ast_ari_response_alloc_failed(response);
+			return;
+		}
+
+		created = 1;
+	} else {
+		void *copy;
+
+		copy = ast_sorcery_copy(sorcery, sorcery_obj);
+		if (!copy) {
+			ast_ari_response_alloc_failed(response);
+			return;
+		}
+
+		ao2_ref(sorcery_obj, -1);
+		sorcery_obj = copy;
+	}
+
+	fields = ast_json_object_get(args->fields, "fields");
+	if (!fields && !created) {
+		/* Whoops. We need data. */
+		ast_ari_response_error(
+			response, 400, "Bad request",
+			"Fields must be provided to update object '%s'",
+			args->id);
+		return;
+	} else if (fields) {
+		size_t i;
+
+		for (i = 0; i < ast_json_array_size(fields); i++) {
+			struct ast_variable *new_var;
+			struct ast_json *json_value = ast_json_array_get(fields, i);
+
+			if (!json_value) {
+				continue;
+			}
+
+			new_var = ast_variable_new(
+				ast_json_string_get(ast_json_object_get(json_value, "attribute")),
+				ast_json_string_get(ast_json_object_get(json_value, "value")),
+				"");
+			if (!new_var) {
+				ast_variables_destroy(update_set);
+				ast_ari_response_alloc_failed(response);
+				return;
+			}
+			ast_variable_list_append(&update_set, new_var);
+		}
+	}
+
+	/* APPLY! Note that a NULL update_set is fine (and necessary), as it
+	 * will force validation on a newly created object.
+	 */
+	if (ast_sorcery_objectset_apply(sorcery, sorcery_obj, update_set)) {
+		ast_variables_destroy(update_set);
+		ast_ari_response_error(
+			response, 400, "Bad request",
+			"%s of object '%s' failed field value validation",
+			created ? "Creation" : "Update",
+			args->id);
+		return;
+	}
+
+	ast_variables_destroy(update_set);
+
+	if (created) {
+		if (ast_sorcery_create(sorcery, sorcery_obj)) {
+			ast_ari_response_error(
+				response, 403, "Forbidden",
+				"Cannot create sorcery objects of type '%s'",
+				args->object_type);
+			return;
+		}
+	} else {
+		if (ast_sorcery_update(sorcery, sorcery_obj)) {
+			ast_ari_response_error(
+				response, 403, "Forbidden",
+				"Cannot update sorcery objects of type '%s'",
+				args->object_type);
+			return;
+		}
+	}
+
+	return_sorcery_object(sorcery, sorcery_obj, response);
+}
+
+
+void ast_ari_asterisk_delete_object(struct ast_variable *headers,
+	struct ast_ari_asterisk_delete_object_args *args,
+	struct ast_ari_response *response)
+{
+	RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
+	RAII_VAR(struct ast_sorcery_object_type *, object_type, NULL, ao2_cleanup);
+	RAII_VAR(void *, sorcery_obj, NULL, ao2_cleanup);
+
+	sorcery = ast_sorcery_retrieve_by_module_name(args->config_class);
+	if (!sorcery) {
+		ast_ari_response_error(
+			response, 404, "Not Found",
+			"configClass '%s' not found",
+			args->config_class);
+		return;
+	}
+
+	object_type = ast_sorcery_get_object_type(sorcery, args->object_type);
+	if (!object_type) {
+		ast_ari_response_error(
+			response, 404, "Not Found",
+			"objectType '%s' not found",
+			args->object_type);
+		return;
+	}
+
+	sorcery_obj = ast_sorcery_retrieve_by_id(sorcery, args->object_type, args->id);
+	if (!sorcery_obj) {
+		ast_ari_response_error(
+			response, 404, "Not Found",
+			"Object with id '%s' not found",
+			args->id);
+		return;
+	}
+
+	if (ast_sorcery_delete(sorcery, sorcery_obj)) {
+		ast_ari_response_error(
+			response, 403, "Forbidden",
+			"Could not delete object with id '%s'",
+			args->id);
+		return;
+	}
+
+	ast_ari_response_no_content(response);
+}
+
+
 void ast_ari_asterisk_get_info(struct ast_variable *headers,
 	struct ast_ari_asterisk_get_info_args *args,
 	struct ast_ari_response *response)
diff --git a/res/ari/resource_asterisk.h b/res/ari/resource_asterisk.h
index 574d947e4e55736634de329d9fa91d3819dd8319..1afc0931769732ecdf9517bc203ca2fcd3a9543f 100644
--- a/res/ari/resource_asterisk.h
+++ b/res/ari/resource_asterisk.h
@@ -39,6 +39,70 @@
 
 #include "asterisk/ari.h"
 
+/*! Argument struct for ast_ari_asterisk_get_object() */
+struct ast_ari_asterisk_get_object_args {
+	/*! The configuration class containing dynamic configuration objects. */
+	const char *config_class;
+	/*! The type of configuration object to retrieve. */
+	const char *object_type;
+	/*! The unique identifier of the object to retrieve. */
+	const char *id;
+};
+/*!
+ * \brief Retrieve a dynamic configuration object.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_asterisk_get_object(struct ast_variable *headers, struct ast_ari_asterisk_get_object_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_asterisk_update_object() */
+struct ast_ari_asterisk_update_object_args {
+	/*! The configuration class containing dynamic configuration objects. */
+	const char *config_class;
+	/*! The type of configuration object to create or update. */
+	const char *object_type;
+	/*! The unique identifier of the object to create or update. */
+	const char *id;
+	/*! The body object should have a value that is a list of ConfigTuples, which provide the fields to update. Ex. [ { "attribute": "directmedia", "value": "false" } ] */
+	struct ast_json *fields;
+};
+/*!
+ * \brief Body parsing function for /asterisk/config/dynamic/{configClass}/{objectType}/{id}.
+ * \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_asterisk_update_object_parse_body(
+	struct ast_json *body,
+	struct ast_ari_asterisk_update_object_args *args);
+
+/*!
+ * \brief Create or update a dynamic configuration object.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_asterisk_update_object(struct ast_variable *headers, struct ast_ari_asterisk_update_object_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_asterisk_delete_object() */
+struct ast_ari_asterisk_delete_object_args {
+	/*! The configuration class containing dynamic configuration objects. */
+	const char *config_class;
+	/*! The type of configuration object to delete. */
+	const char *object_type;
+	/*! The unique identifier of the object to delete. */
+	const char *id;
+};
+/*!
+ * \brief Delete a dynamic configuration object.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_asterisk_delete_object(struct ast_variable *headers, struct ast_ari_asterisk_delete_object_args *args, struct ast_ari_response *response);
 /*! Argument struct for ast_ari_asterisk_get_info() */
 struct ast_ari_asterisk_get_info_args {
 	/*! Array of Filter information returned */
diff --git a/res/res_ari_asterisk.c b/res/res_ari_asterisk.c
index 7d938a0a464a42433f1a21b9b405f417b8e5dc8a..671af59da4797ae842a41113e8f3e2d96b4ba0b7 100644
--- a/res/res_ari_asterisk.c
+++ b/res/res_ari_asterisk.c
@@ -52,6 +52,228 @@ ASTERISK_REGISTER_FILE()
 
 #define MAX_VALS 128
 
+/*!
+ * \brief Parameter parsing callback for /asterisk/config/dynamic/{configClass}/{objectType}/{id}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_asterisk_get_object_cb(
+	struct ast_tcptls_session_instance *ser,
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct ast_ari_response *response)
+{
+	struct ast_ari_asterisk_get_object_args args = {};
+	struct ast_variable *i;
+	RAII_VAR(struct ast_json *, body, NULL, ast_json_unref);
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "configClass") == 0) {
+			args.config_class = (i->value);
+		} else
+		if (strcmp(i->name, "objectType") == 0) {
+			args.object_type = (i->value);
+		} else
+		if (strcmp(i->name, "id") == 0) {
+			args.id = (i->value);
+		} else
+		{}
+	}
+	ast_ari_asterisk_get_object(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 404: /* {configClass|objectType|id} not found */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ast_ari_validate_list(response->message,
+				ast_ari_validate_config_tuple_fn());
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /asterisk/config/dynamic/{configClass}/{objectType}/{id}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /asterisk/config/dynamic/{configClass}/{objectType}/{id}\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+	return;
+}
+int ast_ari_asterisk_update_object_parse_body(
+	struct ast_json *body,
+	struct ast_ari_asterisk_update_object_args *args)
+{
+	/* Parse query parameters out of it */
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /asterisk/config/dynamic/{configClass}/{objectType}/{id}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_asterisk_update_object_cb(
+	struct ast_tcptls_session_instance *ser,
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct ast_ari_response *response)
+{
+	struct ast_ari_asterisk_update_object_args args = {};
+	struct ast_variable *i;
+	RAII_VAR(struct ast_json *, body, NULL, ast_json_unref);
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "configClass") == 0) {
+			args.config_class = (i->value);
+		} else
+		if (strcmp(i->name, "objectType") == 0) {
+			args.object_type = (i->value);
+		} else
+		if (strcmp(i->name, "id") == 0) {
+			args.id = (i->value);
+		} else
+		{}
+	}
+	/* Look for a JSON request entity */
+	body = ast_http_get_json(ser, headers);
+	if (!body) {
+		switch (errno) {
+		case EFBIG:
+			ast_ari_response_error(response, 413, "Request Entity Too Large", "Request body too large");
+			goto fin;
+		case ENOMEM:
+			ast_ari_response_error(response, 500, "Internal Server Error", "Error processing request");
+			goto fin;
+		case EIO:
+			ast_ari_response_error(response, 400, "Bad Request", "Error parsing request body");
+			goto fin;
+		}
+	}
+	args.fields = body;
+	ast_ari_asterisk_update_object(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: /* Bad request body */
+	case 403: /* Could not create or update object */
+	case 404: /* {configClass|objectType} not found */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ast_ari_validate_list(response->message,
+				ast_ari_validate_config_tuple_fn());
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /asterisk/config/dynamic/{configClass}/{objectType}/{id}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /asterisk/config/dynamic/{configClass}/{objectType}/{id}\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+	return;
+}
+/*!
+ * \brief Parameter parsing callback for /asterisk/config/dynamic/{configClass}/{objectType}/{id}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_asterisk_delete_object_cb(
+	struct ast_tcptls_session_instance *ser,
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct ast_ari_response *response)
+{
+	struct ast_ari_asterisk_delete_object_args args = {};
+	struct ast_variable *i;
+	RAII_VAR(struct ast_json *, body, NULL, ast_json_unref);
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "configClass") == 0) {
+			args.config_class = (i->value);
+		} else
+		if (strcmp(i->name, "objectType") == 0) {
+			args.object_type = (i->value);
+		} else
+		if (strcmp(i->name, "id") == 0) {
+			args.id = (i->value);
+		} else
+		{}
+	}
+	ast_ari_asterisk_delete_object(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 403: /* Could not delete object */
+	case 404: /* {configClass|objectType|id} not found */
+		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 /asterisk/config/dynamic/{configClass}/{objectType}/{id}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /asterisk/config/dynamic/{configClass}/{objectType}/{id}\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+	return;
+}
 int ast_ari_asterisk_get_info_parse_body(
 	struct ast_json *body,
 	struct ast_ari_asterisk_get_info_args *args)
@@ -689,6 +911,52 @@ fin: __attribute__((unused))
 	return;
 }
 
+/*! \brief REST handler for /api-docs/asterisk.{format} */
+static struct stasis_rest_handlers asterisk_config_dynamic_configClass_objectType_id = {
+	.path_segment = "id",
+	.is_wildcard = 1,
+	.callbacks = {
+		[AST_HTTP_GET] = ast_ari_asterisk_get_object_cb,
+		[AST_HTTP_PUT] = ast_ari_asterisk_update_object_cb,
+		[AST_HTTP_DELETE] = ast_ari_asterisk_delete_object_cb,
+	},
+	.num_children = 0,
+	.children = {  }
+};
+/*! \brief REST handler for /api-docs/asterisk.{format} */
+static struct stasis_rest_handlers asterisk_config_dynamic_configClass_objectType = {
+	.path_segment = "objectType",
+	.is_wildcard = 1,
+	.callbacks = {
+	},
+	.num_children = 1,
+	.children = { &asterisk_config_dynamic_configClass_objectType_id, }
+};
+/*! \brief REST handler for /api-docs/asterisk.{format} */
+static struct stasis_rest_handlers asterisk_config_dynamic_configClass = {
+	.path_segment = "configClass",
+	.is_wildcard = 1,
+	.callbacks = {
+	},
+	.num_children = 1,
+	.children = { &asterisk_config_dynamic_configClass_objectType, }
+};
+/*! \brief REST handler for /api-docs/asterisk.{format} */
+static struct stasis_rest_handlers asterisk_config_dynamic = {
+	.path_segment = "dynamic",
+	.callbacks = {
+	},
+	.num_children = 1,
+	.children = { &asterisk_config_dynamic_configClass, }
+};
+/*! \brief REST handler for /api-docs/asterisk.{format} */
+static struct stasis_rest_handlers asterisk_config = {
+	.path_segment = "config",
+	.callbacks = {
+	},
+	.num_children = 1,
+	.children = { &asterisk_config_dynamic, }
+};
 /*! \brief REST handler for /api-docs/asterisk.{format} */
 static struct stasis_rest_handlers asterisk_info = {
 	.path_segment = "info",
@@ -735,8 +1003,8 @@ static struct stasis_rest_handlers asterisk = {
 	.path_segment = "asterisk",
 	.callbacks = {
 	},
-	.num_children = 3,
-	.children = { &asterisk_info,&asterisk_modules,&asterisk_variable, }
+	.num_children = 4,
+	.children = { &asterisk_config,&asterisk_info,&asterisk_modules,&asterisk_variable, }
 };
 
 static int load_module(void)
diff --git a/rest-api/api-docs/asterisk.json b/rest-api/api-docs/asterisk.json
index c6968a5ba3a47d596064e034e1340e0428b3707d..2705f45f65b3577a4c08281c5d17813071d543b8 100644
--- a/rest-api/api-docs/asterisk.json
+++ b/rest-api/api-docs/asterisk.json
@@ -7,6 +7,146 @@
 	"basePath": "http://localhost:8088/ari",
 	"resourcePath": "/api-docs/asterisk.{format}",
 	"apis": [
+		{
+			"path": "/asterisk/config/dynamic/{configClass}/{objectType}/{id}",
+			"description": "Asterisk dynamic configuration",
+			"operations": [
+				{
+					"httpMethod": "GET",
+					"summary": "Retrieve a dynamic configuration object.",
+					"nickname": "getObject",
+					"responseClass": "List[ConfigTuple]",
+					"parameters": [
+						{
+							"name": "configClass",
+							"description": "The configuration class containing dynamic configuration objects.",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "objectType",
+							"description": "The type of configuration object to retrieve.",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "id",
+							"description": "The unique identifier of the object to retrieve.",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 404,
+							"reason": "{configClass|objectType|id} not found"
+						}
+					]
+				},
+				{
+					"httpMethod": "PUT",
+					"summary": "Create or update a dynamic configuration object.",
+					"nickname": "updateObject",
+					"responseClass": "List[ConfigTuple]",
+					"parameters": [
+						{
+							"name": "configClass",
+							"description": "The configuration class containing dynamic configuration objects.",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "objectType",
+							"description": "The type of configuration object to create or update.",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "id",
+							"description": "The unique identifier of the object to create or update.",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "fields",
+							"description": "The body object should have a value that is a list of ConfigTuples, which provide the fields to update. Ex. [ { \"attribute\": \"directmedia\", \"value\": \"false\" } ]",
+							"paramType": "body",
+							"required": false,
+							"dataType": "containers",
+							"allowMultiple": false
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 400,
+							"reason": "Bad request body"
+						},
+						{
+							"code": 403,
+							"reason": "Could not create or update object"
+						},
+						{
+							"code": 404,
+							"reason": "{configClass|objectType} not found"
+						}
+					]
+				},
+				{
+					"httpMethod": "DELETE",
+					"summary": "Delete a dynamic configuration object.",
+					"nickname": "deleteObject",
+					"responseClass": "void",
+					"parameters": [
+						{
+							"name": "configClass",
+							"description": "The configuration class containing dynamic configuration objects.",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "objectType",
+							"description": "The type of configuration object to delete.",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "id",
+							"description": "The unique identifier of the object to delete.",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 403,
+							"reason": "Could not delete object"
+						},
+						{
+							"code": 404,
+							"reason": "{configClass|objectType|id} not found"
+						}
+					]
+				}
+			]
+		},
 		{
 			"path": "/asterisk/info",
 			"description": "Asterisk system information (similar to core show settings)",
@@ -403,6 +543,22 @@
 					"description": "The value of the variable requested"
 				}
 			}
+		},
+		"ConfigTuple": {
+			"id": "ConfigTuple",
+			"description": "A key/value pair that makes up part of a configuration object.",
+			"properties": {
+				"attribute": {
+					"required": true,
+					"type": "string",
+					"description": "A configuration object attribute."
+				},
+				"value": {
+					"required": true,
+					"type": "string",
+					"description": "The value for the attribute."
+				}
+			}
 		}
 	}
 }