diff --git a/lib/wp/endpoint.h b/lib/wp/endpoint.h
index 3e476eb0029a9d95da6eb14a2680bf5481684b2a..c30474d0498fd0dfc5d3da83b427a5c6616d50d8 100644
--- a/lib/wp/endpoint.h
+++ b/lib/wp/endpoint.h
@@ -54,6 +54,17 @@ typedef enum { /*< flags >*/
   WP_ENDPOINT_FEATURE_STREAMS,
 } WpEndpointFeatures;
 
+/**
+ * WP_ENDPOINT_FEATURES_STANDARD:
+ *
+ * A constant set of features that contains the standard features that are
+ * available in the #WpEndpoint class.
+ */
+#define WP_ENDPOINT_FEATURES_STANDARD \
+    (WP_PROXY_FEATURES_STANDARD | \
+     WP_ENDPOINT_FEATURE_CONTROLS | \
+     WP_ENDPOINT_FEATURE_STREAMS)
+
 /**
  * WP_TYPE_ENDPOINT:
  *
diff --git a/lib/wp/session.c b/lib/wp/session.c
index 7896c39b6c3ba30d81b4d62e79cea3aaf1d58fb6..b05d479b86300dd5d1e66fd7a5dc1891cd9142ac 100644
--- a/lib/wp/session.c
+++ b/lib/wp/session.c
@@ -50,6 +50,7 @@ struct _WpSessionPrivate
   struct pw_session_info *info;
   struct pw_session *iface;
   struct spa_hook listener;
+  WpObjectManager *endpoints_om;
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE (WpSession, wp_session, WP_TYPE_PROXY)
@@ -65,6 +66,7 @@ wp_session_finalize (GObject * object)
   WpSession *self = WP_SESSION (object);
   WpSessionPrivate *priv = wp_session_get_instance_private (self);
 
+  g_clear_object (&priv->endpoints_om);
   g_clear_pointer (&priv->info, pw_session_info_free);
   g_clear_pointer (&priv->properties, wp_properties_unref);
   wp_spa_props_clear (&priv->spa_props);
@@ -200,6 +202,59 @@ wp_session_param (WpProxy * proxy, gint seq, guint32 id, guint32 index,
   }
 }
 
+static void
+wp_session_enable_feature_endpoints (WpSession * self, guint32 bound_id)
+{
+  WpSessionPrivate *priv = wp_session_get_instance_private (self);
+  g_autoptr (WpCore) core = wp_proxy_get_core (WP_PROXY (self));
+  GVariantBuilder b;
+
+  /* proxy endpoint -> check for session.id in global properties */
+  g_variant_builder_init (&b, G_VARIANT_TYPE ("aa{sv}"));
+  g_variant_builder_open (&b, G_VARIANT_TYPE_VARDICT);
+  g_variant_builder_add (&b, "{sv}", "type",
+      g_variant_new_int32 (WP_OBJECT_MANAGER_CONSTRAINT_PW_GLOBAL_PROPERTY));
+  g_variant_builder_add (&b, "{sv}", "name",
+      g_variant_new_string (PW_KEY_SESSION_ID));
+  g_variant_builder_add (&b, "{sv}", "value",
+      g_variant_new_take_string (g_strdup_printf ("%u", bound_id)));
+  g_variant_builder_close (&b);
+
+  wp_object_manager_add_interest (priv->endpoints_om,
+      WP_TYPE_ENDPOINT,
+      g_variant_builder_end (&b),
+      WP_ENDPOINT_FEATURES_STANDARD);
+
+  /* impl endpoint -> check for session.id in standard properties */
+  g_variant_builder_init (&b, G_VARIANT_TYPE ("aa{sv}"));
+  g_variant_builder_open (&b, G_VARIANT_TYPE_VARDICT);
+  g_variant_builder_add (&b, "{sv}", "type",
+      g_variant_new_int32 (WP_OBJECT_MANAGER_CONSTRAINT_PW_PROPERTY));
+  g_variant_builder_add (&b, "{sv}", "name",
+      g_variant_new_string (PW_KEY_SESSION_ID));
+  g_variant_builder_add (&b, "{sv}", "value",
+      g_variant_new_take_string (g_strdup_printf ("%u", bound_id)));
+  g_variant_builder_close (&b);
+
+  wp_object_manager_add_interest (priv->endpoints_om,
+      WP_TYPE_IMPL_ENDPOINT,
+      g_variant_builder_end (&b),
+      WP_ENDPOINT_FEATURES_STANDARD);
+
+  wp_core_install_object_manager (core, priv->endpoints_om);
+  wp_proxy_set_feature_ready (WP_PROXY (self), WP_SESSION_FEATURE_ENDPOINTS);
+}
+
+static void
+wp_session_bound (WpProxy * proxy, guint32 id)
+{
+  WpSession *self = WP_SESSION (proxy);
+  WpSessionPrivate *priv = wp_session_get_instance_private (self);
+
+  if (priv->endpoints_om)
+    wp_session_enable_feature_endpoints (self, id);
+}
+
 static void
 wp_session_augment (WpProxy * proxy, WpProxyFeatures features)
 {
@@ -217,6 +272,20 @@ wp_session_augment (WpProxy * proxy, WpProxyFeatures features)
     pw_session_enum_params (pw_proxy, 0, SPA_PARAM_PropInfo, 0, -1, NULL);
     pw_session_subscribe_params (pw_proxy, ids, SPA_N_ELEMENTS (ids));
   }
+
+  if (features & WP_SESSION_FEATURE_ENDPOINTS) {
+    WpSessionPrivate *priv =
+        wp_session_get_instance_private (WP_SESSION (proxy));
+
+    priv->endpoints_om = wp_object_manager_new ();
+
+    /* if we are already bound, enable right away;
+       else, continue in the bound() event */
+    if (wp_proxy_get_features (proxy) & WP_PROXY_FEATURE_BOUND) {
+      wp_session_enable_feature_endpoints (WP_SESSION (proxy),
+          wp_proxy_get_bound_id (proxy));
+    }
+  }
 }
 
 static guint32
@@ -267,6 +336,7 @@ wp_session_class_init (WpSessionClass * klass)
 
   proxy_class->pw_proxy_created = wp_session_pw_proxy_created;
   proxy_class->param = wp_session_param;
+  proxy_class->bound = wp_session_bound;
 
   klass->get_default_endpoint = get_default_endpoint;
   klass->set_default_endpoint = set_default_endpoint;
@@ -323,6 +393,70 @@ wp_session_set_default_endpoint (WpSession * self,
   WP_SESSION_GET_CLASS (self)->set_default_endpoint (self, type, id);
 }
 
+/**
+ * wp_session_get_n_endpoints:
+ * @self: the session
+ *
+ * Returns: the number of endpoints of this session
+ */
+guint
+wp_session_get_n_endpoints (WpSession * self)
+{
+  g_return_val_if_fail (WP_IS_SESSION (self), 0);
+  g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) &
+          WP_SESSION_FEATURE_ENDPOINTS, 0);
+
+  WpSessionPrivate *priv = wp_session_get_instance_private (self);
+  return wp_object_manager_get_n_objects (priv->endpoints_om);
+}
+
+/**
+ * wp_session_get_endpoint:
+ * @self: the session
+ * @bound_id: the bound id of the endpoint object to get
+ *
+ * Returns: (transfer full) (nullable): the endpoint that has the given
+ *    @bound_id, or %NULL if there is no such endpoint
+ */
+WpEndpoint *
+wp_session_get_endpoint (WpSession * self, guint32 bound_id)
+{
+  g_return_val_if_fail (WP_IS_SESSION (self), NULL);
+  g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) &
+          WP_SESSION_FEATURE_ENDPOINTS, NULL);
+
+  WpSessionPrivate *priv = wp_session_get_instance_private (self);
+  g_autoptr (GPtrArray) endpoints =
+      wp_object_manager_get_objects (priv->endpoints_om, 0);
+
+  for (guint i = 0; i < endpoints->len; i++) {
+    gpointer proxy = g_ptr_array_index (endpoints, i);
+    g_return_val_if_fail (WP_IS_ENDPOINT (proxy), NULL);
+
+    if (wp_proxy_get_bound_id (WP_PROXY (proxy)) == bound_id)
+      return WP_ENDPOINT (g_object_ref (proxy));
+  }
+  return NULL;
+}
+
+/**
+ * wp_session_get_all_endpoints:
+ * @self: the session
+ *
+ * Returns: (transfer full) (element-type WpEndpoint): array with all
+ *   the session endpoints that belong to this session
+ */
+GPtrArray *
+wp_session_get_all_endpoints (WpSession * self)
+{
+  g_return_val_if_fail (WP_IS_SESSION (self), NULL);
+  g_return_val_if_fail (wp_proxy_get_features (WP_PROXY (self)) &
+          WP_SESSION_FEATURE_ENDPOINTS, NULL);
+
+  WpSessionPrivate *priv = wp_session_get_instance_private (self);
+  return wp_object_manager_get_objects (priv->endpoints_om, 0);
+}
+
 /* WpImplSession */
 
 typedef struct _WpImplSession WpImplSession;
@@ -556,6 +690,20 @@ wp_impl_session_augment (WpProxy * proxy, WpProxyFeatures features)
             wp_properties_peek_dict (priv->properties),
             priv->iface, 0));
   }
+
+  if (features & WP_SESSION_FEATURE_ENDPOINTS) {
+    WpSessionPrivate *priv =
+        wp_session_get_instance_private (WP_SESSION (proxy));
+
+    priv->endpoints_om = wp_object_manager_new ();
+
+    /* if we are already bound, enable right away;
+       else, continue in the bound() event */
+    if (wp_proxy_get_features (proxy) & WP_PROXY_FEATURE_BOUND) {
+      wp_session_enable_feature_endpoints (WP_SESSION (proxy),
+          wp_proxy_get_bound_id (proxy));
+    }
+  }
 }
 
 static void
diff --git a/lib/wp/session.h b/lib/wp/session.h
index 83fe3665a169f88706e00dde1d6a9005b3557fee..0e1ebd6e644b58ac73cb93c6a6928d1407656ae5 100644
--- a/lib/wp/session.h
+++ b/lib/wp/session.h
@@ -10,6 +10,7 @@
 #define __WIREPLUMBER_SESSION_H__
 
 #include "proxy.h"
+#include "endpoint.h"
 
 G_BEGIN_DECLS
 
@@ -32,13 +33,28 @@ typedef enum {
  * @WP_SESSION_FEATURE_DEFAULT_ENDPOINT: enables the use of
  *   wp_session_get_default_endpoint() and wp_session_set_default_endpoint()
  *   to store default endpoint preferences on the session
+ * @WP_SESSION_FEATURE_ENDPOINTS: caches information about endpoints, enabling
+ *   the use of wp_session_get_n_endpoints(), wp_session_get_endpoint() and
+ *   wp_session_get_all_endpoints()
  *
  * An extension of #WpProxyFeatures
  */
 typedef enum { /*< flags >*/
   WP_SESSION_FEATURE_DEFAULT_ENDPOINT = WP_PROXY_FEATURE_LAST,
+  WP_SESSION_FEATURE_ENDPOINTS,
 } WpSessionFeatures;
 
+/**
+ * WP_SESSION_FEATURES_STANDARD:
+ *
+ * A constant set of features that contains the standard features that are
+ * available in the #WpSession class.
+ */
+#define WP_SESSION_FEATURES_STANDARD \
+    (WP_PROXY_FEATURES_STANDARD | \
+     WP_SESSION_FEATURE_DEFAULT_ENDPOINT | \
+     WP_SESSION_FEATURE_ENDPOINTS)
+
 /**
  * WP_TYPE_SESSION:
  *
@@ -66,6 +82,15 @@ WP_API
 void wp_session_set_default_endpoint (WpSession * self,
     WpDefaultEndpointType type, guint32 id);
 
+WP_API
+guint wp_session_get_n_endpoints (WpSession * self);
+
+WP_API
+WpEndpoint * wp_session_get_endpoint (WpSession * self, guint32 bound_id);
+
+WP_API
+GPtrArray * wp_session_get_all_endpoints (WpSession * self);
+
 /**
  * WP_TYPE_IMPL_SESSION:
  *
diff --git a/tests/wp/session.c b/tests/wp/session.c
index 65df4973a6ac2049010c16d7f8c296c56868d8ff..83150aa3eddb8e502289668bc72b78a04e66d42c 100644
--- a/tests/wp/session.c
+++ b/tests/wp/session.c
@@ -222,7 +222,7 @@ test_session_basic (TestSessionFixture *fixture, gconstpointer data)
       (GCallback) test_session_basic_exported_object_removed, fixture);
   wp_object_manager_add_interest (fixture->export_om,
       WP_TYPE_IMPL_SESSION, NULL,
-      WP_PROXY_FEATURES_STANDARD | WP_SESSION_FEATURE_DEFAULT_ENDPOINT);
+      WP_SESSION_FEATURES_STANDARD);
   wp_core_install_object_manager (fixture->export_core, fixture->export_om);
 
   g_assert_true (wp_core_connect (fixture->export_core));
@@ -234,7 +234,7 @@ test_session_basic (TestSessionFixture *fixture, gconstpointer data)
       (GCallback) test_session_basic_proxy_object_removed, fixture);
   wp_object_manager_add_interest (fixture->proxy_om,
       WP_TYPE_SESSION, NULL,
-      WP_PROXY_FEATURES_STANDARD | WP_SESSION_FEATURE_DEFAULT_ENDPOINT);
+      WP_SESSION_FEATURES_STANDARD);
   wp_core_install_object_manager (fixture->proxy_core, fixture->proxy_om);
 
   g_assert_true (wp_core_connect (fixture->proxy_core));