diff --git a/Makefile b/Makefile
index 6cca3aafa581100985a3385284819db48f9708d8..a51534424296bd5d2f145728aede045e6b570527 100644
--- a/Makefile
+++ b/Makefile
@@ -416,6 +416,7 @@ _clean:
 	rm -f main/version.c
 	rm -f doc/core-en_US.xml
 	rm -f doc/full-en_US.xml
+	rm -f docs/rest-api/*.wiki
 	@$(MAKE) -C menuselect clean
 	cp -f .cleancount .lastclean
 
@@ -963,15 +964,15 @@ menuselect-tree: $(foreach dir,$(filter-out main,$(MOD_SUBDIRS)),$(wildcard $(di
 
 # We don't want to require Python or Pystache for every build, so this is its
 # own target.
-stasis-stubs:
+ari-stubs:
 ifeq ($(PYTHON),:)
 	@echo "--------------------------------------------------------------------------"
-	@echo "---        Please install python to build Stasis HTTP stubs            ---"
+	@echo "---        Please install python to build ARI stubs            ---"
 	@echo "--------------------------------------------------------------------------"
 	@false
 else
-	$(PYTHON) rest-api-templates/make_stasis_http_stubs.py \
-		rest-api/resources.json res/
+	$(PYTHON) rest-api-templates/make_ari_stubs.py \
+		rest-api/resources.json .
 endif
 
 .PHONY: menuselect
@@ -993,7 +994,7 @@ endif
 .PHONY: installdirs
 .PHONY: validate-docs
 .PHONY: _clean
-.PHONY: stasis-stubs
+.PHONY: ari-stubs
 .PHONY: $(SUBDIRS_INSTALL)
 .PHONY: $(SUBDIRS_DIST_CLEAN)
 .PHONY: $(SUBDIRS_CLEAN)
diff --git a/include/asterisk/json.h b/include/asterisk/json.h
index 61685fd9f533f77ca8b1ae2347c7989aaa6c8974..0584c99afce65b8444d145fb944c4f31e4314cd3 100644
--- a/include/asterisk/json.h
+++ b/include/asterisk/json.h
@@ -158,6 +158,15 @@ enum ast_json_type
  */
 enum ast_json_type ast_json_typeof(const struct ast_json *value);
 
+/*!
+ * \brief Get the string name for the given type.
+ * \since 12.0.0
+ * \param type Type to convert to string.
+ * \return Simple string for the type name (object, array, string, etc.)
+ * \return \c "?" for invalid types.
+ */
+const char *ast_json_typename(enum ast_json_type type);
+
 /*!@}*/
 
 /*!@{*/
diff --git a/include/asterisk/stasis_http.h b/include/asterisk/stasis_http.h
index 05e9dded7cbc6b86b91e65397953e4fc781ff046..8d5a74ee7a604986b239d80a1ace670512aab7d6 100644
--- a/include/asterisk/stasis_http.h
+++ b/include/asterisk/stasis_http.h
@@ -33,6 +33,12 @@
 #include "asterisk/json.h"
 #include "asterisk/http_websocket.h"
 
+/*!
+ * \brief Configured encoding format for JSON output.
+ * \return JSON output encoding (compact, pretty, etc.)
+ */
+enum ast_json_encoding_format stasis_http_json_format(void);
+
 struct stasis_http_response;
 
 /*!
@@ -141,12 +147,16 @@ struct ari_websocket_session;
 /*!
  * \brief Create an ARI WebSocket session.
  *
+ * If \c NULL is given for the validator function, no validation will be
+ * performed.
+ *
  * \param ws_session Underlying WebSocket session.
+ * \param validator Function to validate outgoing messages.
  * \return New ARI WebSocket session.
  * \return \c NULL on error.
  */
 struct ari_websocket_session *ari_websocket_session_create(
-	struct ast_websocket *ws_session);
+	struct ast_websocket *ws_session, int (*validator)(struct ast_json *));
 
 /*!
  * \brief Read a message from an ARI WebSocket.
diff --git a/main/json.c b/main/json.c
index dff35dbab94ef15ccc68216115cd66287c46261d..c185b053fdc9775193bee20ebe7f7d8212f89aad 100644
--- a/main/json.c
+++ b/main/json.c
@@ -103,6 +103,23 @@ enum ast_json_type ast_json_typeof(const struct ast_json *json)
 	return r;
 }
 
+const char *ast_json_typename(enum ast_json_type type)
+{
+	switch (type) {
+	case AST_JSON_OBJECT: return "object";
+	case AST_JSON_ARRAY: return "array";
+	case AST_JSON_STRING: return "string";
+	case AST_JSON_INTEGER: return "integer";
+	case AST_JSON_REAL: return "real";
+	case AST_JSON_TRUE: return "boolean";
+	case AST_JSON_FALSE: return "boolean";
+	case AST_JSON_NULL: return "null";
+	}
+	ast_assert(0);
+	return "?";
+}
+
+
 struct ast_json *ast_json_true(void)
 {
 	return (struct ast_json *)json_true();
diff --git a/main/stasis_bridging.c b/main/stasis_bridging.c
index 39a8ba892e3a26136f931f4f6ff7596dce9ac1ff..dcf4275aefd10531dfa97b5bb376ce03b4f4fd69 100644
--- a/main/stasis_bridging.c
+++ b/main/stasis_bridging.c
@@ -657,10 +657,10 @@ struct ast_json *ast_bridge_snapshot_to_json(const struct ast_bridge_snapshot *s
 	}
 
 	json_bridge = ast_json_pack("{s: s, s: s, s: s, s: s, s: o}",
-		"bridgeUniqueid", snapshot->uniqueid,
-		"bridgeTechnology", snapshot->technology,
-		"bridgeType", capability2str(snapshot->capabilities),
-		"bridgeClass", snapshot->subclass,
+		"id", snapshot->uniqueid,
+		"technology", snapshot->technology,
+		"bridge_type", capability2str(snapshot->capabilities),
+		"bridge_class", snapshot->subclass,
 		"channels", json_channels);
 	if (!json_bridge) {
 		return NULL;
diff --git a/main/stasis_channels.c b/main/stasis_channels.c
index 6dddb0a5ec992611869625822bb8677e296b86ba..d121279d88ce8e63cefc29a2de40b965efaf847e 100644
--- a/main/stasis_channels.c
+++ b/main/stasis_channels.c
@@ -32,10 +32,11 @@
 
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
-#include "asterisk/stasis.h"
 #include "asterisk/astobj2.h"
-#include "asterisk/stasis_channels.h"
+#include "asterisk/json.h"
 #include "asterisk/pbx.h"
+#include "asterisk/stasis.h"
+#include "asterisk/stasis_channels.h"
 
 /*** DOCUMENTATION
 	<managerEvent language="en_US" name="VarSet">
@@ -621,25 +622,25 @@ struct ast_json *ast_channel_snapshot_to_json(const struct ast_channel_snapshot
 		return NULL;
 	}
 
-	json_chan = ast_json_pack("{ s: s, s: s, s: s, s: s, s: s, s: s, s: s,"
-				  "  s: s, s: s, s: s, s: s, s: o, s: o, s: o,"
-				  "  s: o"
-				  "}",
-				  "name", snapshot->name,
-				  "state", ast_state2str(snapshot->state),
-				  "accountcode", snapshot->accountcode,
-				  "peeraccount", snapshot->peeraccount,
-				  "userfield", snapshot->userfield,
-				  "uniqueid", snapshot->uniqueid,
-				  "linkedid", snapshot->linkedid,
-				  "parkinglot", snapshot->parkinglot,
-				  "hangupsource", snapshot->hangupsource,
-				  "appl", snapshot->appl,
-				  "data", snapshot->data,
-				  "dialplan", ast_json_dialplan_cep(snapshot->context, snapshot->exten, snapshot->priority),
-				  "caller", ast_json_name_number(snapshot->caller_name, snapshot->caller_number),
-				  "connected", ast_json_name_number(snapshot->connected_name, snapshot->connected_number),
-				  "creationtime", ast_json_timeval(snapshot->creationtime, NULL));
+	json_chan = ast_json_pack(
+		/* Broken up into groups of three for readability */
+		"{ s: s, s: s, s: s,"
+		"  s: o, s: o, s: s,"
+		"  s: o, s: o }",
+		/* First line */
+		"id", snapshot->uniqueid,
+		"name", snapshot->name,
+		"state", ast_state2str(snapshot->state),
+		/* Second line */
+		"caller", ast_json_name_number(
+			snapshot->caller_name, snapshot->caller_number),
+		"connected", ast_json_name_number(
+			snapshot->connected_name, snapshot->connected_number),
+		"accountcode", snapshot->accountcode,
+		/* Third line */
+		"dialplan", ast_json_dialplan_cep(
+			snapshot->context, snapshot->exten, snapshot->priority),
+		"creationtime", ast_json_timeval(snapshot->creationtime, NULL));
 
 	return ast_json_ref(json_chan);
 }
@@ -675,6 +676,91 @@ int ast_channel_snapshot_caller_id_equal(
 		strcmp(old_snapshot->caller_name, new_snapshot->caller_name) == 0;
 }
 
+static struct ast_json *channel_blob_to_json(struct stasis_message *message,
+	const char *type)
+{
+	RAII_VAR(struct ast_json *, out, NULL, ast_json_unref);
+	struct ast_channel_blob *channel_blob = stasis_message_data(message);
+	struct ast_json *blob = channel_blob->blob;
+	struct ast_channel_snapshot *snapshot = channel_blob->snapshot;
+	const struct timeval *tv = stasis_message_timestamp(message);
+	int res = 0;
+
+	if (blob == NULL || ast_json_is_null(blob)) {
+		out = ast_json_object_create();
+	} else {
+		/* blobs are immutable, so shallow copies are fine */
+		out = ast_json_copy(blob);
+	}
+
+	if (!out) {
+		return NULL;
+	}
+
+	res |= ast_json_object_set(out, "type", ast_json_string_create(type));
+	res |= ast_json_object_set(out, "timestamp",
+		ast_json_timeval(*tv, NULL));
+
+	/* For global channel messages, the snapshot is optional */
+	if (snapshot) {
+		res |= ast_json_object_set(out, "channel",
+			ast_channel_snapshot_to_json(snapshot));
+	}
+
+	if (res != 0) {
+		return NULL;
+	}
+
+	return ast_json_ref(out);
+}
+
+static struct ast_json *dtmf_end_to_json(struct stasis_message *message)
+{
+	struct ast_channel_blob *channel_blob = stasis_message_data(message);
+	struct ast_json *blob = channel_blob->blob;
+	struct ast_channel_snapshot *snapshot = channel_blob->snapshot;
+	const char *direction =
+		ast_json_string_get(ast_json_object_get(blob, "direction"));
+	const struct timeval *tv = stasis_message_timestamp(message);
+
+	/* Only present received DTMF end events as JSON */
+	if (strcasecmp("Received", direction) != 0) {
+		return NULL;
+	}
+
+	return ast_json_pack("{s: s, s: o, s: O, s: O, s: o}",
+		"type", "ChannelDtmfReceived",
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"digit", ast_json_object_get(blob, "digit"),
+		"duration_ms", ast_json_object_get(blob, "duration_ms"),
+		"channel", ast_channel_snapshot_to_json(snapshot));
+}
+
+static struct ast_json *user_event_to_json(struct stasis_message *message)
+{
+	struct ast_channel_blob *channel_blob = stasis_message_data(message);
+	struct ast_json *blob = channel_blob->blob;
+	struct ast_channel_snapshot *snapshot = channel_blob->snapshot;
+	const struct timeval *tv = stasis_message_timestamp(message);
+
+	return ast_json_pack("{s: s, s: o, s: O, s: O, s: o}",
+		"type", "ChannelUserevent",
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"eventname", ast_json_object_get(blob, "eventname"),
+		"userevent", blob,
+		"channel", ast_channel_snapshot_to_json(snapshot));
+}
+
+static struct ast_json *varset_to_json(struct stasis_message *message)
+{
+	return channel_blob_to_json(message, "ChannelVarset");
+}
+
+static struct ast_json *hangup_request_to_json(struct stasis_message *message)
+{
+	return channel_blob_to_json(message, "ChannelHangupRequest");
+}
+
 /*!
  * @{ \brief Define channel message types.
  */
@@ -682,11 +768,18 @@ STASIS_MESSAGE_TYPE_DEFN(ast_channel_snapshot_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_dial_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_varset_type,
 	.to_ami = varset_to_ami,
+	.to_json = varset_to_json,
+	);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_user_event_type,
+	.to_json = user_event_to_json,
+	);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_hangup_request_type,
+	.to_json = hangup_request_to_json,
 	);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_user_event_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_hangup_request_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_begin_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_end_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_end_type,
+	.to_json = dtmf_end_to_json,
+	);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_hold_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_unhold_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_chanspy_start_type);
diff --git a/main/stasis_endpoints.c b/main/stasis_endpoints.c
index 90d96856759db13d2781384b3df729a19f7473e6..a6756182c78dd4956151fc4812cf80d73a928aaf 100644
--- a/main/stasis_endpoints.c
+++ b/main/stasis_endpoints.c
@@ -239,7 +239,7 @@ struct ast_json *ast_endpoint_snapshot_to_json(
 		"technology", snapshot->tech,
 		"resource", snapshot->resource,
 		"state", ast_endpoint_state_to_string(snapshot->state),
-		"channels");
+		"channel_ids");
 
 	if (json == NULL) {
 		return NULL;
@@ -253,7 +253,7 @@ struct ast_json *ast_endpoint_snapshot_to_json(
 		}
 	}
 
-	channel_array = ast_json_object_get(json, "channels");
+	channel_array = ast_json_object_get(json, "channel_ids");
 	ast_assert(channel_array != NULL);
 	for (i = 0; i < snapshot->num_channels; ++i) {
 		int res = ast_json_array_append(channel_array,
diff --git a/res/Makefile b/res/Makefile
index c69862802a2a0db842e212be2d7a8d64a6275337..1310dae3a08666d191f13296564a3fd7f6226126 100644
--- a/res/Makefile
+++ b/res/Makefile
@@ -83,5 +83,8 @@ $(subst .c,.o,$(wildcard parking/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_par
 res_stasis_http.so: stasis_http/ari_websockets.o
 stasis_http/ari_websockets.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_asterisk)
 
+res_ari_model.so: stasis_http/ari_model_validators.o
+stasis_http/ari_model_validators.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_model)
+
 # Dependencies for res_stasis_http_*.so are generated, so they're in this file
 include stasis_http.make
diff --git a/res/res_ari_model.c b/res/res_ari_model.c
new file mode 100644
index 0000000000000000000000000000000000000000..fd2ec6493ff965fadd5f4461727c11a74d609ee3
--- /dev/null
+++ b/res/res_ari_model.c
@@ -0,0 +1,207 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Implementation Swagger validators.
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "stasis_http/ari_model_validators.h"
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/utils.h"
+
+#include <regex.h>
+
+/* Regex to match date strings */
+static regex_t date_regex;
+
+/* Regex for YYYY-MM-DD */
+#define REGEX_YMD "[0-9]{4}-[01][0-9]-[0-3][0-9]"
+
+/* Regex for hh:mm(:ss(.s)); seconds and subseconds optional
+ * Handles the probably impossible case of a leap second, too */
+#define REGEX_HMS "[0-2][0-9]:[0-5][0-9](:[0-6][0-9](.[0-9]+)?)?"
+
+/* Regex for timezone: (+|-)hh(:mm), with optional colon. */
+#define REGEX_TZ "(Z|[-+][0-2][0-9](:?[0-5][0-9])?)"
+
+/* REGEX for ISO 8601, the time specifier optional */
+#define ISO8601_PATTERN "^" REGEX_YMD "(T" REGEX_HMS REGEX_TZ ")?$"
+
+static int check_type(struct ast_json *json, enum ast_json_type expected)
+{
+	enum ast_json_type actual;
+
+	if (!json) {
+		ast_log(LOG_ERROR, "Expected type %s, was NULL\n",
+			ast_json_typename(expected));
+		return 0;
+	}
+
+	actual = ast_json_typeof(json);
+	if (expected != actual) {
+		ast_log(LOG_ERROR, "Expected type %s, was %s\n",
+			ast_json_typename(expected), ast_json_typename(actual));
+		return 0;
+	}
+	return 1;
+}
+
+static int check_range(intmax_t minval, intmax_t maxval, struct ast_json *json)
+{
+	intmax_t v;
+
+	if (!check_type(json, AST_JSON_INTEGER)) {
+		return 0;
+	}
+
+	v = ast_json_integer_get(json);
+
+	if (v < minval || maxval < v) {
+		ast_log(LOG_ERROR, "Value out of range. Expected %jd <= %jd <= %jd\n", minval, v, maxval);
+		return 0;
+	}
+	return 1;
+}
+
+int ari_validate_void(struct ast_json *json)
+{
+	return check_type(json, AST_JSON_NULL);
+}
+
+int ari_validate_byte(struct ast_json *json)
+{
+	/* Java bytes are signed, which accounts for great fun for all */
+	return check_range(-128, 255, json);
+}
+
+int ari_validate_boolean(struct ast_json *json)
+{
+	enum ast_json_type actual = ast_json_typeof(json);
+	switch (actual) {
+	case AST_JSON_TRUE:
+	case AST_JSON_FALSE:
+		return 1;
+	default:
+		ast_log(LOG_ERROR, "Expected type boolean, was %s\n",
+			ast_json_typename(actual));
+		return 0;
+	}
+}
+
+int ari_validate_int(struct ast_json *json)
+{
+	/* Swagger int's are 32-bit */
+	return check_range(-2147483648, 2147483647, json);
+}
+
+int ari_validate_long(struct ast_json *json)
+{
+	/* All integral values are valid longs. No need for range check. */
+	return check_type(json, AST_JSON_INTEGER);
+}
+
+int ari_validate_float(struct ast_json *json)
+{
+	return check_type(json, AST_JSON_REAL);
+}
+
+int ari_validate_double(struct ast_json *json)
+{
+	return check_type(json, AST_JSON_REAL);
+}
+
+int ari_validate_string(struct ast_json *json)
+{
+	return check_type(json, AST_JSON_STRING);
+}
+
+int ari_validate_date(struct ast_json *json)
+{
+	/* Dates are ISO-8601 strings */
+	const char *str;
+	if (!check_type(json, AST_JSON_STRING)) {
+		return 0;
+	}
+	str = ast_json_string_get(json);
+	ast_assert(str != NULL);
+	if (regexec(&date_regex, str, 0, NULL, 0) != 0) {
+		ast_log(LOG_ERROR, "Date field is malformed: '%s'\n", str);
+		return 0;
+	}
+	return 1;
+}
+
+int ari_validate_list(struct ast_json *json, int (*fn)(struct ast_json *))
+{
+	int res = 1;
+	size_t i;
+
+	if (!check_type(json, AST_JSON_ARRAY)) {
+		return 0;
+	}
+
+	for (i = 0; i < ast_json_array_size(json); ++i) {
+		int member_res;
+		member_res = fn(ast_json_array_get(json, i));
+		if (!member_res) {
+			ast_log(LOG_ERROR,
+				"Array member %zd failed validation\n", i);
+			res = 0;
+		}
+	}
+
+	return res;
+}
+
+static int load_module(void)
+{
+	int res;
+	res = regcomp(&date_regex, ISO8601_PATTERN,
+		REG_EXTENDED | REG_ICASE | REG_NOSUB);
+
+	if (res != 0) {
+		return AST_MODULE_LOAD_FAILURE;
+	}
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	regfree(&date_regex);
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY,
+	AST_MODFLAG_LOAD_ORDER | AST_MODFLAG_GLOBAL_SYMBOLS,
+	"ARI Model validators",
+	.load = load_module,
+	.unload = unload_module,
+	.load_pri = AST_MODPRI_APP_DEPEND,
+        );
diff --git a/res/res_ari_model.exports.in b/res/res_ari_model.exports.in
new file mode 100644
index 0000000000000000000000000000000000000000..160e23f43ea79b5e45a311c5cabda1b4325ac93e
--- /dev/null
+++ b/res/res_ari_model.exports.in
@@ -0,0 +1,6 @@
+{
+	global:
+		LINKER_SYMBOL_PREFIXari_*;
+	local:
+		*;
+};
diff --git a/res/res_stasis.c b/res/res_stasis.c
index de432e409bd2dcb6acc7f6457b7004b1d9e81c96..ed3823051cd1d4c88dbf8f404284b1662b7d86e4 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -48,7 +48,6 @@
  */
 
 /*** MODULEINFO
-	<depend>res_stasis_json_events</depend>
 	<support_level>core</support_level>
  ***/
 
@@ -66,7 +65,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/strings.h"
 #include "stasis/app.h"
 #include "stasis/control.h"
-#include "stasis_json/resource_events.h"
 
 /*! Time to wait for a frame in the application */
 #define MAX_WAIT_MS 200
@@ -233,28 +231,60 @@ static struct ao2_container *get_apps_watching_channel(const char *uniqueid)
 /*! \brief Typedef for callbacks that get called on channel snapshot updates */
 typedef struct ast_json *(*channel_snapshot_monitor)(
 	struct ast_channel_snapshot *old_snapshot,
-	struct ast_channel_snapshot *new_snapshot);
+	struct ast_channel_snapshot *new_snapshot,
+	const struct timeval *tv);
+
+static struct ast_json *simple_channel_event(
+	const char *type,
+	struct ast_channel_snapshot *snapshot,
+	const struct timeval *tv)
+{
+	return ast_json_pack("{s: s, s: o, s: o}",
+		"type", type,
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"channel", ast_channel_snapshot_to_json(snapshot));
+}
+
+static struct ast_json *channel_created_event(
+	struct ast_channel_snapshot *snapshot,
+	const struct timeval *tv)
+{
+	return simple_channel_event("ChannelCreated", snapshot, tv);
+}
+
+static struct ast_json *channel_destroyed_event(
+	struct ast_channel_snapshot *snapshot,
+	const struct timeval *tv)
+{
+	return ast_json_pack("{s: s, s: o, s: i, s: s, s: o}",
+		"type", "ChannelDestroyed",
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"cause", snapshot->hangupcause,
+		"cause_txt", ast_cause2str(snapshot->hangupcause),
+		"channel", ast_channel_snapshot_to_json(snapshot));
+}
+
+static struct ast_json *channel_state_change_event(
+	struct ast_channel_snapshot *snapshot,
+	const struct timeval *tv)
+{
+	return simple_channel_event("ChannelStateChange", snapshot, tv);
+}
 
 /*! \brief Handle channel state changes */
 static struct ast_json *channel_state(
 	struct ast_channel_snapshot *old_snapshot,
-	struct ast_channel_snapshot *new_snapshot)
+	struct ast_channel_snapshot *new_snapshot,
+	const struct timeval *tv)
 {
-	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
 	struct ast_channel_snapshot *snapshot = new_snapshot ? new_snapshot : old_snapshot;
 
 	if (!old_snapshot) {
-		return stasis_json_event_channel_created_create(snapshot);
+		return channel_created_event(snapshot, tv);
 	} else if (!new_snapshot) {
-		json = ast_json_pack("{s: i, s: s}",
-			"cause", snapshot->hangupcause,
-			"cause_txt", ast_cause2str(snapshot->hangupcause));
-		if (!json) {
-			return NULL;
-		}
-		return stasis_json_event_channel_destroyed_create(snapshot, json);
+		return channel_destroyed_event(snapshot, tv);
 	} else if (old_snapshot->state != new_snapshot->state) {
-		return stasis_json_event_channel_state_change_create(snapshot);
+		return channel_state_change_event(snapshot, tv);
 	}
 
 	return NULL;
@@ -262,7 +292,8 @@ static struct ast_json *channel_state(
 
 static struct ast_json *channel_dialplan(
 	struct ast_channel_snapshot *old_snapshot,
-	struct ast_channel_snapshot *new_snapshot)
+	struct ast_channel_snapshot *new_snapshot,
+	const struct timeval *tv)
 {
 	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
 
@@ -280,19 +311,18 @@ static struct ast_json *channel_dialplan(
 		return NULL;
 	}
 
-	json = ast_json_pack("{s: s, s: s}",
-		"application", new_snapshot->appl,
-		"application_data", new_snapshot->data);
-	if (!json) {
-		return NULL;
-	}
-
-	return stasis_json_event_channel_dialplan_create(new_snapshot, json);
+	return ast_json_pack("{s: s, s: o, s: s, s: s, s: o}",
+		"type", "ChannelDialplan",
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"dialplan_app", new_snapshot->appl,
+		"dialplan_app_data", new_snapshot->data,
+		"channel", ast_channel_snapshot_to_json(new_snapshot));
 }
 
 static struct ast_json *channel_callerid(
 	struct ast_channel_snapshot *old_snapshot,
-	struct ast_channel_snapshot *new_snapshot)
+	struct ast_channel_snapshot *new_snapshot,
+	const struct timeval *tv)
 {
 	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
 
@@ -305,29 +335,16 @@ static struct ast_json *channel_callerid(
 		return NULL;
 	}
 
-	json = ast_json_pack("{s: i, s: s}",
+	return ast_json_pack("{s: s, s: o, s: i, s: s, s: o}",
+		"type", "ChannelCallerId",
+		"timestamp", ast_json_timeval(*tv, NULL),
 		"caller_presentation", new_snapshot->caller_pres,
-		"caller_presentation_txt", ast_describe_caller_presentation(new_snapshot->caller_pres));
-	if (!json) {
-		return NULL;
-	}
-
-	return stasis_json_event_channel_caller_id_create(new_snapshot, json);
-}
-
-static struct ast_json *channel_snapshot(
-	struct ast_channel_snapshot *old_snapshot,
-	struct ast_channel_snapshot *new_snapshot)
-{
-	if (!new_snapshot) {
-		return NULL;
-	}
-
-	return stasis_json_event_channel_snapshot_create(new_snapshot);
+		"caller_presentation_txt", ast_describe_caller_presentation(
+			new_snapshot->caller_pres),
+		"channel", ast_channel_snapshot_to_json(new_snapshot));
 }
 
 channel_snapshot_monitor channel_monitors[] = {
-	channel_snapshot,
 	channel_state,
 	channel_dialplan,
 	channel_callerid
@@ -351,6 +368,9 @@ static void sub_channel_snapshot_handler(void *data,
 	struct stasis_cache_update *update = stasis_message_data(message);
 	struct ast_channel_snapshot *new_snapshot = stasis_message_data(update->new_snapshot);
 	struct ast_channel_snapshot *old_snapshot = stasis_message_data(update->old_snapshot);
+	/* Pull timestamp from the new snapshot, or from the update message
+	 * when there isn't one. */
+	const struct timeval *tv = update->new_snapshot ? stasis_message_timestamp(update->new_snapshot) : stasis_message_timestamp(message);
 	int i;
 
 	watching_apps = get_apps_watching_channel(new_snapshot ? new_snapshot->uniqueid : old_snapshot->uniqueid);
@@ -361,7 +381,7 @@ static void sub_channel_snapshot_handler(void *data,
 	for (i = 0; i < ARRAY_LEN(channel_monitors); ++i) {
 		RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
 
-		msg = channel_monitors[i](old_snapshot, new_snapshot);
+		msg = channel_monitors[i](old_snapshot, new_snapshot, tv);
 		if (msg) {
 			ao2_callback(watching_apps, OBJ_NODATA, app_send_cb, msg);
 		}
@@ -373,22 +393,26 @@ static void distribute_message(struct ao2_container *apps, struct ast_json *msg)
 	ao2_callback(apps, OBJ_NODATA, app_send_cb, msg);
 }
 
-static void generic_blob_handler(struct ast_channel_blob *obj, channel_blob_handler_cb handler_cb)
+static void sub_channel_blob_handler(void *data,
+		struct stasis_subscription *sub,
+		struct stasis_topic *topic,
+		struct stasis_message *message)
 {
 	RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
 	RAII_VAR(struct ao2_container *, watching_apps, NULL, ao2_cleanup);
+	struct ast_channel_blob *obj = stasis_message_data(message);
 
 	if (!obj->snapshot) {
 		return;
 	}
 
-	watching_apps = get_apps_watching_channel(obj->snapshot->uniqueid);
-	if (!watching_apps) {
+	msg = stasis_message_to_json(message);
+	if (!msg) {
 		return;
 	}
 
-	msg = handler_cb(obj);
-	if (!msg) {
+	watching_apps = get_apps_watching_channel(obj->snapshot->uniqueid);
+	if (!watching_apps) {
 		return;
 	}
 
@@ -446,7 +470,6 @@ int app_send_start_msg(struct app *app, struct ast_channel *chan,
 	int argc, char *argv[])
 {
 	RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
 	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
 
 	struct ast_json *json_args;
@@ -460,13 +483,16 @@ int app_send_start_msg(struct app *app, struct ast_channel *chan,
 		return -1;
 	}
 
-	blob = ast_json_pack("{s: []}", "args");
-	if (!blob) {
+	msg = ast_json_pack("{s: s, s: [], s: o}",
+		"type", "StasisStart",
+		"args",
+		"channel", ast_channel_snapshot_to_json(snapshot));
+	if (!msg) {
 		return -1;
 	}
 
 	/* Append arguments to args array */
-	json_args = ast_json_object_get(blob, "args");
+	json_args = ast_json_object_get(msg, "args");
 	ast_assert(json_args != NULL);
 	for (i = 0; i < argc; ++i) {
 		int r = ast_json_array_append(json_args,
@@ -477,11 +503,6 @@ int app_send_start_msg(struct app *app, struct ast_channel *chan,
 		}
 	}
 
-	msg = stasis_json_event_stasis_start_create(snapshot, blob);
-	if (!msg) {
-		return -1;
-	}
-
 	app_send(app, msg);
 	return 0;
 }
@@ -499,7 +520,9 @@ int app_send_end_msg(struct app *app, struct ast_channel *chan)
 		return -1;
 	}
 
-	msg = stasis_json_event_stasis_end_create(snapshot);
+	msg = ast_json_pack("{s: s, s: o}",
+		"type", "StasisEnd",
+		"channel", ast_channel_snapshot_to_json(snapshot));
 	if (!msg) {
 		return -1;
 	}
@@ -633,15 +656,13 @@ int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data)
 	app = ao2_find(apps_registry, app_name, OBJ_KEY | OBJ_NOLOCK);
 
 	if (app) {
-		RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
 		RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
 
-		blob = ast_json_pack("{s: s}", "application", app_name);
-		if (blob) {
-			msg = stasis_json_event_application_replaced_create(blob);
-			if (msg) {
-				app_send(app, msg);
-			}
+		msg = ast_json_pack("{s: s, s: s}",
+			"type", "ApplicationReplaced",
+			"application", app_name);
+		if (msg) {
+			app_send(app, msg);
 		}
 
 		app_update(app, handler, data);
@@ -665,82 +686,6 @@ void stasis_app_unregister(const char *app_name)
 	}
 }
 
-static struct ast_json *handle_blob_dtmf(struct ast_channel_blob *obj)
-{
-	RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
-	const char *direction;
-
-	/* To simplify events, we'll only generate on receive */
-	direction = ast_json_string_get(
-		ast_json_object_get(obj->blob, "direction"));
-
-	if (strcmp("Received", direction) != 0) {
-		return NULL;
-	}
-
-	extra = ast_json_pack(
-		"{s: o}",
-		"digit", ast_json_ref(ast_json_object_get(obj->blob, "digit")));
-	if (!extra) {
-		return NULL;
-	}
-
-	return stasis_json_event_channel_dtmf_received_create(obj->snapshot, extra);
-}
-
-/* To simplify events, we'll only generate on DTMF end (dtmf_end type) */
-static void sub_dtmf_handler(void *data,
-		struct stasis_subscription *sub,
-		struct stasis_topic *topic,
-		struct stasis_message *message)
-{
-	struct ast_channel_blob *obj = stasis_message_data(message);
-	generic_blob_handler(obj, handle_blob_dtmf);
-}
-
-static struct ast_json *handle_blob_userevent(struct ast_channel_blob *obj)
-{
-	return stasis_json_event_channel_userevent_create(obj->snapshot, obj->blob);
-}
-
-static void sub_userevent_handler(void *data,
-		struct stasis_subscription *sub,
-		struct stasis_topic *topic,
-		struct stasis_message *message)
-{
-	struct ast_channel_blob *obj = stasis_message_data(message);
-	generic_blob_handler(obj, handle_blob_userevent);
-}
-
-static struct ast_json *handle_blob_hangup_request(struct ast_channel_blob *obj)
-{
-	return stasis_json_event_channel_hangup_request_create(obj->snapshot, obj->blob);
-}
-
-static void sub_hangup_request_handler(void *data,
-		struct stasis_subscription *sub,
-		struct stasis_topic *topic,
-		struct stasis_message *message)
-{
-	struct ast_channel_blob *obj = stasis_message_data(message);
-	generic_blob_handler(obj, handle_blob_hangup_request);
-}
-
-static struct ast_json *handle_blob_varset(struct ast_channel_blob *obj)
-{
-	return stasis_json_event_channel_varset_create(obj->snapshot, obj->blob);
-}
-
-static void sub_varset_handler(void *data,
-		struct stasis_subscription *sub,
-		struct stasis_topic *topic,
-		struct stasis_message *message)
-{
-	struct ast_channel_blob *obj = stasis_message_data(message);
-	generic_blob_handler(obj, handle_blob_varset);
-}
-
 void stasis_app_ref(void)
 {
 	ast_module_ref(ast_module_info->self);
@@ -788,6 +733,30 @@ static int remove_bridge_cb(void *obj, void *arg, int flags)
 	return 0;
 }
 
+static struct ast_json *simple_bridge_event(
+	const char *type,
+	struct ast_bridge_snapshot *snapshot,
+	const struct timeval *tv)
+{
+	return ast_json_pack("{s: s, s: o, s: o}",
+		"type", type,
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"bridge", ast_bridge_snapshot_to_json(snapshot));
+}
+
+static struct ast_json *simple_bridge_channel_event(
+	const char *type,
+	struct ast_bridge_snapshot *bridge_snapshot,
+	struct ast_channel_snapshot *channel_snapshot,
+	const struct timeval *tv)
+{
+	return ast_json_pack("{s: s, s: o, s: o}",
+		"type", type,
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"bridge", ast_bridge_snapshot_to_json(bridge_snapshot),
+		"channel", ast_channel_snapshot_to_json(channel_snapshot));
+}
+
 static void sub_bridge_snapshot_handler(void *data,
 		struct stasis_subscription *sub,
 		struct stasis_topic *topic,
@@ -797,6 +766,8 @@ static void sub_bridge_snapshot_handler(void *data,
 	struct stasis_cache_update *update = stasis_message_data(message);
 	struct ast_bridge_snapshot *new_snapshot = stasis_message_data(update->new_snapshot);
 	struct ast_bridge_snapshot *old_snapshot = stasis_message_data(update->old_snapshot);
+	const struct timeval *tv = update->new_snapshot ? stasis_message_timestamp(update->new_snapshot) : stasis_message_timestamp(message);
+
 	RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
 
 	watching_apps = get_apps_watching_bridge(new_snapshot ? new_snapshot->uniqueid : old_snapshot->uniqueid);
@@ -810,11 +781,11 @@ static void sub_bridge_snapshot_handler(void *data,
 		/* The bridge has gone away. Create the message, make sure no apps are
 		 * watching this bridge anymore, and destroy the bridge's control
 		 * structure */
-		msg = stasis_json_event_bridge_destroyed_create(old_snapshot);
+		msg = simple_bridge_event("BridgeDestroyed", old_snapshot, tv);
 		ao2_callback(watching_apps, OBJ_NODATA, remove_bridge_cb, bridge_id);
 		stasis_app_bridge_destroy(old_snapshot->uniqueid);
 	} else if (!old_snapshot) {
-		msg = stasis_json_event_bridge_created_create(old_snapshot);
+		msg = simple_bridge_event("BridgeCreated", old_snapshot, tv);
 	}
 
 	if (!msg) {
@@ -865,6 +836,7 @@ static void sub_bridge_merge_handler(void *data,
 	struct ast_bridge_merge_message *merge = stasis_message_data(message);
 	RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
 	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+	const struct timeval *tv = stasis_message_timestamp(message);
 
 	watching_apps_to = get_apps_watching_bridge(merge->to->uniqueid);
 	if (watching_apps_to) {
@@ -881,16 +853,16 @@ static void sub_bridge_merge_handler(void *data,
 		return;
 	}
 
-	/* The secondary bridge has to be packed into JSON by hand because the auto-generated
-	 * JSON event generator can only handle one instance of a given snapshot type in an
-	 * elegant way */
-	blob = ast_json_pack("{s: o}", "bridge_from", ast_bridge_snapshot_to_json(merge->from));
-	if (!blob) {
+	msg = ast_json_pack("{s: s, s: o, s: o, s: o}",
+		"type", "BridgeMerged",
+		"timestamp", ast_json_timeval(*tv, NULL),
+		"bridge", ast_bridge_snapshot_to_json(merge->to),
+		"bridge_from", ast_bridge_snapshot_to_json(merge->from));
+
+	if (!msg) {
 		return;
 	}
 
-	msg = stasis_json_event_bridge_merged_create(merge->to, blob);
-
 	distribute_message(watching_apps_all, msg);
 }
 
@@ -920,7 +892,8 @@ static void sub_bridge_enter_handler(void *data,
 		return;
 	}
 
-	msg = stasis_json_event_channel_entered_bridge_create(obj->bridge, obj->channel);
+	msg = simple_bridge_channel_event("ChannelEnteredBridge", obj->bridge,
+		obj->channel, stasis_message_timestamp(message));
 
 	distribute_message(watching_apps_all, msg);
 }
@@ -939,7 +912,8 @@ static void sub_bridge_leave_handler(void *data,
 		return;
 	}
 
-	msg = stasis_json_event_channel_left_bridge_create(obj->bridge, obj->channel);
+	msg = simple_bridge_channel_event("ChannelLeftBridge", obj->bridge,
+		obj->channel, stasis_message_timestamp(message));
 
 	distribute_message(watching_apps_bridge, msg);
 }
@@ -972,10 +946,16 @@ static int load_module(void)
 	}
 
 	r |= stasis_message_router_add(channel_router, stasis_cache_update_type(), sub_channel_snapshot_handler, NULL);
-	r |= stasis_message_router_add(channel_router, ast_channel_user_event_type(), sub_userevent_handler, NULL);
-	r |= stasis_message_router_add(channel_router, ast_channel_varset_type(), sub_varset_handler, NULL);
-	r |= stasis_message_router_add(channel_router, ast_channel_dtmf_begin_type(), sub_dtmf_handler, NULL);
-	r |= stasis_message_router_add(channel_router, ast_channel_hangup_request_type(), sub_hangup_request_handler, NULL);
+	/* TODO: This could be handled a lot better. Instead of subscribing to
+	 * the one caching topic and filtering out messages by channel id, we
+	 * should have individual caching topics per-channel, with a shared
+	 * back-end cache. That would simplify a lot of what's going on right
+	 * here.
+	 */
+	r |= stasis_message_router_add(channel_router, ast_channel_user_event_type(), sub_channel_blob_handler, NULL);
+	r |= stasis_message_router_add(channel_router, ast_channel_varset_type(), sub_channel_blob_handler, NULL);
+	r |= stasis_message_router_add(channel_router, ast_channel_dtmf_end_type(), sub_channel_blob_handler, NULL);
+	r |= stasis_message_router_add(channel_router, ast_channel_hangup_request_type(), sub_channel_blob_handler, NULL);
 	if (r) {
 		return AST_MODULE_LOAD_FAILURE;
 	}
diff --git a/res/res_stasis_http.c b/res/res_stasis_http.c
index fce10814635f593fc57ca5eb99ba4a91e144b11f..3ff6482b5bcd1daaa5e20c0670d7b09da35682f3 100644
--- a/res/res_stasis_http.c
+++ b/res/res_stasis_http.c
@@ -324,7 +324,7 @@ void stasis_http_response_ok(struct stasis_http_response *response,
 
 void stasis_http_response_no_content(struct stasis_http_response *response)
 {
-	response->message = NULL;
+	response->message = ast_json_null();
 	response->response_code = 204;
 	response->response_text = "No Content";
 }
@@ -386,9 +386,7 @@ static void handle_options(struct stasis_rest_handlers *handler,
 
 	/* Regular OPTIONS response */
 	add_allow_header(handler, response);
-	response->response_code = 204;
-	response->response_text = "No Content";
-	response->message = NULL;
+	stasis_http_response_no_content(response);
 
 	/* Parse CORS headers */
 	for (header = headers; header != NULL; header = header->next) {
@@ -797,6 +795,11 @@ static void process_cors_request(struct ast_variable *headers,
 	 */
 }
 
+enum ast_json_encoding_format stasis_http_json_format(void)
+{
+	RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup);
+	return cfg->global->format;
+}
 
 /*!
  * \internal
@@ -819,7 +822,6 @@ static int stasis_http_callback(struct ast_tcptls_session_instance *ser,
 				struct ast_variable *get_params,
 				struct ast_variable *headers)
 {
-	RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup);
 	RAII_VAR(struct ast_str *, response_headers, ast_str_create(40), ast_free);
 	RAII_VAR(struct ast_str *, response_body, ast_str_create(256), ast_free);
 	struct stasis_http_response response = {};
@@ -859,11 +861,10 @@ static int stasis_http_callback(struct ast_tcptls_session_instance *ser,
 		return 0;
 	}
 
-	/* Leaving message unset is only allowed for 204 (No Content).
-	 * If you explicitly want to have no content for a different return
-	 * code, set message to ast_json_null().
+	/* If you explicitly want to have no content, set message to
+	 * ast_json_null().
 	 */
-	ast_assert(response.response_code == 204 || response.message != NULL);
+	ast_assert(response.message != NULL);
 	ast_assert(response.response_code > 0);
 
 	ast_str_append(&response_headers, 0, "%s", ast_str_buffer(response.headers));
@@ -874,7 +875,7 @@ static int stasis_http_callback(struct ast_tcptls_session_instance *ser,
 	if (response.message && !ast_json_is_null(response.message)) {
 		ast_str_append(&response_headers, 0,
 			       "Content-type: application/json\r\n");
-		if (ast_json_dump_str_format(response.message, &response_body, cfg->global->format) != 0) {
+		if (ast_json_dump_str_format(response.message, &response_body, stasis_http_json_format()) != 0) {
 			/* Error encoding response */
 			response.response_code = 500;
 			response.response_text = "Internal Server Error";
diff --git a/res/res_stasis_http_asterisk.c b/res/res_stasis_http_asterisk.c
index 9f4fd63e1516945b290d36072b420b04143d920c..01f082ad69c4fdb5933f6bd1bc585750fe993560 100644
--- a/res/res_stasis_http_asterisk.c
+++ b/res/res_stasis_http_asterisk.c
@@ -44,6 +44,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/stasis_app.h"
 #include "stasis_http/resource_asterisk.h"
+#if defined(AST_DEVMODE)
+#include "stasis_http/ari_model_validators.h"
+#endif
 
 /*!
  * \brief Parameter parsing callback for /asterisk/info.
@@ -53,9 +56,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_asterisk_info_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_asterisk_info_args args = {};
 	struct ast_variable *i;
 
@@ -66,6 +74,29 @@ static void stasis_http_get_asterisk_info_cb(
 		{}
 	}
 	stasis_http_get_asterisk_info(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_asterisk_info(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /asterisk/info\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /asterisk/info\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 
 /*! \brief REST handler for /api-docs/asterisk.{format} */
diff --git a/res/res_stasis_http_bridges.c b/res/res_stasis_http_bridges.c
index 717b2f83fa6f81d47fa757178ddc3256714ca269..a4801df13dd97ff35b9ba0d1967a6afa5cfe85a0 100644
--- a/res/res_stasis_http_bridges.c
+++ b/res/res_stasis_http_bridges.c
@@ -44,6 +44,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/stasis_app.h"
 #include "stasis_http/resource_bridges.h"
+#if defined(AST_DEVMODE)
+#include "stasis_http/ari_model_validators.h"
+#endif
 
 /*!
  * \brief Parameter parsing callback for /bridges.
@@ -53,11 +56,39 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_bridges_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_bridges_args args = {};
 	stasis_http_get_bridges(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_list(response->message,
+				ari_validate_bridge);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /bridges\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /bridges\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /bridges.
@@ -67,9 +98,14 @@ static void stasis_http_get_bridges_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_new_bridge_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_new_bridge_args args = {};
 	struct ast_variable *i;
 
@@ -80,6 +116,29 @@ static void stasis_http_new_bridge_cb(
 		{}
 	}
 	stasis_http_new_bridge(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_bridge(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /bridges\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /bridges\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /bridges/{bridgeId}.
@@ -89,9 +148,14 @@ static void stasis_http_new_bridge_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_bridge_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_bridge_args args = {};
 	struct ast_variable *i;
 
@@ -102,6 +166,30 @@ static void stasis_http_get_bridge_cb(
 		{}
 	}
 	stasis_http_get_bridge(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	case 404: /* Bridge not found */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_bridge(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /bridges/{bridgeId}.
@@ -111,9 +199,14 @@ static void stasis_http_get_bridge_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_delete_bridge_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_delete_bridge_args args = {};
 	struct ast_variable *i;
 
@@ -124,6 +217,30 @@ static void stasis_http_delete_bridge_cb(
 		{}
 	}
 	stasis_http_delete_bridge(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	case 404: /* Bridge not found */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /bridges/{bridgeId}/addChannel.
@@ -133,9 +250,14 @@ static void stasis_http_delete_bridge_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_add_channel_to_bridge_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_add_channel_to_bridge_args args = {};
 	struct ast_variable *i;
 
@@ -152,6 +274,32 @@ static void stasis_http_add_channel_to_bridge_cb(
 		{}
 	}
 	stasis_http_add_channel_to_bridge(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	case 404: /* Bridge not found */
+	case 409: /* Bridge not in Stasis application */
+	case 422: /* Channel not found, or not in Stasis application */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/addChannel\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/addChannel\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /bridges/{bridgeId}/removeChannel.
@@ -161,9 +309,14 @@ static void stasis_http_add_channel_to_bridge_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_remove_channel_from_bridge_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_remove_channel_from_bridge_args args = {};
 	struct ast_variable *i;
 
@@ -180,6 +333,29 @@ static void stasis_http_remove_channel_from_bridge_cb(
 		{}
 	}
 	stasis_http_remove_channel_from_bridge(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/removeChannel\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/removeChannel\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /bridges/{bridgeId}/record.
@@ -189,9 +365,14 @@ static void stasis_http_remove_channel_from_bridge_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_record_bridge_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_record_bridge_args args = {};
 	struct ast_variable *i;
 
@@ -223,6 +404,29 @@ static void stasis_http_record_bridge_cb(
 		{}
 	}
 	stasis_http_record_bridge(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_live_recording(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/record\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/record\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 
 /*! \brief REST handler for /api-docs/bridges.{format} */
diff --git a/res/res_stasis_http_channels.c b/res/res_stasis_http_channels.c
index c865b393176fb8d509a1da0b2123830d746da9a1..ebcc9e8800e8cae1bfccfe39d547197b44acd82f 100644
--- a/res/res_stasis_http_channels.c
+++ b/res/res_stasis_http_channels.c
@@ -44,6 +44,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/stasis_app.h"
 #include "stasis_http/resource_channels.h"
+#if defined(AST_DEVMODE)
+#include "stasis_http/ari_model_validators.h"
+#endif
 
 /*!
  * \brief Parameter parsing callback for /channels.
@@ -53,11 +56,39 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_channels_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_channels_args args = {};
 	stasis_http_get_channels(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_list(response->message,
+				ari_validate_channel);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /channels.
@@ -67,9 +98,14 @@ static void stasis_http_get_channels_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_originate_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_originate_args args = {};
 	struct ast_variable *i;
 
@@ -101,6 +137,29 @@ static void stasis_http_originate_cb(
 		{}
 	}
 	stasis_http_originate(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /channels/{channelId}.
@@ -110,9 +169,14 @@ static void stasis_http_originate_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_channel_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_channel_args args = {};
 	struct ast_variable *i;
 
@@ -123,6 +187,30 @@ static void stasis_http_get_channel_cb(
 		{}
 	}
 	stasis_http_get_channel(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	case 404: /* Channel not found */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_channel(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /channels/{channelId}.
@@ -132,9 +220,14 @@ static void stasis_http_get_channel_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_delete_channel_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_delete_channel_args args = {};
 	struct ast_variable *i;
 
@@ -145,6 +238,30 @@ static void stasis_http_delete_channel_cb(
 		{}
 	}
 	stasis_http_delete_channel(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	case 404: /* Channel not found */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /channels/{channelId}/dial.
@@ -154,9 +271,14 @@ static void stasis_http_delete_channel_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_dial_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_dial_args args = {};
 	struct ast_variable *i;
 
@@ -182,6 +304,31 @@ static void stasis_http_dial_cb(
 		{}
 	}
 	stasis_http_dial(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	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 = ari_validate_dialed(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/dial\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/dial\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /channels/{channelId}/continue.
@@ -191,9 +338,14 @@ static void stasis_http_dial_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_continue_in_dialplan_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_continue_in_dialplan_args args = {};
 	struct ast_variable *i;
 
@@ -216,6 +368,31 @@ static void stasis_http_continue_in_dialplan_cb(
 		{}
 	}
 	stasis_http_continue_in_dialplan(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	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 = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/continue\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/continue\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /channels/{channelId}/answer.
@@ -225,9 +402,14 @@ static void stasis_http_continue_in_dialplan_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_answer_channel_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_answer_channel_args args = {};
 	struct ast_variable *i;
 
@@ -238,6 +420,31 @@ static void stasis_http_answer_channel_cb(
 		{}
 	}
 	stasis_http_answer_channel(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	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 = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/answer\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/answer\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /channels/{channelId}/mute.
@@ -247,9 +454,14 @@ static void stasis_http_answer_channel_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_mute_channel_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_mute_channel_args args = {};
 	struct ast_variable *i;
 
@@ -266,6 +478,31 @@ static void stasis_http_mute_channel_cb(
 		{}
 	}
 	stasis_http_mute_channel(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	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 = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/mute\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/mute\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /channels/{channelId}/unmute.
@@ -275,9 +512,14 @@ static void stasis_http_mute_channel_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_unmute_channel_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_unmute_channel_args args = {};
 	struct ast_variable *i;
 
@@ -294,6 +536,31 @@ static void stasis_http_unmute_channel_cb(
 		{}
 	}
 	stasis_http_unmute_channel(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	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 = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/unmute\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/unmute\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /channels/{channelId}/hold.
@@ -303,9 +570,14 @@ static void stasis_http_unmute_channel_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_hold_channel_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_hold_channel_args args = {};
 	struct ast_variable *i;
 
@@ -316,6 +588,31 @@ static void stasis_http_hold_channel_cb(
 		{}
 	}
 	stasis_http_hold_channel(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	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 = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/hold\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/hold\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /channels/{channelId}/unhold.
@@ -325,9 +622,14 @@ static void stasis_http_hold_channel_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_unhold_channel_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_unhold_channel_args args = {};
 	struct ast_variable *i;
 
@@ -338,6 +640,31 @@ static void stasis_http_unhold_channel_cb(
 		{}
 	}
 	stasis_http_unhold_channel(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	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 = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/unhold\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/unhold\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /channels/{channelId}/play.
@@ -347,9 +674,14 @@ static void stasis_http_unhold_channel_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_play_on_channel_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_play_on_channel_args args = {};
 	struct ast_variable *i;
 
@@ -375,6 +707,31 @@ static void stasis_http_play_on_channel_cb(
 		{}
 	}
 	stasis_http_play_on_channel(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	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 = ari_validate_playback(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/play\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/play\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /channels/{channelId}/record.
@@ -384,9 +741,14 @@ static void stasis_http_play_on_channel_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_record_channel_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_record_channel_args args = {};
 	struct ast_variable *i;
 
@@ -421,6 +783,31 @@ static void stasis_http_record_channel_cb(
 		{}
 	}
 	stasis_http_record_channel(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	case 404: /* Channel not found */
+	case 409: /* Channel is not in a Stasis application, or the channel is currently bridged with other channels. */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/record\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/record\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 
 /*! \brief REST handler for /api-docs/channels.{format} */
diff --git a/res/res_stasis_http_endpoints.c b/res/res_stasis_http_endpoints.c
index 81cdfeb0fb2ae9dfaa98080080b3dd2196676de6..332333030abf9d11bbb980d339a16239917f5f01 100644
--- a/res/res_stasis_http_endpoints.c
+++ b/res/res_stasis_http_endpoints.c
@@ -44,6 +44,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/stasis_app.h"
 #include "stasis_http/resource_endpoints.h"
+#if defined(AST_DEVMODE)
+#include "stasis_http/ari_model_validators.h"
+#endif
 
 /*!
  * \brief Parameter parsing callback for /endpoints.
@@ -53,11 +56,39 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_endpoints_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_endpoints_args args = {};
 	stasis_http_get_endpoints(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_list(response->message,
+				ari_validate_endpoint);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /endpoints\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /endpoints\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /endpoints/{tech}.
@@ -67,9 +98,14 @@ static void stasis_http_get_endpoints_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_endpoints_by_tech_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_endpoints_by_tech_args args = {};
 	struct ast_variable *i;
 
@@ -80,6 +116,29 @@ static void stasis_http_get_endpoints_by_tech_cb(
 		{}
 	}
 	stasis_http_get_endpoints_by_tech(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_list(response->message,
+				ari_validate_endpoint);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /endpoints/{tech}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /endpoints/{tech}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /endpoints/{tech}/{resource}.
@@ -89,9 +148,14 @@ static void stasis_http_get_endpoints_by_tech_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_endpoint_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_endpoint_args args = {};
 	struct ast_variable *i;
 
@@ -105,6 +169,29 @@ static void stasis_http_get_endpoint_cb(
 		{}
 	}
 	stasis_http_get_endpoint(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_endpoint(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /endpoints/{tech}/{resource}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /endpoints/{tech}/{resource}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 
 /*! \brief REST handler for /api-docs/endpoints.{format} */
diff --git a/res/res_stasis_http_events.c b/res/res_stasis_http_events.c
index 909c2d659799cfb72a66a7a4fb5f3e97dd26c3a0..4217263129ff591056744366db03c629208a79b8 100644
--- a/res/res_stasis_http_events.c
+++ b/res/res_stasis_http_events.c
@@ -44,6 +44,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/stasis_app.h"
 #include "stasis_http/resource_events.h"
+#if defined(AST_DEVMODE)
+#include "stasis_http/ari_model_validators.h"
+#endif
 
 static void stasis_http_event_websocket_ws_cb(struct ast_websocket *ws_session,
 	struct ast_variable *get_params, struct ast_variable *headers)
@@ -59,7 +62,12 @@ static void stasis_http_event_websocket_ws_cb(struct ast_websocket *ws_session,
 		} else
 		{}
 	}
-	session = ari_websocket_session_create(ws_session);
+#if defined(AST_DEVMODE)
+	session = ari_websocket_session_create(ws_session,
+		ari_validate_event);
+#else
+	session = ari_websocket_session_create(ws_session, NULL);
+#endif
 	if (!session) {
 		ast_log(LOG_ERROR, "Failed to create ARI session\n");
 		return;
diff --git a/res/res_stasis_http_playback.c b/res/res_stasis_http_playback.c
index 4608686bc7097e7f2d8bbf6b16fc159fc9f82587..0e56e622924576d3af613c087bda9b5444c086ad 100644
--- a/res/res_stasis_http_playback.c
+++ b/res/res_stasis_http_playback.c
@@ -44,6 +44,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/stasis_app.h"
 #include "stasis_http/resource_playback.h"
+#if defined(AST_DEVMODE)
+#include "stasis_http/ari_model_validators.h"
+#endif
 
 /*!
  * \brief Parameter parsing callback for /playback/{playbackId}.
@@ -53,9 +56,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_playback_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_playback_args args = {};
 	struct ast_variable *i;
 
@@ -66,6 +74,29 @@ static void stasis_http_get_playback_cb(
 		{}
 	}
 	stasis_http_get_playback(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_playback(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /playback/{playbackId}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /playback/{playbackId}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /playback/{playbackId}.
@@ -75,9 +106,14 @@ static void stasis_http_get_playback_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_stop_playback_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_stop_playback_args args = {};
 	struct ast_variable *i;
 
@@ -88,6 +124,29 @@ static void stasis_http_stop_playback_cb(
 		{}
 	}
 	stasis_http_stop_playback(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_playback(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /playback/{playbackId}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /playback/{playbackId}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /playback/{playbackId}/control.
@@ -97,9 +156,14 @@ static void stasis_http_stop_playback_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_control_playback_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_control_playback_args args = {};
 	struct ast_variable *i;
 
@@ -116,6 +180,32 @@ static void stasis_http_control_playback_cb(
 		{}
 	}
 	stasis_http_control_playback(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+	case 400: /* The provided operation parameter was invalid */
+	case 404: /* The playback cannot be found */
+	case 409: /* The operation cannot be performed in the playback's current state */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_playback(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /playback/{playbackId}/control\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /playback/{playbackId}/control\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 
 /*! \brief REST handler for /api-docs/playback.{format} */
diff --git a/res/res_stasis_http_recordings.c b/res/res_stasis_http_recordings.c
index 7d89393bcaa9be7359db3641da42180271b1b079..4aa43c9be3b8e47d695d4940ca4819e34b7bd4c8 100644
--- a/res/res_stasis_http_recordings.c
+++ b/res/res_stasis_http_recordings.c
@@ -44,21 +44,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/stasis_app.h"
 #include "stasis_http/resource_recordings.h"
+#if defined(AST_DEVMODE)
+#include "stasis_http/ari_model_validators.h"
+#endif
 
-/*!
- * \brief Parameter parsing callback for /recordings.
- * \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 stasis_http_get_recordings_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
-{
-	struct ast_get_recordings_args args = {};
-	stasis_http_get_recordings(headers, &args, response);
-}
 /*!
  * \brief Parameter parsing callback for /recordings/stored.
  * \param get_params GET parameters in the HTTP request.
@@ -67,11 +56,39 @@ static void stasis_http_get_recordings_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_stored_recordings_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_stored_recordings_args args = {};
 	stasis_http_get_stored_recordings(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_list(response->message,
+				ari_validate_stored_recording);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/stored\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/stored\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /recordings/stored/{recordingId}.
@@ -81,9 +98,14 @@ static void stasis_http_get_stored_recordings_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_stored_recording_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_stored_recording_args args = {};
 	struct ast_variable *i;
 
@@ -94,6 +116,29 @@ static void stasis_http_get_stored_recording_cb(
 		{}
 	}
 	stasis_http_get_stored_recording(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_stored_recording(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/stored/{recordingId}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/stored/{recordingId}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /recordings/stored/{recordingId}.
@@ -103,9 +148,14 @@ static void stasis_http_get_stored_recording_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_delete_stored_recording_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_delete_stored_recording_args args = {};
 	struct ast_variable *i;
 
@@ -116,6 +166,29 @@ static void stasis_http_delete_stored_recording_cb(
 		{}
 	}
 	stasis_http_delete_stored_recording(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/stored/{recordingId}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/stored/{recordingId}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /recordings/live.
@@ -125,11 +198,39 @@ static void stasis_http_delete_stored_recording_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_live_recordings_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_live_recordings_args args = {};
 	stasis_http_get_live_recordings(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_list(response->message,
+				ari_validate_live_recording);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /recordings/live/{recordingId}.
@@ -139,9 +240,14 @@ static void stasis_http_get_live_recordings_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_live_recording_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_live_recording_args args = {};
 	struct ast_variable *i;
 
@@ -152,6 +258,29 @@ static void stasis_http_get_live_recording_cb(
 		{}
 	}
 	stasis_http_get_live_recording(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_live_recording(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /recordings/live/{recordingId}.
@@ -161,9 +290,14 @@ static void stasis_http_get_live_recording_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_cancel_recording_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_cancel_recording_args args = {};
 	struct ast_variable *i;
 
@@ -174,6 +308,29 @@ static void stasis_http_cancel_recording_cb(
 		{}
 	}
 	stasis_http_cancel_recording(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /recordings/live/{recordingId}/stop.
@@ -183,9 +340,14 @@ static void stasis_http_cancel_recording_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_stop_recording_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_stop_recording_args args = {};
 	struct ast_variable *i;
 
@@ -196,6 +358,29 @@ static void stasis_http_stop_recording_cb(
 		{}
 	}
 	stasis_http_stop_recording(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/stop\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/stop\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /recordings/live/{recordingId}/pause.
@@ -205,9 +390,14 @@ static void stasis_http_stop_recording_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_pause_recording_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_pause_recording_args args = {};
 	struct ast_variable *i;
 
@@ -218,6 +408,29 @@ static void stasis_http_pause_recording_cb(
 		{}
 	}
 	stasis_http_pause_recording(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/pause\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/pause\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /recordings/live/{recordingId}/unpause.
@@ -227,9 +440,14 @@ static void stasis_http_pause_recording_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_unpause_recording_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_unpause_recording_args args = {};
 	struct ast_variable *i;
 
@@ -240,6 +458,29 @@ static void stasis_http_unpause_recording_cb(
 		{}
 	}
 	stasis_http_unpause_recording(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/unpause\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/unpause\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /recordings/live/{recordingId}/mute.
@@ -249,9 +490,14 @@ static void stasis_http_unpause_recording_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_mute_recording_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_mute_recording_args args = {};
 	struct ast_variable *i;
 
@@ -262,6 +508,29 @@ static void stasis_http_mute_recording_cb(
 		{}
 	}
 	stasis_http_mute_recording(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/mute\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/mute\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /recordings/live/{recordingId}/unmute.
@@ -271,9 +540,14 @@ static void stasis_http_mute_recording_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_unmute_recording_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_unmute_recording_args args = {};
 	struct ast_variable *i;
 
@@ -284,6 +558,29 @@ static void stasis_http_unmute_recording_cb(
 		{}
 	}
 	stasis_http_unmute_recording(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/unmute\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/unmute\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 
 /*! \brief REST handler for /api-docs/recordings.{format} */
@@ -375,7 +672,6 @@ static struct stasis_rest_handlers recordings_live = {
 static struct stasis_rest_handlers recordings = {
 	.path_segment = "recordings",
 	.callbacks = {
-		[AST_HTTP_GET] = stasis_http_get_recordings_cb,
 	},
 	.num_children = 2,
 	.children = { &recordings_stored,&recordings_live, }
diff --git a/res/res_stasis_http_sounds.c b/res/res_stasis_http_sounds.c
index 975ca038877bed9a4e0863dd29eca321704fa751..da0206223bb8fa07a1a63491cddfeebe7bb2f298 100644
--- a/res/res_stasis_http_sounds.c
+++ b/res/res_stasis_http_sounds.c
@@ -44,6 +44,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/stasis_app.h"
 #include "stasis_http/resource_sounds.h"
+#if defined(AST_DEVMODE)
+#include "stasis_http/ari_model_validators.h"
+#endif
 
 /*!
  * \brief Parameter parsing callback for /sounds.
@@ -53,9 +56,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_sounds_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_sounds_args args = {};
 	struct ast_variable *i;
 
@@ -69,6 +77,29 @@ static void stasis_http_get_sounds_cb(
 		{}
 	}
 	stasis_http_get_sounds(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_list(response->message,
+				ari_validate_sound);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /sounds\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /sounds\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 /*!
  * \brief Parameter parsing callback for /sounds/{soundId}.
@@ -78,9 +109,14 @@ static void stasis_http_get_sounds_cb(
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_get_stored_sound_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 	struct ast_get_stored_sound_args args = {};
 	struct ast_variable *i;
 
@@ -91,6 +127,29 @@ static void stasis_http_get_stored_sound_cb(
 		{}
 	}
 	stasis_http_get_stored_sound(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ari_validate_sound(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /sounds/{soundId}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /sounds/{soundId}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 
 /*! \brief REST handler for /api-docs/sounds.{format} */
diff --git a/res/res_stasis_json_asterisk.c b/res/res_stasis_json_asterisk.c
deleted file mode 100644
index 830a2cfabec12fcad79d75d37b5fd22fd00f6ade..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_asterisk.c
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/res_stasis_http_resource.c.mustache
- */
-
-/*! \file
- *
- * \brief Asterisk resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/module.h"
-#include "asterisk/json.h"
-#include "stasis_json/resource_asterisk.h"
-static int load_module(void)
-{
-	return 0;
-}
-
-static int unload_module(void)
-{
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Stasis JSON Generators and Validators - Asterisk resources",
-	.load = load_module,
-	.unload = unload_module,
-	.load_pri = AST_MODPRI_DEFAULT,
-	);
diff --git a/res/res_stasis_json_asterisk.exports.in b/res/res_stasis_json_asterisk.exports.in
deleted file mode 100644
index 5e767549c47d22e877c78bf7d7feac6d0744bddf..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_asterisk.exports.in
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-	local:
-		*;
-};
diff --git a/res/res_stasis_json_bridges.c b/res/res_stasis_json_bridges.c
deleted file mode 100644
index 90977bff4dc326fa6efecdfb2b462adb320841b5..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_bridges.c
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/res_stasis_http_resource.c.mustache
- */
-
-/*! \file
- *
- * \brief Bridge resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/module.h"
-#include "asterisk/json.h"
-#include "stasis_json/resource_bridges.h"
-static int load_module(void)
-{
-	return 0;
-}
-
-static int unload_module(void)
-{
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Stasis JSON Generators and Validators - Bridge resources",
-	.load = load_module,
-	.unload = unload_module,
-	.load_pri = AST_MODPRI_DEFAULT,
-	);
diff --git a/res/res_stasis_json_bridges.exports.in b/res/res_stasis_json_bridges.exports.in
deleted file mode 100644
index 5e767549c47d22e877c78bf7d7feac6d0744bddf..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_bridges.exports.in
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-	local:
-		*;
-};
diff --git a/res/res_stasis_json_channels.c b/res/res_stasis_json_channels.c
deleted file mode 100644
index 3f85736b35096074e7e54cf186dbd1b849b87c5d..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_channels.c
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/res_stasis_http_resource.c.mustache
- */
-
-/*! \file
- *
- * \brief Channel resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/module.h"
-#include "asterisk/json.h"
-#include "stasis_json/resource_channels.h"
-static int load_module(void)
-{
-	return 0;
-}
-
-static int unload_module(void)
-{
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Stasis JSON Generators and Validators - Channel resources",
-	.load = load_module,
-	.unload = unload_module,
-	.load_pri = AST_MODPRI_DEFAULT,
-	);
diff --git a/res/res_stasis_json_channels.exports.in b/res/res_stasis_json_channels.exports.in
deleted file mode 100644
index 5e767549c47d22e877c78bf7d7feac6d0744bddf..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_channels.exports.in
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-	local:
-		*;
-};
diff --git a/res/res_stasis_json_endpoints.c b/res/res_stasis_json_endpoints.c
deleted file mode 100644
index be214e038d9862c739286c5092ea6081f646a3ee..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_endpoints.c
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/res_stasis_http_resource.c.mustache
- */
-
-/*! \file
- *
- * \brief Endpoint resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/module.h"
-#include "asterisk/json.h"
-#include "stasis_json/resource_endpoints.h"
-static int load_module(void)
-{
-	return 0;
-}
-
-static int unload_module(void)
-{
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Stasis JSON Generators and Validators - Endpoint resources",
-	.load = load_module,
-	.unload = unload_module,
-	.load_pri = AST_MODPRI_DEFAULT,
-	);
diff --git a/res/res_stasis_json_endpoints.exports.in b/res/res_stasis_json_endpoints.exports.in
deleted file mode 100644
index 5e767549c47d22e877c78bf7d7feac6d0744bddf..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_endpoints.exports.in
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-	local:
-		*;
-};
diff --git a/res/res_stasis_json_events.c b/res/res_stasis_json_events.c
deleted file mode 100644
index 4b966e235d507fb3324dfef83744ed9be2b1aaf5..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_events.c
+++ /dev/null
@@ -1,818 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/res_stasis_http_resource.c.mustache
- */
-
-/*! \file
- *
- * \brief WebSocket resource
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/module.h"
-#include "asterisk/json.h"
-#include "stasis_json/resource_events.h"
-#include "asterisk/stasis_channels.h"
-#include "asterisk/stasis_bridging.h"
-
-struct ast_json *stasis_json_event_channel_userevent_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "channel") == NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-
-	validator = ast_json_object_get(blob, "eventname");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	event = ast_json_deep_copy(blob);
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "channel_userevent", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_bridge_created_create(
-	struct ast_bridge_snapshot *bridge_snapshot
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	int ret;
-
-	ast_assert(bridge_snapshot != NULL);
-
-	event = ast_json_object_create();
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "bridge_created", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_playback_finished_create(
-	struct ast_json *blob
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
-
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-
-	validator = ast_json_object_get(blob, "playback");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	event = ast_json_deep_copy(blob);
-	if (!event) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "playback_finished", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_channel_snapshot_create(
-	struct ast_channel_snapshot *channel_snapshot
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-
-	event = ast_json_object_create();
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "channel_snapshot", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_channel_caller_id_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "channel") == NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-
-	validator = ast_json_object_get(blob, "caller_presentation_txt");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	validator = ast_json_object_get(blob, "caller_presentation");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	event = ast_json_deep_copy(blob);
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "channel_caller_id", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_playback_started_create(
-	struct ast_json *blob
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
-
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-
-	validator = ast_json_object_get(blob, "playback");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	event = ast_json_deep_copy(blob);
-	if (!event) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "playback_started", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_channel_varset_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "channel") == NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-
-	validator = ast_json_object_get(blob, "variable");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	validator = ast_json_object_get(blob, "value");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	event = ast_json_deep_copy(blob);
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "channel_varset", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_bridge_destroyed_create(
-	struct ast_bridge_snapshot *bridge_snapshot
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	int ret;
-
-	ast_assert(bridge_snapshot != NULL);
-
-	event = ast_json_object_create();
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "bridge_destroyed", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_application_replaced_create(
-	struct ast_json *blob
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
-
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-
-	validator = ast_json_object_get(blob, "application");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	event = ast_json_deep_copy(blob);
-	if (!event) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "application_replaced", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_channel_destroyed_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "channel") == NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-
-	validator = ast_json_object_get(blob, "cause");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	validator = ast_json_object_get(blob, "cause_txt");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	event = ast_json_deep_copy(blob);
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "channel_destroyed", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_bridge_merged_create(
-	struct ast_bridge_snapshot *bridge_snapshot,
-	struct ast_json *blob
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
-	int ret;
-
-	ast_assert(bridge_snapshot != NULL);
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "bridge") == NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-
-	validator = ast_json_object_get(blob, "bridge_from");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	event = ast_json_deep_copy(blob);
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "bridge_merged", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_channel_left_bridge_create(
-	struct ast_bridge_snapshot *bridge_snapshot,
-	struct ast_channel_snapshot *channel_snapshot
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-	ast_assert(bridge_snapshot != NULL);
-
-	event = ast_json_object_create();
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "channel_left_bridge", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_channel_created_create(
-	struct ast_channel_snapshot *channel_snapshot
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-
-	event = ast_json_object_create();
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "channel_created", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_stasis_start_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "channel") == NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-
-	validator = ast_json_object_get(blob, "args");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	event = ast_json_deep_copy(blob);
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "stasis_start", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_channel_dialplan_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "channel") == NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-
-	validator = ast_json_object_get(blob, "application");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	validator = ast_json_object_get(blob, "application_data");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	event = ast_json_deep_copy(blob);
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "channel_dialplan", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_channel_state_change_create(
-	struct ast_channel_snapshot *channel_snapshot
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-
-	event = ast_json_object_create();
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "channel_state_change", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_channel_hangup_request_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "channel") == NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-
-	validator = ast_json_object_get(blob, "soft");
-	if (validator) {
-		/* do validation? XXX */
-	}
-
-	validator = ast_json_object_get(blob, "cause");
-	if (validator) {
-		/* do validation? XXX */
-	}
-
-	event = ast_json_deep_copy(blob);
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "channel_hangup_request", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_channel_entered_bridge_create(
-	struct ast_bridge_snapshot *bridge_snapshot,
-	struct ast_channel_snapshot *channel_snapshot
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-	ast_assert(bridge_snapshot != NULL);
-
-	event = ast_json_object_create();
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "channel_entered_bridge", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_channel_dtmf_received_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	struct ast_json *validator;
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-	ast_assert(blob != NULL);
-	ast_assert(ast_json_object_get(blob, "channel") == NULL);
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-
-	validator = ast_json_object_get(blob, "digit");
-	if (validator) {
-		/* do validation? XXX */
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-	}
-
-	event = ast_json_deep_copy(blob);
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "channel_dtmf_received", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-struct ast_json *stasis_json_event_stasis_end_create(
-	struct ast_channel_snapshot *channel_snapshot
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-	int ret;
-
-	ast_assert(channel_snapshot != NULL);
-
-	event = ast_json_object_create();
-	if (!event) {
-		return NULL;
-	}
-
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-	message = ast_json_pack("{s: o}", "stasis_end", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-static int load_module(void)
-{
-	return 0;
-}
-
-static int unload_module(void)
-{
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Stasis JSON Generators and Validators - WebSocket resource",
-	.load = load_module,
-	.unload = unload_module,
-	.load_pri = AST_MODPRI_DEFAULT,
-	);
diff --git a/res/res_stasis_json_events.exports.in b/res/res_stasis_json_events.exports.in
deleted file mode 100644
index 5865c026b9bfd66e6b4ef4392d7c92b415e9a47b..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_events.exports.in
+++ /dev/null
@@ -1,25 +0,0 @@
-{
-	global:
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_userevent_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_bridge_created_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_playback_finished_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_snapshot_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_caller_id_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_playback_started_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_varset_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_bridge_destroyed_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_application_replaced_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_destroyed_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_bridge_merged_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_left_bridge_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_created_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_stasis_start_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_dialplan_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_state_change_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_hangup_request_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_entered_bridge_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_channel_dtmf_received_create;
-		LINKER_SYMBOL_PREFIXstasis_json_event_stasis_end_create;
-	local:
-		*;
-};
diff --git a/res/res_stasis_json_playback.c b/res/res_stasis_json_playback.c
deleted file mode 100644
index 16218c92d63cd413e5eedb480ebc2bbddf268300..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_playback.c
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/res_stasis_http_resource.c.mustache
- */
-
-/*! \file
- *
- * \brief Playback control resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/module.h"
-#include "asterisk/json.h"
-#include "stasis_json/resource_playback.h"
-static int load_module(void)
-{
-	return 0;
-}
-
-static int unload_module(void)
-{
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Stasis JSON Generators and Validators - Playback control resources",
-	.load = load_module,
-	.unload = unload_module,
-	.load_pri = AST_MODPRI_DEFAULT,
-	);
diff --git a/res/res_stasis_json_playback.exports.in b/res/res_stasis_json_playback.exports.in
deleted file mode 100644
index 5e767549c47d22e877c78bf7d7feac6d0744bddf..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_playback.exports.in
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-	local:
-		*;
-};
diff --git a/res/res_stasis_json_recordings.c b/res/res_stasis_json_recordings.c
deleted file mode 100644
index 73935dede23c9837a0a4748c7b2eb9ce6b37479b..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_recordings.c
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/res_stasis_http_resource.c.mustache
- */
-
-/*! \file
- *
- * \brief Recording resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/module.h"
-#include "asterisk/json.h"
-#include "stasis_json/resource_recordings.h"
-static int load_module(void)
-{
-	return 0;
-}
-
-static int unload_module(void)
-{
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Stasis JSON Generators and Validators - Recording resources",
-	.load = load_module,
-	.unload = unload_module,
-	.load_pri = AST_MODPRI_DEFAULT,
-	);
diff --git a/res/res_stasis_json_recordings.exports.in b/res/res_stasis_json_recordings.exports.in
deleted file mode 100644
index 5e767549c47d22e877c78bf7d7feac6d0744bddf..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_recordings.exports.in
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-	local:
-		*;
-};
diff --git a/res/res_stasis_json_sounds.c b/res/res_stasis_json_sounds.c
deleted file mode 100644
index cc6d5ae17079286faeaad09366ff4becd3b1888b..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_sounds.c
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/res_stasis_http_resource.c.mustache
- */
-
-/*! \file
- *
- * \brief Sound resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/module.h"
-#include "asterisk/json.h"
-#include "stasis_json/resource_sounds.h"
-static int load_module(void)
-{
-	return 0;
-}
-
-static int unload_module(void)
-{
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Stasis JSON Generators and Validators - Sound resources",
-	.load = load_module,
-	.unload = unload_module,
-	.load_pri = AST_MODPRI_DEFAULT,
-	);
diff --git a/res/res_stasis_json_sounds.exports.in b/res/res_stasis_json_sounds.exports.in
deleted file mode 100644
index 5e767549c47d22e877c78bf7d7feac6d0744bddf..0000000000000000000000000000000000000000
--- a/res/res_stasis_json_sounds.exports.in
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-	local:
-		*;
-};
diff --git a/res/stasis_http/ari_model_validators.c b/res/stasis_http/ari_model_validators.c
new file mode 100644
index 0000000000000000000000000000000000000000..b41c15473a007872a3171260d50b085bb3a6b009
--- /dev/null
+++ b/res/stasis_http/ari_model_validators.c
@@ -0,0 +1,2567 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Generated file - Build validators for ARI model objects.
+ */
+
+ /*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/ari_model_validators.h.mustache
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "ari_model_validators.h"
+
+int ari_validate_asterisk_info(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		{
+			ast_log(LOG_ERROR,
+				"ARI AsteriskInfo has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	return res;
+}
+
+int ari_validate_endpoint(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_channel_ids = 0;
+	int has_resource = 0;
+	int has_technology = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("channel_ids", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel_ids = 1;
+			prop_is_valid = ari_validate_list(
+				ast_json_object_iter_value(iter),
+				ari_validate_string);
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Endpoint field channel_ids failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("resource", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_resource = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Endpoint field resource failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("state", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Endpoint field state failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("technology", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_technology = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Endpoint field technology failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI Endpoint has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_channel_ids) {
+		ast_log(LOG_ERROR, "ARI Endpoint missing required field channel_ids\n");
+		res = 0;
+	}
+
+	if (!has_resource) {
+		ast_log(LOG_ERROR, "ARI Endpoint missing required field resource\n");
+		res = 0;
+	}
+
+	if (!has_technology) {
+		ast_log(LOG_ERROR, "ARI Endpoint missing required field technology\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_caller_id(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_name = 0;
+	int has_number = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("name", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_name = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI CallerID field name failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("number", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_number = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI CallerID field number failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI CallerID has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_name) {
+		ast_log(LOG_ERROR, "ARI CallerID missing required field name\n");
+		res = 0;
+	}
+
+	if (!has_number) {
+		ast_log(LOG_ERROR, "ARI CallerID missing required field number\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_channel(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_accountcode = 0;
+	int has_caller = 0;
+	int has_connected = 0;
+	int has_creationtime = 0;
+	int has_dialplan = 0;
+	int has_id = 0;
+	int has_name = 0;
+	int has_state = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("accountcode", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_accountcode = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Channel field accountcode failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("caller", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_caller = 1;
+			prop_is_valid = ari_validate_caller_id(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Channel field caller failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("connected", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_connected = 1;
+			prop_is_valid = ari_validate_caller_id(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Channel field connected failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("creationtime", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_creationtime = 1;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Channel field creationtime failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("dialplan", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_dialplan = 1;
+			prop_is_valid = ari_validate_dialplan_cep(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Channel field dialplan failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("id", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_id = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Channel field id failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("name", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_name = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Channel field name failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("state", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_state = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Channel field state failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI Channel has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_accountcode) {
+		ast_log(LOG_ERROR, "ARI Channel missing required field accountcode\n");
+		res = 0;
+	}
+
+	if (!has_caller) {
+		ast_log(LOG_ERROR, "ARI Channel missing required field caller\n");
+		res = 0;
+	}
+
+	if (!has_connected) {
+		ast_log(LOG_ERROR, "ARI Channel missing required field connected\n");
+		res = 0;
+	}
+
+	if (!has_creationtime) {
+		ast_log(LOG_ERROR, "ARI Channel missing required field creationtime\n");
+		res = 0;
+	}
+
+	if (!has_dialplan) {
+		ast_log(LOG_ERROR, "ARI Channel missing required field dialplan\n");
+		res = 0;
+	}
+
+	if (!has_id) {
+		ast_log(LOG_ERROR, "ARI Channel missing required field id\n");
+		res = 0;
+	}
+
+	if (!has_name) {
+		ast_log(LOG_ERROR, "ARI Channel missing required field name\n");
+		res = 0;
+	}
+
+	if (!has_state) {
+		ast_log(LOG_ERROR, "ARI Channel missing required field state\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_dialed(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		{
+			ast_log(LOG_ERROR,
+				"ARI Dialed has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	return res;
+}
+
+int ari_validate_dialplan_cep(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_context = 0;
+	int has_exten = 0;
+	int has_priority = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("context", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_context = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI DialplanCEP field context failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("exten", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_exten = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI DialplanCEP field exten failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("priority", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_priority = 1;
+			prop_is_valid = ari_validate_long(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI DialplanCEP field priority failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI DialplanCEP has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_context) {
+		ast_log(LOG_ERROR, "ARI DialplanCEP missing required field context\n");
+		res = 0;
+	}
+
+	if (!has_exten) {
+		ast_log(LOG_ERROR, "ARI DialplanCEP missing required field exten\n");
+		res = 0;
+	}
+
+	if (!has_priority) {
+		ast_log(LOG_ERROR, "ARI DialplanCEP missing required field priority\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_bridge(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_bridge_class = 0;
+	int has_bridge_type = 0;
+	int has_channels = 0;
+	int has_id = 0;
+	int has_technology = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("bridge_class", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_bridge_class = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Bridge field bridge_class failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("bridge_type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_bridge_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Bridge field bridge_type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channels", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channels = 1;
+			prop_is_valid = ari_validate_list(
+				ast_json_object_iter_value(iter),
+				ari_validate_string);
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Bridge field channels failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("id", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_id = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Bridge field id failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("technology", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_technology = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Bridge field technology failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI Bridge has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_bridge_class) {
+		ast_log(LOG_ERROR, "ARI Bridge missing required field bridge_class\n");
+		res = 0;
+	}
+
+	if (!has_bridge_type) {
+		ast_log(LOG_ERROR, "ARI Bridge missing required field bridge_type\n");
+		res = 0;
+	}
+
+	if (!has_channels) {
+		ast_log(LOG_ERROR, "ARI Bridge missing required field channels\n");
+		res = 0;
+	}
+
+	if (!has_id) {
+		ast_log(LOG_ERROR, "ARI Bridge missing required field id\n");
+		res = 0;
+	}
+
+	if (!has_technology) {
+		ast_log(LOG_ERROR, "ARI Bridge missing required field technology\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_live_recording(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_id = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("id", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_id = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI LiveRecording field id failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI LiveRecording has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_id) {
+		ast_log(LOG_ERROR, "ARI LiveRecording missing required field id\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_stored_recording(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_formats = 0;
+	int has_id = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("duration_seconds", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_int(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StoredRecording field duration_seconds failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("formats", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_formats = 1;
+			prop_is_valid = ari_validate_list(
+				ast_json_object_iter_value(iter),
+				ari_validate_string);
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StoredRecording field formats failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("id", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_id = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StoredRecording field id failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("time", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StoredRecording field time failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI StoredRecording has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_formats) {
+		ast_log(LOG_ERROR, "ARI StoredRecording missing required field formats\n");
+		res = 0;
+	}
+
+	if (!has_id) {
+		ast_log(LOG_ERROR, "ARI StoredRecording missing required field id\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_format_lang_pair(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_format = 0;
+	int has_language = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("format", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_format = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI FormatLangPair field format failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("language", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_language = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI FormatLangPair field language failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI FormatLangPair has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_format) {
+		ast_log(LOG_ERROR, "ARI FormatLangPair missing required field format\n");
+		res = 0;
+	}
+
+	if (!has_language) {
+		ast_log(LOG_ERROR, "ARI FormatLangPair missing required field language\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_sound(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_formats = 0;
+	int has_id = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("formats", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_formats = 1;
+			prop_is_valid = ari_validate_list(
+				ast_json_object_iter_value(iter),
+				ari_validate_format_lang_pair);
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Sound field formats failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("id", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_id = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Sound field id failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("text", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Sound field text failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI Sound has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_formats) {
+		ast_log(LOG_ERROR, "ARI Sound missing required field formats\n");
+		res = 0;
+	}
+
+	if (!has_id) {
+		ast_log(LOG_ERROR, "ARI Sound missing required field id\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_playback(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_id = 0;
+	int has_media_uri = 0;
+	int has_state = 0;
+	int has_target_uri = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("id", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_id = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Playback field id failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("language", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Playback field language failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("media_uri", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_media_uri = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Playback field media_uri failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("state", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_state = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Playback field state failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("target_uri", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_target_uri = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Playback field target_uri failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI Playback has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_id) {
+		ast_log(LOG_ERROR, "ARI Playback missing required field id\n");
+		res = 0;
+	}
+
+	if (!has_media_uri) {
+		ast_log(LOG_ERROR, "ARI Playback missing required field media_uri\n");
+		res = 0;
+	}
+
+	if (!has_state) {
+		ast_log(LOG_ERROR, "ARI Playback missing required field state\n");
+		res = 0;
+	}
+
+	if (!has_target_uri) {
+		ast_log(LOG_ERROR, "ARI Playback missing required field target_uri\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_application_replaced(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ApplicationReplaced field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ApplicationReplaced field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ApplicationReplaced field type failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ApplicationReplaced has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI ApplicationReplaced missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI ApplicationReplaced missing required field type\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_bridge_created(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_bridge = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeCreated field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeCreated field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeCreated field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("bridge", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_bridge = 1;
+			prop_is_valid = ari_validate_bridge(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeCreated field bridge failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI BridgeCreated has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI BridgeCreated missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI BridgeCreated missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_bridge) {
+		ast_log(LOG_ERROR, "ARI BridgeCreated missing required field bridge\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_bridge_destroyed(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_bridge = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeDestroyed field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeDestroyed field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeDestroyed field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("bridge", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_bridge = 1;
+			prop_is_valid = ari_validate_bridge(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeDestroyed field bridge failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI BridgeDestroyed has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI BridgeDestroyed missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI BridgeDestroyed missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_bridge) {
+		ast_log(LOG_ERROR, "ARI BridgeDestroyed missing required field bridge\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_bridge_merged(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_bridge = 0;
+	int has_bridge_from = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeMerged field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeMerged field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeMerged field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("bridge", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_bridge = 1;
+			prop_is_valid = ari_validate_bridge(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeMerged field bridge failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("bridge_from", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_bridge_from = 1;
+			prop_is_valid = ari_validate_bridge(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI BridgeMerged field bridge_from failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI BridgeMerged has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI BridgeMerged missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI BridgeMerged missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_bridge) {
+		ast_log(LOG_ERROR, "ARI BridgeMerged missing required field bridge\n");
+		res = 0;
+	}
+
+	if (!has_bridge_from) {
+		ast_log(LOG_ERROR, "ARI BridgeMerged missing required field bridge_from\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_channel_caller_id(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_caller_presentation = 0;
+	int has_caller_presentation_txt = 0;
+	int has_channel = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelCallerId field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelCallerId field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelCallerId field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("caller_presentation", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_caller_presentation = 1;
+			prop_is_valid = ari_validate_int(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelCallerId field caller_presentation failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("caller_presentation_txt", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_caller_presentation_txt = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelCallerId field caller_presentation_txt failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel = 1;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelCallerId field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ChannelCallerId has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI ChannelCallerId missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI ChannelCallerId missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_caller_presentation) {
+		ast_log(LOG_ERROR, "ARI ChannelCallerId missing required field caller_presentation\n");
+		res = 0;
+	}
+
+	if (!has_caller_presentation_txt) {
+		ast_log(LOG_ERROR, "ARI ChannelCallerId missing required field caller_presentation_txt\n");
+		res = 0;
+	}
+
+	if (!has_channel) {
+		ast_log(LOG_ERROR, "ARI ChannelCallerId missing required field channel\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_channel_created(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_channel = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelCreated field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelCreated field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelCreated field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel = 1;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelCreated field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ChannelCreated has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI ChannelCreated missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI ChannelCreated missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_channel) {
+		ast_log(LOG_ERROR, "ARI ChannelCreated missing required field channel\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_channel_destroyed(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_cause = 0;
+	int has_cause_txt = 0;
+	int has_channel = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDestroyed field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDestroyed field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDestroyed field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("cause", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_cause = 1;
+			prop_is_valid = ari_validate_int(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDestroyed field cause failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("cause_txt", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_cause_txt = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDestroyed field cause_txt failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel = 1;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDestroyed field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ChannelDestroyed has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI ChannelDestroyed missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI ChannelDestroyed missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_cause) {
+		ast_log(LOG_ERROR, "ARI ChannelDestroyed missing required field cause\n");
+		res = 0;
+	}
+
+	if (!has_cause_txt) {
+		ast_log(LOG_ERROR, "ARI ChannelDestroyed missing required field cause_txt\n");
+		res = 0;
+	}
+
+	if (!has_channel) {
+		ast_log(LOG_ERROR, "ARI ChannelDestroyed missing required field channel\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_channel_dialplan(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_channel = 0;
+	int has_dialplan_app = 0;
+	int has_dialplan_app_data = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDialplan field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDialplan field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDialplan field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel = 1;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDialplan field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("dialplan_app", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_dialplan_app = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDialplan field dialplan_app failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("dialplan_app_data", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_dialplan_app_data = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDialplan field dialplan_app_data failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ChannelDialplan has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI ChannelDialplan missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI ChannelDialplan missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_channel) {
+		ast_log(LOG_ERROR, "ARI ChannelDialplan missing required field channel\n");
+		res = 0;
+	}
+
+	if (!has_dialplan_app) {
+		ast_log(LOG_ERROR, "ARI ChannelDialplan missing required field dialplan_app\n");
+		res = 0;
+	}
+
+	if (!has_dialplan_app_data) {
+		ast_log(LOG_ERROR, "ARI ChannelDialplan missing required field dialplan_app_data\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_channel_dtmf_received(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_channel = 0;
+	int has_digit = 0;
+	int has_duration_ms = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDtmfReceived field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDtmfReceived field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDtmfReceived field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel = 1;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDtmfReceived field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("digit", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_digit = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDtmfReceived field digit failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("duration_ms", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_duration_ms = 1;
+			prop_is_valid = ari_validate_int(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelDtmfReceived field duration_ms failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ChannelDtmfReceived has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI ChannelDtmfReceived missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI ChannelDtmfReceived missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_channel) {
+		ast_log(LOG_ERROR, "ARI ChannelDtmfReceived missing required field channel\n");
+		res = 0;
+	}
+
+	if (!has_digit) {
+		ast_log(LOG_ERROR, "ARI ChannelDtmfReceived missing required field digit\n");
+		res = 0;
+	}
+
+	if (!has_duration_ms) {
+		ast_log(LOG_ERROR, "ARI ChannelDtmfReceived missing required field duration_ms\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_channel_entered_bridge(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_bridge = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelEnteredBridge field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelEnteredBridge field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelEnteredBridge field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("bridge", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_bridge = 1;
+			prop_is_valid = ari_validate_bridge(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelEnteredBridge field bridge failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelEnteredBridge field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ChannelEnteredBridge has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI ChannelEnteredBridge missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI ChannelEnteredBridge missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_bridge) {
+		ast_log(LOG_ERROR, "ARI ChannelEnteredBridge missing required field bridge\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_channel_hangup_request(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_channel = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelHangupRequest field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelHangupRequest field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelHangupRequest field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("cause", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_int(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelHangupRequest field cause failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel = 1;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelHangupRequest field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("soft", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_boolean(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelHangupRequest field soft failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ChannelHangupRequest has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI ChannelHangupRequest missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI ChannelHangupRequest missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_channel) {
+		ast_log(LOG_ERROR, "ARI ChannelHangupRequest missing required field channel\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_channel_left_bridge(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_bridge = 0;
+	int has_channel = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelLeftBridge field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelLeftBridge field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelLeftBridge field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("bridge", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_bridge = 1;
+			prop_is_valid = ari_validate_bridge(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelLeftBridge field bridge failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel = 1;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelLeftBridge field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ChannelLeftBridge has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI ChannelLeftBridge missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI ChannelLeftBridge missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_bridge) {
+		ast_log(LOG_ERROR, "ARI ChannelLeftBridge missing required field bridge\n");
+		res = 0;
+	}
+
+	if (!has_channel) {
+		ast_log(LOG_ERROR, "ARI ChannelLeftBridge missing required field channel\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_channel_state_change(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_channel = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelStateChange field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelStateChange field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelStateChange field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel = 1;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelStateChange field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ChannelStateChange has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI ChannelStateChange missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI ChannelStateChange missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_channel) {
+		ast_log(LOG_ERROR, "ARI ChannelStateChange missing required field channel\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_channel_userevent(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_channel = 0;
+	int has_eventname = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelUserevent field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelUserevent field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelUserevent field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel = 1;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelUserevent field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("eventname", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_eventname = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelUserevent field eventname failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ChannelUserevent has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI ChannelUserevent missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI ChannelUserevent missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_channel) {
+		ast_log(LOG_ERROR, "ARI ChannelUserevent missing required field channel\n");
+		res = 0;
+	}
+
+	if (!has_eventname) {
+		ast_log(LOG_ERROR, "ARI ChannelUserevent missing required field eventname\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_channel_varset(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_value = 0;
+	int has_variable = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelVarset field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelVarset field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelVarset field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelVarset field channel 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 = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelVarset field value failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("variable", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_variable = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ChannelVarset field variable failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ChannelVarset has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI ChannelVarset missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI ChannelVarset missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_value) {
+		ast_log(LOG_ERROR, "ARI ChannelVarset missing required field value\n");
+		res = 0;
+	}
+
+	if (!has_variable) {
+		ast_log(LOG_ERROR, "ARI ChannelVarset missing required field variable\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_event(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	const char *discriminator;
+
+	discriminator = ast_json_string_get(ast_json_object_get(json, "type"));
+	if (!discriminator) {
+		ast_log(LOG_ERROR, "ARI Event missing required field type");
+		return 0;
+	}
+
+	if (strcmp("Event", discriminator) == 0) {
+		/* Self type; fall through */
+	} else
+	if (strcmp("ApplicationReplaced", discriminator) == 0) {
+		return ari_validate_application_replaced(json);
+	} else
+	if (strcmp("BridgeCreated", discriminator) == 0) {
+		return ari_validate_bridge_created(json);
+	} else
+	if (strcmp("BridgeDestroyed", discriminator) == 0) {
+		return ari_validate_bridge_destroyed(json);
+	} else
+	if (strcmp("BridgeMerged", discriminator) == 0) {
+		return ari_validate_bridge_merged(json);
+	} else
+	if (strcmp("ChannelCallerId", discriminator) == 0) {
+		return ari_validate_channel_caller_id(json);
+	} else
+	if (strcmp("ChannelCreated", discriminator) == 0) {
+		return ari_validate_channel_created(json);
+	} else
+	if (strcmp("ChannelDestroyed", discriminator) == 0) {
+		return ari_validate_channel_destroyed(json);
+	} else
+	if (strcmp("ChannelDialplan", discriminator) == 0) {
+		return ari_validate_channel_dialplan(json);
+	} else
+	if (strcmp("ChannelDtmfReceived", discriminator) == 0) {
+		return ari_validate_channel_dtmf_received(json);
+	} else
+	if (strcmp("ChannelEnteredBridge", discriminator) == 0) {
+		return ari_validate_channel_entered_bridge(json);
+	} else
+	if (strcmp("ChannelHangupRequest", discriminator) == 0) {
+		return ari_validate_channel_hangup_request(json);
+	} else
+	if (strcmp("ChannelLeftBridge", discriminator) == 0) {
+		return ari_validate_channel_left_bridge(json);
+	} else
+	if (strcmp("ChannelStateChange", discriminator) == 0) {
+		return ari_validate_channel_state_change(json);
+	} else
+	if (strcmp("ChannelUserevent", discriminator) == 0) {
+		return ari_validate_channel_userevent(json);
+	} else
+	if (strcmp("ChannelVarset", discriminator) == 0) {
+		return ari_validate_channel_varset(json);
+	} else
+	if (strcmp("PlaybackFinished", discriminator) == 0) {
+		return ari_validate_playback_finished(json);
+	} else
+	if (strcmp("PlaybackStarted", discriminator) == 0) {
+		return ari_validate_playback_started(json);
+	} else
+	if (strcmp("StasisEnd", discriminator) == 0) {
+		return ari_validate_stasis_end(json);
+	} else
+	if (strcmp("StasisStart", discriminator) == 0) {
+		return ari_validate_stasis_start(json);
+	} else
+	{
+		ast_log(LOG_ERROR, "ARI Event has undocumented subtype %s\n",
+			discriminator);
+		res = 0;
+	}
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Event field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Event field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Event field type failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI Event has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI Event missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI Event missing required field type\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_playback_finished(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_playback = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI PlaybackFinished field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI PlaybackFinished field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI PlaybackFinished field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("playback", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_playback = 1;
+			prop_is_valid = ari_validate_playback(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI PlaybackFinished field playback failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI PlaybackFinished has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI PlaybackFinished missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI PlaybackFinished missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_playback) {
+		ast_log(LOG_ERROR, "ARI PlaybackFinished missing required field playback\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_playback_started(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_playback = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI PlaybackStarted field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI PlaybackStarted field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI PlaybackStarted field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("playback", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_playback = 1;
+			prop_is_valid = ari_validate_playback(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI PlaybackStarted field playback failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI PlaybackStarted has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI PlaybackStarted missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI PlaybackStarted missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_playback) {
+		ast_log(LOG_ERROR, "ARI PlaybackStarted missing required field playback\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_stasis_end(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_channel = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StasisEnd field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StasisEnd field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StasisEnd field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel = 1;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StasisEnd field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI StasisEnd has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI StasisEnd missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI StasisEnd missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_channel) {
+		ast_log(LOG_ERROR, "ARI StasisEnd missing required field channel\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+int ari_validate_stasis_start(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_application = 0;
+	int has_type = 0;
+	int has_args = 0;
+	int has_channel = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StasisStart field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StasisStart field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StasisStart field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("args", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_args = 1;
+			prop_is_valid = ari_validate_list(
+				ast_json_object_iter_value(iter),
+				ari_validate_string);
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StasisStart field args failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel = 1;
+			prop_is_valid = ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI StasisStart field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI StasisStart has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI StasisStart missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI StasisStart missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_args) {
+		ast_log(LOG_ERROR, "ARI StasisStart missing required field args\n");
+		res = 0;
+	}
+
+	if (!has_channel) {
+		ast_log(LOG_ERROR, "ARI StasisStart missing required field channel\n");
+		res = 0;
+	}
+
+	return res;
+}
diff --git a/res/stasis_http/ari_model_validators.h b/res/stasis_http/ari_model_validators.h
new file mode 100644
index 0000000000000000000000000000000000000000..c4d0f27c28aef2073cd4359cad4aa5ea7f9275e1
--- /dev/null
+++ b/res/stasis_http/ari_model_validators.h
@@ -0,0 +1,659 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Generated file - Build validators for ARI model objects.
+ */
+
+ /*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!!                               DO NOT EDIT                        !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/ari_model_validators.h.mustache
+ */
+
+#ifndef _ASTERISK_ARI_MODEL_H
+#define _ASTERISK_ARI_MODEL_H
+
+#include "asterisk/json.h"
+
+/*! @{ */
+
+/*!
+ * \brief Validator for native Swagger void.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_void(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger byte.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_byte(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger boolean.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_boolean(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger int.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_int(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger long.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_long(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger float.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_float(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger double.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_double(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger string.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_string(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger date.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_date(struct ast_json *json);
+
+/*!
+ * \brief Validator for a Swagger List[]/JSON array.
+ *
+ * \param json JSON object to validate.
+ * \param fn Validator to call on every element in the array.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_list(struct ast_json *json, int (*fn)(struct ast_json *));
+
+/*! @} */
+
+/*!
+ * \brief Validator for AsteriskInfo.
+ *
+ * Asterisk system information
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_asterisk_info(struct ast_json *json);
+
+/*!
+ * \brief Validator for Endpoint.
+ *
+ * An external device that may offer/accept calls to/from Asterisk.
+ *
+ * Unlike most resources, which have a single unique identifier, an endpoint is uniquely identified by the technology/resource pair.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_endpoint(struct ast_json *json);
+
+/*!
+ * \brief Validator for CallerID.
+ *
+ * Caller identification
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_caller_id(struct ast_json *json);
+
+/*!
+ * \brief Validator for Channel.
+ *
+ * A specific communication connection between Asterisk and an Endpoint.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_channel(struct ast_json *json);
+
+/*!
+ * \brief Validator for Dialed.
+ *
+ * Dialed channel information.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_dialed(struct ast_json *json);
+
+/*!
+ * \brief Validator for DialplanCEP.
+ *
+ * Dialplan location (context/extension/priority)
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_dialplan_cep(struct ast_json *json);
+
+/*!
+ * \brief Validator for Bridge.
+ *
+ * The merging of media from one or more channels.
+ *
+ * Everyone on the bridge receives the same audio.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_bridge(struct ast_json *json);
+
+/*!
+ * \brief Validator for LiveRecording.
+ *
+ * A recording that is in progress
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_live_recording(struct ast_json *json);
+
+/*!
+ * \brief Validator for StoredRecording.
+ *
+ * A past recording that may be played back.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_stored_recording(struct ast_json *json);
+
+/*!
+ * \brief Validator for FormatLangPair.
+ *
+ * Identifies the format and language of a sound file
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_format_lang_pair(struct ast_json *json);
+
+/*!
+ * \brief Validator for Sound.
+ *
+ * A media file that may be played back.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_sound(struct ast_json *json);
+
+/*!
+ * \brief Validator for Playback.
+ *
+ * Object representing the playback of media to a channel
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_playback(struct ast_json *json);
+
+/*!
+ * \brief Validator for ApplicationReplaced.
+ *
+ * Notification that another WebSocket has taken over for an application.
+ *
+ * An application may only be subscribed to by a single WebSocket at a time. If multiple WebSockets attempt to subscribe to the same application, the newer WebSocket wins, and the older one receives this event.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_application_replaced(struct ast_json *json);
+
+/*!
+ * \brief Validator for BridgeCreated.
+ *
+ * Notification that a bridge has been created.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_bridge_created(struct ast_json *json);
+
+/*!
+ * \brief Validator for BridgeDestroyed.
+ *
+ * Notification that a bridge has been destroyed.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_bridge_destroyed(struct ast_json *json);
+
+/*!
+ * \brief Validator for BridgeMerged.
+ *
+ * Notification that one bridge has merged into another.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_bridge_merged(struct ast_json *json);
+
+/*!
+ * \brief Validator for ChannelCallerId.
+ *
+ * Channel changed Caller ID.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_channel_caller_id(struct ast_json *json);
+
+/*!
+ * \brief Validator for ChannelCreated.
+ *
+ * Notification that a channel has been created.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_channel_created(struct ast_json *json);
+
+/*!
+ * \brief Validator for ChannelDestroyed.
+ *
+ * Notification that a channel has been destroyed.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_channel_destroyed(struct ast_json *json);
+
+/*!
+ * \brief Validator for ChannelDialplan.
+ *
+ * Channel changed location in the dialplan.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_channel_dialplan(struct ast_json *json);
+
+/*!
+ * \brief Validator for ChannelDtmfReceived.
+ *
+ * DTMF received on a channel.
+ *
+ * This event is sent when the DTMF ends. There is no notification about the start of DTMF
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_channel_dtmf_received(struct ast_json *json);
+
+/*!
+ * \brief Validator for ChannelEnteredBridge.
+ *
+ * Notification that a channel has entered a bridge.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_channel_entered_bridge(struct ast_json *json);
+
+/*!
+ * \brief Validator for ChannelHangupRequest.
+ *
+ * A hangup was requested on the channel.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_channel_hangup_request(struct ast_json *json);
+
+/*!
+ * \brief Validator for ChannelLeftBridge.
+ *
+ * Notification that a channel has left a bridge.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_channel_left_bridge(struct ast_json *json);
+
+/*!
+ * \brief Validator for ChannelStateChange.
+ *
+ * Notification of a channel's state change.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_channel_state_change(struct ast_json *json);
+
+/*!
+ * \brief Validator for ChannelUserevent.
+ *
+ * User-generated event with additional user-defined fields in the object.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_channel_userevent(struct ast_json *json);
+
+/*!
+ * \brief Validator for ChannelVarset.
+ *
+ * Channel variable changed.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_channel_varset(struct ast_json *json);
+
+/*!
+ * \brief Validator for Event.
+ *
+ * Base type for asynchronous events from Asterisk.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_event(struct ast_json *json);
+
+/*!
+ * \brief Validator for PlaybackFinished.
+ *
+ * Event showing the completion of a media playback operation.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_playback_finished(struct ast_json *json);
+
+/*!
+ * \brief Validator for PlaybackStarted.
+ *
+ * Event showing the start of a media playback operation.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_playback_started(struct ast_json *json);
+
+/*!
+ * \brief Validator for StasisEnd.
+ *
+ * Notification that a channel has left a Stasis appliction.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_stasis_end(struct ast_json *json);
+
+/*!
+ * \brief Validator for StasisStart.
+ *
+ * Notification that a channel has entered a Stasis appliction.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_stasis_start(struct ast_json *json);
+
+/*
+ * JSON models
+ *
+ * AsteriskInfo
+ * Endpoint
+ * - channel_ids: List[string] (required)
+ * - resource: string (required)
+ * - state: string
+ * - technology: string (required)
+ * CallerID
+ * - name: string (required)
+ * - number: string (required)
+ * Channel
+ * - accountcode: string (required)
+ * - caller: CallerID (required)
+ * - connected: CallerID (required)
+ * - creationtime: Date (required)
+ * - dialplan: DialplanCEP (required)
+ * - id: string (required)
+ * - name: string (required)
+ * - state: string (required)
+ * Dialed
+ * DialplanCEP
+ * - context: string (required)
+ * - exten: string (required)
+ * - priority: long (required)
+ * Bridge
+ * - bridge_class: string (required)
+ * - bridge_type: string (required)
+ * - channels: List[string] (required)
+ * - id: string (required)
+ * - technology: string (required)
+ * LiveRecording
+ * - id: string (required)
+ * StoredRecording
+ * - duration_seconds: int
+ * - formats: List[string] (required)
+ * - id: string (required)
+ * - time: Date
+ * FormatLangPair
+ * - format: string (required)
+ * - language: string (required)
+ * Sound
+ * - formats: List[FormatLangPair] (required)
+ * - id: string (required)
+ * - text: string
+ * Playback
+ * - id: string (required)
+ * - language: string
+ * - media_uri: string (required)
+ * - state: string (required)
+ * - target_uri: string (required)
+ * ApplicationReplaced
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * BridgeCreated
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - bridge: Bridge (required)
+ * BridgeDestroyed
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - bridge: Bridge (required)
+ * BridgeMerged
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - bridge: Bridge (required)
+ * - bridge_from: Bridge (required)
+ * ChannelCallerId
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - caller_presentation: int (required)
+ * - caller_presentation_txt: string (required)
+ * - channel: Channel (required)
+ * ChannelCreated
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - channel: Channel (required)
+ * ChannelDestroyed
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - cause: int (required)
+ * - cause_txt: string (required)
+ * - channel: Channel (required)
+ * ChannelDialplan
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - channel: Channel (required)
+ * - dialplan_app: string (required)
+ * - dialplan_app_data: string (required)
+ * ChannelDtmfReceived
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - channel: Channel (required)
+ * - digit: string (required)
+ * - duration_ms: int (required)
+ * ChannelEnteredBridge
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - bridge: Bridge (required)
+ * - channel: Channel
+ * ChannelHangupRequest
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - cause: int
+ * - channel: Channel (required)
+ * - soft: boolean
+ * ChannelLeftBridge
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - bridge: Bridge (required)
+ * - channel: Channel (required)
+ * ChannelStateChange
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - channel: Channel (required)
+ * ChannelUserevent
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - channel: Channel (required)
+ * - eventname: string (required)
+ * ChannelVarset
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - channel: Channel
+ * - value: string (required)
+ * - variable: string (required)
+ * Event
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * PlaybackFinished
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - playback: Playback (required)
+ * PlaybackStarted
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - playback: Playback (required)
+ * StasisEnd
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - channel: Channel (required)
+ * StasisStart
+ * - application: string (required)
+ * - timestamp: Date
+ * - type: string (required)
+ * - args: List[string] (required)
+ * - channel: Channel (required)
+ */
+
+#endif /* _ASTERISK_ARI_MODEL_H */
diff --git a/res/stasis_http/ari_websockets.c b/res/stasis_http/ari_websockets.c
index e6b316b5724ea8ecbb24799f986f318c18b1ee43..60a18465706a58df3d541bda69acdb33b8fa881f 100644
--- a/res/stasis_http/ari_websockets.c
+++ b/res/stasis_http/ari_websockets.c
@@ -31,6 +31,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 struct ari_websocket_session {
 	struct ast_websocket *ws_session;
+	int (*validator)(struct ast_json *);
 };
 
 static void websocket_session_dtor(void *obj)
@@ -41,8 +42,16 @@ static void websocket_session_dtor(void *obj)
 	session->ws_session = NULL;
 }
 
+/*!
+ * \brief Validator that always succeeds.
+ */
+static int null_validator(struct ast_json *json)
+{
+	return 1;
+}
+
 struct ari_websocket_session *ari_websocket_session_create(
-	struct ast_websocket *ws_session)
+	struct ast_websocket *ws_session, int (*validator)(struct ast_json *))
 {
 	RAII_VAR(struct ari_websocket_session *, session, NULL, ao2_cleanup);
 
@@ -50,6 +59,10 @@ struct ari_websocket_session *ari_websocket_session_create(
 		return NULL;
 	}
 
+	if (validator == NULL) {
+		validator = null_validator;
+	}
+
 	if (ast_websocket_set_nonblock(ws_session) != 0) {
 		ast_log(LOG_ERROR,
 			"Stasis web socket failed to set nonblock; closing\n");
@@ -63,6 +76,7 @@ struct ari_websocket_session *ari_websocket_session_create(
 
 	ao2_ref(ws_session, +1);
 	session->ws_session = ws_session;
+	session->validator = validator;
 
 	ao2_ref(session, +1);
 	return session;
@@ -109,10 +123,24 @@ struct ast_json *ari_websocket_session_read(
 	return ast_json_ref(message);
 }
 
+#define VALIDATION_FAILED					\
+	"{ \"error\": \"Outgoing message failed validation\" }"
+
 int ari_websocket_session_write(struct ari_websocket_session *session,
 	struct ast_json *message)
 {
-	RAII_VAR(char *, str, ast_json_dump_string(message), ast_free);
+	RAII_VAR(char *, str, NULL, ast_free);
+
+#ifdef AST_DEVMODE
+	if (!session->validator(message)) {
+		ast_log(LOG_ERROR, "Outgoing message failed validation\n");
+		return ast_websocket_write(session->ws_session,
+			AST_WEBSOCKET_OPCODE_TEXT, VALIDATION_FAILED,
+			strlen(VALIDATION_FAILED));
+	}
+#endif
+
+	str = ast_json_dump_string_format(message, stasis_http_json_format());
 
 	if (str == NULL) {
 		ast_log(LOG_ERROR, "Failed to encode JSON object\n");
diff --git a/res/stasis_http/resource_recordings.c b/res/stasis_http/resource_recordings.c
index 2400a6876b091cfc6b00436802a1c022abe33ecd..7d31c42aa20faa80d749618cacc36ea9a3244aa7 100644
--- a/res/stasis_http/resource_recordings.c
+++ b/res/stasis_http/resource_recordings.c
@@ -29,10 +29,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #include "resource_recordings.h"
 
-void stasis_http_get_recordings(struct ast_variable *headers, struct ast_get_recordings_args *args, struct stasis_http_response *response)
-{
-	ast_log(LOG_ERROR, "TODO: stasis_http_get_recordings\n");
-}
 void stasis_http_get_stored_recordings(struct ast_variable *headers, struct ast_get_stored_recordings_args *args, struct stasis_http_response *response)
 {
 	ast_log(LOG_ERROR, "TODO: stasis_http_get_stored_recordings\n");
diff --git a/res/stasis_http/resource_recordings.h b/res/stasis_http/resource_recordings.h
index ee48e43b778054d4687e1f11de0f4a8add8a0af8..acccc124bb1ed6acfa1f85ce17cb759349e0a69e 100644
--- a/res/stasis_http/resource_recordings.h
+++ b/res/stasis_http/resource_recordings.h
@@ -39,17 +39,6 @@
 
 #include "asterisk/stasis_http.h"
 
-/*! \brief Argument struct for stasis_http_get_recordings() */
-struct ast_get_recordings_args {
-};
-/*!
- * \brief List all recordings.
- *
- * \param headers HTTP headers
- * \param args Swagger parameters
- * \param[out] response HTTP response
- */
-void stasis_http_get_recordings(struct ast_variable *headers, struct ast_get_recordings_args *args, struct stasis_http_response *response);
 /*! \brief Argument struct for stasis_http_get_stored_recordings() */
 struct ast_get_stored_recordings_args {
 };
diff --git a/res/stasis_json/resource_asterisk.h b/res/stasis_json/resource_asterisk.h
deleted file mode 100644
index 5a717d005bc3b1f3d042e1068f131c0c70182dad..0000000000000000000000000000000000000000
--- a/res/stasis_json/resource_asterisk.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Generated file - declares stubs to be implemented in
- * res/stasis_json/resource_asterisk.c
- *
- * Asterisk resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/stasis_http_resource.h.mustache
- */
-
-#ifndef _ASTERISK_RESOURCE_ASTERISK_H
-#define _ASTERISK_RESOURCE_ASTERISK_H
-
-/*
- * JSON models
- *
- * AsteriskInfo
- */
-
-#endif /* _ASTERISK_RESOURCE_ASTERISK_H */
diff --git a/res/stasis_json/resource_bridges.h b/res/stasis_json/resource_bridges.h
deleted file mode 100644
index cf2d03dc7f346759e080c468ba116338fe3538f2..0000000000000000000000000000000000000000
--- a/res/stasis_json/resource_bridges.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Generated file - declares stubs to be implemented in
- * res/stasis_json/resource_bridges.c
- *
- * Bridge resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/stasis_http_resource.h.mustache
- */
-
-#ifndef _ASTERISK_RESOURCE_BRIDGES_H
-#define _ASTERISK_RESOURCE_BRIDGES_H
-
-/*
- * JSON models
- *
- * Bridge
- * - channels: List[string] (required)
- * - bridgeType: string (required)
- */
-
-#endif /* _ASTERISK_RESOURCE_BRIDGES_H */
diff --git a/res/stasis_json/resource_channels.h b/res/stasis_json/resource_channels.h
deleted file mode 100644
index c98743c36b55a064c7e497ea9b2861d68275d981..0000000000000000000000000000000000000000
--- a/res/stasis_json/resource_channels.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Generated file - declares stubs to be implemented in
- * res/stasis_json/resource_channels.c
- *
- * Channel resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/stasis_http_resource.h.mustache
- */
-
-#ifndef _ASTERISK_RESOURCE_CHANNELS_H
-#define _ASTERISK_RESOURCE_CHANNELS_H
-
-/*
- * JSON models
- *
- * DialplanCEP
- * - priority: long (required)
- * - exten: string (required)
- * - context: string (required)
- * Playback
- * - language: string
- * - media_uri: string (required)
- * - id: string (required)
- * - target_uri: string (required)
- * - state: string (required)
- * Channel
- * - accountcode: string (required)
- * - linkedid: string (required)
- * - name: string (required)
- * - userfield: string (required)
- * - caller: CallerID (required)
- * - creationtime: Date (required)
- * - state: string (required)
- * - parkinglot: string (required)
- * - peeraccount: string (required)
- * - appl: string (required)
- * - connected: CallerID (required)
- * - uniqueid: string (required)
- * - hangupsource: string (required)
- * - dialplan: DialplanCEP (required)
- * - data: string (required)
- * CallerID
- * - name: string (required)
- * - number: string (required)
- * Dialed
- */
-
-#endif /* _ASTERISK_RESOURCE_CHANNELS_H */
diff --git a/res/stasis_json/resource_endpoints.h b/res/stasis_json/resource_endpoints.h
deleted file mode 100644
index 7f2e4233c5bef774e0e6d168a56c1af1b58f034c..0000000000000000000000000000000000000000
--- a/res/stasis_json/resource_endpoints.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Generated file - declares stubs to be implemented in
- * res/stasis_json/resource_endpoints.c
- *
- * Endpoint resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/stasis_http_resource.h.mustache
- */
-
-#ifndef _ASTERISK_RESOURCE_ENDPOINTS_H
-#define _ASTERISK_RESOURCE_ENDPOINTS_H
-
-/*
- * JSON models
- *
- * Endpoint
- * - resource: string (required)
- * - technology: string (required)
- */
-
-#endif /* _ASTERISK_RESOURCE_ENDPOINTS_H */
diff --git a/res/stasis_json/resource_events.h b/res/stasis_json/resource_events.h
deleted file mode 100644
index a2af30daaae0915ed56b694563a0684fab3315d5..0000000000000000000000000000000000000000
--- a/res/stasis_json/resource_events.h
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Generated file - declares stubs to be implemented in
- * res/stasis_json/resource_events.c
- *
- * WebSocket resource
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/stasis_http_resource.h.mustache
- */
-
-#ifndef _ASTERISK_RESOURCE_EVENTS_H
-#define _ASTERISK_RESOURCE_EVENTS_H
-
-struct ast_channel_snapshot;
-struct ast_bridge_snapshot;
-
-/*!
- * \brief User-generated event with additional user-defined fields in the object.
- *
- * \param channel The channel that signaled the user event.
- * \param blob JSON blob containing the following parameters:
- * - eventname: string - The name of the user event. (required)
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_channel_userevent_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	);
-
-/*!
- * \brief Notification that a bridge has been created.
- *
- * \param bridge The bridge to be used to generate this event
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_bridge_created_create(
-	struct ast_bridge_snapshot *bridge_snapshot
-	);
-
-/*!
- * \brief Event showing the completion of a media playback operation.
- *
- * \param blob JSON blob containing the following parameters:
- * - playback: Playback - Playback control object (required)
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_playback_finished_create(
-	struct ast_json *blob
-	);
-
-/*!
- * \brief Some part of channel state changed.
- *
- * \param channel The channel to be used to generate this event
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_channel_snapshot_create(
-	struct ast_channel_snapshot *channel_snapshot
-	);
-
-/*!
- * \brief Channel changed Caller ID.
- *
- * \param channel The channel that changed Caller ID.
- * \param blob JSON blob containing the following parameters:
- * - caller_presentation_txt: string - The text representation of the Caller Presentation value. (required)
- * - caller_presentation: integer - The integer representation of the Caller Presentation value. (required)
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_channel_caller_id_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	);
-
-/*!
- * \brief Event showing the start of a media playback operation.
- *
- * \param blob JSON blob containing the following parameters:
- * - playback: Playback - Playback control object (required)
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_playback_started_create(
-	struct ast_json *blob
-	);
-
-/*!
- * \brief Channel variable changed.
- *
- * \param channel The channel on which the variable was set.
- * \param blob JSON blob containing the following parameters:
- * - variable: string - The variable that changed. (required)
- * - value: string - The new value of the variable. (required)
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_channel_varset_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	);
-
-/*!
- * \brief Notification that a bridge has been destroyed.
- *
- * \param bridge The bridge to be used to generate this event
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_bridge_destroyed_create(
-	struct ast_bridge_snapshot *bridge_snapshot
-	);
-
-/*!
- * \brief Notification that another WebSocket has taken over for an application.
- *
- * \param blob JSON blob containing the following parameters:
- * - application: string  (required)
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_application_replaced_create(
-	struct ast_json *blob
-	);
-
-/*!
- * \brief Notification that a channel has been destroyed.
- *
- * \param channel The channel to be used to generate this event
- * \param blob JSON blob containing the following parameters:
- * - cause: integer - Integer representation of the cause of the hangup (required)
- * - cause_txt: string - Text representation of the cause of the hangup (required)
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_channel_destroyed_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	);
-
-/*!
- * \brief Notification that one bridge has merged into another.
- *
- * \param bridge The bridge to be used to generate this event
- * \param blob JSON blob containing the following parameters:
- * - bridge_from: Bridge  (required)
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_bridge_merged_create(
-	struct ast_bridge_snapshot *bridge_snapshot,
-	struct ast_json *blob
-	);
-
-/*!
- * \brief Notification that a channel has left a bridge.
- *
- * \param channel The channel to be used to generate this event
- * \param bridge The bridge to be used to generate this event
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_channel_left_bridge_create(
-	struct ast_bridge_snapshot *bridge_snapshot,
-	struct ast_channel_snapshot *channel_snapshot
-	);
-
-/*!
- * \brief Notification that a channel has been created.
- *
- * \param channel The channel to be used to generate this event
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_channel_created_create(
-	struct ast_channel_snapshot *channel_snapshot
-	);
-
-/*!
- * \brief Notification that a channel has entered a Stasis appliction.
- *
- * \param channel The channel to be used to generate this event
- * \param blob JSON blob containing the following parameters:
- * - args: List[string] - Arguments to the application (required)
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_stasis_start_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	);
-
-/*!
- * \brief Channel changed location in the dialplan.
- *
- * \param channel The channel that changed dialplan location.
- * \param blob JSON blob containing the following parameters:
- * - application: string - The application that the channel is currently in. (required)
- * - application_data: string - The data that was passed to the application when it was invoked. (required)
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_channel_dialplan_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	);
-
-/*!
- * \brief Notification of a channel's state change.
- *
- * \param channel The channel to be used to generate this event
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_channel_state_change_create(
-	struct ast_channel_snapshot *channel_snapshot
-	);
-
-/*!
- * \brief A hangup was requested on the channel.
- *
- * \param channel The channel on which the hangup was requested.
- * \param blob JSON blob containing the following parameters:
- * - soft: boolean - Whether the hangup request was a soft hangup request.
- * - cause: integer - Integer representation of the cause of the hangup.
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_channel_hangup_request_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	);
-
-/*!
- * \brief Notification that a channel has entered a bridge.
- *
- * \param channel The channel to be used to generate this event
- * \param bridge The bridge to be used to generate this event
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_channel_entered_bridge_create(
-	struct ast_bridge_snapshot *bridge_snapshot,
-	struct ast_channel_snapshot *channel_snapshot
-	);
-
-/*!
- * \brief DTMF received on a channel.
- *
- * \param channel The channel on which DTMF was received
- * \param blob JSON blob containing the following parameters:
- * - digit: string - DTMF digit received (0-9, A-E, # or *) (required)
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_channel_dtmf_received_create(
-	struct ast_channel_snapshot *channel_snapshot,
-	struct ast_json *blob
-	);
-
-/*!
- * \brief Notification that a channel has left a Stasis appliction.
- *
- * \param channel The channel to be used to generate this event
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-struct ast_json *stasis_json_event_stasis_end_create(
-	struct ast_channel_snapshot *channel_snapshot
-	);
-
-/*
- * JSON models
- *
- * ChannelUserevent
- * - eventname: string (required)
- * BridgeCreated
- * PlaybackFinished
- * - playback: Playback (required)
- * ChannelSnapshot
- * ChannelCallerId
- * - caller_presentation_txt: string (required)
- * - caller_presentation: integer (required)
- * PlaybackStarted
- * - playback: Playback (required)
- * ChannelVarset
- * - variable: string (required)
- * - value: string (required)
- * BridgeDestroyed
- * ApplicationReplaced
- * - application: string (required)
- * ChannelDestroyed
- * - cause: integer (required)
- * - cause_txt: string (required)
- * BridgeMerged
- * - bridge_from: Bridge (required)
- * ChannelLeftBridge
- * ChannelCreated
- * StasisStart
- * - args: List[string] (required)
- * ChannelDialplan
- * - application: string (required)
- * - application_data: string (required)
- * ChannelStateChange
- * ChannelHangupRequest
- * - soft: boolean
- * - cause: integer
- * ChannelEnteredBridge
- * ChannelDtmfReceived
- * - digit: string (required)
- * Event
- * - channel_varset: ChannelVarset
- * - channel_created: ChannelCreated
- * - channel_destroyed: ChannelDestroyed
- * - channel_entered_bridge: ChannelEnteredBridge
- * - channel_left_bridge: ChannelLeftBridge
- * - bridge_merged: BridgeMerged
- * - channel_dialplan: ChannelDialplan
- * - application_replaced: ApplicationReplaced
- * - channel_state_change: ChannelStateChange
- * - bridge_created: BridgeCreated
- * - application: string (required)
- * - channel_hangup_request: ChannelHangupRequest
- * - channel_userevent: ChannelUserevent
- * - stasis_start: StasisStart
- * - channel_snapshot: ChannelSnapshot
- * - channel_dtmf_received: ChannelDtmfReceived
- * - channel_caller_id: ChannelCallerId
- * - bridge_destroyed: BridgeDestroyed
- * - playback_started: PlaybackStarted
- * - playback_finished: PlaybackFinished
- * - stasis_end: StasisEnd
- * StasisEnd
- */
-
-#endif /* _ASTERISK_RESOURCE_EVENTS_H */
diff --git a/res/stasis_json/resource_playback.h b/res/stasis_json/resource_playback.h
deleted file mode 100644
index e84e6de0dfa1f202b9d64153cdbee650dcd0458e..0000000000000000000000000000000000000000
--- a/res/stasis_json/resource_playback.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Generated file - declares stubs to be implemented in
- * res/stasis_json/resource_playback.c
- *
- * Playback control resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/stasis_http_resource.h.mustache
- */
-
-#ifndef _ASTERISK_RESOURCE_PLAYBACK_H
-#define _ASTERISK_RESOURCE_PLAYBACK_H
-
-/*
- * JSON models
- *
- * Playback
- * - id: string (required)
- */
-
-#endif /* _ASTERISK_RESOURCE_PLAYBACK_H */
diff --git a/res/stasis_json/resource_recordings.h b/res/stasis_json/resource_recordings.h
deleted file mode 100644
index b460fb7696527959f8b95303a857cf9f2db1f965..0000000000000000000000000000000000000000
--- a/res/stasis_json/resource_recordings.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Generated file - declares stubs to be implemented in
- * res/stasis_json/resource_recordings.c
- *
- * Recording resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/stasis_http_resource.h.mustache
- */
-
-#ifndef _ASTERISK_RESOURCE_RECORDINGS_H
-#define _ASTERISK_RESOURCE_RECORDINGS_H
-
-/*
- * JSON models
- *
- * Recording
- * - id: string (required)
- * StoredRecording
- * - durationSeconds: int
- * - time: Date
- * - id: string (required)
- * - formats: List[string] (required)
- * LiveRecording
- * - id: string (required)
- */
-
-#endif /* _ASTERISK_RESOURCE_RECORDINGS_H */
diff --git a/res/stasis_json/resource_sounds.h b/res/stasis_json/resource_sounds.h
deleted file mode 100644
index d7f8714e692dfa50ef35484b2802a82a597cba47..0000000000000000000000000000000000000000
--- a/res/stasis_json/resource_sounds.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2012 - 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Generated file - declares stubs to be implemented in
- * res/stasis_json/resource_sounds.c
- *
- * Sound resources
- *
- * \author David M. Lee, II <dlee@digium.com>
- */
-
-/*
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !!!!!                               DO NOT EDIT                        !!!!!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/stasis_http_resource.h.mustache
- */
-
-#ifndef _ASTERISK_RESOURCE_SOUNDS_H
-#define _ASTERISK_RESOURCE_SOUNDS_H
-
-/*
- * JSON models
- *
- * Sound
- * - text: string
- * - id: string (required)
- * - formats: List[FormatLangPair] (required)
- * FormatLangPair
- * - language: string (required)
- * - format: string (required)
- */
-
-#endif /* _ASTERISK_RESOURCE_SOUNDS_H */
diff --git a/rest-api-templates/api.wiki.mustache b/rest-api-templates/api.wiki.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..c70e58fc367092bce8b30ae258962bfc673f209a
--- /dev/null
+++ b/rest-api-templates/api.wiki.mustache
@@ -0,0 +1,47 @@
+{{#api_declaration}}
+h1. {{name_title}}
+
+|| Method || Path || Return Model || Summary ||
+{{#apis}}
+{{#operations}}
+| {{http_method}} | [{{wiki_path}}|#{{nickname}}] | {{#response_class}}{{#is_primitive}}{{name}}{{/is_primitive}}{{^is_primitive}}[{{wiki_name}}|{{wiki_prefix}} REST Data Models#{{singular_name}}]{{/is_primitive}}{{/response_class}} | {{summary}} |
+{{/operations}}
+{{/apis}}
+{{#apis}}
+{{#operations}}
+
+{anchor:{{nickname}}}
+h2. {{http_method}} {{wiki_path}}
+
+{{{summary}}}{{#notes}} {{{notes}}}{{/notes}}
+{{#has_path_parameters}}
+
+h3. Path parameters
+{{#path_parameters}}
+* {{name}}: {{data_type}}{{#default_value}} = {{default_value}}{{/default_value}} - {{description}}
+{{/path_parameters}}
+{{/has_path_parameters}}
+{{#has_query_parameters}}
+
+h3. Query parameters
+{{#query_parameters}}
+* {{name}}: {{data_type}}{{#default_value}} = {{default_value}}{{/default_value}} -{{#required}} *(required)*{{/required}} {{description}}
+{{/query_parameters}}
+{{/has_query_parameters}}
+{{#has_header_parameters}}
+
+h3. Header parameters
+{{#header_parameters}}
+* {{name}}: {{data_type}}{{#default_value}} = {{default_value}}{{/default_value}} -{{#required}} *(required)*{{/required}} {{description}}
+{{/header_parameters}}
+{{/has_header_parameters}}
+{{#has_error_responses}}
+
+h3. Error Responses
+{{#error_responses}}
+* {{code}} - {{{reason}}}
+{{/error_responses}}
+{{/has_error_responses}}
+{{/operations}}
+{{/apis}}
+{{/api_declaration}}
diff --git a/rest-api-templates/ari_model_validators.c.mustache b/rest-api-templates/ari_model_validators.c.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..0e87f8e248df188d53edff0fc5e8af256946611a
--- /dev/null
+++ b/rest-api-templates/ari_model_validators.c.mustache
@@ -0,0 +1,117 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Generated file - Build validators for ARI model objects.
+ */
+
+ /*
+{{> do-not-edit}}
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/ari_model_validators.h.mustache
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "ari_model_validators.h"
+{{#apis}}
+{{#api_declaration}}
+{{#models}}
+
+int ari_validate_{{c_id}}(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+{{#properties}}
+{{#required}}
+	int has_{{name}} = 0;
+{{/required}}
+{{/properties}}
+{{#has_subtypes}}
+	const char *discriminator;
+
+	discriminator = ast_json_string_get(ast_json_object_get(json, "{{discriminator.name}}"));
+	if (!discriminator) {
+		ast_log(LOG_ERROR, "ARI {{id}} missing required field {{discriminator.name}}");
+		return 0;
+	}
+
+	if (strcmp("{{id}}", discriminator) == 0) {
+		/* Self type; fall through */
+	} else
+{{#subtypes}}
+	if (strcmp("{{id}}", discriminator) == 0) {
+		return ari_validate_{{c_id}}(json);
+	} else
+{{/subtypes}}
+	{
+		ast_log(LOG_ERROR, "ARI {{id}} has undocumented subtype %s\n",
+			discriminator);
+		res = 0;
+	}
+{{/has_subtypes}}
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+{{#properties}}
+		if (strcmp("{{name}}", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+{{#required}}
+			has_{{name}} = 1;
+{{/required}}
+{{#type}}
+{{#is_list}}
+			prop_is_valid = ari_validate_list(
+				ast_json_object_iter_value(iter),
+				ari_validate_{{c_singular_name}});
+{{/is_list}}
+{{^is_list}}
+			prop_is_valid = ari_validate_{{c_name}}(
+				ast_json_object_iter_value(iter));
+{{/is_list}}
+{{/type}}
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI {{id}} field {{name}} failed validation\n");
+				res = 0;
+			}
+		} else
+{{/properties}}
+		{
+			ast_log(LOG_ERROR,
+				"ARI {{id}} has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+{{#properties}}
+{{#required}}
+	if (!has_{{name}}) {
+		ast_log(LOG_ERROR, "ARI {{id}} missing required field {{name}}\n");
+		res = 0;
+	}
+
+{{/required}}
+{{/properties}}
+	return res;
+}
+{{/models}}
+{{/api_declaration}}
+{{/apis}}
diff --git a/rest-api-templates/ari_model_validators.h.mustache b/rest-api-templates/ari_model_validators.h.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..65efbbd85a3f3548c898d4316b95a5f09e954a08
--- /dev/null
+++ b/rest-api-templates/ari_model_validators.h.mustache
@@ -0,0 +1,159 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Generated file - Build validators for ARI model objects.
+ */
+
+ /*
+{{> do-not-edit}}
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/ari_model_validators.h.mustache
+ */
+
+#ifndef _ASTERISK_ARI_MODEL_H
+#define _ASTERISK_ARI_MODEL_H
+
+#include "asterisk/json.h"
+
+/*! @{ */
+
+/*!
+ * \brief Validator for native Swagger void.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_void(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger byte.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_byte(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger boolean.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_boolean(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger int.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_int(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger long.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_long(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger float.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_float(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger double.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_double(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger string.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_string(struct ast_json *json);
+
+/*!
+ * \brief Validator for native Swagger date.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_date(struct ast_json *json);
+
+/*!
+ * \brief Validator for a Swagger List[]/JSON array.
+ *
+ * \param json JSON object to validate.
+ * \param fn Validator to call on every element in the array.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_list(struct ast_json *json, int (*fn)(struct ast_json *));
+
+/*! @} */
+{{#apis}}
+{{#api_declaration}}
+{{#models}}
+
+/*!
+ * \brief Validator for {{id}}.
+ *
+ * {{{description_dox}}}
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ari_validate_{{c_id}}(struct ast_json *json);
+{{/models}}
+{{/api_declaration}}
+{{/apis}}
+
+/*
+ * JSON models
+ *
+{{#apis}}
+{{#api_declaration}}
+{{#models}}
+ * {{id}}
+{{#properties}}
+ * - {{name}}: {{type.name}}{{#required}} (required){{/required}}
+{{/properties}}
+{{/models}}
+{{/api_declaration}}
+{{/apis}} */
+
+#endif /* _ASTERISK_ARI_MODEL_H */
diff --git a/rest-api-templates/asterisk_processor.py b/rest-api-templates/asterisk_processor.py
index af5f5bdfe8df9303ab22983898e22521dea8240b..0260b6b55bab02c80088062afc57804df7e327b1 100644
--- a/rest-api-templates/asterisk_processor.py
+++ b/rest-api-templates/asterisk_processor.py
@@ -24,6 +24,11 @@ import re
 
 from swagger_model import *
 
+try:
+    from collections import OrderedDict
+except ImportError:
+    from odict import OrderedDict
+
 
 def simple_name(name):
     """Removes the {markers} from a path segement.
@@ -35,6 +40,14 @@ def simple_name(name):
     return name
 
 
+def wikify(str):
+    """Escapes a string for the wiki.
+
+    @param str: String to escape
+    """
+    return re.sub(r'([{}\[\]])', r'\\\1', str)
+
+
 def snakify(name):
     """Helper to take a camelCase or dash-seperated name and make it
     snake_case.
@@ -107,6 +120,7 @@ class PathSegment(Stringify):
         """
         return len(self.__children)
 
+
 class AsteriskProcessor(SwaggerPostProcessor):
     """A SwaggerPostProcessor which adds fields needed to generate Asterisk
     RESTful HTTP binding code.
@@ -131,12 +145,17 @@ class AsteriskProcessor(SwaggerPostProcessor):
         'double': 'atof',
     }
 
-    def process_api(self, resource_api, context):
+    def __init__(self, wiki_prefix):
+        self.wiki_prefix = wiki_prefix
+
+    def process_resource_api(self, resource_api, context):
+        resource_api.wiki_prefix = self.wiki_prefix
         # Derive a resource name from the API declaration's filename
         resource_api.name = re.sub('\..*', '',
                                    os.path.basename(resource_api.path))
-        # Now in all caps, from include guard
+        # Now in all caps, for include guard
         resource_api.name_caps = resource_api.name.upper()
+        resource_api.name_title = resource_api.name.capitalize()
         # Construct the PathSegement tree for the API.
         if resource_api.api_declaration:
             resource_api.root_path = PathSegment('', None)
@@ -145,17 +164,6 @@ class AsteriskProcessor(SwaggerPostProcessor):
                 for operation in api.operations:
                     segment.operations.append(operation)
                 api.full_name = segment.full_name
-            resource_api.api_declaration.has_events = False
-            for model in resource_api.api_declaration.models:
-                if model.id == "Event":
-                    resource_api.api_declaration.has_events = True
-                    break
-            if resource_api.api_declaration.has_events:
-                resource_api.api_declaration.events = \
-                    [self.process_model(model, context) for model in \
-                        resource_api.api_declaration.models if model.id != "Event"]
-            else:
-                resource_api.api_declaration.events = []
 
             # Since every API path should start with /[resource], root should
             # have exactly one child.
@@ -169,6 +177,9 @@ class AsteriskProcessor(SwaggerPostProcessor):
                     "API declaration name should match", context)
             resource_api.root_full_name = resource_api.root_path.full_name
 
+    def process_api(self, api, context):
+        api.wiki_path = wikify(api.path)
+
     def process_operation(self, operation, context):
         # Nicknames are camelcase, Asterisk coding is snake case
         operation.c_nickname = snakify(operation.nickname)
@@ -179,7 +190,7 @@ class AsteriskProcessor(SwaggerPostProcessor):
     def process_parameter(self, parameter, context):
         if not parameter.data_type in self.type_mapping:
             raise SwaggerError(
-                "Invalid parameter type %s" % paramter.data_type, context)
+                "Invalid parameter type %s" % parameter.data_type, context)
         # Parameter names are camelcase, Asterisk convention is snake case
         parameter.c_name = snakify(parameter.name)
         parameter.c_data_type = self.type_mapping[parameter.data_type]
@@ -191,41 +202,19 @@ class AsteriskProcessor(SwaggerPostProcessor):
             parameter.c_space = ' '
 
     def process_model(self, model, context):
+        model.description_dox = model.description.replace('\n', '\n * ')
+        model.description_dox = re.sub(' *\n', '\n', model.description_dox)
         model.c_id = snakify(model.id)
-        model.channel = False
-        model.channel_desc = ""
-        model.bridge = False
-        model.bridge_desc = ""
-        model.properties = [self.process_property(model, prop, context) for prop in model.properties]
-        model.properties = [prop for prop in model.properties if prop]
-	model.has_properties = (len(model.properties) != 0)
         return model
 
-    def process_property(self, model, prop, context):
-        # process channel separately since it will be pulled out
-        if prop.name == 'channel' and prop.type == 'Channel':
-            model.channel = True
-            model.channel_desc = prop.description or ""
-            return None
-
-        # process bridge separately since it will be pulled out
-        if prop.name == 'bridge' and prop.type == 'Bridge':
-            model.bridge = True
-            model.bridge_desc = prop.description or ""
-            return None
-
-	prop.c_name = snakify(prop.name)
-        if prop.type in self.type_mapping:
-            prop.c_type = self.type_mapping[prop.type]
-            prop.c_convert = self.convert_mapping[prop.c_type]
-        else:
-            prop.c_type = "Property type %s not mappable to a C type" % (prop.type)
-            prop.c_convert = "Property type %s not mappable to a C conversion" % (prop.type)
-            #raise SwaggerError(
-            #    "Invalid property type %s" % prop.type, context)
-        # You shouldn't put a space between 'char *' and the variable
-        if prop.c_type.endswith('*'):
-            prop.c_space = ''
-        else:
-            prop.c_space = ' '
-        return prop
+    def process_property(self, prop, context):
+        if "-" in prop.name:
+            raise SwaggerError("Property names cannot have dashes", context)
+        if prop.name != prop.name.lower():
+            raise SwaggerError("Property name should be all lowercase",
+                               context)
+
+    def process_type(self, swagger_type, context):
+        swagger_type.c_name = snakify(swagger_type.name)
+        swagger_type.c_singular_name = snakify(swagger_type.singular_name)
+        swagger_type.wiki_name = wikify(swagger_type.name)
diff --git a/rest-api-templates/event_function_decl.mustache b/rest-api-templates/event_function_decl.mustache
deleted file mode 100644
index fd2c7eb5bbc1a0d4fc0ddb9e0b7debe2819e133b..0000000000000000000000000000000000000000
--- a/rest-api-templates/event_function_decl.mustache
+++ /dev/null
@@ -1,10 +0,0 @@
-struct ast_json *stasis_json_event_{{c_id}}_create(
-{{#bridge}}
-	struct ast_bridge_snapshot *bridge_snapshot{{#channel}},{{/channel}}{{^channel}}{{#has_properties}},{{/has_properties}}{{/channel}}
-{{/bridge}}
-{{#channel}}
-	struct ast_channel_snapshot *channel_snapshot{{#has_properties}},{{/has_properties}}
-{{/channel}}
-{{#has_properties}}
-	struct ast_json *blob
-{{/has_properties}}
diff --git a/rest-api-templates/make_stasis_http_stubs.py b/rest-api-templates/make_ari_stubs.py
similarity index 73%
rename from rest-api-templates/make_stasis_http_stubs.py
rename to rest-api-templates/make_ari_stubs.py
index 1114ea46e179c3f3147c221ea5fb9601cd917eb5..6f59e3813aa3950412c2514a714a4327968ef4f9 100755
--- a/rest-api-templates/make_stasis_http_stubs.py
+++ b/rest-api-templates/make_ari_stubs.py
@@ -22,7 +22,6 @@ except ImportError:
     print >> sys.stderr, "Pystache required. Please sudo pip install pystache."
 
 import os.path
-import pystache
 import sys
 
 from asterisk_processor import AsteriskProcessor
@@ -40,23 +39,27 @@ def rel(file):
     """
     return os.path.join(TOPDIR, file)
 
+WIKI_PREFIX = 'Asterisk 12'
+
 API_TRANSFORMS = [
+    Transform(rel('api.wiki.mustache'),
+              'doc/rest-api/%s {{name_title}} REST API.wiki' % WIKI_PREFIX),
     Transform(rel('res_stasis_http_resource.c.mustache'),
-              'res_stasis_http_{{name}}.c'),
+              'res/res_stasis_http_{{name}}.c'),
     Transform(rel('stasis_http_resource.h.mustache'),
-              'stasis_http/resource_{{name}}.h'),
+              'res/stasis_http/resource_{{name}}.h'),
     Transform(rel('stasis_http_resource.c.mustache'),
-              'stasis_http/resource_{{name}}.c', False),
-    Transform(rel('res_stasis_json_resource.c.mustache'),
-              'res_stasis_json_{{name}}.c'),
-    Transform(rel('res_stasis_json_resource.exports.mustache'),
-              'res_stasis_json_{{name}}.exports.in'),
-    Transform(rel('stasis_json_resource.h.mustache'),
-              'stasis_json/resource_{{name}}.h'),
+              'res/stasis_http/resource_{{name}}.c', overwrite=False),
 ]
 
 RESOURCES_TRANSFORMS = [
-    Transform(rel('stasis_http.make.mustache'), 'stasis_http.make'),
+    Transform(rel('models.wiki.mustache'),
+              'doc/rest-api/%s REST Data Models.wiki' % WIKI_PREFIX),
+    Transform(rel('stasis_http.make.mustache'), 'res/stasis_http.make'),
+    Transform(rel('ari_model_validators.h.mustache'),
+              'res/stasis_http/ari_model_validators.h'),
+    Transform(rel('ari_model_validators.c.mustache'),
+              'res/stasis_http/ari_model_validators.c'),
 ]
 
 
@@ -71,7 +74,7 @@ def main(argv):
     source = args[1]
     dest_dir = args[2]
     renderer = pystache.Renderer(search_dirs=[TOPDIR], missing_tags='strict')
-    processor = AsteriskProcessor()
+    processor = AsteriskProcessor(wiki_prefix=WIKI_PREFIX)
 
     # Build the models
     base_dir = os.path.dirname(source)
diff --git a/rest-api-templates/models.wiki.mustache b/rest-api-templates/models.wiki.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..e3d3eb95caf98333d2524a0897182de03394fd8e
--- /dev/null
+++ b/rest-api-templates/models.wiki.mustache
@@ -0,0 +1,22 @@
+{toc}
+
+{{#apis}}
+{{#api_declaration}}
+{{#models}}
+h1. {{id}}
+{{#extends}}Base type: [{{extends}}|#{{extends}}]{{/extends}}
+{{#has_subtypes}}Subtypes:{{#subtypes}} [{{id}}|#{{id}}]{{/subtypes}}{{/has_subtypes}}
+{{#description}}
+
+{{{description}}}
+{{/description}}
+{code:language=javascript|collapse=true}
+{{{model_json}}}
+{code}
+{{#properties}}
+* {{name}}: {{#type}}{{#is_primitive}}{{wiki_name}}{{/is_primitive}}{{^is_primitive}}[{{wiki_name}}|#{{singular_name}}]{{/is_primitive}}{{/type}}{{^required}} _(optional)_{{/required}}{{#description}} - {{{description}}}{{/description}}
+{{/properties}}
+
+{{/models}}
+{{/api_declaration}}
+{{/apis}}
diff --git a/rest-api-templates/res_stasis_http_resource.c.mustache b/rest-api-templates/res_stasis_http_resource.c.mustache
index 0bdc1d0140714333ee51f0ba58e9ee11494f9d22..0f0535bcf0d3d6edf92e8da7ebeffbf950a647ad 100644
--- a/rest-api-templates/res_stasis_http_resource.c.mustache
+++ b/rest-api-templates/res_stasis_http_resource.c.mustache
@@ -49,6 +49,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/stasis_app.h"
 #include "stasis_http/resource_{{name}}.h"
+#if defined(AST_DEVMODE)
+#include "stasis_http/ari_model_validators.h"
+#endif
 
 {{#apis}}
 {{#operations}}
@@ -61,11 +64,50 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  * \param[out] response Response to the HTTP request.
  */
 static void stasis_http_{{c_nickname}}_cb(
-    struct ast_variable *get_params, struct ast_variable *path_vars,
-    struct ast_variable *headers, struct stasis_http_response *response)
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct stasis_http_response *response)
 {
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
 {{> param_parsing}}
 	stasis_http_{{c_nickname}}(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 500: /* Internal server error */
+{{#error_responses}}
+	case {{code}}: /* {{{reason}}} */
+{{/error_responses}}
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+{{#response_class}}
+{{#is_list}}
+			is_valid = ari_validate_list(response->message,
+				ari_validate_{{c_singular_name}});
+{{/is_list}}
+{{^is_list}}
+			is_valid = ari_validate_{{c_name}}(
+				response->message);
+{{/is_list}}
+{{/response_class}}
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for {{path}}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for {{path}}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
 }
 {{/is_req}}
 {{#is_websocket}}
@@ -81,7 +123,12 @@ static void stasis_http_{{c_nickname}}_ws_cb(struct ast_websocket *ws_session,
 	struct ast_variable *path_vars = NULL;
 {{/has_path_parameters}}
 {{> param_parsing}}
-	session = ari_websocket_session_create(ws_session);
+#if defined(AST_DEVMODE)
+	session = ari_websocket_session_create(ws_session,
+		ari_validate_{{response_class.c_name}});
+#else
+	session = ari_websocket_session_create(ws_session, NULL);
+#endif
 	if (!session) {
 		ast_log(LOG_ERROR, "Failed to create ARI session\n");
 		return;
diff --git a/rest-api-templates/res_stasis_json_resource.c.mustache b/rest-api-templates/res_stasis_json_resource.c.mustache
deleted file mode 100644
index a25bdc22821261c7fa68715ba9f5264fccb0f5de..0000000000000000000000000000000000000000
--- a/rest-api-templates/res_stasis_json_resource.c.mustache
+++ /dev/null
@@ -1,151 +0,0 @@
-{{#api_declaration}}
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * {{{copyright}}}
- *
- * {{{author}}}
-{{! Template Copyright
- * Copyright (C) 2013, Digium, Inc.
- *
- * Kinsey Moore <kmoore@digium.com>
-}}
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-{{! Template for rendering the res_ module for an HTTP resource. }}
-/*
-{{> do-not-edit}}
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/res_stasis_http_resource.c.mustache
- */
-
-/*! \file
- *
- * \brief {{{description}}}
- *
- * \author {{{author}}}
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/module.h"
-#include "asterisk/json.h"
-#include "stasis_json/resource_{{name}}.h"
-{{#has_events}}
-#include "asterisk/stasis_channels.h"
-#include "asterisk/stasis_bridging.h"
-
-{{#events}}
-{{> event_function_decl}}
-	)
-{
-	RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-{{#has_properties}}
-	struct ast_json *validator;
-{{/has_properties}}
-{{#channel}}
-	int ret;
-{{/channel}}
-{{#bridge}}
-{{^channel}}
-	int ret;
-{{/channel}}
-{{/bridge}}
-
-{{#channel}}
-	ast_assert(channel_snapshot != NULL);
-{{/channel}}
-{{#bridge}}
-	ast_assert(bridge_snapshot != NULL);
-{{/bridge}}
-{{#has_properties}}
-	ast_assert(blob != NULL);
-{{#channel}}
-	ast_assert(ast_json_object_get(blob, "channel") == NULL);
-{{/channel}}
-{{#bridge}}
-	ast_assert(ast_json_object_get(blob, "bridge") == NULL);
-{{/bridge}}
-	ast_assert(ast_json_object_get(blob, "type") == NULL);
-{{#properties}}
-
-	validator = ast_json_object_get(blob, "{{name}}");
-	if (validator) {
-		/* do validation? XXX */
-{{#required}}
-	} else {
-		/* fail message generation if the required parameter doesn't exist */
-		return NULL;
-{{/required}}
-	}
-{{/properties}}
-
-	event = ast_json_deep_copy(blob);
-{{/has_properties}}
-{{^has_properties}}
-
-	event = ast_json_object_create();
-{{/has_properties}}
-	if (!event) {
-		return NULL;
-	}
-
-{{#channel}}
-	ret = ast_json_object_set(event,
-		"channel", ast_channel_snapshot_to_json(channel_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-{{/channel}}
-{{#bridge}}
-	ret = ast_json_object_set(event,
-		"bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
-	if (ret) {
-		return NULL;
-	}
-
-{{/bridge}}
-	message = ast_json_pack("{s: o}", "{{c_id}}", ast_json_ref(event));
-	if (!message) {
-		return NULL;
-	}
-
-	return ast_json_ref(message);
-}
-
-{{/events}}
-{{/has_events}}
-static int load_module(void)
-{
-	return 0;
-}
-
-static int unload_module(void)
-{
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Stasis JSON Generators and Validators - {{{description}}}",
-	.load = load_module,
-	.unload = unload_module,
-	.load_pri = AST_MODPRI_DEFAULT,
-	);
-{{/api_declaration}}
diff --git a/rest-api-templates/res_stasis_json_resource.exports.mustache b/rest-api-templates/res_stasis_json_resource.exports.mustache
deleted file mode 100644
index 0f958fa04890c3f4a9ba4c382208fe03dcce7f8e..0000000000000000000000000000000000000000
--- a/rest-api-templates/res_stasis_json_resource.exports.mustache
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-{{#api_declaration}}
-{{#has_events}}
-	global:
-{{#events}}
-		LINKER_SYMBOL_PREFIXstasis_json_event_{{c_id}}_create;
-{{/events}}
-{{/has_events}}
-{{/api_declaration}}
-	local:
-		*;
-};
diff --git a/rest-api-templates/stasis_json_resource.h.mustache b/rest-api-templates/stasis_json_resource.h.mustache
deleted file mode 100644
index 8cfd2c1f7e185b0a635d39ecaa99bd08dd06aff8..0000000000000000000000000000000000000000
--- a/rest-api-templates/stasis_json_resource.h.mustache
+++ /dev/null
@@ -1,83 +0,0 @@
-{{#api_declaration}}
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * {{{copyright}}}
- *
- * {{{author}}}
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Generated file - declares stubs to be implemented in
- * res/stasis_json/resource_{{name}}.c
- *
- * {{{description}}}
- *
- * \author {{{author}}}
- */
-
-/*
-{{> do-not-edit}}
- * This file is generated by a mustache template. Please see the original
- * template in rest-api-templates/stasis_http_resource.h.mustache
- */
-
-#ifndef _ASTERISK_RESOURCE_{{name_caps}}_H
-#define _ASTERISK_RESOURCE_{{name_caps}}_H
-
-{{#has_events}}
-struct ast_channel_snapshot;
-struct ast_bridge_snapshot;
-
-{{#events}}
-/*!
- * \brief {{description}}
-{{#notes}}
- *
- * {{{notes}}}
-{{/notes}}
- *
-{{#channel}}
- * \param channel {{#channel_desc}}{{channel_desc}}{{/channel_desc}}{{^channel_desc}}The channel to be used to generate this event{{/channel_desc}}
-{{/channel}}
-{{#bridge}}
- * \param bridge {{#bridge_desc}}{{bridge_desc}}{{/bridge_desc}}{{^bridge_desc}}The bridge to be used to generate this event{{/bridge_desc}}
-{{/bridge}}
-{{#has_properties}}
- * \param blob JSON blob containing the following parameters:
-{{/has_properties}}
-{{#properties}}
- * - {{name}}: {{type}} {{#description}}- {{description}}{{/description}}{{#required}} (required){{/required}}
-{{/properties}}
- *
- * \retval NULL on error
- * \retval JSON (ast_json) describing the event
- */
-{{> event_function_decl}}
-	);
-
-{{/events}}
-{{/has_events}}
-/*
- * JSON models
- *
-{{#models}}
- * {{id}}
-{{#properties}}
- * - {{name}}: {{type}}{{#required}} (required){{/required}}
-{{/properties}}
-{{/models}} */
-
-#endif /* _ASTERISK_RESOURCE_{{name_caps}}_H */
-{{/api_declaration}}
diff --git a/rest-api-templates/swagger_model.py b/rest-api-templates/swagger_model.py
index 47461b40682077881a99ab53bad99e5b83147902..2907688c5231680a2584a32f2c147f1ef3462e5e 100644
--- a/rest-api-templates/swagger_model.py
+++ b/rest-api-templates/swagger_model.py
@@ -29,16 +29,101 @@ See https://github.com/wordnik/swagger-core/wiki/API-Declaration for the spec.
 import json
 import os.path
 import pprint
+import re
 import sys
 import traceback
 
-try:
-    from collections import OrderedDict
-except ImportError:
-    from odict import OrderedDict
+# I'm not quite sure what was in Swagger 1.2, but apparently I missed it
+SWAGGER_VERSIONS = ["1.1", "1.3"]
 
+SWAGGER_PRIMITIVES = [
+    'void',
+    'string',
+    'boolean',
+    'number',
+    'int',
+    'long',
+    'double',
+    'float',
+    'Date',
+]
 
-SWAGGER_VERSION = "1.1"
+
+class Stringify(object):
+    """Simple mix-in to make the repr of the model classes more meaningful.
+    """
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__, pprint.saferepr(self.__dict__))
+
+
+def compare_versions(lhs, rhs):
+    '''Performs a lexicographical comparison between two version numbers.
+
+    This properly handles simple major.minor.whatever.sure.why.not version
+    numbers, but fails miserably if there's any letters in there.
+
+    For reference:
+      1.0 == 1.0
+      1.0 < 1.0.1
+      1.2 < 1.10
+
+    @param lhs Left hand side of the comparison
+    @param rhs Right hand side of the comparison
+    @return  < 0 if lhs  < rhs
+    @return == 0 if lhs == rhs
+    @return  > 0 if lhs  > rhs
+    '''
+    lhs = [int(v) for v in lhs.split('.')]
+    rhs = [int(v) for v in rhs.split('.')]
+    return cmp(lhs, rhs)
+
+
+class ParsingContext(object):
+    """Context information for parsing.
+
+    This object is immutable. To change contexts (like adding an item to the
+    stack), use the next() and next_stack() functions to build a new one.
+    """
+
+    def __init__(self, swagger_version, stack):
+        self.__swagger_version = swagger_version
+        self.__stack = stack
+
+    def __repr__(self):
+        return "ParsingContext(swagger_version=%s, stack=%s)" % (
+            self.swagger_version, self.stack)
+
+    def get_swagger_version(self):
+        return self.__swagger_version
+
+    def get_stack(self):
+        return self.__stack
+
+    swagger_version = property(get_swagger_version)
+
+    stack = property(get_stack)
+
+    def version_less_than(self, ver):
+        return compare_versions(self.swagger_version, ver) < 0
+
+    def next_stack(self, json, id_field):
+        """Returns a new item pushed to the stack.
+
+        @param json: Current JSON object.
+        @param id_field: Field identifying this object.
+        @return New context with additional item in the stack.
+        """
+        if not id_field in json:
+            raise SwaggerError("Missing id_field: %s" % id_field, self)
+        new_stack = self.stack + ['%s=%s' % (id_field, str(json[id_field]))]
+        return ParsingContext(self.swagger_version, new_stack)
+
+    def next(self, version=None, stack=None):
+        if version is None:
+            version = self.version
+        if stack is None:
+            stack = self.stack
+        return ParsingContext(version, stack)
 
 
 class SwaggerError(Exception):
@@ -50,7 +135,7 @@ class SwaggerError(Exception):
         """Ctor.
 
         @param msg: String message for the error.
-        @param context: Array of strings for current context in the API.
+        @param context: ParsingContext object
         @param cause: Optional exception that caused this one.
         """
         super(Exception, self).__init__(msg, context, cause)
@@ -61,7 +146,7 @@ class SwaggerPostProcessor(object):
     fields to model objects for additional information to use in the
     templates.
     """
-    def process_api(self, resource_api, context):
+    def process_resource_api(self, resource_api, context):
         """Post process a ResourceApi object.
 
         @param resource_api: ResourceApi object.
@@ -69,6 +154,14 @@ class SwaggerPostProcessor(object):
         """
         pass
 
+    def process_api(self, api, context):
+        """Post process an Api object.
+
+        @param api: Api object.
+        @param context: Current context in the API.
+        """
+        pass
+
     def process_operation(self, operation, context):
         """Post process a Operation object.
 
@@ -85,12 +178,37 @@ class SwaggerPostProcessor(object):
         """
         pass
 
+    def process_model(self, model, context):
+        """Post process a Model object.
 
-class Stringify(object):
-    """Simple mix-in to make the repr of the model classes more meaningful.
-    """
-    def __repr__(self):
-        return "%s(%s)" % (self.__class__, pprint.saferepr(self.__dict__))
+        @param model: Model object.
+        @param context: Current context in the API.
+        """
+        pass
+
+    def process_property(self, property, context):
+        """Post process a Property object.
+
+        @param property: Property object.
+        @param context: Current context in the API.
+        """
+        pass
+
+    def process_type(self, swagger_type, context):
+        """Post process a SwaggerType object.
+
+        @param swagger_type: ResourceListing object.
+        @param context: Current context in the API.
+        """
+        pass
+
+    def process_resource_listing(self, resource_listing, context):
+        """Post process the overall ResourceListing object.
+
+        @param resource_listing: ResourceListing object.
+        @param context: Current context in the API.
+        """
+        pass
 
 
 class AllowableRange(Stringify):
@@ -158,17 +276,22 @@ class Parameter(Stringify):
         self.allow_multiple = None
 
     def load(self, parameter_json, processor, context):
-        context = add_context(context, parameter_json, 'name')
+        context = context.next_stack(parameter_json, 'name')
         validate_required_fields(parameter_json, self.required_fields, context)
         self.name = parameter_json.get('name')
         self.param_type = parameter_json.get('paramType')
         self.description = parameter_json.get('description') or ''
         self.data_type = parameter_json.get('dataType')
         self.required = parameter_json.get('required') or False
+        self.default_value = parameter_json.get('defaultValue')
         self.allowable_values = load_allowable_values(
             parameter_json.get('allowableValues'), context)
         self.allow_multiple = parameter_json.get('allowMultiple') or False
         processor.process_parameter(self, context)
+        if parameter_json.get('allowedValues'):
+            raise SwaggerError(
+                "Field 'allowedValues' invalid; use 'allowableValues'",
+                context)
         return self
 
     def is_type(self, other_type):
@@ -188,13 +311,41 @@ class ErrorResponse(Stringify):
         self.reason = None
 
     def load(self, err_json, processor, context):
-        context = add_context(context, err_json, 'code')
+        context = context.next_stack(err_json, 'code')
         validate_required_fields(err_json, self.required_fields, context)
         self.code = err_json.get('code')
         self.reason = err_json.get('reason')
         return self
 
 
+class SwaggerType(Stringify):
+    """Model of a data type.
+    """
+
+    def __init__(self):
+        self.name = None
+        self.is_discriminator = None
+        self.is_list = None
+        self.singular_name = None
+        self.is_primitive = None
+
+    def load(self, type_name, processor, context):
+        # Some common errors
+        if type_name == 'integer':
+            raise SwaggerError("The type for integer should be 'int'", context)
+
+        self.name = type_name
+        type_param = get_list_parameter_type(self.name)
+        self.is_list = type_param is not None
+        if self.is_list:
+            self.singular_name = type_param
+        else:
+            self.singular_name = self.name
+        self.is_primitive = self.singular_name in SWAGGER_PRIMITIVES
+        processor.process_type(self, context)
+        return self
+
+
 class Operation(Stringify):
     """Model of an operation on an API
 
@@ -213,11 +364,14 @@ class Operation(Stringify):
         self.error_responses = []
 
     def load(self, op_json, processor, context):
-        context = add_context(context, op_json, 'nickname')
+        context = context.next_stack(op_json, 'nickname')
         validate_required_fields(op_json, self.required_fields, context)
         self.http_method = op_json.get('httpMethod')
         self.nickname = op_json.get('nickname')
-        self.response_class = op_json.get('responseClass')
+        response_class = op_json.get('responseClass')
+        self.response_class = response_class and SwaggerType().load(
+            response_class, processor, context)
+
         # Specifying WebSocket URL's is our own extension
         self.is_websocket = op_json.get('upgrade') == 'websocket'
         self.is_req = not self.is_websocket
@@ -247,6 +401,7 @@ class Operation(Stringify):
         err_json = op_json.get('errorResponses') or []
         self.error_responses = [
             ErrorResponse().load(j, processor, context) for j in err_json]
+        self.has_error_responses = self.error_responses != []
         processor.process_operation(self, context)
         return self
 
@@ -265,7 +420,7 @@ class Api(Stringify):
         self.operations = []
 
     def load(self, api_json, processor, context):
-        context = add_context(context, api_json, 'path')
+        context = context.next_stack(api_json, 'path')
         validate_required_fields(api_json, self.required_fields, context)
         self.path = api_json.get('path')
         self.description = api_json.get('description')
@@ -274,9 +429,20 @@ class Api(Stringify):
             Operation().load(j, processor, context) for j in op_json]
         self.has_websocket = \
             filter(lambda op: op.is_websocket, self.operations) != []
+        processor.process_api(self, context)
         return self
 
 
+def get_list_parameter_type(type_string):
+    """Returns the type parameter if the given type_string is List[].
+
+    @param type_string: Type string to parse
+    @returns Type parameter of the list, or None if not a List.
+    """
+    list_match = re.match('^List\[(.*)\]$', type_string)
+    return list_match and list_match.group(1)
+
+
 class Property(Stringify):
     """Model of a Swagger property.
 
@@ -293,9 +459,15 @@ class Property(Stringify):
 
     def load(self, property_json, processor, context):
         validate_required_fields(property_json, self.required_fields, context)
-        self.type = property_json.get('type')
+        # Bit of a hack, but properties do not self-identify
+        context = context.next_stack({'name': self.name}, 'name')
         self.description = property_json.get('description') or ''
         self.required = property_json.get('required') or False
+
+        type = property_json.get('type')
+        self.type = type and SwaggerType().load(type, processor, context)
+
+        processor.process_property(self, context)
         return self
 
 
@@ -305,24 +477,95 @@ class Model(Stringify):
     See https://github.com/wordnik/swagger-core/wiki/datatypes
     """
 
+    required_fields = ['description', 'properties']
+
     def __init__(self):
         self.id = None
+        self.extends = None
+        self.extends_type = None
         self.notes = None
         self.description = None
-        self.properties = None
+        self.__properties = None
+        self.__discriminator = None
+        self.__subtypes = []
 
     def load(self, id, model_json, processor, context):
-        context = add_context(context, model_json, 'id')
-        # This arrangement is required by the Swagger API spec
+        context = context.next_stack(model_json, 'id')
+        validate_required_fields(model_json, self.required_fields, context)
+        # The duplication of the model's id is required by the Swagger spec.
         self.id = model_json.get('id')
         if id != self.id:
-            raise SwaggerError("Model id doesn't match name", c)
+            raise SwaggerError("Model id doesn't match name", context)
+        self.extends = model_json.get('extends')
+        if self.extends and context.version_less_than("1.3"):
+            raise SwaggerError("Type extension support added in Swagger 1.3",
+                               context)
         self.description = model_json.get('description')
         props = model_json.get('properties').items() or []
-        self.properties = [
+        self.__properties = [
             Property(k).load(j, processor, context) for (k, j) in props]
+        self.__properties = sorted(self.__properties, key=lambda p: p.name)
+
+        discriminator = model_json.get('discriminator')
+
+        if discriminator:
+            if context.version_less_than("1.3"):
+                raise SwaggerError("Discriminator support added in Swagger 1.3",
+                                   context)
+
+            discr_props = [p for p in self.__properties if p.name == discriminator]
+            if not discr_props:
+                raise SwaggerError(
+                    "Discriminator '%s' does not name a property of '%s'" % (
+                        discriminator, self.id),
+                    context)
+
+            self.__discriminator = discr_props[0]
+
+        self.model_json = json.dumps(model_json,
+                                     indent=2, separators=(',', ': '))
+
+        processor.process_model(self, context)
         return self
 
+    def add_subtype(self, subtype):
+        """Add subtype to this model.
+
+        @param subtype: Model instance for the subtype.
+        """
+        self.__subtypes.append(subtype)
+
+    def set_extends_type(self, extends_type):
+        self.extends_type = extends_type
+
+    def discriminator(self):
+        """Returns the discriminator, digging through base types if needed.
+        """
+        return self.__discriminator or \
+            self.extends_type and self.extends_type.discriminator()
+
+    def properties(self):
+        base_props = []
+        if self.extends_type:
+            base_props = self.extends_type.properties()
+        return base_props + self.__properties
+
+    def has_properties(self):
+        return len(self.properties()) > 0
+
+    def subtypes(self):
+        """Returns the full list of all subtypes.
+        """
+        res = self.__subtypes + \
+            [subsubtypes for subtype in self.__subtypes
+             for subsubtypes in subtype.subtypes()]
+        return sorted(res, key=lambda m: m.id)
+
+    def has_subtypes(self):
+        """Returns True if type has any subtypes.
+        """
+        return len(self.subtypes()) > 0
+
 
 class ApiDeclaration(Stringify):
     """Model class for an API Declaration.
@@ -345,8 +588,8 @@ class ApiDeclaration(Stringify):
         self.apis = []
         self.models = []
 
-    def load_file(self, api_declaration_file, processor, context=[]):
-        context = context + [api_declaration_file]
+    def load_file(self, api_declaration_file, processor):
+        context = ParsingContext(None, [api_declaration_file])
         try:
             return self.__load_file(api_declaration_file, processor, context)
         except SwaggerError:
@@ -376,9 +619,10 @@ class ApiDeclaration(Stringify):
         """
         # If the version doesn't match, all bets are off.
         self.swagger_version = api_decl_json.get('swaggerVersion')
-        if self.swagger_version != SWAGGER_VERSION:
+        context = context.next(version=self.swagger_version)
+        if not self.swagger_version in SWAGGER_VERSIONS:
             raise SwaggerError(
-                "Unsupported Swagger version %s" % swagger_version, context)
+                "Unsupported Swagger version %s" % self.swagger_version, context)
 
         validate_required_fields(api_decl_json, self.required_fields, context)
 
@@ -391,9 +635,19 @@ class ApiDeclaration(Stringify):
         self.apis = [
             Api().load(j, processor, context) for j in api_json]
         models = api_decl_json.get('models').items() or []
-        self.models = [
-            Model().load(k, j, processor, context) for (k, j) in models]
-
+        self.models = [Model().load(id, json, processor, context)
+                       for (id, json) in models]
+        self.models = sorted(self.models, key=lambda m: m.id)
+        # Now link all base/extended types
+        model_dict = dict((m.id, m) for m in self.models)
+        for m in self.models:
+            if m.extends:
+                extends_type = model_dict.get(m.extends)
+                if not extends_type:
+                    raise SwaggerError("%s extends non-existing model %s",
+                                       m.id, m.extends)
+                extends_type.add_subtype(m)
+                m.set_extends_type(extends_type)
         return self
 
 
@@ -409,20 +663,20 @@ class ResourceApi(Stringify):
         self.api_declaration = None
 
     def load(self, api_json, processor, context):
-        context = add_context(context, api_json, 'path')
+        context = context.next_stack(api_json, 'path')
         validate_required_fields(api_json, self.required_fields, context)
         self.path = api_json['path']
         self.description = api_json['description']
 
         if not self.path or self.path[0] != '/':
             raise SwaggerError("Path must start with /", context)
-        processor.process_api(self, context)
+        processor.process_resource_api(self, context)
         return self
 
     def load_api_declaration(self, base_dir, processor):
         self.file = (base_dir + self.path).replace('{format}', 'json')
         self.api_declaration = ApiDeclaration().load_file(self.file, processor)
-        processor.process_api(self, [self.file])
+        processor.process_resource_api(self, [self.file])
 
 
 class ResourceListing(Stringify):
@@ -438,7 +692,7 @@ class ResourceListing(Stringify):
         self.apis = None
 
     def load_file(self, resource_file, processor):
-        context = [resource_file]
+        context = ParsingContext(None, [resource_file])
         try:
             return self.__load_file(resource_file, processor, context)
         except SwaggerError:
@@ -455,7 +709,7 @@ class ResourceListing(Stringify):
     def load(self, resources_json, processor, context):
         # If the version doesn't match, all bets are off.
         self.swagger_version = resources_json.get('swaggerVersion')
-        if self.swagger_version != SWAGGER_VERSION:
+        if not self.swagger_version in SWAGGER_VERSIONS:
             raise SwaggerError(
                 "Unsupported Swagger version %s" % swagger_version, context)
 
@@ -465,6 +719,7 @@ class ResourceListing(Stringify):
         apis_json = resources_json['apis']
         self.apis = [
             ResourceApi().load(j, processor, context) for j in apis_json]
+        processor.process_resource_listing(self, context)
         return self
 
 
@@ -482,16 +737,3 @@ def validate_required_fields(json, required_fields, context):
     if missing_fields:
         raise SwaggerError(
             "Missing fields: %s" % ', '.join(missing_fields), context)
-
-
-def add_context(context, json, id_field):
-    """Returns a new context with a new item added to it.
-
-    @param context: Old context.
-    @param json: Current JSON object.
-    @param id_field: Field identifying this object.
-    @return New context with additional item.
-    """
-    if not id_field in json:
-        raise SwaggerError("Missing id_field: %s" % id_field, context)
-    return context + ['%s=%s' % (id_field, str(json[id_field]))]
diff --git a/rest-api-templates/transform.py b/rest-api-templates/transform.py
index d0ef3c4a162eb243514363db33dd69f5dd3fa1ed..fc12efe85938107a8810009519858d785f83b735 100644
--- a/rest-api-templates/transform.py
+++ b/rest-api-templates/transform.py
@@ -16,8 +16,11 @@
 # at the top of the source tree.
 #
 
+import filecmp
 import os.path
 import pystache
+import shutil
+import tempfile
 
 
 class Transform(object):
@@ -46,8 +49,14 @@ class Transform(object):
         """
         dest_file = pystache.render(self.dest_file_template, model)
         dest_file = os.path.join(dest_dir, dest_file)
-        if os.path.exists(dest_file) and not self.overwrite:
+        dest_exists = os.path.exists(dest_file)
+        if dest_exists and not self.overwrite:
             return
-        print "Rendering %s" % dest_file
-        with open(dest_file, "w") as out:
+        tmp_file = tempfile.mkstemp()
+        with tempfile.NamedTemporaryFile() as out:
             out.write(renderer.render(self.template, model))
+            out.flush()
+
+            if not dest_exists or not filecmp.cmp(out.name, dest_file):
+                print "Writing %s" % dest_file
+                shutil.copyfile(out.name, dest_file)
diff --git a/rest-api/api-docs/asterisk.json b/rest-api/api-docs/asterisk.json
index ef6c7b864ca4c4b77b8b197ec0d66b9c6f7d768f..8ee88e4394df92f2010fe35592d95ca508a61030 100644
--- a/rest-api/api-docs/asterisk.json
+++ b/rest-api/api-docs/asterisk.json
@@ -41,6 +41,7 @@
 	"models": {
 		"AsteriskInfo": {
 			"id": "AsteriskInfo",
+			"description": "Asterisk system information",
 			"properties": {}
 		}
 	}
diff --git a/rest-api/api-docs/bridges.json b/rest-api/api-docs/bridges.json
index 5b0cf629852958dfa0427b6a6ee5cd358783a807..87d5b3d4f52cd2054954cd914d2b0ffb1f82c67c 100644
--- a/rest-api/api-docs/bridges.json
+++ b/rest-api/api-docs/bridges.json
@@ -31,8 +31,8 @@
 							"required": false,
 							"allowMultiple": false,
 							"dataType": "string",
-							"allowedValues": {
-								"type": "LIST",
+							"allowableValues": {
+								"valueType": "LIST",
 								"values": [
 									"mixing",
 									"holding"
@@ -61,6 +61,12 @@
 							"allowMultiple": false,
 							"dataType": "string"
 						}
+					],
+					"errorResponses": [
+						{
+							"code": 404,
+							"reason": "Bridge not found"
+						}
 					]
 				},
 				{
@@ -78,6 +84,12 @@
 							"allowMultiple": false,
 							"dataType": "string"
 						}
+					],
+					"errorResponses": [
+						{
+							"code": 404,
+							"reason": "Bridge not found"
+						}
 					]
 				}
 			]
@@ -108,6 +120,20 @@
 							"allowMultiple": true,
 							"dataType": "string"
 						}
+					],
+					"errorResponses": [
+						{
+							"code": 404,
+							"reason": "Bridge not found"
+						},
+						{
+							"code": 409,
+							"reason": "Bridge not in Stasis application"
+						},
+						{
+							"code": 422,
+							"reason": "Channel not found, or not in Stasis application"
+						}
 					]
 				}
 			]
@@ -231,19 +257,35 @@
 	"models": {
 		"Bridge": {
 			"id": "Bridge",
+			"description": "The merging of media from one or more channels.\n\nEveryone on the bridge receives the same audio.",
 			"properties": {
-				"bridgeType": {
+				"id": {
+					"type": "string",
+					"description": "Unique identifier for this bridge",
+					"required": true
+				},
+				"technology": {
+					"type": "string",
+					"description": "Name of the current bridging technology",
+					"required": true
+				},
+				"bridge_type": {
 					"type": "string",
 					"description": "Type of bridge technology",
 					"required": true,
-					"allowedValues": {
-						"type": "LIST",
+					"allowableValues": {
+						"valueType": "LIST",
 						"values": [
 							"mixing",
 							"holding"
 						]
 					}
 				},
+				"bridge_class": {
+					"type": "string",
+					"description": "Bridging class",
+					"required": true
+				},
 				"channels": {
 					"type": "List[string]",
 					"description": "Id's of channels participating in this bridge",
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index 623cb17bb24827352b4f81ec64f416fcd7401318..f013ef6416d3c97242b01cfd59c8b5d36b89bb71 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -286,6 +286,10 @@
 						{
 							"code": 404,
 							"reason": "Channel not found"
+						},
+						{
+							"code": 409,
+							"reason": "Channel not in a Stasis application"
 						}
 					]
 				}
@@ -616,11 +620,7 @@
 						},
 						{
 							"code": 409,
-							"reason": "Channel is not in a Stasis application."
-						},
-						{
-							"code": 409,
-							"reason": "The channel is currently bridged with other channels."
+							"reason": "Channel is not in a Stasis application, or the channel is currently bridged with other channels."
 						}
 					]
 				}
@@ -630,10 +630,12 @@
 	"models": {
 		"Dialed": {
 			"id": "Dialed",
+			"description": "Dialed channel information.",
 			"properties": {}
 		},
 		"DialplanCEP": {
 			"id": "DialplanCEP",
+			"description": "Dialplan location (context/extension/priority)",
 			"properties": {
 				"context": {
 					"required": true,
@@ -654,6 +656,7 @@
 		},
 		"CallerID": {
 			"id": "CallerID",
+			"description": "Caller identification",
 			"properties": {
 				"name": {
 					"required": true,
@@ -667,11 +670,12 @@
 		},
 		"Channel": {
 			"id": "Channel",
+			"description": "A specific communication connection between Asterisk and an Endpoint.",
 			"properties": {
-				"uniqueid": {
+				"id": {
 					"required": true,
 					"type": "string",
-					"description": "Unique identifier of the channel"
+					"description": "Unique identifier of the channel.\n\nThis is the same as the Uniqueid field in AMI."
 				},
 				"name": {
 					"required": true,
@@ -680,99 +684,47 @@
 				},
 				"state": {
 					"required": true,
-					"type": "string"
-				},
-				"accountcode": {
-					"required": true,
-					"type": "string"
-				},
-				"peeraccount": {
-					"required": true,
-					"type": "string"
-				},
-				"userfield": {
-					"required": true,
-					"type": "string"
+					"type": "string",
+					"allowableValues": {
+						"valueType": "LIST",
+						"values": [
+							"Down",
+							"Rsrved",
+							"OffHook",
+							"Dialing",
+							"Ring",
+							"Ringing",
+							"Up",
+							"Busy",
+							"Dialing Offhook",
+							"Pre-ring",
+							"Unknown"
+						]
+					}
 				},
-				"linkedid": {
+				"caller": {
 					"required": true,
-					"type": "string"
+					"type": "CallerID"
 				},
-				"parkinglot": {
+				"connected": {
 					"required": true,
-					"type": "string"
+					"type": "CallerID"
 				},
-				"hangupsource": {
+				"accountcode": {
 					"required": true,
 					"type": "string"
 				},
-				"appl": {
-					"required": true,
-					"type": "string",
-					"description": "Currently executing dialplan application"
-				},
-				"data": {
-					"required": true,
-					"type": "string",
-					"description": "Arguments passed to appl"
-				},
 				"dialplan": {
 					"required": true,
 					"type": "DialplanCEP",
 					"description": "Current location in the dialplan"
 				},
-				"caller": {
-					"required": true,
-					"type": "CallerID"
-				},
-				"connected": {
-					"required": true,
-					"type": "CallerID"
-				},
 				"creationtime": {
 					"required": true,
 					"type": "Date",
 					"description": "Timestamp when channel was created"
 				}
 			}
-		},
-		"Playback": {
-			"id": "Playback",
-			"description": "Object representing the playback of media to a channel",
-			"properties": {
-				"id": {
-					"type": "string",
-					"description": "ID for this playback operation",
-					"required": true
-				},
-				"media_uri": {
-					"type": "string",
-					"description": "URI for the media to play back.",
-					"required": true
-				},
-				"target_uri": {
-					"type": "string",
-					"description": "URI for the channel or bridge to play the media on",
-					"required": true
-				},
-				"language": {
-					"type": "string",
-					"description": "For media types that support multiple languages, the language requested for playback."
-				},
-				"state": {
-					"type": "string",
-					"description": "Current state of the playback operation.",
-					"required": true,
-					"allowableValues": {
-						"valueType": "LIST",
-						"values": [
-							"queued",
-							"playing",
-							"complete"
-						]
-					}
-				}
-			}
 		}
 	}
 }
diff --git a/rest-api/api-docs/endpoints.json b/rest-api/api-docs/endpoints.json
index d3d77d84aa939d0672e4743db68cb5ebfa779f3e..9d0ff1840379ca322c6cb1e8d280bdf604e1a87f 100644
--- a/rest-api/api-docs/endpoints.json
+++ b/rest-api/api-docs/endpoints.json
@@ -69,7 +69,7 @@
 	"models": {
 		"Endpoint": {
 			"id": "Endpoint",
-			"description": "A snapshot of an endpoint. Unlike most resources, which have a single unique identifier, an endpoint is uniquely identified by the technology/resource pair.",
+			"description": "An external device that may offer/accept calls to/from Asterisk.\n\nUnlike most resources, which have a single unique identifier, an endpoint is uniquely identified by the technology/resource pair.",
 			"properties": {
 				"technology": {
 					"type": "string",
@@ -80,6 +80,24 @@
 					"type": "string",
 					"description": "Identifier of the endpoint, specific to the given technology.",
 					"required": true
+				},
+				"state": {
+					"type": "string",
+					"description": "Endpoint's state",
+					"required": false,
+					"allowableValues": {
+						"valueType": "LIST",
+						"values": [
+							"unknown",
+							"offline",
+							"online"
+						]
+					}
+				},
+				"channel_ids": {
+					"type": "List[string]",
+					"description": "Id's of channels associated with this endpoint",
+					"required": true
 				}
 			}
 		}
diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json
index 56a05e4ee9d49622ecbea1121a07478d5adac39b..79908eff79f6b28fb3362ddcafe72d09a76fc563 100644
--- a/rest-api/api-docs/events.json
+++ b/rest-api/api-docs/events.json
@@ -3,7 +3,7 @@
 	"_author": "David M. Lee, II <dlee@digium.com>",
 	"_svn_revision": "$Revision$",
 	"apiVersion": "0.0.1",
-	"swaggerVersion": "1.1",
+	"swaggerVersion": "1.3",
 	"basePath": "http://localhost:8088/stasis",
 	"resourcePath": "/api-docs/events.{format}",
 	"apis": [
@@ -35,37 +35,29 @@
 	"models": {
 		"Event": {
 			"id": "Event",
-			"description": "Asynchronous events from Asterisk. The non-required fields of this object are mutually exclusive.",
+			"description": "Base type for asynchronous events from Asterisk.",
+			"discriminator": "type",
 			"properties": {
+				"type": {
+					"type": "string",
+					"required": true,
+					"description": "Indicates the type of this event."
+				},
 				"application": {
 					"type": "string",
 					"description": "Name of the application receiving the event.",
 					"required": true
 				},
-				"application_replaced": { "type": "ApplicationReplaced" },
-				"bridge_created": { "type": "BridgeCreated" },
-				"bridge_destroyed": { "type": "BridgeDestroyed" },
-				"bridge_merged": { "type": "BridgeMerged" },
-				"channel_created": { "type": "ChannelCreated" },
-				"channel_destroyed": { "type": "ChannelDestroyed" },
-				"channel_snapshot": { "type": "ChannelSnapshot" },
-				"channel_entered_bridge": { "type": "ChannelEnteredBridge" },
-				"channel_left_bridge": { "type": "ChannelLeftBridge" },
-				"channel_state_change": { "type": "ChannelStateChange" },
-				"channel_dtmf_received": { "type": "ChannelDtmfReceived" },
-				"channel_dialplan": { "type": "ChannelDialplan" },
-				"channel_caller_id": { "type": "ChannelCallerId" },
-				"channel_userevent": { "type": "ChannelUserevent" },
-				"channel_hangup_request": { "type": "ChannelHangupRequest" },
-				"channel_varset": { "type": "ChannelVarset" },
-				"stasis_end": { "type": "StasisEnd" },
-				"stasis_start": { "type": "StasisStart" },
-				"playback_started": { "type": "PlaybackStarted" },
-				"playback_finished": { "type": "PlaybackFinished" }
+				"timestamp": {
+					"type": "Date",
+					"description": "Time at which this event was created.",
+					"required": false
+				}
 			}
 		},
 		"PlaybackStarted": {
 			"id": "PlaybackStarted",
+			"extends": "Event",
 			"description": "Event showing the start of a media playback operation.",
 			"properties": {
 				"playback": {
@@ -77,6 +69,7 @@
 		},
 		"PlaybackFinished": {
 			"id": "PlaybackFinished",
+			"extends": "Event",
 			"description": "Event showing the completion of a media playback operation.",
 			"properties": {
 				"playback": {
@@ -88,17 +81,13 @@
 		},
 		"ApplicationReplaced": {
 			"id": "ApplicationReplaced",
-			"description": "Notification that another WebSocket has taken over for an application.",
-			"notes": "An application may only be subscribed to by a single WebSocket at a time. If multiple WebSockets attempt to subscribe to the same application, the newer WebSocket wins, and the older one receives this event.",
-			"properties": {
-				"application": {
-					"required": true,
-					"type": "string"
-				}
-			}
+			"extends": "Event",
+			"description": "Notification that another WebSocket has taken over for an application.\n\nAn application may only be subscribed to by a single WebSocket at a time. If multiple WebSockets attempt to subscribe to the same application, the newer WebSocket wins, and the older one receives this event.",
+			"properties": {}
 		},
 		"BridgeCreated": {
 			"id": "BridgeCreated",
+			"extends": "Event",
 			"description": "Notification that a bridge has been created.",
 			"properties": {
 				"bridge": {
@@ -109,6 +98,7 @@
 		},
 		"BridgeDestroyed": {
 			"id": "BridgeDestroyed",
+			"extends": "Event",
 			"description": "Notification that a bridge has been destroyed.",
 			"properties": {
 				"bridge": {
@@ -119,6 +109,7 @@
 		},
 		"BridgeMerged": {
 			"id": "BridgeMerged",
+			"extends": "Event",
 			"description": "Notification that one bridge has merged into another.",
 			"properties": {
 				"bridge": {
@@ -133,6 +124,7 @@
 		},
 		"ChannelCreated": {
 			"id": "ChannelCreated",
+			"extends": "Event",
 			"description": "Notification that a channel has been created.",
 			"properties": {
 				"channel": {
@@ -141,24 +133,15 @@
 				}
 			}
 		},
-		"ChannelSnapshot": {
-			"id": "ChannelSnapshot",
-			"description": "Some part of channel state changed.",
-			"properties": {
-				"channel": {
-					"required": true,
-					"type": "Channel"
-				}
-			}
-		},
 		"ChannelDestroyed": {
 			"id": "ChannelDestroyed",
+			"extends": "Event",
 			"description": "Notification that a channel has been destroyed.",
 			"properties": {
 				"cause": {
 					"required": true,
 					"description": "Integer representation of the cause of the hangup",
-					"type": "integer"
+					"type": "int"
 				},
 				"cause_txt": {
 					"required": true,
@@ -173,6 +156,7 @@
 		},
 		"ChannelEnteredBridge": {
 			"id": "ChannelEnteredBridge",
+			"extends": "Event",
 			"description": "Notification that a channel has entered a bridge.",
 			"properties": {
 				"bridge": {
@@ -186,6 +170,7 @@
 		},
 		"ChannelLeftBridge": {
 			"id": "ChannelLeftBridge",
+			"extends": "Event",
 			"description": "Notification that a channel has left a bridge.",
 			"properties": {
 				"bridge": {
@@ -200,6 +185,7 @@
 		},
 		"ChannelStateChange": {
 			"id": "ChannelStateChange",
+			"extends": "Event",
 			"description": "Notification of a channel's state change.",
 			"properties": {
 				"channel": {
@@ -210,14 +196,19 @@
 		},
 		"ChannelDtmfReceived": {
 			"id": "ChannelDtmfReceived",
-			"description": "DTMF received on a channel.",
-			"notes": "This event is sent when the DTMF ends. There is no notification about the start of DTMF",
+			"extends": "Event",
+			"description": "DTMF received on a channel.\n\nThis event is sent when the DTMF ends. There is no notification about the start of DTMF",
 			"properties": {
 				"digit": {
 					"required": true,
 					"type": "string",
 					"description": "DTMF digit received (0-9, A-E, # or *)"
 				},
+				"duration_ms": {
+					"required": true,
+					"type": "int",
+					"description": "Number of milliseconds DTMF was received"
+				},
 				"channel": {
 					"required": true,
 					"type": "Channel",
@@ -227,32 +218,34 @@
 		},
 		"ChannelDialplan": {
 			"id": "ChannelDialplan",
+			"extends": "Event",
 			"description": "Channel changed location in the dialplan.",
 			"properties": {
-				"application": {
+				"channel": {
 					"required": true,
-					"type": "string",
-					"description": "The application that the channel is currently in."
+					"type": "Channel",
+					"description": "The channel that changed dialplan location."
 				},
-				"application_data": {
+				"dialplan_app": {
 					"required": true,
 					"type": "string",
-					"description": "The data that was passed to the application when it was invoked."
+					"description": "The application about to be executed."
 				},
-				"channel": {
+				"dialplan_app_data": {
 					"required": true,
-					"type": "Channel",
-					"description": "The channel that changed dialplan location."
+					"type": "string",
+					"description": "The data to be passed to the application."
 				}
 			}
 		},
 		"ChannelCallerId": {
 			"id": "ChannelCallerId",
+			"extends": "Event",
 			"description": "Channel changed Caller ID.",
 			"properties": {
 				"caller_presentation": {
 					"required": true,
-					"type": "integer",
+					"type": "int",
 					"description": "The integer representation of the Caller Presentation value."
 				},
 				"caller_presentation_txt": {
@@ -269,6 +262,7 @@
 		},
 		"ChannelUserevent": {
 			"id": "ChannelUserevent",
+			"extends": "Event",
 			"description": "User-generated event with additional user-defined fields in the object.",
 			"properties": {
 				"eventname": {
@@ -285,10 +279,11 @@
 		},
 		"ChannelHangupRequest": {
 			"id": "ChannelHangupRequest",
+			"extends": "Event",
 			"description": "A hangup was requested on the channel.",
 			"properties": {
 				"cause": {
-					"type": "integer",
+					"type": "int",
 					"description": "Integer representation of the cause of the hangup."
 				},
 				"soft": {
@@ -304,6 +299,7 @@
 		},
 		"ChannelVarset": {
 			"id": "ChannelVarset",
+			"extends": "Event",
 			"description": "Channel variable changed.",
 			"properties": {
 				"variable": {
@@ -317,14 +313,15 @@
 					"description": "The new value of the variable."
 				},
 				"channel": {
-					"required": true,
+					"required": false,
 					"type": "Channel",
-					"description": "The channel on which the variable was set."
+					"description": "The channel on which the variable was set.\n\nIf missing, the variable is a global variable."
 				}
 			}
 		},
 		"StasisEnd": {
 			"id": "StasisEnd",
+			"extends": "Event",
 			"description": "Notification that a channel has left a Stasis appliction.",
 			"properties": {
 				"channel": {
@@ -335,6 +332,7 @@
 		},
 		"StasisStart": {
 			"id": "StasisStart",
+			"extends": "Event",
 			"description": "Notification that a channel has entered a Stasis appliction.",
 			"properties": {
 				"args": {
diff --git a/rest-api/api-docs/playback.json b/rest-api/api-docs/playback.json
index 38ca5e1a7aafbb817661cc997fb510472e2fb820..884c0db2605a1f24bc89406aafa2c6b0fa9fa76d 100644
--- a/rest-api/api-docs/playback.json
+++ b/rest-api/api-docs/playback.json
@@ -103,11 +103,39 @@
 	"models": {
 		"Playback": {
 			"id": "Playback",
+			"description": "Object representing the playback of media to a channel",
 			"properties": {
 				"id": {
+					"type": "string",
+					"description": "ID for this playback operation",
+					"required": true
+				},
+				"media_uri": {
+					"type": "string",
+					"description": "URI for the media to play back.",
+					"required": true
+				},
+				"target_uri": {
+					"type": "string",
+					"description": "URI for the channel or bridge to play the media on",
+					"required": true
+				},
+				"language": {
+					"type": "string",
+					"description": "For media types that support multiple languages, the language requested for playback."
+				},
+				"state": {
+					"type": "string",
+					"description": "Current state of the playback operation.",
 					"required": true,
-					"description": "Playback's identifier.",
-					"type": "string"
+					"allowableValues": {
+						"valueType": "LIST",
+						"values": [
+							"queued",
+							"playing",
+							"complete"
+						]
+					}
 				}
 			}
 		}
diff --git a/rest-api/api-docs/recordings.json b/rest-api/api-docs/recordings.json
index 2f5f92a08848a0fd4f8710ed6fd317c343251a14..ce11d17c25edb3d7c9ec9f65275673a01b21b89f 100644
--- a/rest-api/api-docs/recordings.json
+++ b/rest-api/api-docs/recordings.json
@@ -7,18 +7,6 @@
 	"basePath": "http://localhost:8088/stasis",
 	"resourcePath": "/api-docs/recordings.{format}",
 	"apis": [
-		{
-			"path": "/recordings",
-			"description": "Recordings",
-			"operations": [
-				{
-					"httpMethod": "GET",
-					"summary": "List all recordings.",
-					"nickname": "getRecordings",
-					"responseClass": "List[Recording]"
-				}
-			]
-		},
 		{
 			"path": "/recordings/stored",
 			"description": "Recordings",
@@ -226,17 +214,9 @@
 		}
 	],
 	"models": {
-		"Recording": {
-			"id": "Recording",
-			"properties": {
-				"id": {
-					"required": true,
-					"type": "string"
-				}
-			}
-		},
 		"StoredRecording": {
 			"id": "StoredRecording",
+			"description": "A past recording that may be played back.",
 			"properties": {
 				"id": {
 					"required": true,
@@ -246,7 +226,7 @@
 					"required": true,
 					"type": "List[string]"
 				},
-				"durationSeconds": {
+				"duration_seconds": {
 					"required": false,
 					"type": "int"
 				},
@@ -259,6 +239,7 @@
 		},
 		"LiveRecording": {
 			"id": "LiveRecording",
+			"description": "A recording that is in progress",
 			"properties": {
 				"id": {
 					"required": true,
diff --git a/rest-api/api-docs/sounds.json b/rest-api/api-docs/sounds.json
index 06d84ea7e05b368f42527dc21b0b8032b44b1062..103738c45ae6d614ec06c245d5c10d391603fcc4 100644
--- a/rest-api/api-docs/sounds.json
+++ b/rest-api/api-docs/sounds.json
@@ -60,6 +60,7 @@
 	"models": {
 		"FormatLangPair": {
 			"id": "FormatLangPair",
+			"description": "Identifies the format and language of a sound file",
 			"properties": {
 				"language": {
 					"required": true,
@@ -73,6 +74,7 @@
 		},
 		"Sound": {
 			"id": "Sound",
+			"description": "A media file that may be played back.",
 			"properties": {
 				"id": {
 					"required": true,
diff --git a/tests/test_ari_model.c b/tests/test_ari_model.c
new file mode 100644
index 0000000000000000000000000000000000000000..21ad80ab53c336ece390d246736de54359eb0dd1
--- /dev/null
+++ b/tests/test_ari_model.c
@@ -0,0 +1,431 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Test the native ARI JSON validators.
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<depend>res_ari_model</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "../res/stasis_http/ari_model_validators.h"
+
+AST_TEST_DEFINE(validate_byte)
+{
+	RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref);
+	RAII_VAR(struct ast_json *, str, NULL, ast_json_unref);
+	int res;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = "/ari/validators/";
+		info->summary = "Test byte validation";
+		info->description =
+			"Test byte validation";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	uut = ast_json_integer_create(-128);
+	ast_test_validate(test, NULL != uut);
+	ast_test_validate(test, ari_validate_byte(uut));
+
+	res = ast_json_integer_set(uut, 0);
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, ari_validate_byte(uut));
+
+	res = ast_json_integer_set(uut, 255);
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, ari_validate_byte(uut));
+
+	res = ast_json_integer_set(uut, -129);
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, !ari_validate_byte(uut));
+
+	res = ast_json_integer_set(uut, 256);
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, !ari_validate_byte(uut));
+
+	str = ast_json_string_create("not a byte");
+	ast_test_validate(test, NULL != str);
+	ast_test_validate(test, !ari_validate_byte(str));
+
+	/* Even if the string has an integral value */
+	res = ast_json_string_set(str, "0");
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, !ari_validate_byte(str));
+
+	ast_test_validate(test, !ari_validate_byte(ast_json_null()));
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(validate_boolean)
+{
+	RAII_VAR(struct ast_json *, str, NULL, ast_json_unref);
+	int res;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = "/ari/validators/";
+		info->summary = "Test byte validation";
+		info->description =
+			"Test byte validation";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_validate(test, ari_validate_boolean(ast_json_true()));
+	ast_test_validate(test, ari_validate_boolean(ast_json_false()));
+
+	str = ast_json_string_create("not a bool");
+	ast_test_validate(test, NULL != str);
+	ast_test_validate(test, !ari_validate_boolean(str));
+
+	/* Even if the string has a boolean value */
+	res = ast_json_string_set(str, "true");
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, !ari_validate_boolean(str));
+
+	/* Even if the string has a boolean text in it */
+	res = ast_json_string_set(str, "true");
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, !ari_validate_boolean(str));
+
+	ast_test_validate(test, !ari_validate_boolean(ast_json_null()));
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(validate_int)
+{
+	RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref);
+	RAII_VAR(struct ast_json *, str, NULL, ast_json_unref);
+	int res;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = "/ari/validators/";
+		info->summary = "Test int validation";
+		info->description =
+			"Test int validation";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	uut = ast_json_integer_create(-2147483648);
+	ast_test_validate(test, NULL != uut);
+	ast_test_validate(test, ari_validate_int(uut));
+
+	res = ast_json_integer_set(uut, 0);
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, ari_validate_int(uut));
+
+	res = ast_json_integer_set(uut, 2147483647);
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, ari_validate_int(uut));
+
+	res = ast_json_integer_set(uut, -2147483649LL);
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, !ari_validate_int(uut));
+
+	res = ast_json_integer_set(uut, 2147483648LL);
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, !ari_validate_int(uut));
+
+	str = ast_json_string_create("not a int");
+	ast_test_validate(test, NULL != str);
+	ast_test_validate(test, !ari_validate_int(str));
+
+	/* Even if the string has an integral value */
+	res = ast_json_string_set(str, "0");
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, !ari_validate_int(str));
+
+	ast_test_validate(test, !ari_validate_int(ast_json_null()));
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(validate_long)
+{
+	RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref);
+	RAII_VAR(struct ast_json *, str, NULL, ast_json_unref);
+	int res;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = "/ari/validators/";
+		info->summary = "Test long validation";
+		info->description =
+			"Test long validation";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	uut = ast_json_integer_create(0);
+	ast_test_validate(test, NULL != uut);
+	ast_test_validate(test, ari_validate_long(uut));
+
+	str = ast_json_string_create("not a long");
+	ast_test_validate(test, NULL != str);
+	ast_test_validate(test, !ari_validate_long(str));
+
+	/* Even if the string has an integral value */
+	res = ast_json_string_set(str, "0");
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, !ari_validate_long(str));
+
+	ast_test_validate(test, !ari_validate_long(ast_json_null()));
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(validate_string)
+{
+	RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref);
+	RAII_VAR(struct ast_json *, str, NULL, ast_json_unref);
+	int res;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = "/ari/validators/";
+		info->summary = "Test string validation";
+		info->description =
+			"Test string validation";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	uut = ast_json_string_create("text");
+	ast_test_validate(test, NULL != uut);
+	ast_test_validate(test, ari_validate_string(uut));
+
+	res = ast_json_string_set(uut, "");
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, ari_validate_string(uut));
+
+	ast_test_validate(test, !ari_validate_string(ast_json_null()));
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(validate_date)
+{
+	RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref);
+	RAII_VAR(struct ast_json *, str, NULL, ast_json_unref);
+	enum ast_test_result_state test_res;
+	int res;
+	int i;
+	const char *valid_dates[] = {
+		/* Time is optional */
+		"2013-06-17",
+		/* Seconds are optional */
+		"2013-06-17T23:59Z",
+		/* Subseconds are optional */
+		"2013-06-17T23:59:59Z",
+		/* Leap seconds are valid */
+		"2013-06-30T23:59:61Z",
+		/* Subseconds are allowed */
+		"2013-06-17T23:59:59.999999Z",
+		/* Now with -06:00 for the timezone */
+		"2013-06-17T23:59-06:00",
+		"2013-06-17T23:59:59-06:00",
+		"2013-06-30T23:59:61-06:00",
+		"2013-06-17T23:59:59.999999-06:00",
+		/* Again, with +06:30 for the timezone */
+		"2013-06-17T23:59+06:30",
+		"2013-06-17T23:59:59+06:30",
+		"2013-06-30T23:59:61+06:30",
+		"2013-06-17T23:59:59.999999+06:30",
+		/* So the colon in the timezone is optional */
+		"2013-06-17T23:59-0600",
+		"2013-06-17T23:59:59-0600",
+		"2013-06-30T23:59:61-0600",
+		"2013-06-17T23:59:59.999999-0600",
+		/* Sure, why not */
+		"2013-06-17T23:59+0630",
+		"2013-06-17T23:59:59+0630",
+		"2013-06-30T23:59:61+0630",
+		"2013-06-17T23:59:59.999999+0630",
+		"9999-12-31T23:59:61.999999Z",
+		/* In fact, you don't even have to specify minutes */
+		"2013-06-17T23:59-06",
+		"2013-06-17T23:59:59-06",
+		"2013-06-30T23:59:61-06",
+		"2013-06-17T23:59:59.999999-06",
+	};
+
+	/* There are lots of invalid dates that the validator lets through.
+	 * Those would be strings properly formatted as a ridiculous date. Such
+	 * as 0000-00-00, or 9999-19-39. Those are harder to catch with a regex,
+	 * and actually aren't as important. So long as the valid dates pass the
+	 * validator, and poorly formatted dates are rejected, it's fine.
+	 * Catching the occasional ridiculous date is just bonus.
+	 */
+	const char *invalid_dates[] = {
+		"",
+		"Not a date",
+		"2013-06-17T", /* Missing time, but has T */
+		"2013-06-17T23:59:59.Z", /* Missing subsecond, but has dot */
+		"2013-06-17T23:59", /* Missing timezone, but has time */
+		"2013-06-17T23:59:59.999999", /* Missing timezone */
+		"9999-99-31T23:59:61.999999Z", /* Invalid month */
+		"9999-12-99T23:59:61.999999Z", /* Invalid day */
+		"9999-12-31T99:59:61.999999Z", /* Invalid hour */
+		"9999-12-31T23:99:61.999999Z", /* Invalid minute */
+		"9999-12-31T23:59:99.999999Z", /* Invalid second */
+		"2013-06-17T23:59:59.999999-99:00", /* Invalid timezone */
+		"2013-06-17T23:59:59.999999-06:99", /* Invalid timezone */
+		"2013-06-17T23:59:59.999999-06:", /* Invalid timezone */
+		"2013-06-17T23:59:59.999999-06:0", /* Invalid timezone */
+		"2013-06-17T23:59:59.999999-060", /* Invalid timezone */
+	};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = "/ari/validators/";
+		info->summary = "Test date validation";
+		info->description =
+			"Test date validation";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	uut = ast_json_string_create("");
+	ast_test_validate(test, NULL != uut);
+
+	/* Instead of using ast_test_validate, we'll collect the results from
+	 * several test cases, since we have so many */
+	test_res = AST_TEST_PASS;
+	for (i = 0; i < ARRAY_LEN(valid_dates); ++i) {
+		res = ast_json_string_set(uut, valid_dates[i]);
+		ast_test_validate(test, 0 == res);
+		if (!ari_validate_date(uut)) {
+			ast_test_status_update(test,
+				"Expected '%s' to be a valid date\n",
+				valid_dates[i]);
+			test_res = AST_TEST_FAIL;
+		}
+	}
+
+	for (i = 0; i < ARRAY_LEN(invalid_dates); ++i) {
+		res = ast_json_string_set(uut, invalid_dates[i]);
+		ast_test_validate(test, 0 == res);
+		if (ari_validate_date(uut)) {
+			ast_test_status_update(test,
+				"Expected '%s' to be an invalid date\n",
+				invalid_dates[i]);
+			test_res = AST_TEST_FAIL;
+		}
+	}
+
+	ast_test_validate(test, !ari_validate_string(ast_json_null()));
+
+	return test_res;
+}
+
+AST_TEST_DEFINE(validate_list)
+{
+	RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref);
+	RAII_VAR(struct ast_json *, str, NULL, ast_json_unref);
+	int res;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = "/ari/validators/";
+		info->summary = "Test list validation";
+		info->description =
+			"Test list validation";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	uut = ast_json_array_create();
+	ast_test_validate(test, NULL != uut);
+	ast_test_validate(test, ari_validate_list(uut, ari_validate_string));
+	ast_test_validate(test, ari_validate_list(uut, ari_validate_int));
+
+	res = ast_json_array_append(uut, ast_json_string_create(""));
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, ari_validate_list(uut, ari_validate_string));
+	ast_test_validate(test, !ari_validate_list(uut, ari_validate_int));
+
+	res = ast_json_array_append(uut, ast_json_integer_create(0));
+	ast_test_validate(test, 0 == res);
+	ast_test_validate(test, !ari_validate_list(uut, ari_validate_string));
+	ast_test_validate(test, !ari_validate_list(uut, ari_validate_int));
+
+	ast_test_validate(test,
+		!ari_validate_list(ast_json_null(), ari_validate_string));
+
+	return AST_TEST_PASS;
+}
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(validate_byte);
+	AST_TEST_UNREGISTER(validate_boolean);
+	AST_TEST_UNREGISTER(validate_int);
+	AST_TEST_UNREGISTER(validate_long);
+	AST_TEST_UNREGISTER(validate_string);
+	AST_TEST_UNREGISTER(validate_date);
+	AST_TEST_UNREGISTER(validate_list);
+	return 0;
+}
+
+static int load_module(void)
+{
+	AST_TEST_REGISTER(validate_byte);
+	AST_TEST_REGISTER(validate_boolean);
+	AST_TEST_REGISTER(validate_int);
+	AST_TEST_REGISTER(validate_long);
+	AST_TEST_REGISTER(validate_string);
+	AST_TEST_REGISTER(validate_date);
+	AST_TEST_REGISTER(validate_list);
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Skeleton (sample) Test");
diff --git a/tests/test_res_stasis.c b/tests/test_res_stasis.c
index 321f40f3d59b1015ae968aab2c6aac680243eee1..5865f0951dd88179d254d99577bcb8181a8fd111 100644
--- a/tests/test_res_stasis.c
+++ b/tests/test_res_stasis.c
@@ -157,7 +157,9 @@ AST_TEST_DEFINE(app_replaced)
 
 	stasis_app_register(app_name, test_handler, app_data1);
 	stasis_app_register(app_name, test_handler, app_data2);
-	expected_message1 = ast_json_pack("[{s: {s: s}}]", "application_replaced", "application", app_name);
+	expected_message1 = ast_json_pack("[{s: s, s: s}]",
+		"type", "ApplicationReplaced",
+		"application", app_name);
 	message = ast_json_pack("{ s: o }", "test-message", ast_json_null());
 	expected_message2 = ast_json_pack("[o]", ast_json_ref(message));
 
diff --git a/tests/test_stasis_channels.c b/tests/test_stasis_channels.c
index 214d773963e710a27a2b35b552656ff8f908b1b9..a3f8828197c45c6312df3620f7e9bf9283d080ff 100644
--- a/tests/test_stasis_channels.c
+++ b/tests/test_stasis_channels.c
@@ -256,8 +256,7 @@ AST_TEST_DEFINE(channel_snapshot_json)
 	ast_test_validate(test, NULL != snapshot);
 
 	actual = ast_channel_snapshot_to_json(snapshot);
-	expected = ast_json_pack("{ s: s, s: s, s: s, s: s, s: s, s: s, s: s,"
-				 "  s: s, s: s, s: s, s: s,"
+	expected = ast_json_pack("{ s: s, s: s, s: s, s: s,"
 				 "  s: { s: s, s: s, s: i },"
 				 "  s: { s: s, s: s },"
 				 "  s: { s: s, s: s },"
@@ -266,14 +265,7 @@ AST_TEST_DEFINE(channel_snapshot_json)
 				 "name", "TEST/name",
 				 "state", "Down",
 				 "accountcode", "acctcode",
-				 "peeraccount", "",
-				 "userfield", "",
-				 "uniqueid", ast_channel_uniqueid(chan),
-				 "linkedid", ast_channel_uniqueid(chan),
-				 "parkinglot", "",
-				 "hangupsource", "",
-				 "appl", "",
-				 "data", "",
+				 "id", ast_channel_uniqueid(chan),
 				 "dialplan",
 				 "context", "context",
 				 "exten", "exten",