diff --git a/include/asterisk/acl.h b/include/asterisk/acl.h
index a0f06df57b58c065ea9910f3b77df8e3b9e3a21b..d1773b6b16b6a916b2b85a3a845800d51326bb60 100644
--- a/include/asterisk/acl.h
+++ b/include/asterisk/acl.h
@@ -134,6 +134,13 @@ void ast_copy_ha(const struct ast_ha *from, struct ast_ha *to);
  */
 struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha *path, int *error);
 
+/*!
+ * \brief Convert HAs to a comma separated string value
+ * \param ha the starting ha head
+ * \param buf string buffer to convert data to
+ */
+void ast_ha_join(const struct ast_ha *ha, struct ast_str **buf);
+
 /*!
  * \brief Add a rule to an ACL struct
  *
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 8c78720841f1c3728990cc2b8efd480538f8dff6..b6dd441d4d7c5638f7ee5a3fe7f14166eb2a0ae4 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -1559,5 +1559,154 @@ void *ast_sip_dict_set(pj_pool_t* pool, void *ht,
 #define ast_sip_mod_data_set(pool, mod_data, id, key, val)		\
 	mod_data[id] = ast_sip_dict_set(pool, mod_data[id], key, val)
 
+/*!
+ * \brief Function pointer for contact callbacks.
+ */
+typedef int (*on_contact_t)(const struct ast_sip_aor *aor,
+			    const struct ast_sip_contact *contact,
+			    int last, void *arg);
+
+/*!
+ * \brief For every contact on an AOR call the given 'on_contact' handler.
+ *
+ * \param aor the aor containing a list of contacts to iterate
+ * \param on_contact callback on each contact on an AOR
+ * \param arg user data passed to handler
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_for_each_contact(const struct ast_sip_aor *aor,
+			     on_contact_t on_contact, void *arg);
+
+/*!
+ * \brief Handler used to convert a contact to a string.
+ *
+ * \param aor the aor containing a list of contacts to iterate
+ * \param contact the contact to convert
+ * \param last is this the last contact
+ * \param arg user data passed to handler
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_contact_to_str(const struct ast_sip_aor *aor,
+			   const struct ast_sip_contact *contact,
+			   int last, void *arg);
+
+/*!
+ * \brief For every aor in the comma separated aors string call the
+ *        given 'on_aor' handler.
+ *
+ * \param aors a comma separated list of aors
+ * \param on_aor callback for each aor
+ * \param arg user data passed to handler
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_for_each_aor(const char *aors, ao2_callback_fn on_aor, void *arg);
+
+/*!
+ * \brief For every auth in the array call the given 'on_auth' handler.
+ *
+ * \param array an array of auths
+ * \param on_auth callback for each auth
+ * \param arg user data passed to handler
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_for_each_auth(const struct ast_sip_auth_array *array,
+			  ao2_callback_fn on_auth, void *arg);
+
+/*!
+ * \brief Converts the given auth type to a string
+ *
+ * \param type the auth type to convert
+ * \retval a string representative of the auth type
+ */
+const char *ast_sip_auth_type_to_str(enum ast_sip_auth_type type);
+
+/*!
+ * \brief Converts an auths array to a string of comma separated values
+ *
+ * \param auths an auth array
+ * \param buf the string buffer to write the object data
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_auths_to_str(const struct ast_sip_auth_array *auths, char **buf);
+
+/*
+ * \brief AMI variable container
+ */
+struct ast_sip_ami {
+	/*! Manager session */
+	struct mansession *s;
+	/*! Manager message */
+	const struct message *m;
+	/*! user specified argument data */
+	void *arg;
+};
+
+/*!
+ * \brief Creates a string to store AMI event data in.
+ *
+ * \param event the event to set
+ * \param ami AMI session and message container
+ * \retval an initialized ast_str or NULL on error.
+ */
+struct ast_str *ast_sip_create_ami_event(const char *event,
+					 struct ast_sip_ami *ami);
+
+/*!
+ * \brief An entity responsible formatting endpoint information.
+ */
+struct ast_sip_endpoint_formatter {
+	/*!
+	 * \brief Callback used to format endpoint information over AMI.
+	 */
+	int (*format_ami)(const struct ast_sip_endpoint *endpoint,
+			  struct ast_sip_ami *ami);
+	AST_RWLIST_ENTRY(ast_sip_endpoint_formatter) next;
+};
+
+/*!
+ * \brief Register an endpoint formatter.
+ *
+ * \param obj the formatter to register
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+int ast_sip_register_endpoint_formatter(struct ast_sip_endpoint_formatter *obj);
+
+/*!
+ * \brief Unregister an endpoint formatter.
+ *
+ * \param obj the formatter to unregister
+ */
+void ast_sip_unregister_endpoint_formatter(struct ast_sip_endpoint_formatter *obj);
+
+/*!
+ * \brief Converts a sorcery object to a string of object properties.
+ *
+ * \param obj the sorcery object to convert
+ * \param str the string buffer to write the object data
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_sorcery_object_to_ami(const void *obj, struct ast_str **buf);
+
+/*!
+ * \brief Formats the endpoint and sends over AMI.
+ *
+ * \param endpoint the endpoint to format and send
+ * \param endpoint ami AMI variable container
+ * \param count the number of formatters operated on
+ * \retval 0 Success, otherwise non-zero on error
+ */
+int ast_sip_format_endpoint_ami(struct ast_sip_endpoint *endpoint,
+				struct ast_sip_ami *ami, int *count);
+
+/*!
+ * \brief Format auth details for AMI.
+ *
+ * \param auths an auth array
+ * \param ami ami variable container
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_format_auths_ami(const struct ast_sip_auth_array *auths,
+			     struct ast_sip_ami *ami);
 
 #endif /* _RES_PJSIP_H */
diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h
index d985b9019c6e4cf772839ea14534f66c654d10e5..2f676f193807c9cd321c8cf0cacba39afc3127c5 100644
--- a/include/asterisk/res_pjsip_pubsub.h
+++ b/include/asterisk/res_pjsip_pubsub.h
@@ -252,7 +252,7 @@ struct ast_sip_subscription_handler {
 	 * during this callback. The handler MUST, however, begin the destruction
 	 * process for the subscription during this callback.
 	 */
-   void (*subscription_shutdown)(struct ast_sip_subscription *subscription);
+	void (*subscription_shutdown)(struct ast_sip_subscription *subscription);
 
 	/*!
 	 * \brief Called when a SUBSCRIBE arrives in order to create a new subscription
@@ -366,6 +366,16 @@ struct ast_sip_subscription_handler {
 	 * \retval non-zero Failure
 	 */
 	int (*refresh_subscription)(struct ast_sip_subscription *sub);
+
+	/*!
+	 * \brief Converts the subscriber to AMI
+	 *
+	 * This is a subscriber callback.
+	 *
+	 * \param sub The subscription
+	 * \param buf The string to write AMI data
+	 */
+	void (*to_ami)(struct ast_sip_subscription *sub, struct ast_str **buf);
 	AST_LIST_ENTRY(ast_sip_subscription_handler) next;
 };
 
diff --git a/include/asterisk/sorcery.h b/include/asterisk/sorcery.h
index dad0c439c1caebc42ab4f22441e0c3b77fe6b5ee..8ce3eccf3f3c425bd7a1d9d19e4fa32843aa8ec6 100644
--- a/include/asterisk/sorcery.h
+++ b/include/asterisk/sorcery.h
@@ -102,6 +102,9 @@ extern "C" {
 /*! \brief Maximum size of an object type */
 #define MAX_OBJECT_TYPE 64
 
+/*! \brief Maximum length of an object field name */
+#define MAX_OBJECT_FIELD 128
+
 /*!
  * \brief Retrieval flags
  */
diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h
index fc92d4889f5567bd8341c7190a766e3954445df4..5dbebba95a85f7c91855d9e946dbc61dd4564a4d 100644
--- a/include/asterisk/strings.h
+++ b/include/asterisk/strings.h
@@ -332,7 +332,23 @@ int attribute_pure ast_true(const char *val);
 int attribute_pure ast_false(const char *val);
 
 /*
- *  \brief Join an array of strings into a single string.
+ * \brief Join an array of strings into a single string.
+ * \param s the resulting string buffer
+ * \param len the length of the result buffer, s
+ * \param w an array of strings to join.
+ * \param size the number of elements to join
+ * \param delim delimiter between elements
+ *
+ * This function will join all of the strings in the array 'w' into a single
+ * string.  It will also place 'delim' in the result buffer in between each
+ * string from 'w'.
+ * \since 12
+*/
+void ast_join_delim(char *s, size_t len, const char * const w[],
+		    unsigned int size, char delim);
+
+/*
+ * \brief Join an array of strings into a single string.
  * \param s the resulting string buffer
  * \param len the length of the result buffer, s
  * \param w an array of strings to join.
@@ -341,7 +357,33 @@ int attribute_pure ast_false(const char *val);
  * string.  It will also place a space in the result buffer in between each
  * string from 'w'.
 */
-void ast_join(char *s, size_t len, const char * const w[]);
+#define ast_join(s, len, w) ast_join_delim(s, len, w, -1, ' ')
+
+/*
+ * \brief Attempts to convert the given string to camel case using
+ *        the specified delimiter.
+ *
+ * note - returned string needs to be freed
+ *
+ * \param s the string to convert
+ * \param delim delimiter to parse out
+ *
+ * \retval The string converted to "CamelCase"
+ * \since 12
+*/
+char *ast_to_camel_case_delim(const char *s, const char *delim);
+
+/*
+ * \brief Attempts to convert the given string to camel case using
+ *        an underscore as the specified delimiter.
+ *
+ * note - returned string needs to be freed
+ *
+ * \param s the string to convert
+ *
+ * \retval The string converted to "CamelCase"
+*/
+#define ast_to_camel_case(s) ast_to_camel_case_delim(s, "_")
 
 /*
   \brief Parse a time (integer) string.
diff --git a/include/asterisk/utils.h b/include/asterisk/utils.h
index e24dc02d89097cbc3ef7371a32d4f80136b44822..e441ba0558b7553de15c46f24279e522d3985461 100644
--- a/include/asterisk/utils.h
+++ b/include/asterisk/utils.h
@@ -755,6 +755,24 @@ int ast_safe_mkdir(const char *base_path, const char *path, int mode);
 
 #define ARRAY_LEN(a) (size_t) (sizeof(a) / sizeof(0[a]))
 
+/*!
+ * \brief Checks to see if value is within the given bounds
+ *
+ * \param v the value to check
+ * \param min minimum lower bound (inclusive)
+ * \param max maximum upper bound (inclusive)
+ * \return 0 if value out of bounds, otherwise true (non-zero)
+ */
+#define IN_BOUNDS(v, min, max) ((v) >= (min)) && ((v) <= (max))
+
+/*!
+ * \brief Checks to see if value is within the bounds of the given array
+ *
+ * \param v the value to check
+ * \param a the array to bound check
+ * \return 0 if value out of bounds, otherwise true (non-zero)
+ */
+#define ARRAY_IN_BOUNDS(v, a) IN_BOUNDS(v, 0, ARRAY_LEN(a) - 1)
 
 /* Definition for Digest authorization */
 struct ast_http_digest {
diff --git a/main/acl.c b/main/acl.c
index a4567beda9f38bc9edb10c3ee178e73468b00b02..c341125fd681afe3d91220a98433ba69cb2a8d14 100644
--- a/main/acl.c
+++ b/main/acl.c
@@ -664,6 +664,19 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha
 	return ret;
 }
 
+void ast_ha_join(const struct ast_ha *ha, struct ast_str **buf)
+{
+	for (; ha; ha = ha->next) {
+		const char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
+		ast_str_append(buf, 0, "%s%s/%s",
+			       ha->sense == AST_SENSE_ALLOW ? "!" : "",
+			       addr, ast_sockaddr_stringify_addr(&ha->netmask));
+		if (ha->next) {
+			ast_str_append(buf, 0, ",");
+		}
+	}
+}
+
 enum ast_acl_sense ast_apply_acl(struct ast_acl_list *acl_list, const struct ast_sockaddr *addr, const char *purpose)
 {
 	struct ast_acl *acl;
diff --git a/main/sorcery.c b/main/sorcery.c
index 3b58188dac993148ae00daeef6bfb8455b0ac06c..99ee2a90ebe4adac252f42c37d645c2e96e1b5d1 100644
--- a/main/sorcery.c
+++ b/main/sorcery.c
@@ -52,9 +52,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 /*! \brief Number of buckets for types (should be prime for performance reasons) */
 #define TYPE_BUCKETS 53
 
-/*! \brief Maximum length of an object field name */
-#define MAX_OBJECT_FIELD 128
-
 /*! \brief Thread pool for observers */
 static struct ast_threadpool *threadpool;
 
diff --git a/main/utils.c b/main/utils.c
index 8252488a9e07bc3cae32e90502e57e465f201d45..b1a95425c503a76cd317eb76bb8222c5132ea82c 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -1707,7 +1707,7 @@ char *ast_process_quotes_and_slashes(char *start, char find, char replace_with)
 	return dataPut;
 }
 
-void ast_join(char *s, size_t len, const char * const w[])
+void ast_join_delim(char *s, size_t len, const char * const w[], unsigned int size, char delim)
 {
 	int x, ofs = 0;
 	const char *src;
@@ -1715,9 +1715,9 @@ void ast_join(char *s, size_t len, const char * const w[])
 	/* Join words into a string */
 	if (!s)
 		return;
-	for (x = 0; ofs < len && w[x]; x++) {
+	for (x = 0; ofs < len && w[x] && x < size; x++) {
 		if (x > 0)
-			s[ofs++] = ' ';
+			s[ofs++] = delim;
 		for (src = w[x]; *src && ofs < len; src++)
 			s[ofs++] = *src;
 	}
@@ -1726,6 +1726,25 @@ void ast_join(char *s, size_t len, const char * const w[])
 	s[ofs] = '\0';
 }
 
+char *ast_to_camel_case_delim(const char *s, const char *delim)
+{
+	char *res = ast_strdup(s);
+	char *front, *back, *buf = res;
+	int size;
+
+	front = strtok_r(buf, delim, &back);
+
+	while (front) {
+		size = strlen(front);
+		*front = toupper(*front);
+		ast_copy_string(buf, front, size + 1);
+		buf += size;
+		front = strtok_r(NULL, delim, &back);
+	}
+
+	return res;
+}
+
 /*
  * stringfields support routines.
  */
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index cda22a3f559a09bf23362ddd20a1fbd47f6fa046..db4f32f2cbf5e5f5fde5c8fc2333edb8ab9c33af 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -1021,6 +1021,41 @@
 			<para>Qualify a chan_pjsip endpoint.</para>
 		</description>
 	</manager>
+	<manager name="PJSIPShowEndpoints" language="en_US">
+		<synopsis>
+			Lists PJSIP endpoints.
+		</synopsis>
+		<syntax />
+		<description>
+			<para>
+			Provides a listing of all endpoints.  For each endpoint an <literal>EndpointList</literal> event
+			is raised that contains relevant attributes and status information.  Once all
+			endpoints have been listed an <literal>EndpointListComplete</literal> event is issued.
+                        </para>
+		</description>
+	</manager>
+	<manager name="PJSIPShowEndpoint" language="en_US">
+		<synopsis>
+			Detail listing of an endpoint and its objects.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="Endpoint" required="true">
+				<para>The endpoint to list.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>
+			Provides a detailed listing of options for a given endpoint.  Events are issued
+			showing the configuration and status of the endpoint and associated objects.  These
+			events include <literal>EndpointDetail</literal>, <literal>AorDetail</literal>,
+			<literal>AuthDetail</literal>, <literal>TransportDetail</literal>, and
+			<literal>IdentifyDetail</literal>.  Some events may be listed multiple times if multiple objects are
+			associated (for instance AoRs).  Once all detail events have been raised a final
+			<literal>EndpointDetailComplete</literal> event is issued.
+                        </para>
+		</description>
+	</manager>
  ***/
 
 
@@ -1205,6 +1240,49 @@ struct ast_sip_endpoint *ast_sip_identify_endpoint(pjsip_rx_data *rdata)
 	return endpoint;
 }
 
+AST_RWLIST_HEAD_STATIC(endpoint_formatters, ast_sip_endpoint_formatter);
+
+int ast_sip_register_endpoint_formatter(struct ast_sip_endpoint_formatter *obj)
+{
+	SCOPED_LOCK(lock, &endpoint_formatters, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+	AST_RWLIST_INSERT_TAIL(&endpoint_formatters, obj, next);
+	ast_module_ref(ast_module_info->self);
+	return 0;
+}
+
+void ast_sip_unregister_endpoint_formatter(struct ast_sip_endpoint_formatter *obj)
+{
+	struct ast_sip_endpoint_formatter *i;
+	SCOPED_LOCK(lock, &endpoint_formatters, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&endpoint_formatters, i, next) {
+		if (i == obj) {
+			AST_RWLIST_REMOVE_CURRENT(next);
+			ast_module_unref(ast_module_info->self);
+			break;
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+}
+
+int ast_sip_format_endpoint_ami(struct ast_sip_endpoint *endpoint,
+				struct ast_sip_ami *ami, int *count)
+{
+	int res = 0;
+	struct ast_sip_endpoint_formatter *i;
+	SCOPED_LOCK(lock, &endpoint_formatters, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+	*count = 0;
+	AST_RWLIST_TRAVERSE(&endpoint_formatters, i, next) {
+		if (i->format_ami && ((res = i->format_ami(endpoint, ami)) < 0)) {
+			return res;
+		}
+
+		if (!res) {
+			(*count)++;
+		}
+	}
+	return 0;
+}
+
 pjsip_endpoint *ast_sip_get_pjsip_endpoint(void)
 {
 	return ast_pjsip_endpoint;
@@ -1964,7 +2042,7 @@ static int load_module(void)
 
 	ast_sip_initialize_global_headers();
 
-	if (ast_res_pjsip_initialize_configuration()) {
+	if (ast_res_pjsip_initialize_configuration(ast_module_info)) {
 		ast_log(LOG_ERROR, "Failed to initialize SIP configuration. Aborting load\n");
 		ast_sip_destroy_global_headers();
 		stop_monitor_thread();
diff --git a/res/res_pjsip.exports.in b/res/res_pjsip.exports.in
index 5afafc481ee1b56c5217d0a229520c4f34b5bb67..8b62abbfe45b84b19d89f93f71ac332a76eecf08 100644
--- a/res/res_pjsip.exports.in
+++ b/res/res_pjsip.exports.in
@@ -1,77 +1,8 @@
 {
 	global:
-		LINKER_SYMBOL_PREFIXast_sip_register_service;
-		LINKER_SYMBOL_PREFIXast_sip_unregister_service;
-		LINKER_SYMBOL_PREFIXast_sip_register_authenticator;
-		LINKER_SYMBOL_PREFIXast_sip_unregister_authenticator;
-		LINKER_SYMBOL_PREFIXast_sip_register_outbound_authenticator;
-		LINKER_SYMBOL_PREFIXast_sip_unregister_outbound_authenticator;
-		LINKER_SYMBOL_PREFIXast_sip_register_endpoint_identifier;
-		LINKER_SYMBOL_PREFIXast_sip_unregister_endpoint_identifier;
-		LINKER_SYMBOL_PREFIXast_sip_create_serializer;
-		LINKER_SYMBOL_PREFIXast_sip_push_task;
-		LINKER_SYMBOL_PREFIXast_sip_push_task_synchronous;
-		LINKER_SYMBOL_PREFIXast_sip_create_request;
-		LINKER_SYMBOL_PREFIXast_sip_create_request_with_auth;
-		LINKER_SYMBOL_PREFIXast_sip_send_request;
-		LINKER_SYMBOL_PREFIXast_sip_requires_authentication;
-		LINKER_SYMBOL_PREFIXast_sip_authenticate_request;
-		LINKER_SYMBOL_PREFIXast_sip_get_authentication_credentials;
-		LINKER_SYMBOL_PREFIXast_sip_check_authentication;
-		LINKER_SYMBOL_PREFIXast_sip_create_auth_challenge_response;
-		LINKER_SYMBOL_PREFIXast_sip_set_outbound_authentication_credentials;
-		LINKER_SYMBOL_PREFIXast_sip_dialog_setup_outbound_authentication;
-		LINKER_SYMBOL_PREFIXast_sip_add_digest_to_challenge;
-		LINKER_SYMBOL_PREFIXast_sip_identify_endpoint;
-		LINKER_SYMBOL_PREFIXast_sip_add_header;
-		LINKER_SYMBOL_PREFIXast_sip_add_body;
-		LINKER_SYMBOL_PREFIXast_sip_add_body_multipart;
-		LINKER_SYMBOL_PREFIXast_sip_append_body;
-		LINKER_SYMBOL_PREFIXast_sip_get_pjsip_endpoint;
-		LINKER_SYMBOL_PREFIXast_sip_endpoint_alloc;
-		LINKER_SYMBOL_PREFIXast_sip_get_endpoints;
+		LINKER_SYMBOL_PREFIXast_sip_*;
 		LINKER_SYMBOL_PREFIXast_copy_pj_str;
-		LINKER_SYMBOL_PREFIXast_sip_get_sorcery;
-		LINKER_SYMBOL_PREFIXast_sip_create_dialog_uac;
-		LINKER_SYMBOL_PREFIXast_sip_create_dialog_uas;
-		LINKER_SYMBOL_PREFIXast_sip_location_retrieve_aor;
-		LINKER_SYMBOL_PREFIXast_sip_location_retrieve_first_aor_contact;
-		LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact_from_aor_list;
-		LINKER_SYMBOL_PREFIXast_sip_location_retrieve_aor_contacts;
-		LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact;
-		LINKER_SYMBOL_PREFIXast_sip_location_add_contact_transport;
-		LINKER_SYMBOL_PREFIXast_sip_location_delete_contact_transport;
-		LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact_transport_by_uri;
-		LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact_transport_by_transport;
-		LINKER_SYMBOL_PREFIXast_sip_location_add_contact;
-		LINKER_SYMBOL_PREFIXast_sip_location_update_contact;
-		LINKER_SYMBOL_PREFIXast_sip_location_delete_contact;
 		LINKER_SYMBOL_PREFIXast_pjsip_rdata_get_endpoint;
-		LINKER_SYMBOL_PREFIXast_sip_thread_is_servant;
-		LINKER_SYMBOL_PREFIXast_sip_dialog_set_serializer;
-		LINKER_SYMBOL_PREFIXast_sip_dialog_set_endpoint;
-		LINKER_SYMBOL_PREFIXast_sip_dialog_get_endpoint;
-		LINKER_SYMBOL_PREFIXast_sip_retrieve_auths;
-		LINKER_SYMBOL_PREFIXast_sip_cleanup_auths;
-		LINKER_SYMBOL_PREFIXast_sip_is_content_type;
-		LINKER_SYMBOL_PREFIXast_sip_get_artificial_endpoint;
-		LINKER_SYMBOL_PREFIXast_sip_get_artificial_auth;
-		LINKER_SYMBOL_PREFIXast_sip_report_invalid_endpoint;
-		LINKER_SYMBOL_PREFIXast_sip_report_failed_acl;
-		LINKER_SYMBOL_PREFIXast_sip_report_auth_failed_challenge_response;
-		LINKER_SYMBOL_PREFIXast_sip_report_auth_success;
-		LINKER_SYMBOL_PREFIXast_sip_report_auth_challenge_sent;
-		LINKER_SYMBOL_PREFIXast_sip_report_req_no_support;
-		LINKER_SYMBOL_PREFIXast_sip_report_mem_limit;
-		LINKER_SYMBOL_PREFIXast_sip_initialize_global_headers;
-		LINKER_SYMBOL_PREFIXast_sip_destroy_global_headers;
-		LINKER_SYMBOL_PREFIXast_sip_add_global_request_header;
-		LINKER_SYMBOL_PREFIXast_sip_add_global_response_header;
-		LINKER_SYMBOL_PREFIXast_sip_initialize_sorcery_global;
-		LINKER_SYMBOL_PREFIXast_sip_auth_array_init;
-		LINKER_SYMBOL_PREFIXast_sip_auth_array_destroy;
-		LINKER_SYMBOL_PREFIXast_sip_dict_get;
-		LINKER_SYMBOL_PREFIXast_sip_dict_set;
 	local:
 		*;
 };
diff --git a/res/res_pjsip/config_auth.c b/res/res_pjsip/config_auth.c
index 860f33b56440943b7c52bf76a473b876dc90c959..afa26867e3a54c86e96a867c4f64726676292008 100644
--- a/res/res_pjsip/config_auth.c
+++ b/res/res_pjsip/config_auth.c
@@ -23,6 +23,7 @@
 #include "asterisk/res_pjsip.h"
 #include "asterisk/logger.h"
 #include "asterisk/sorcery.h"
+#include "include/res_pjsip_private.h"
 
 static void auth_destroy(void *obj)
 {
@@ -61,6 +62,24 @@ static int auth_type_handler(const struct aco_option *opt, struct ast_variable *
 	return 0;
 }
 
+static const char *auth_types_map[] = {
+	[AST_SIP_AUTH_TYPE_USER_PASS] = "userpass",
+	[AST_SIP_AUTH_TYPE_MD5] = "md5"
+};
+
+const char *ast_sip_auth_type_to_str(enum ast_sip_auth_type type)
+{
+	return ARRAY_IN_BOUNDS(type, auth_types_map) ?
+		auth_types_map[type] : "";
+}
+
+static int auth_type_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_auth *auth = obj;
+	*buf = ast_strdup(ast_sip_auth_type_to_str(auth->type));
+	return 0;
+}
+
 static int auth_apply(const struct ast_sorcery *sorcery, void *obj)
 {
 	struct ast_sip_auth *auth = obj;
@@ -99,6 +118,84 @@ static int auth_apply(const struct ast_sorcery *sorcery, void *obj)
 	return res;
 }
 
+int ast_sip_for_each_auth(const struct ast_sip_auth_array *array,
+			  ao2_callback_fn on_auth, void *arg)
+{
+	int i;
+
+	if (!array || !array->num) {
+		return 0;
+	}
+
+	for (i = 0; i < array->num; ++i) {
+		RAII_VAR(struct ast_sip_auth *, auth, ast_sorcery_retrieve_by_id(
+				 ast_sip_get_sorcery(), SIP_SORCERY_AUTH_TYPE,
+				 array->names[i]), ao2_cleanup);
+
+		if (!auth) {
+			continue;
+		}
+
+		if (on_auth(auth, arg, 0)) {
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int sip_auth_to_ami(const struct ast_sip_auth *auth,
+			   struct ast_str **buf)
+{
+	return ast_sip_sorcery_object_to_ami(auth, buf);
+}
+
+static int format_ami_auth_handler(void *obj, void *arg, int flags)
+{
+	const struct ast_sip_auth *auth = obj;
+	struct ast_sip_ami *ami = arg;
+	const struct ast_sip_endpoint *endpoint = ami->arg;
+	RAII_VAR(struct ast_str *, buf,
+		 ast_sip_create_ami_event("AuthDetail", ami), ast_free);
+
+	if (!buf) {
+		return -1;
+	}
+
+	if (sip_auth_to_ami(auth, &buf)) {
+		return -1;
+	}
+
+	if (endpoint) {
+		ast_str_append(&buf, 0, "EndpointName: %s\r\n",
+		       ast_sorcery_object_get_id(endpoint));
+	}
+
+	astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
+	return 0;
+}
+
+int ast_sip_format_auths_ami(const struct ast_sip_auth_array *auths,
+			     struct ast_sip_ami *ami)
+{
+	return ast_sip_for_each_auth(auths, format_ami_auth_handler, ami);
+}
+
+static int format_ami_endpoint_auth(const struct ast_sip_endpoint *endpoint,
+				    struct ast_sip_ami *ami)
+{
+	ami->arg = (void *)endpoint;
+	if (ast_sip_format_auths_ami(&endpoint->inbound_auths, ami)) {
+		return -1;
+	}
+
+	return ast_sip_format_auths_ami(&endpoint->outbound_auths, ami);
+}
+
+static struct ast_sip_endpoint_formatter endpoint_auth_formatter = {
+	.format_ami = format_ami_endpoint_auth
+};
+
 /*! \brief Initialize sorcery with auth support */
 int ast_sip_initialize_sorcery_auth(struct ast_sorcery *sorcery)
 {
@@ -121,7 +218,8 @@ int ast_sip_initialize_sorcery_auth(struct ast_sorcery *sorcery)
 	ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "nonce_lifetime",
 			"32", OPT_UINT_T, 0, FLDSET(struct ast_sip_auth, nonce_lifetime));
 	ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "auth_type",
-			"userpass", auth_type_handler, NULL, 0, 0);
+			"userpass", auth_type_handler, auth_type_to_str, 0, 0);
 
+	ast_sip_register_endpoint_formatter(&endpoint_auth_formatter);
 	return 0;
 }
diff --git a/res/res_pjsip/config_transport.c b/res/res_pjsip/config_transport.c
index 4ace1821b042b9e42455b392c0bb10a910b99fd5..370e49cb0f74b726bbf1ba9debec0e8094b8b7bb 100644
--- a/res/res_pjsip/config_transport.c
+++ b/res/res_pjsip/config_transport.c
@@ -26,6 +26,45 @@
 #include "asterisk/astobj2.h"
 #include "asterisk/sorcery.h"
 #include "asterisk/acl.h"
+#include "include/res_pjsip_private.h"
+
+static int sip_transport_to_ami(const struct ast_sip_transport *transport,
+				struct ast_str **buf)
+{
+	return ast_sip_sorcery_object_to_ami(transport, buf);
+}
+
+static int format_ami_endpoint_transport(const struct ast_sip_endpoint *endpoint,
+					 struct ast_sip_ami *ami)
+{
+	RAII_VAR(struct ast_str *, buf,
+		 ast_sip_create_ami_event("TransportDetail", ami), ast_free);
+	RAII_VAR(struct ast_sip_transport *,
+		 transport, ast_sorcery_retrieve_by_id(
+			 ast_sip_get_sorcery(), "transport",
+			 endpoint->transport), ao2_cleanup);
+	if (!buf) {
+		return -1;
+	}
+
+	if (!transport) {
+		astman_send_error_va(ami->s, ami->m, "Unable to retrieve "
+				     "transport %s\n", endpoint->transport);
+		return -1;
+	}
+
+	sip_transport_to_ami(transport, &buf);
+
+	ast_str_append(&buf, 0, "EndpointName: %s\r\n",
+		       ast_sorcery_object_get_id(endpoint));
+
+	astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
+	return 0;
+}
+
+struct ast_sip_endpoint_formatter endpoint_transport_formatter = {
+	.format_ami = format_ami_endpoint_transport
+};
 
 static int destroy_transport_state(void *data)
 {
@@ -213,6 +252,25 @@ static int transport_protocol_handler(const struct aco_option *opt, struct ast_v
 	return 0;
 }
 
+static const char *transport_types[] = {
+	[AST_TRANSPORT_UDP] = "udp",
+	[AST_TRANSPORT_TCP] = "tcp",
+	[AST_TRANSPORT_TLS] = "tls",
+	[AST_TRANSPORT_WS] = "ws",
+	[AST_TRANSPORT_WSS] = "wss"
+};
+
+static int transport_protocol_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_transport *transport = obj;
+
+	if (ARRAY_IN_BOUNDS(transport->type, transport_types)) {
+		*buf = ast_strdup(transport_types[transport->type]);
+	}
+
+	return 0;
+}
+
 /*! \brief Custom handler for turning a string bind into a pj_sockaddr */
 static int transport_bind_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
@@ -222,6 +280,20 @@ static int transport_bind_handler(const struct aco_option *opt, struct ast_varia
 	return (pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&buf, var->value), &transport->host) != PJ_SUCCESS) ? -1 : 0;
 }
 
+static int transport_bind_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_transport *transport = obj;
+
+	if (!(*buf = ast_calloc(MAX_OBJECT_FIELD, sizeof(char)))) {
+		return -1;
+	}
+
+	/* include port as well as brackets if IPv6 */
+	pj_sockaddr_print(&transport->host, *buf, MAX_OBJECT_FIELD, 1 | 2);
+
+	return 0;
+}
+
 /*! \brief Custom handler for TLS boolean settings */
 static int transport_tls_bool_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
@@ -240,6 +312,27 @@ static int transport_tls_bool_handler(const struct aco_option *opt, struct ast_v
 	return 0;
 }
 
+static int verify_server_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_transport *transport = obj;
+	*buf = ast_strdup(AST_YESNO(transport->tls.verify_server));
+	return 0;
+}
+
+static int verify_client_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_transport *transport = obj;
+	*buf = ast_strdup(AST_YESNO(transport->tls.verify_client));
+	return 0;
+}
+
+static int require_client_cert_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_transport *transport = obj;
+	*buf = ast_strdup(AST_YESNO(transport->tls.require_client_cert));
+	return 0;
+}
+
 /*! \brief Custom handler for TLS method setting */
 static int transport_tls_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
@@ -264,6 +357,24 @@ static int transport_tls_method_handler(const struct aco_option *opt, struct ast
 	return 0;
 }
 
+static const char *tls_method_map[] = {
+	[PJSIP_SSL_DEFAULT_METHOD] = "default",
+	[PJSIP_SSL_UNSPECIFIED_METHOD] = "unspecified",
+	[PJSIP_TLSV1_METHOD] = "tlsv1",
+	[PJSIP_SSLV2_METHOD] = "sslv2",
+	[PJSIP_SSLV3_METHOD] = "sslv3",
+	[PJSIP_SSLV23_METHOD] = "sslv23",
+};
+
+static int tls_method_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_transport *transport = obj;
+	if (ARRAY_IN_BOUNDS(transport->tls.method, tls_method_map)) {
+		*buf = ast_strdup(tls_method_map[transport->tls.method]);
+	}
+	return 0;
+}
+
 /*! \brief Custom handler for TLS cipher setting */
 static int transport_tls_cipher_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
@@ -291,6 +402,27 @@ static int transport_tls_cipher_handler(const struct aco_option *opt, struct ast
 	}
 }
 
+static int transport_tls_cipher_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
+	const struct ast_sip_transport *transport = obj;
+	int i;
+
+	if (!str) {
+		return -1;
+	}
+
+	for (i = 0; i < transport->tls.ciphers_num; ++i) {
+		ast_str_append(&str, 0, "%s", pj_ssl_cipher_name(transport->ciphers[i]));
+		if (i < transport->tls.ciphers_num - 1) {
+			ast_str_append(&str, 0, ",");
+		}
+	}
+
+	*buf = ast_strdup(ast_str_buffer(str));
+	return 0;
+}
+
 /*! \brief Custom handler for localnet setting */
 static int transport_localnet_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
@@ -304,6 +436,16 @@ static int transport_localnet_handler(const struct aco_option *opt, struct ast_v
 	return error;
 }
 
+static int localnet_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
+	const struct ast_sip_transport *transport = obj;
+
+	ast_ha_join(transport->localnet, &str);
+	*buf = ast_strdup(ast_str_buffer(str));
+	return 0;
+}
+
 /*! \brief Initialize sorcery with transport support */
 int ast_sip_initialize_sorcery_transport(struct ast_sorcery *sorcery)
 {
@@ -314,8 +456,8 @@ int ast_sip_initialize_sorcery_transport(struct ast_sorcery *sorcery)
 	}
 
 	ast_sorcery_object_field_register(sorcery, "transport", "type", "", OPT_NOOP_T, 0, 0);
-	ast_sorcery_object_field_register_custom(sorcery, "transport", "protocol", "udp", transport_protocol_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sorcery, "transport", "bind", "", transport_bind_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "protocol", "udp", transport_protocol_handler, transport_protocol_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "bind", "", transport_bind_handler, transport_bind_to_str, 0, 0);
 	ast_sorcery_object_field_register(sorcery, "transport", "async_operations", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, async_operations));
 	ast_sorcery_object_field_register(sorcery, "transport", "ca_list_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, ca_list_file));
 	ast_sorcery_object_field_register(sorcery, "transport", "cert_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, cert_file));
@@ -325,14 +467,15 @@ int ast_sip_initialize_sorcery_transport(struct ast_sorcery *sorcery)
 	ast_sorcery_object_field_register(sorcery, "transport", "external_signaling_port", "0", OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_transport, external_signaling_port), 0, 65535);
 	ast_sorcery_object_field_register(sorcery, "transport", "external_media_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, external_media_address));
 	ast_sorcery_object_field_register(sorcery, "transport", "domain", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, domain));
-	ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_server", "", transport_tls_bool_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_client", "", transport_tls_bool_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sorcery, "transport", "require_client_cert", "", transport_tls_bool_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sorcery, "transport", "method", "", transport_tls_method_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sorcery, "transport", "cipher", "", transport_tls_cipher_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sorcery, "transport", "local_net", "", transport_localnet_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_server", "", transport_tls_bool_handler, verify_server_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_client", "", transport_tls_bool_handler, verify_client_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "require_client_cert", "", transport_tls_bool_handler, require_client_cert_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "method", "", transport_tls_method_handler, tls_method_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "cipher", "", transport_tls_cipher_handler, transport_tls_cipher_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "local_net", "", transport_localnet_handler, localnet_to_str, 0, 0);
 	ast_sorcery_object_field_register(sorcery, "transport", "tos", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, tos));
 	ast_sorcery_object_field_register(sorcery, "transport", "cos", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, cos));
 
+	ast_sip_register_endpoint_formatter(&endpoint_transport_formatter);
 	return 0;
 }
diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h
index c3e6c21924b6eaf422404f9ad1168b7c45c6fcdd..0ee62529f603ffed2d9fafff2cac08b1b1e99803 100644
--- a/res/res_pjsip/include/res_pjsip_private.h
+++ b/res/res_pjsip/include/res_pjsip_private.h
@@ -8,13 +8,17 @@
 #ifndef RES_PJSIP_PRIVATE_H_
 #define RES_PJSIP_PRIVATE_H_
 
+#include "asterisk/module.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_endpoints.h"
+
 struct ao2_container;
 struct ast_threadpool_options;
 
 /*!
  * \brief Initialize the configuration for res_pjsip
  */
-int ast_res_pjsip_initialize_configuration(void);
+int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_module_info);
 
 /*!
  * \brief Annihilate the configuration objects
@@ -82,4 +86,23 @@ void ast_res_pjsip_cleanup_options_handling(void);
  */
 void sip_get_threadpool_options(struct ast_threadpool_options *threadpool_options);
 
+/*!
+ * \brief Function pointer for channel snapshot callbacks.
+ */
+typedef int (*on_channel_snapshot_t)(
+	const struct ast_channel_snapshot *snapshot, int last, void *arg);
+
+/*!
+ * \brief For every channel snapshot on an endpoint snapshot call the given
+ *        'on_channel_snapshot' handler.
+ *
+ * \param endpoint_snapshot snapshot of an endpoint
+ * \param on_channel_snapshot callback for each channel snapshot
+ * \param arg user data passed to handler
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_for_each_channel_snapshot(const struct ast_endpoint_snapshot *endpoint_snapshot,
+				      on_channel_snapshot_t on_channel_snapshot,
+				      void *arg);
+
 #endif /* RES_PJSIP_PRIVATE_H_ */
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index 8569e7a3831662abe05713aea83b8bd625feef6b..7f4889fe8872eb6a26fa01f42afa57e40ed9e875 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -300,6 +300,123 @@ static int permanent_uri_handler(const struct aco_option *opt, struct ast_variab
 	return 0;
 }
 
+int ast_sip_for_each_aor(const char *aors, ao2_callback_fn on_aor, void *arg)
+{
+	char *copy, *name;
+
+	if (!on_aor || ast_strlen_zero(aors)) {
+		return 0;
+	}
+
+	copy = ast_strdupa(aors);
+	while ((name = strsep(&copy, ","))) {
+		RAII_VAR(struct ast_sip_aor *, aor,
+			 ast_sip_location_retrieve_aor(name), ao2_cleanup);
+
+		if (!aor) {
+			continue;
+		}
+
+		if (on_aor(aor, arg, 0)) {
+			return -1;
+		}
+	}
+	ast_free(copy);
+	return 0;
+}
+
+int ast_sip_for_each_contact(const struct ast_sip_aor *aor,
+			     on_contact_t on_contact, void *arg)
+{
+	RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
+	struct ast_sip_contact *contact;
+	int num;
+	struct ao2_iterator i;
+
+	if (!on_contact ||
+	    !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
+		return 0;
+	}
+
+	num = ao2_container_count(contacts);
+	i = ao2_iterator_init(contacts, 0);
+	while ((contact = ao2_iterator_next(&i))) {
+		int res = on_contact(aor, contact, --num == 0, arg);
+
+		ao2_ref(contact, -1);
+		if (res) {
+			return -1;
+		}
+	}
+	ao2_iterator_destroy(&i);
+	return 0;
+}
+
+int ast_sip_contact_to_str(const struct ast_sip_aor *aor,
+			   const struct ast_sip_contact *contact,
+			   int last, void *arg)
+{
+	struct ast_str **buf = arg;
+
+	ast_str_append(buf, 0, "%s/%s",
+		       ast_sorcery_object_get_id(aor), contact->uri);
+
+	if (!last) {
+		ast_str_append(buf, 0, ",");
+	}
+
+	return 0;
+}
+
+static int sip_aor_to_ami(const struct ast_sip_aor *aor, struct ast_str **buf)
+{
+	return ast_sip_sorcery_object_to_ami(aor, buf);
+}
+
+static int format_ami_aor_handler(void *obj, void *arg, int flags)
+{
+	const struct ast_sip_aor *aor = obj;
+	struct ast_sip_ami *ami = arg;
+	const struct ast_sip_endpoint *endpoint = ami->arg;
+	RAII_VAR(struct ast_str *, buf,
+		 ast_sip_create_ami_event("AorDetail", ami), ast_free);
+
+	int num;
+	RAII_VAR(struct ao2_container *, contacts,
+		 ast_sip_location_retrieve_aor_contacts(aor), ao2_cleanup);
+
+	if (!buf) {
+		return -1;
+	}
+
+	sip_aor_to_ami(aor, &buf);
+	ast_str_append(&buf, 0, "Contacts: ");
+	ast_sip_for_each_contact(aor, ast_sip_contact_to_str, &buf);
+	ast_str_append(&buf, 0, "\r\n");
+
+	num = ao2_container_count(contacts);
+	ast_str_append(&buf, 0, "TotalContacts: %d\r\n", num);
+	ast_str_append(&buf, 0, "ContactsRegistered: %d\r\n",
+		       num - ao2_container_count(aor->permanent_contacts));
+	ast_str_append(&buf, 0, "EndpointName: %s\r\n",
+		       ast_sorcery_object_get_id(endpoint));
+
+	astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
+	return 0;
+}
+
+static int format_ami_endpoint_aor(const struct ast_sip_endpoint *endpoint,
+				   struct ast_sip_ami *ami)
+{
+	ami->arg = (void *)endpoint;
+	return ast_sip_for_each_aor(endpoint->aors,
+				    format_ami_aor_handler, ami);
+}
+
+struct ast_sip_endpoint_formatter endpoint_aor_formatter = {
+	.format_ami = format_ami_endpoint_aor
+};
+
 /*! \brief Initialize sorcery with location support */
 int ast_sip_initialize_sorcery_location(struct ast_sorcery *sorcery)
 {
@@ -328,6 +445,7 @@ int ast_sip_initialize_sorcery_location(struct ast_sorcery *sorcery)
 	ast_sorcery_object_field_register_custom(sorcery, "aor", "contact", "", permanent_uri_handler, NULL, 0, 0);
 	ast_sorcery_object_field_register(sorcery, "aor", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_aor, mailboxes));
 
+	ast_sip_register_endpoint_formatter(&endpoint_aor_formatter);
 	return 0;
 }
 
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index d8e781a5613fe2dd88b53b3b491df51e73ba2903..24c53e8a994160ab1a5da7c8d0a4791af7643565 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -13,11 +13,11 @@
 #include "asterisk/res_pjsip.h"
 #include "include/res_pjsip_private.h"
 #include "asterisk/cli.h"
+#include "asterisk/manager.h"
 #include "asterisk/astobj2.h"
 #include "asterisk/utils.h"
 #include "asterisk/sorcery.h"
 #include "asterisk/callerid.h"
-#include "asterisk/stasis_endpoints.h"
 
 /*! \brief Number of buckets for persistent endpoint information */
 #define PERSISTENT_BUCKETS 53
@@ -242,6 +242,25 @@ static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var,
 	return 0;
 }
 
+static int dtmf_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+
+	switch (endpoint->dtmf) {
+	case AST_SIP_DTMF_RFC_4733 :
+		*buf = "rfc4733"; break;
+	case AST_SIP_DTMF_INBAND :
+		*buf = "inband"; break;
+	case AST_SIP_DTMF_INFO :
+		*buf = "info"; break;
+	default:
+		*buf = "none";
+	}
+
+	*buf = ast_strdup(*buf);
+	return 0;
+}
+
 static int prack_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
@@ -259,6 +278,22 @@ static int prack_handler(const struct aco_option *opt, struct ast_variable *var,
 	return 0;
 }
 
+static int prack_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+
+	if (endpoint->extensions.flags & PJSIP_INV_REQUIRE_100REL) {
+		*buf = "required";
+	} else if (endpoint->extensions.flags & PJSIP_INV_SUPPORT_100REL) {
+		*buf = "yes";
+	} else {
+		*buf = "no";
+	}
+
+	*buf = ast_strdup(*buf);
+	return 0;
+}
+
 static int timers_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
@@ -278,6 +313,24 @@ static int timers_handler(const struct aco_option *opt, struct ast_variable *var
 	return 0;
 }
 
+static int timers_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+
+	if (endpoint->extensions.flags & PJSIP_INV_ALWAYS_USE_TIMER) {
+		*buf = "always";
+	} else if (endpoint->extensions.flags & PJSIP_INV_REQUIRE_TIMER) {
+		*buf = "required";
+	} else if (endpoint->extensions.flags & PJSIP_INV_SUPPORT_TIMER) {
+		*buf = "yes";
+	} else {
+		*buf = "no";
+	}
+
+	*buf = ast_strdup(*buf);
+	return 0;
+}
+
 void ast_sip_auth_array_destroy(struct ast_sip_auth_array *auths)
 {
 	int i;
@@ -344,6 +397,32 @@ static int outbound_auth_handler(const struct aco_option *opt, struct ast_variab
 	return ast_sip_auth_array_init(&endpoint->outbound_auths, var->value);
 }
 
+int ast_sip_auths_to_str(const struct ast_sip_auth_array *auths, char **buf)
+{
+	if (!auths || !auths->num) {
+		return 0;
+	}
+
+	if (!(*buf = ast_calloc(MAX_OBJECT_FIELD, sizeof(char)))) {
+		return -1;
+	}
+
+	ast_join_delim(*buf, MAX_OBJECT_FIELD, auths->names, auths->num, ',');
+	return 0;
+}
+
+static int inbound_auths_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	return ast_sip_auths_to_str(&endpoint->inbound_auths, buf);
+}
+
+static int outbound_auths_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	return ast_sip_auths_to_str(&endpoint->outbound_auths, buf);
+}
+
 static int ident_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
@@ -362,6 +441,20 @@ static int ident_handler(const struct aco_option *opt, struct ast_variable *var,
 	return 0;
 }
 
+static int ident_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	switch (endpoint->ident_method) {
+	case AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME :
+		*buf = "username"; break;
+	default:
+		return 0;
+	}
+
+	*buf = ast_strdup(*buf);
+	return 0;
+}
+
 static int direct_media_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
@@ -378,6 +471,20 @@ static int direct_media_method_handler(const struct aco_option *opt, struct ast_
 	return 0;
 }
 
+static const char *id_configuration_refresh_methods[] = {
+	[AST_SIP_SESSION_REFRESH_METHOD_INVITE] = "invite",
+	[AST_SIP_SESSION_REFRESH_METHOD_UPDATE] = "update"
+};
+
+static int direct_media_method_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	if (ARRAY_IN_BOUNDS(endpoint->id.refresh_method, id_configuration_refresh_methods)) {
+		*buf = ast_strdup(id_configuration_refresh_methods[endpoint->id.refresh_method]);
+	}
+	return 0;
+}
+
 static int connected_line_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
@@ -394,6 +501,13 @@ static int connected_line_method_handler(const struct aco_option *opt, struct as
 	return 0;
 }
 
+static int connected_line_method_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	*buf = ast_strdup(id_configuration_refresh_methods[endpoint->id.refresh_method]);
+	return 0;
+}
+
 static int direct_media_glare_mitigation_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
@@ -413,6 +527,22 @@ static int direct_media_glare_mitigation_handler(const struct aco_option *opt, s
 	return 0;
 }
 
+static const char *direct_media_glare_mitigation_map[] = {
+	[AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_NONE] = "none",
+	[AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_OUTGOING] = "outgoing",
+	[AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_INCOMING] = "incoming"
+};
+
+static int direct_media_glare_mitigation_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	if (ARRAY_IN_BOUNDS(endpoint->media.direct_media.glare_mitigation, direct_media_glare_mitigation_map)) {
+		*buf = ast_strdup(direct_media_glare_mitigation_map[endpoint->media.direct_media.glare_mitigation]);
+	}
+
+	return 0;
+}
+
 static int caller_id_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
@@ -437,6 +567,29 @@ static int caller_id_handler(const struct aco_option *opt, struct ast_variable *
 	return 0;
 }
 
+static int caller_id_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	const char *name = S_COR(endpoint->id.self.name.valid,
+				 endpoint->id.self.name.str, NULL);
+	const char *number = S_COR(endpoint->id.self.number.valid,
+				   endpoint->id.self.number.str, NULL);
+
+	/* make sure size is at least 10 - that should cover the "<unknown>"
+	   case as well as any additional formatting characters added in
+	   the name and/or number case. */
+	int size = 10;
+	size += name ? strlen(name) : 0;
+	size += number ? strlen(number) : 0;
+
+	if (!(*buf = ast_calloc(size + 1, sizeof(char)))) {
+		return -1;
+	}
+
+	ast_callerid_merge(*buf, size + 1, name, number, NULL);
+	return 0;
+}
+
 static int caller_id_privacy_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
@@ -449,6 +602,16 @@ static int caller_id_privacy_handler(const struct aco_option *opt, struct ast_va
 	return 0;
 }
 
+static int caller_id_privacy_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	const char *presentation = ast_named_caller_presentation(
+		endpoint->id.self.name.presentation);
+
+	*buf = ast_strdup(presentation);
+	return 0;
+}
+
 static int caller_id_tag_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
@@ -456,6 +619,13 @@ static int caller_id_tag_handler(const struct aco_option *opt, struct ast_variab
 	return endpoint->id.self.tag ? 0 : -1;
 }
 
+static int caller_id_tag_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	*buf = ast_strdup(endpoint->id.self.tag);
+	return 0;
+}
+
 static int media_encryption_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
@@ -474,6 +644,23 @@ static int media_encryption_handler(const struct aco_option *opt, struct ast_var
 	return 0;
 }
 
+static const char *media_encryption_map[] = {
+	[AST_SIP_MEDIA_TRANSPORT_INVALID] = "invalid",
+	[AST_SIP_MEDIA_ENCRYPT_NONE] = "none",
+	[AST_SIP_MEDIA_ENCRYPT_SDES] = "sdes",
+	[AST_SIP_MEDIA_ENCRYPT_DTLS] = "dtls",
+};
+
+static int media_encryption_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	if (ARRAY_IN_BOUNDS(endpoint->media.rtp.encryption, media_encryption_map)) {
+		*buf = ast_strdup(media_encryption_map[
+					  endpoint->media.rtp.encryption]);
+	}
+	return 0;
+}
+
 static int group_handler(const struct aco_option *opt,
 			 struct ast_variable *var, void *obj)
 {
@@ -494,6 +681,30 @@ static int group_handler(const struct aco_option *opt,
 	return 0;
 }
 
+static int callgroup_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+
+	if (!(*buf = ast_calloc(MAX_OBJECT_FIELD, sizeof(char)))) {
+		return -1;
+	}
+
+	ast_print_group(*buf, MAX_OBJECT_FIELD, endpoint->pickup.callgroup);
+	return 0;
+}
+
+static int pickupgroup_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+
+	if (!(*buf = ast_calloc(MAX_OBJECT_FIELD, sizeof(char)))) {
+		return -1;
+	}
+
+	ast_print_group(*buf, MAX_OBJECT_FIELD, endpoint->pickup.pickupgroup);
+	return 0;
+}
+
 static int named_groups_handler(const struct aco_option *opt,
 				struct ast_variable *var, void *obj)
 {
@@ -516,6 +727,26 @@ static int named_groups_handler(const struct aco_option *opt,
 	return 0;
 }
 
+static int named_callgroups_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
+
+	ast_print_namedgroups(&str, endpoint->pickup.named_callgroups);
+	*buf = ast_strdup(ast_str_buffer(str));
+	return 0;
+}
+
+static int named_pickupgroups_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
+
+	ast_print_namedgroups(&str, endpoint->pickup.named_pickupgroups);
+	*buf = ast_strdup(ast_str_buffer(str));
+	return 0;
+}
+
 static int dtls_handler(const struct aco_option *opt,
 			 struct ast_variable *var, void *obj)
 {
@@ -535,6 +766,72 @@ static int dtls_handler(const struct aco_option *opt,
 	return ast_rtp_dtls_cfg_parse(&endpoint->media.rtp.dtls_cfg, name, var->value);
 }
 
+static int dtlsverify_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	*buf = ast_strdup(AST_YESNO(endpoint->media.rtp.dtls_cfg.verify));
+	return 0;
+}
+
+static int dtlsrekey_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+
+	return ast_asprintf(
+		buf, "%d", endpoint->media.rtp.dtls_cfg.rekey) >=0 ? 0 : -1;
+}
+
+static int dtlscertfile_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	*buf = ast_strdup(endpoint->media.rtp.dtls_cfg.certfile);
+	return 0;
+}
+
+static int dtlsprivatekey_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	*buf = ast_strdup(endpoint->media.rtp.dtls_cfg.pvtfile);
+	return 0;
+}
+
+static int dtlscipher_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	*buf = ast_strdup(endpoint->media.rtp.dtls_cfg.cipher);
+	return 0;
+}
+
+static int dtlscafile_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	*buf = ast_strdup(endpoint->media.rtp.dtls_cfg.cafile);
+	return 0;
+}
+
+static int dtlscapath_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	*buf = ast_strdup(endpoint->media.rtp.dtls_cfg.capath);
+	return 0;
+}
+
+static const char *ast_rtp_dtls_setup_map[] = {
+	[AST_RTP_DTLS_SETUP_ACTIVE] = "active",
+	[AST_RTP_DTLS_SETUP_PASSIVE] = "passive",
+	[AST_RTP_DTLS_SETUP_ACTPASS] = "actpass",
+	[AST_RTP_DTLS_SETUP_HOLDCONN] = "holdconn",
+};
+
+static int dtlssetup_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	if (ARRAY_IN_BOUNDS(endpoint->media.rtp.dtls_cfg.default_setup, ast_rtp_dtls_setup_map)) {
+		*buf = ast_strdup(ast_rtp_dtls_setup_map[endpoint->media.rtp.dtls_cfg.default_setup]);
+	}
+	return 0;
+}
+
 static int t38udptl_ec_handler(const struct aco_option *opt,
 	struct ast_variable *var, void *obj)
 {
@@ -553,6 +850,22 @@ static int t38udptl_ec_handler(const struct aco_option *opt,
 	return 0;
 }
 
+static const char *ast_t38_ec_modes_map[] = {
+	[UDPTL_ERROR_CORRECTION_NONE] = "none",
+	[UDPTL_ERROR_CORRECTION_FEC] = "fec",
+	[UDPTL_ERROR_CORRECTION_REDUNDANCY] = "redundancy"
+};
+
+static int t38udptl_ec_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	if (ARRAY_IN_BOUNDS(endpoint->media.t38.error_correction, ast_t38_ec_modes_map)) {
+		*buf = ast_strdup(ast_t38_ec_modes_map[
+					  endpoint->media.t38.error_correction]);
+	}
+	return 0;
+}
+
 static void *sip_nat_hook_alloc(const char *name)
 {
 	return ast_sorcery_generic_alloc(sizeof(struct ast_sip_nat_hook), NULL);
@@ -631,12 +944,305 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o
 	return 0;
 }
 
-int ast_res_pjsip_initialize_configuration(void)
+static const char *get_device_state(const struct ast_sip_endpoint *endpoint)
+{
+	char device[MAX_OBJECT_FIELD];
+
+	snprintf(device, MAX_OBJECT_FIELD, "PJSIP/%s", ast_sorcery_object_get_id(endpoint));
+	return ast_devstate2str(ast_device_state(device));
+}
+
+static struct ast_endpoint_snapshot *sip_get_endpoint_snapshot(
+	const struct ast_sip_endpoint *endpoint)
+{
+	return ast_endpoint_latest_snapshot(
+		ast_endpoint_get_tech(endpoint->persistent),
+		ast_endpoint_get_resource(endpoint->persistent));
+}
+
+int ast_sip_for_each_channel_snapshot(
+	const struct ast_endpoint_snapshot *endpoint_snapshot,
+	on_channel_snapshot_t on_channel_snapshot, void *arg)
+{
+	int num, num_channels = endpoint_snapshot->num_channels;
+	RAII_VAR(struct stasis_cache *, cache, NULL, ao2_cleanup);
+
+	if (!on_channel_snapshot || !num_channels ||
+	    !(cache = ast_channel_cache())) {
+		return 0;
+	}
+
+	ao2_ref(cache, +1);
+
+	for (num = 0; num < num_channels; ++num) {
+		RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+		struct ast_channel_snapshot *snapshot;
+
+		msg = stasis_cache_get(cache, ast_channel_snapshot_type(),
+			endpoint_snapshot->channel_ids[num]);
+
+		if (!(snapshot = stasis_message_data(msg))) {
+			continue;
+		}
+
+		if (on_channel_snapshot(
+			    snapshot, num == (num_channels - 1), arg)) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int active_channels_to_str_cb(const struct ast_channel_snapshot *snapshot,
+				     int last, void *arg)
+{
+	struct ast_str **buf = arg;
+	if (last) {
+		ast_str_append(buf, 0, "%s", snapshot->name);
+	} else {
+		ast_str_append(buf, 0, "%s,", snapshot->name);
+	}
+	return 0;
+}
+
+static void active_channels_to_str(const struct ast_sip_endpoint *endpoint,
+				   struct ast_str **str)
+{
+
+	RAII_VAR(struct ast_endpoint_snapshot *, endpoint_snapshot,
+		 sip_get_endpoint_snapshot(endpoint), ao2_cleanup);
+
+	if (endpoint_snapshot) {
+		return;
+	}
+
+	ast_sip_for_each_channel_snapshot(endpoint_snapshot,
+					  active_channels_to_str_cb, str);
+}
+
+#define AMI_DEFAULT_STR_SIZE 512
+
+struct ast_str *ast_sip_create_ami_event(const char *event, struct ast_sip_ami *ami)
+{
+	struct ast_str *buf = ast_str_create(AMI_DEFAULT_STR_SIZE);
+
+	if (!(buf)) {
+		astman_send_error_va(ami->s, ami->m, "Unable create event "
+				     "for %s\n", event);
+		return NULL;
+	}
+
+	ast_str_set(&buf, 0, "Event: %s\r\n", event);
+	return buf;
+}
+
+static void sip_sorcery_object_ami_set_type_name(const void *obj, struct ast_str **buf)
+{
+	ast_str_append(buf, 0, "ObjectType: %s\r\n",
+		       ast_sorcery_object_get_type(obj));
+	ast_str_append(buf, 0, "ObjectName: %s\r\n",
+		       ast_sorcery_object_get_id(obj));
+}
+
+int ast_sip_sorcery_object_to_ami(const void *obj, struct ast_str **buf)
+{
+	RAII_VAR(struct ast_variable *, objset, ast_sorcery_objectset_create(
+			 ast_sip_get_sorcery(), obj), ast_variables_destroy);
+	struct ast_variable *i;
+
+	if (!objset) {
+		return -1;
+	}
+
+	sip_sorcery_object_ami_set_type_name(obj, buf);
+
+	for (i = objset; i; i = i->next) {
+		RAII_VAR(char *, camel, ast_to_camel_case(i->name), ast_free);
+		ast_str_append(buf, 0, "%s: %s\r\n", camel, i->value);
+	}
+
+	return 0;
+}
+
+static int sip_endpoints_aors_ami(void *obj, void *arg, int flags)
+{
+	const struct ast_sip_aor *aor = obj;
+	struct ast_str **buf = arg;
+
+	ast_str_append(buf, 0, "Contacts: ");
+	ast_sip_for_each_contact(aor, ast_sip_contact_to_str, arg);
+	ast_str_append(buf, 0, "\r\n");
+
+	return 0;
+}
+
+static int sip_endpoint_to_ami(const struct ast_sip_endpoint *endpoint,
+			       struct ast_str **buf)
+{
+	if (ast_sip_sorcery_object_to_ami(endpoint, buf)) {
+		return -1;
+	}
+
+	ast_str_append(buf, 0, "DeviceState: %s\r\n",
+		       get_device_state(endpoint));
+
+	ast_str_append(buf, 0, "ActiveChannels: ");
+	active_channels_to_str(endpoint, buf);
+	ast_str_append(buf, 0, "\r\n");
+
+	return 0;
+}
+
+static int format_ami_endpoint(const struct ast_sip_endpoint *endpoint,
+			       struct ast_sip_ami *ami)
+{
+	RAII_VAR(struct ast_str *, buf,
+		 ast_sip_create_ami_event("EndpointDetail", ami), ast_free);
+
+	if (!buf) {
+		return -1;
+	}
+
+	sip_endpoint_to_ami(endpoint, &buf);
+	astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
+	return 0;
+}
+
+#define AMI_SHOW_ENDPOINTS "PJSIPShowEndpoints"
+#define AMI_SHOW_ENDPOINT "PJSIPShowEndpoint"
+
+static int ami_show_endpoint(struct mansession *s, const struct message *m)
+{
+	struct ast_sip_ami ami = { .s = s, .m = m };
+	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+	const char *endpoint_name = astman_get_header(m, "Endpoint");
+	int count = 0;
+
+	if (ast_strlen_zero(endpoint_name)) {
+		astman_send_error_va(s, m, "%s requires an endpoint name\n",
+			AMI_SHOW_ENDPOINT);
+		return 0;
+	}
+
+	if (!strncasecmp(endpoint_name, "pjsip/", 6)) {
+		endpoint_name += 6;
+	}
+
+	if (!(endpoint = ast_sorcery_retrieve_by_id(
+		      ast_sip_get_sorcery(), "endpoint", endpoint_name))) {
+		astman_send_error_va(s, m, "Unable to retrieve endpoint %s\n",
+			endpoint_name);
+		return -1;
+	}
+
+	astman_send_listack(s, m, "Following are Events for each object "
+			    "associated with the the Endpoint", "start");
+
+	/* the endpoint detail needs to always come first so apply as such */
+	if (format_ami_endpoint(endpoint, &ami) ||
+	    ast_sip_format_endpoint_ami(endpoint, &ami, &count)) {
+		astman_send_error_va(s, m, "Unable to format endpoint %s\n",
+			endpoint_name);
+	}
+
+	astman_append(s,
+		      "Event: EndpointDetailComplete\r\n"
+		      "EventList: Complete\r\n"
+		      "ListItems: %d\r\n\r\n", count + 1);
+	return 0;
+}
+
+static int format_str_append_auth(const struct ast_sip_auth_array *auths,
+				  struct ast_str **buf)
+{
+	char *str = NULL;
+	if (ast_sip_auths_to_str(auths, &str)) {
+		return -1;
+	}
+	ast_str_append(buf, 0, "%s", str ? str : "");
+	ast_free(str);
+	return 0;
+}
+
+static int format_ami_endpoints(void *obj, void *arg, int flags)
+{
+
+	struct ast_sip_endpoint *endpoint = obj;
+	struct ast_sip_ami *ami = arg;
+	RAII_VAR(struct ast_str *, buf,
+		 ast_sip_create_ami_event("EndpointList", ami), ast_free);
+
+	if (!buf) {
+		return -1;
+	}
+
+	sip_sorcery_object_ami_set_type_name(endpoint, &buf);
+	ast_str_append(&buf, 0, "Transport: %s\r\n",
+		       endpoint->transport);
+	ast_str_append(&buf, 0, "Aor: %s\r\n",
+		       endpoint->aors);
+
+	ast_str_append(&buf, 0, "Auths: ");
+	format_str_append_auth(&endpoint->inbound_auths, &buf);
+	ast_str_append(&buf, 0, "\r\n");
+
+	ast_str_append(&buf, 0, "OutboundAuths: ");
+	format_str_append_auth(&endpoint->outbound_auths, &buf);
+	ast_str_append(&buf, 0, "\r\n");
+
+	ast_sip_for_each_aor(endpoint->aors,
+			     sip_endpoints_aors_ami, &buf);
+
+	ast_str_append(&buf, 0, "DeviceState: %s\r\n",
+		       get_device_state(endpoint));
+
+	ast_str_append(&buf, 0, "ActiveChannels: ");
+	active_channels_to_str(endpoint, &buf);
+	ast_str_append(&buf, 0, "\r\n");
+
+	astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
+	return 0;
+}
+
+static int ami_show_endpoints(struct mansession *s, const struct message *m)
+{
+	struct ast_sip_ami ami = { .s = s, .m = m };
+	RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup);
+	int num;
+
+	endpoints = ast_sip_get_endpoints();
+	if (!endpoints) {
+		return -1;
+	}
+
+	if (!(num = ao2_container_count(endpoints))) {
+		astman_send_error(s, m, "No endpoints found\n");
+		return 0;
+	}
+
+	astman_send_listack(s, m, "A listing of Endpoints follows, "
+			    "presented as EndpointList events", "start");
+
+	ao2_callback(endpoints, OBJ_NODATA, format_ami_endpoints, &ami);
+
+	astman_append(s,
+		      "Event: EndpointListComplete\r\n"
+		      "EventList: Complete\r\n"
+		      "ListItems: %d\r\n\r\n", num);
+	return 0;
+}
+
+int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_module_info)
 {
 	if (ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands))) {
 		return -1;
 	}
 
+	if (ast_manager_register_xml(AMI_SHOW_ENDPOINTS, EVENT_FLAG_SYSTEM, ami_show_endpoints) ||
+	    ast_manager_register_xml(AMI_SHOW_ENDPOINT, EVENT_FLAG_SYSTEM, ami_show_endpoint)) {
+		return -1;
+	}
+
 	if (!(persistent_endpoints = ao2_container_alloc(PERSISTENT_BUCKETS, persistent_endpoint_hash, persistent_endpoint_cmp))) {
 		return -1;
 	}
@@ -672,7 +1278,7 @@ int ast_res_pjsip_initialize_configuration(void)
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "context", "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, context));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "disallow", "", OPT_CODEC_T, 0, FLDSET(struct ast_sip_endpoint, media.prefs, media.codecs));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow", "", OPT_CODEC_T, 1, FLDSET(struct ast_sip_endpoint, media.prefs, media.codecs));
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtmf_mode", "rfc4733", dtmf_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtmf_mode", "rfc4733", dtmf_handler, dtmf_to_str, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_ipv6", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.ipv6));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_symmetric", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.symmetric));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "ice_support", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.ice_support));
@@ -682,23 +1288,23 @@ int ast_res_pjsip_initialize_configuration(void)
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "transport", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, transport));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, outbound_proxy));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "moh_suggest", "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, mohsuggest));
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "100rel", "yes", prack_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "timers", "yes", timers_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "100rel", "yes", prack_handler, prack_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "timers", "yes", timers_handler, timers_to_str, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "timers_min_se", "90", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, extensions.timer.min_se));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "timers_sess_expires", "1800", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, extensions.timer.sess_expires));
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "auth", "", inbound_auth_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outbound_auth", "", outbound_auth_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "auth", "", inbound_auth_handler, inbound_auths_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aors", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, aors));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "media_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.address));
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "identify_by", "username", ident_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "identify_by", "username", ident_handler, ident_to_str, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "direct_media", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.direct_media.enabled));
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_method", "invite", direct_media_method_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "connected_line_method", "invite", connected_line_method_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_glare_mitigation", "none", direct_media_glare_mitigation_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_method", "invite", direct_media_method_handler, direct_media_method_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "connected_line_method", "invite", connected_line_method_handler, connected_line_method_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_glare_mitigation", "none", direct_media_glare_mitigation_handler, direct_media_glare_mitigation_to_str, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "disable_direct_media_on_nat", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.direct_media.disable_on_nat));
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid", "", caller_id_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid_privacy", "", caller_id_privacy_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid_tag", "", caller_id_tag_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid", "", caller_id_handler, caller_id_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid_privacy", "", caller_id_privacy_handler, caller_id_privacy_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid_tag", "", caller_id_tag_handler, caller_id_tag_to_str, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "trust_id_inbound", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.trust_inbound));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "trust_id_outbound", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.trust_outbound));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_pai", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_pai));
@@ -706,17 +1312,17 @@ int ast_res_pjsip_initialize_configuration(void)
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_diversion", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_diversion));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, subscription.mwi.mailboxes));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aggregate_mwi", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.mwi.aggregate));
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "media_encryption", "no", media_encryption_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "media_encryption", "no", media_encryption_handler, media_encryption_to_str, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "use_avpf", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.use_avpf));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "one_touch_recording", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, info.recording.enabled));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "inband_progress", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, inband_progress));
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "call_group", "", group_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "pickup_group", "", group_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "named_call_group", "", named_groups_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "named_pickup_group", "", named_groups_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "call_group", "", group_handler, callgroup_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "pickup_group", "", group_handler, pickupgroup_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "named_call_group", "", named_groups_handler, named_callgroups_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "named_pickup_group", "", named_groups_handler, named_pickupgroups_to_str, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "device_state_busy_at", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, devicestate_busy_at));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "t38_udptl", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.t38.enabled));
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "t38_udptl_ec", "none", t38udptl_ec_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "t38_udptl_ec", "none", t38udptl_ec_handler, t38udptl_ec_to_str, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "t38_udptl_maxdatagram", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.t38.maxdatagram));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "fax_detect", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, faxdetect));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "t38_udptl_nat", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.t38.nat));
@@ -738,14 +1344,14 @@ int ast_res_pjsip_initialize_configuration(void)
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "from_domain", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, fromdomain));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mwi_from_user", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, subscription.mwi.fromuser));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_engine", "asterisk", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.rtp.engine));
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_verify", "", dtls_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_rekey", "", dtls_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_cert_file", "", dtls_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_private_key", "", dtls_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_cipher", "", dtls_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_ca_file", "", dtls_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_ca_path", "", dtls_handler, NULL, 0, 0);
-	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_setup", "", dtls_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_verify", "", dtls_handler, dtlsverify_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_rekey", "", dtls_handler, dtlsrekey_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_cert_file", "", dtls_handler, dtlscertfile_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_private_key", "", dtls_handler, dtlsprivatekey_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_cipher", "", dtls_handler, dtlscipher_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_ca_file", "", dtls_handler, dtlscafile_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_ca_path", "", dtls_handler, dtlscapath_to_str, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_setup", "", dtls_handler, dtlssetup_to_str, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "srtp_tag_32", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.srtp_tag_32));
 
 	if (ast_sip_initialize_sorcery_transport(sip_sorcery)) {
@@ -793,6 +1399,8 @@ int ast_res_pjsip_initialize_configuration(void)
 void ast_res_pjsip_destroy_configuration(void)
 {
 	ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
+	ast_manager_unregister(AMI_SHOW_ENDPOINT);
+	ast_manager_unregister(AMI_SHOW_ENDPOINTS);
 	ast_sorcery_unref(sip_sorcery);
 }
 
diff --git a/res/res_pjsip_endpoint_identifier_ip.c b/res/res_pjsip_endpoint_identifier_ip.c
index a8240e5dda583cf0560894a9e63a9491c4cccaf2..fe2948a1635b425497f8dd2fe3a647f0fa4eaeb3 100644
--- a/res/res_pjsip_endpoint_identifier_ip.c
+++ b/res/res_pjsip_endpoint_identifier_ip.c
@@ -29,6 +29,8 @@
 #include "asterisk/res_pjsip.h"
 #include "asterisk/module.h"
 #include "asterisk/acl.h"
+#include "asterisk/manager.h"
+#include "res_pjsip/include/res_pjsip_private.h"
 
 /*** DOCUMENTATION
 	<configInfo name="res_pjsip_endpoint_identifier_ip" language="en_US">
@@ -164,6 +166,68 @@ static int ip_identify_match_handler(const struct aco_option *opt, struct ast_va
 	return error;
 }
 
+static int ip_identify_match_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
+	const struct ip_identify_match *identify = obj;
+
+	ast_ha_join(identify->matches, &str);
+	*buf = ast_strdup(ast_str_buffer(str));
+	return 0;
+}
+
+static int sip_identify_to_ami(const struct ip_identify_match *identify,
+			       struct ast_str **buf)
+{
+	return ast_sip_sorcery_object_to_ami(identify, buf);
+}
+
+static int find_identify_by_endpoint(void *obj, void *arg, int flags)
+{
+	struct ip_identify_match *identify = obj;
+	const char *endpoint_name = arg;
+
+	return strcmp(identify->endpoint_name, endpoint_name) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static int format_ami_endpoint_identify(const struct ast_sip_endpoint *endpoint,
+					struct ast_sip_ami *ami)
+{
+	RAII_VAR(struct ao2_container *, identifies, NULL, ao2_cleanup);
+	RAII_VAR(struct ip_identify_match *, identify, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_str *, buf, NULL, ast_free);
+
+	if (!(identifies = ast_sorcery_retrieve_by_fields(
+		      ast_sip_get_sorcery(), "identify", AST_RETRIEVE_FLAG_MULTIPLE |
+		      AST_RETRIEVE_FLAG_ALL, NULL))) {
+		return -1;
+	}
+
+	if (!(identify = ao2_callback(identifies, OBJ_NOLOCK,
+				      find_identify_by_endpoint,
+				      (void*)ast_sorcery_object_get_id(endpoint)))) {
+		return 1;
+	}
+
+	if (!(buf = ast_sip_create_ami_event("IdentifyDetail", ami))) {
+		return -1;
+	}
+
+	if (sip_identify_to_ami(identify, &buf)) {
+		return -1;
+	}
+
+	ast_str_append(&buf, 0, "EndpointName: %s\r\n",
+		       ast_sorcery_object_get_id(endpoint));
+
+	astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
+	return 0;
+}
+
+struct ast_sip_endpoint_formatter endpoint_identify_formatter = {
+	.format_ami = format_ami_endpoint_identify
+};
+
 static int load_module(void)
 {
 	ast_sorcery_apply_default(ast_sip_get_sorcery(), "identify", "config", "pjsip.conf,criteria=type=identify");
@@ -174,10 +238,11 @@ static int load_module(void)
 
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "type", "", OPT_NOOP_T, 0, 0);
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, endpoint_name));
-	ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "identify", "match", "", ip_identify_match_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "identify", "match", "", ip_identify_match_handler, ip_identify_match_to_str, 0, 0);
 	ast_sorcery_reload_object(ast_sip_get_sorcery(), "identify");
 
 	ast_sip_register_endpoint_identifier(&ip_identifier);
+	ast_sip_register_endpoint_formatter(&endpoint_identify_formatter);
 
 	return AST_MODULE_LOAD_SUCCESS;
 }
diff --git a/res/res_pjsip_exten_state.c b/res/res_pjsip_exten_state.c
index 0144e1cbbb1be6317085be338cb25de513dc7215..f312a522c890cb3b5da1fe97ed9e45067660e698 100644
--- a/res/res_pjsip_exten_state.c
+++ b/res/res_pjsip_exten_state.c
@@ -515,6 +515,18 @@ static void subscription_terminated(struct ast_sip_subscription *sub,
 	send_notify(exten_state_sub, NULL, PJSIP_EVSUB_STATE_TERMINATED);
 }
 
+static void to_ami(struct ast_sip_subscription *sub,
+		   struct ast_str **buf)
+{
+	struct exten_state_subscription *exten_state_sub =
+		get_exten_state_sub(sub);
+
+	ast_str_append(buf, 0, "SubscriptionType: extension_state\r\n"
+		       "Extension: %s\r\nExtensionStates: %s\r\n",
+		       exten_state_sub->exten, ast_extension_state2str(
+			       exten_state_sub->last_exten_state));
+}
+
 #define DEFAULT_PRESENCE_BODY "application/pidf+xml"
 
 /*!
@@ -545,6 +557,7 @@ static struct ast_sip_subscription_handler *create_and_register_handler(
 	handler->resubscribe = resubscribe;
 	handler->subscription_timeout = subscription_timeout;
 	handler->subscription_terminated = subscription_terminated;
+	handler->to_ami = to_ami;
 
 	if (ast_sip_register_subscription_handler(handler)) {
 		ast_log(LOG_WARNING, "Unable to register subscription handler %s\n",
diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c
index bd865eec4c4f44a6e999e02e4406e7bc4c1084d6..4f32f382aa7556dce88f732645c7abeaf125c3a2 100644
--- a/res/res_pjsip_mwi.c
+++ b/res/res_pjsip_mwi.c
@@ -54,6 +54,7 @@ static void mwi_notify_response(struct ast_sip_subscription *sub, pjsip_rx_data
 static void mwi_notify_request(struct ast_sip_subscription *sub, pjsip_rx_data *rdata,
 		struct ast_sip_subscription_response_data *response_data);
 static int mwi_refresh_subscription(struct ast_sip_subscription *sub);
+static void mwi_to_ami(struct ast_sip_subscription *sub, struct ast_str **buf);
 
 static struct ast_sip_subscription_handler mwi_handler = {
 	.event_name = "message-summary",
@@ -67,6 +68,7 @@ static struct ast_sip_subscription_handler mwi_handler = {
 	.notify_response = mwi_notify_response,
 	.notify_request = mwi_notify_request,
 	.refresh_subscription = mwi_refresh_subscription,
+	.to_ami = mwi_to_ami,
 };
 
 /*!
@@ -118,11 +120,11 @@ static struct mwi_stasis_subscription *mwi_stasis_subscription_alloc(const char
 {
 	struct mwi_stasis_subscription *mwi_stasis_sub;
 	struct stasis_topic *topic;
-	
+
 	if (!mwi_sub) {
 		return NULL;
 	}
-	
+
 	mwi_stasis_sub = ao2_alloc(sizeof(*mwi_stasis_sub) + strlen(mailbox), NULL);
 	if (!mwi_stasis_sub) {
 		return NULL;
@@ -209,7 +211,7 @@ static struct mwi_subscription *mwi_subscription_alloc(struct ast_sip_endpoint *
 static int mwi_sub_hash(const void *obj, int flags)
 {
 	const struct mwi_subscription *mwi_sub = obj;
-	
+
 	return ast_str_hash(mwi_sub->id);
 }
 
@@ -572,6 +574,44 @@ static int mwi_refresh_subscription(struct ast_sip_subscription *sub)
 	return 0;
 }
 
+static void mwi_subscription_mailboxes_str(struct ao2_container *stasis_subs,
+					   struct ast_str **str)
+{
+	int num = ao2_container_count(stasis_subs);
+
+	struct mwi_stasis_subscription *node;
+	struct ao2_iterator i = ao2_iterator_init(stasis_subs, 0);
+
+	while ((node = ao2_iterator_next(&i))) {
+		if (--num) {
+			ast_str_append(str, 0, "%s,", node->mailbox);
+		} else {
+			ast_str_append(str, 0, "%s", node->mailbox);
+		}
+		ao2_ref(node, -1);
+	}
+	ao2_iterator_destroy(&i);
+}
+
+static void mwi_to_ami(struct ast_sip_subscription *sub,
+		       struct ast_str **buf)
+{
+	struct mwi_subscription *mwi_sub;
+	RAII_VAR(struct ast_datastore *, mwi_datastore,
+			ast_sip_subscription_get_datastore(sub, "MWI datastore"), ao2_cleanup);
+
+	if (!mwi_datastore) {
+		return;
+	}
+
+	mwi_sub = mwi_datastore->data;
+
+	ast_str_append(buf, 0, "SubscriptionType: mwi\r\n");
+	ast_str_append(buf, 0, "Mailboxes: ");
+	mwi_subscription_mailboxes_str(mwi_sub->stasis_subs, buf);
+	ast_str_append(buf, 0, "\r\n");
+}
+
 static int serialized_notify(void *userdata)
 {
 	struct mwi_subscription *mwi_sub = userdata;
diff --git a/res/res_pjsip_outbound_registration.c b/res/res_pjsip_outbound_registration.c
index 823304edf44e223b1c9991db9aaf100dabcdd3e4..b2ccd3b95465bad8017568f2e1169f0303d01e8f 100644
--- a/res/res_pjsip_outbound_registration.c
+++ b/res/res_pjsip_outbound_registration.c
@@ -32,6 +32,7 @@
 #include "asterisk/taskprocessor.h"
 #include "asterisk/cli.h"
 #include "asterisk/stasis_system.h"
+#include "res_pjsip/include/res_pjsip_private.h"
 
 /*** DOCUMENTATION
 	<configInfo name="res_pjsip_outbound_registration" language="en_US">
@@ -129,6 +130,20 @@
 			</parameter>
 		</syntax>
 	</manager>
+	<manager name="PJSIPShowRegistrationsOutbound" language="en_US">
+		<synopsis>
+			Lists PJSIP outbound registrations.
+		</synopsis>
+		<syntax />
+		<description>
+			<para>
+			In response <literal>OutboundRegistrationDetail</literal> events showing configuration and status
+			information are raised for each outbound registration object. <literal>AuthDetail</literal>
+			events are raised for each associated auth object as well.  Once all events are completed an
+			<literal>OutboundRegistrationDetailComplete</literal> is issued.
+                        </para>
+		</description>
+	</manager>
  ***/
 
 /*! \brief Amount of buffer time (in seconds) before expiration that we re-register at */
@@ -796,6 +811,12 @@ static int outbound_auth_handler(const struct aco_option *opt, struct ast_variab
 	return ast_sip_auth_array_init(&registration->outbound_auths, var->value);
 }
 
+static int outbound_auths_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct sip_outbound_registration *registration = obj;
+	return ast_sip_auths_to_str(&registration->outbound_auths, buf);
+}
+
 static struct sip_outbound_registration *retrieve_registration(const char *registration_name)
 {
 	return ast_sorcery_retrieve_by_id(
@@ -937,6 +958,85 @@ static struct ast_cli_entry cli_outbound_registration[] = {
 	AST_CLI_DEFINE(cli_unregister, "Send a REGISTER request to an outbound registration target with a expiration of 0")
 };
 
+struct sip_ami_outbound {
+	struct ast_sip_ami *ami;
+	int registered;
+	int not_registered;
+	struct sip_outbound_registration *registration;
+};
+
+static int ami_outbound_registration_task(void *obj)
+{
+	struct sip_ami_outbound *ami = obj;
+	RAII_VAR(struct ast_str *, buf,
+		 ast_sip_create_ami_event("OutboundRegistrationDetail", ami->ami), ast_free);
+
+	if (!buf) {
+		return -1;
+	}
+
+	ast_sip_sorcery_object_to_ami(ami->registration, &buf);
+
+	if (ami->registration->state) {
+		pjsip_regc_info info;
+		if (ami->registration->state->client_state->status ==
+		    SIP_REGISTRATION_REGISTERED) {
+			++ami->registered;
+		} else {
+			++ami->not_registered;
+		}
+
+		ast_str_append(&buf, 0, "Status: %s%s",
+			       sip_outbound_registration_status_str[
+				       ami->registration->state->client_state->status], "\r\n");
+
+		pjsip_regc_get_info(ami->registration->state->client_state->client, &info);
+		ast_str_append(&buf, 0, "NextReg: %d%s", info.next_reg, "\r\n");
+	}
+
+	astman_append(ami->ami->s, "%s\r\n", ast_str_buffer(buf));
+	return ast_sip_format_auths_ami(&ami->registration->outbound_auths, ami->ami);
+}
+
+static int ami_outbound_registration_detail(void *obj, void *arg, int flags)
+{
+	struct sip_ami_outbound *ami = arg;
+
+	ami->registration = obj;
+	return ast_sip_push_task_synchronous(
+		NULL, ami_outbound_registration_task, ami);
+}
+
+static int ami_show_outbound_registrations(struct mansession *s,
+					   const struct message *m)
+{
+	struct ast_sip_ami ami = { s = s, m = m };
+	struct sip_ami_outbound ami_outbound = { .ami = &ami };
+	RAII_VAR(struct ao2_container *, regs, ast_sorcery_retrieve_by_fields(
+			 ast_sip_get_sorcery(), "registration", AST_RETRIEVE_FLAG_MULTIPLE |
+			 AST_RETRIEVE_FLAG_ALL, NULL), ao2_cleanup);
+
+	if (!regs) {
+		astman_send_error(s, m, "Unable to retreive "
+				  "outbound registrations\n");
+		return -1;
+	}
+
+	astman_send_listack(s, m, "Following are Events for each Outbound "
+			    "registration", "start");
+
+	ao2_callback(regs, OBJ_NODATA, ami_outbound_registration_detail, &ami_outbound);
+
+	astman_append(s,
+		      "Event: OutboundRegistrationDetailComplete\r\n"
+		      "EventList: Complete\r\n"
+		      "Registered: %d\r\n"
+		      "NotRegistered: %d\r\n\r\n",
+		      ami_outbound.registered,
+		      ami_outbound.not_registered);
+	return 0;
+}
+
 static int load_module(void)
 {
 	ast_sorcery_apply_default(ast_sip_get_sorcery(), "registration", "config", "pjsip.conf,criteria=type=registration");
@@ -956,12 +1056,13 @@ static int load_module(void)
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "forbidden_retry_interval", "0", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, forbidden_retry_interval));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "max_retries", "10", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, max_retries));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "auth_rejection_permanent", "yes", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, auth_rejection_permanent));
-	ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "outbound_auth", "", outbound_auth_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, 0, 0);
 	ast_sorcery_reload_object(ast_sip_get_sorcery(), "registration");
 	sip_outbound_registration_perform_all();
 
 	ast_cli_register_multiple(cli_outbound_registration, ARRAY_LEN(cli_outbound_registration));
 	ast_manager_register_xml("PJSIPUnregister", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_unregister);
+	ast_manager_register_xml("PJSIPShowRegistrationsOutbound", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING,ami_show_outbound_registrations);
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
@@ -975,6 +1076,7 @@ static int reload_module(void)
 static int unload_module(void)
 {
 	ast_cli_unregister_multiple(cli_outbound_registration, ARRAY_LEN(cli_outbound_registration));
+	ast_manager_unregister("PJSIPShowRegistrationsOutbound");
 	ast_manager_unregister("PJSIPUnregister");
 	return 0;
 }
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index 4daacd42c30ff036b675b7431fb4750b8716e32d..5bcfd07cec2078a4ae2eb8e590afe05eccccda96 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -40,6 +40,38 @@
 #include "asterisk/taskprocessor.h"
 #include "asterisk/sched.h"
 #include "asterisk/res_pjsip.h"
+#include "asterisk/callerid.h"
+#include "asterisk/manager.h"
+#include "res_pjsip/include/res_pjsip_private.h"
+
+/*** DOCUMENTATION
+	<manager name="PJSIPShowSubscriptionsInbound" language="en_US">
+		<synopsis>
+			Lists subscriptions.
+		</synopsis>
+		<syntax />
+		<description>
+			<para>
+			Provides a listing of all inbound subscriptions.  An event <literal>InboundSubscriptionDetail</literal>
+			is issued for each subscription object.  Once all detail events are completed an
+			<literal>InboundSubscriptionDetailComplete</literal> event is issued.
+                        </para>
+		</description>
+	</manager>
+	<manager name="PJSIPShowSubscriptionsOutbound" language="en_US">
+		<synopsis>
+			Lists subscriptions.
+		</synopsis>
+		<syntax />
+		<description>
+			<para>
+			Provides a listing of all outbound subscriptions.  An event <literal>OutboundSubscriptionDetail</literal>
+			is issued for each subscription object.  Once all detail events are completed an
+			<literal>OutboundSubscriptionDetailComplete</literal> event is issued.
+                        </para>
+		</description>
+	</manager>
+ ***/
 
 static pj_bool_t pubsub_on_rx_request(pjsip_rx_data *rdata);
 
@@ -163,8 +195,88 @@ struct ast_sip_subscription {
 	pjsip_evsub *evsub;
 	/*! The underlying PJSIP dialog */
 	pjsip_dialog *dlg;
+	/*! Next item in the list */
+	AST_LIST_ENTRY(ast_sip_subscription) next;
+};
+
+static const char *sip_subscription_roles_map[] = {
+	[AST_SIP_SUBSCRIBER] = "Subscriber",
+	[AST_SIP_NOTIFIER] = "Notifier"
 };
 
+AST_RWLIST_HEAD_STATIC(subscriptions, ast_sip_subscription);
+
+static void add_subscription(struct ast_sip_subscription *obj)
+{
+	SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+	AST_RWLIST_INSERT_TAIL(&subscriptions, obj, next);
+	ast_module_ref(ast_module_info->self);
+}
+
+static void remove_subscription(struct ast_sip_subscription *obj)
+{
+	struct ast_sip_subscription *i;
+	SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&subscriptions, i, next) {
+		if (i == obj) {
+			AST_RWLIST_REMOVE_CURRENT(next);
+			ast_module_unref(ast_module_info->self);
+			break;
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+}
+
+typedef int (*on_subscription_t)(struct ast_sip_subscription *sub, void *arg);
+
+static int for_each_subscription(on_subscription_t on_subscription, void *arg)
+{
+	int num = 0;
+	struct ast_sip_subscription *i;
+	SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+
+	if (!on_subscription) {
+		return num;
+	}
+
+	AST_RWLIST_TRAVERSE(&subscriptions, i, next) {
+		if (on_subscription(i, arg)) {
+			break;
+		}
+		++num;
+	}
+	return num;
+}
+
+static void sip_subscription_to_ami(struct ast_sip_subscription *sub,
+				    struct ast_str **buf)
+{
+	char str[256];
+	struct ast_sip_endpoint_id_configuration *id = &sub->endpoint->id;
+
+	ast_str_append(buf, 0, "Role: %s\r\n",
+		       sip_subscription_roles_map[sub->role]);
+	ast_str_append(buf, 0, "Endpoint: %s\r\n",
+		       ast_sorcery_object_get_id(sub->endpoint));
+
+	ast_copy_pj_str(str, &sub->dlg->call_id->id, sizeof(str));
+	ast_str_append(buf, 0, "Callid: %s\r\n", str);
+
+	ast_str_append(buf, 0, "State: %s\r\n", pjsip_evsub_get_state_name(
+			       ast_sip_subscription_get_evsub(sub)));
+
+	ast_callerid_merge(str, sizeof(str),
+			   S_COR(id->self.name.valid, id->self.name.str, NULL),
+			   S_COR(id->self.number.valid, id->self.number.str, NULL),
+			   "Unknown");
+
+	ast_str_append(buf, 0, "Callerid: %s\r\n", str);
+
+	if (sub->handler->to_ami) {
+		sub->handler->to_ami(sub, buf);
+	}
+}
+
 #define DATASTORE_BUCKETS 53
 
 #define DEFAULT_EXPIRES 3600
@@ -196,6 +308,7 @@ static void subscription_destructor(void *obj)
 	struct ast_sip_subscription *sub = obj;
 
 	ast_debug(3, "Destroying SIP subscription\n");
+	remove_subscription(sub);
 
 	ao2_cleanup(sub->datastores);
 	ao2_cleanup(sub->endpoint);
@@ -311,6 +424,8 @@ struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_su
 	ao2_ref(endpoint, +1);
 	sub->endpoint = endpoint;
 	sub->handler = handler;
+
+	add_subscription(sub);
 	return sub;
 }
 
@@ -1114,6 +1229,71 @@ static void pubsub_on_server_timeout(pjsip_evsub *evsub)
 	ast_sip_push_task(sub->serializer, serialized_pubsub_on_server_timeout, sub);
 }
 
+static int ami_subscription_detail(struct ast_sip_subscription *sub,
+				   struct ast_sip_ami *ami,
+				   const char *event)
+{
+	RAII_VAR(struct ast_str *, buf,
+		 ast_sip_create_ami_event(event, ami), ast_free);
+
+	if (!buf) {
+		return -1;
+	}
+
+	sip_subscription_to_ami(sub, &buf);
+	astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
+	return 0;
+}
+
+static int ami_subscription_detail_inbound(struct ast_sip_subscription *sub, void *arg)
+{
+	return sub->role == AST_SIP_NOTIFIER ? ami_subscription_detail(
+		sub, arg, "InboundSubscriptionDetail") : 0;
+}
+
+static int ami_subscription_detail_outbound(struct ast_sip_subscription *sub, void *arg)
+{
+	return sub->role == AST_SIP_SUBSCRIBER ? ami_subscription_detail(
+		sub, arg, "OutboundSubscriptionDetail") : 0;
+}
+
+static int ami_show_subscriptions_inbound(struct mansession *s, const struct message *m)
+{
+	struct ast_sip_ami ami = { .s = s, .m = m };
+	int num;
+
+	astman_send_listack(s, m, "Following are Events for "
+			    "each inbound Subscription", "start");
+
+	num = for_each_subscription(ami_subscription_detail_inbound, &ami);
+
+	astman_append(s,
+		      "Event: InboundSubscriptionDetailComplete\r\n"
+		      "EventList: Complete\r\n"
+		      "ListItems: %d\r\n\r\n", num);
+	return 0;
+}
+
+static int ami_show_subscriptions_outbound(struct mansession *s, const struct message *m)
+{
+	struct ast_sip_ami ami = { .s = s, .m = m };
+	int num;
+
+	astman_send_listack(s, m, "Following are Events for "
+			    "each outbound Subscription", "start");
+
+	num = for_each_subscription(ami_subscription_detail_outbound, &ami);
+
+	astman_append(s,
+		      "Event: OutboundSubscriptionDetailComplete\r\n"
+		      "EventList: Complete\r\n"
+		      "ListItems: %d\r\n\r\n", num);
+	return 0;
+}
+
+#define AMI_SHOW_SUBSCRIPTIONS_INBOUND "PJSIPShowSubscriptionsInbound"
+#define AMI_SHOW_SUBSCRIPTIONS_OUTBOUND "PJSIPShowSubscriptionsOutbound"
+
 static int load_module(void)
 {
 	static const pj_str_t str_PUBLISH = { "PUBLISH", 7 };
@@ -1139,11 +1319,19 @@ static int load_module(void)
 		return AST_MODULE_LOAD_FAILURE;
 	}
 
+	ast_manager_register_xml(AMI_SHOW_SUBSCRIPTIONS_INBOUND, EVENT_FLAG_SYSTEM,
+				 ami_show_subscriptions_inbound);
+	ast_manager_register_xml(AMI_SHOW_SUBSCRIPTIONS_OUTBOUND, EVENT_FLAG_SYSTEM,
+				 ami_show_subscriptions_outbound);
+
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
 static int unload_module(void)
 {
+	ast_manager_unregister(AMI_SHOW_SUBSCRIPTIONS_OUTBOUND);
+	ast_manager_unregister(AMI_SHOW_SUBSCRIPTIONS_INBOUND);
+
 	if (sched) {
 		ast_sched_context_destroy(sched);
 	}
diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c
index 143f96d7970236e31b36f9c703c4ae6d228bd802..82d97a0d52f0e06548d36738cb58b757ffe84995 100644
--- a/res/res_pjsip_registrar.c
+++ b/res/res_pjsip_registrar.c
@@ -31,6 +31,25 @@
 #include "asterisk/module.h"
 #include "asterisk/test.h"
 #include "asterisk/taskprocessor.h"
+#include "asterisk/manager.h"
+#include "res_pjsip/include/res_pjsip_private.h"
+
+/*** DOCUMENTATION
+	<manager name="PJSIPShowRegistrationsInbound" language="en_US">
+		<synopsis>
+			Lists PJSIP inbound registrations.
+		</synopsis>
+		<syntax />
+		<description>
+			<para>
+			In response <literal>InboundRegistrationDetail</literal> events showing configuration and status
+			information are raised for each inbound registration object.  As well as <literal>AuthDetail</literal>
+			events for each associated auth object.  Once all events are completed an
+			<literal>InboundRegistrationDetailComplete</literal> is issued.
+                        </para>
+		</description>
+	</manager>
+ ***/
 
 /*! \brief Internal function which returns the expiration time for a contact */
 static int registrar_get_expiration(const struct ast_sip_aor *aor, const pjsip_contact_hdr *contact, const pjsip_rx_data *rdata)
@@ -569,6 +588,66 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
 	return PJ_TRUE;
 }
 
+static int ami_registrations_aor(void *obj, void *arg, int flags)
+{
+	struct ast_sip_aor *aor = obj;
+	struct ast_sip_ami *ami = arg;
+	int *count = ami->arg;
+	RAII_VAR(struct ast_str *, buf,
+		 ast_sip_create_ami_event("InboundRegistrationDetail", ami), ast_free);
+
+	if (!buf) {
+		return -1;
+	}
+
+	ast_sip_sorcery_object_to_ami(aor, &buf);
+	ast_str_append(&buf, 0, "Contacts: ");
+	ast_sip_for_each_contact(aor, ast_sip_contact_to_str, &buf);
+	ast_str_append(&buf, 0, "\r\n");
+
+	astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
+	(*count)++;
+	return 0;
+}
+
+static int ami_registrations_endpoint(void *obj, void *arg, int flags)
+{
+	struct ast_sip_endpoint *endpoint = obj;
+	return ast_sip_for_each_aor(
+		endpoint->aors, ami_registrations_aor, arg);
+}
+
+static int ami_registrations_endpoints(void *arg)
+{
+	RAII_VAR(struct ao2_container *, endpoints,
+		 ast_sip_get_endpoints(), ao2_cleanup);
+
+	if (!endpoints) {
+		return 0;
+	}
+
+	ao2_callback(endpoints, OBJ_NODATA, ami_registrations_endpoint, arg);
+	return 0;
+}
+
+static int ami_show_registrations(struct mansession *s, const struct message *m)
+{
+	int count = 0;
+	struct ast_sip_ami ami = { .s = s, .m = m, .arg = &count };
+	astman_send_listack(s, m, "Following are Events for each Inbound "
+			    "registration", "start");
+
+	ami_registrations_endpoints(&ami);
+
+	astman_append(s,
+		      "Event: InboundRegistrationDetailComplete\r\n"
+		      "EventList: Complete\r\n"
+		      "ListItems: %d\r\n\r\n", count);
+	return 0;
+}
+
+#define AMI_SHOW_REGISTRATIONS "PJSIPShowRegistrationsInbound"
+
 static pjsip_module registrar_module = {
 	.name = { "Registrar", 9 },
 	.id = -1,
@@ -594,11 +673,15 @@ static int load_module(void)
 		return AST_MODULE_LOAD_DECLINE;
 	}
 
+	ast_manager_register_xml(AMI_SHOW_REGISTRATIONS, EVENT_FLAG_SYSTEM,
+				 ami_show_registrations);
+
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
 static int unload_module(void)
 {
+	ast_manager_unregister(AMI_SHOW_REGISTRATIONS);
 	ast_sip_unregister_service(&registrar_module);
 
 	ao2_cleanup(serializers);