diff --git a/lib/wp/endpoint-stream.c b/lib/wp/endpoint-stream.c
index 995b707d6dfacc6d6102c8e1f9a9afdc00537959..8a4ee979db62f15dcfe46a235968f8ad4bb0a7d5 100644
--- a/lib/wp/endpoint-stream.c
+++ b/lib/wp/endpoint-stream.c
@@ -708,6 +708,7 @@ wp_impl_endpoint_stream_augment (WpProxy * proxy, WpProxyFeatures features)
 
   if (features & WP_PROXY_FEATURE_INFO) {
     const gchar *key, *value;
+    g_autoptr (WpProxy) endpoint = NULL;
 
     /* initialize info struct */
     priv->info = &self->info;
@@ -718,6 +719,11 @@ wp_impl_endpoint_stream_augment (WpProxy * proxy, WpProxyFeatures features)
         &self->info.name,
         &immutable_props);
 
+    endpoint = wp_session_item_get_associated_proxy (
+        WP_SESSION_ITEM (self->item), WP_TYPE_ENDPOINT);
+    self->info.endpoint_id =
+        endpoint ? wp_proxy_get_bound_id (endpoint) : SPA_ID_INVALID;
+
     populate_endpoint_stream_info (self, PW_ENDPOINT_STREAM_CHANGE_MASK_ALL);
 
     /* subscribe to changes */
@@ -729,6 +735,10 @@ wp_impl_endpoint_stream_augment (WpProxy * proxy, WpProxyFeatures features)
     props = wp_properties_new (
         PW_KEY_ENDPOINT_STREAM_NAME, self->info.name,
         NULL);
+    if (self->info.endpoint_id != SPA_ID_INVALID) {
+      wp_properties_setf (props, PW_KEY_ENDPOINT_ID, "%u",
+          self->info.endpoint_id);
+    }
     while (g_variant_iter_next (immutable_props, "{&s&s}", &key, &value)) {
       wp_properties_set (props, key, value);
     }
diff --git a/lib/wp/endpoint.c b/lib/wp/endpoint.c
index 7722cc4f838528d3968528b7fcf4a00358ed33c7..d1e807f9e107d7b7bbe65dc2f788cd2b8a85b356 100644
--- a/lib/wp/endpoint.c
+++ b/lib/wp/endpoint.c
@@ -647,10 +647,10 @@ populate_endpoint_info (WpImplEndpoint * self, guint32 change_mask)
   }
 
   if (change_mask & PW_ENDPOINT_CHANGE_MASK_SESSION) {
-    g_autoptr (WpSession) session =
-        wp_session_item_get_session (WP_SESSION_ITEM (self->item));
+    g_autoptr (WpProxy) session = wp_session_item_get_associated_proxy (
+        WP_SESSION_ITEM (self->item), WP_TYPE_SESSION);
     self->info.session_id =
-        session ? wp_proxy_get_bound_id (WP_PROXY (session)) : SPA_ID_INVALID;
+        session ? wp_proxy_get_bound_id (session) : SPA_ID_INVALID;
   }
 
   if (change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) {
diff --git a/lib/wp/session-item.c b/lib/wp/session-item.c
index 402873d8ca0508a92eb6e112e7e877e541367400..10aef8a9d7cfab78407f9daa8f79140c59a825bb 100644
--- a/lib/wp/session-item.c
+++ b/lib/wp/session-item.c
@@ -117,6 +117,34 @@ wp_session_item_finalize (GObject * object)
   G_OBJECT_CLASS (wp_session_item_parent_class)->finalize (object);
 }
 
+static gpointer
+wp_session_item_default_get_associated_proxy (WpSessionItem * self,
+    GType proxy_type)
+{
+  WpSessionItemPrivate *priv;
+
+  if (WP_IS_SI_STREAM (self)) {
+    WpSiEndpoint *ep = wp_si_stream_get_parent_endpoint (WP_SI_STREAM (self));
+    priv = wp_session_item_get_instance_private (WP_SESSION_ITEM (ep));
+  } else {
+    priv = wp_session_item_get_instance_private (self);
+  }
+
+  if (proxy_type == WP_TYPE_SESSION) {
+    return g_weak_ref_get (&priv->session);
+  }
+  else if (proxy_type == WP_TYPE_ENDPOINT) {
+    return priv->impl_endpoint ? g_object_ref (priv->impl_endpoint) : NULL;
+  }
+  else if (proxy_type == WP_TYPE_ENDPOINT_STREAM) {
+    gpointer impl_stream = priv->impl_streams ?
+        g_hash_table_lookup (priv->impl_streams, self) : NULL;
+    return impl_stream ? g_object_ref (impl_stream) : NULL;
+  }
+
+  return NULL;
+}
+
 static guint
 wp_session_item_default_get_next_step (WpSessionItem * self,
     WpTransition * transition, guint step)
@@ -352,6 +380,7 @@ wp_session_item_class_init (WpSessionItemClass * klass)
   object_class->dispose = wp_session_item_dispose;
   object_class->finalize = wp_session_item_finalize;
 
+  klass->get_associated_proxy = wp_session_item_default_get_associated_proxy;
   klass->get_next_step = wp_session_item_default_get_next_step;
   klass->execute_step = wp_session_item_default_execute_step;
   klass->reset = wp_session_item_default_reset;
@@ -370,23 +399,6 @@ wp_session_item_class_init (WpSessionItemClass * klass)
       G_TYPE_NONE, 1, WP_TYPE_SI_FLAGS);
 }
 
-/**
- * wp_session_item_get_session:
- * @self: the session item
- *
- * Returns: (nullable) (transfer full): the session that owns this item, or
- *   %NULL if this item is not part of a session
- */
-WpSession *
-wp_session_item_get_session (WpSessionItem * self)
-{
-  g_return_val_if_fail (WP_IS_SESSION_ITEM (self), NULL);
-
-  WpSessionItemPrivate *priv =
-      wp_session_item_get_instance_private (self);
-  return g_weak_ref_get (&priv->session);
-}
-
 /**
  * wp_session_item_get_flags:
  * @self: the session item
@@ -455,6 +467,37 @@ wp_session_item_clear_flag (WpSessionItem * self, WpSiFlags flag)
   }
 }
 
+/**
+ * wp_session_item_get_associated_proxy: (virtual get_associated_proxy)
+ * @self: the session item
+ * @proxy_type: a #WpProxy subclass #GType
+ *
+ * An associated proxy is a #WpProxy subclass instance that is somehow related
+ * to this item. For example:
+ *  - An exported #WpSiEndpoint should have at least:
+ *      - an associated #WpEndpoint
+ *      - an associated #WpSession
+ *  - An exported #WpSiStream should have at least:
+ *      - an associated #WpEndpointStream
+ *      - an associated #WpEndpoint
+ *  - In cases where the item wraps a single PipeWire node, it should also
+ *    have an associated #WpNode
+ *
+ * Returns: (nullable) (transfer full) (type WpProxy): the associated proxy
+ *   of the specified @proxy_type, or %NULL if there is no association to
+ *   such a proxy
+ */
+gpointer
+wp_session_item_get_associated_proxy (WpSessionItem * self, GType proxy_type)
+{
+  g_return_val_if_fail (WP_IS_SESSION_ITEM (self), NULL);
+  g_return_val_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->get_associated_proxy,
+      NULL);
+  g_return_val_if_fail (g_type_is_a (proxy_type, WP_TYPE_PROXY), NULL);
+
+  return WP_SESSION_ITEM_GET_CLASS (self)->get_associated_proxy (self, proxy_type);
+}
+
 /**
  * wp_session_item_configure: (virtual configure)
  * @self: the session item
diff --git a/lib/wp/session-item.h b/lib/wp/session-item.h
index 07407dc838a0cb1e62fa874f3fa4ede20f9af287..96cf638c568a689515726ddae3676d190c99a0b7 100644
--- a/lib/wp/session-item.h
+++ b/lib/wp/session-item.h
@@ -64,6 +64,7 @@ typedef enum {
 
 /**
  * WpSessionItemClass:
+ * @get_associated_proxy: See wp_session_item_get_associated_proxy()
  * @configure: See wp_session_item_configure()
  * @get_configuration: See wp_session_item_get_configuration()
  * @get_next_step: Implements #WpTransitionClass.get_next_step() for the
@@ -79,6 +80,8 @@ struct _WpSessionItemClass
 {
   GObjectClass parent_class;
 
+  gpointer (*get_associated_proxy) (WpSessionItem * self, GType proxy_type);
+
   gboolean (*configure) (WpSessionItem * self, GVariant * args);
   GVariant * (*get_configuration) (WpSessionItem * self);
 
@@ -97,10 +100,7 @@ struct _WpSessionItemClass
   void (*unexport) (WpSessionItem * self);
 };
 
-/* properties */
-
-WP_API
-WpSession * wp_session_item_get_session (WpSessionItem * self);
+/* flags */
 
 WP_API
 WpSiFlags wp_session_item_get_flags (WpSessionItem * self);
@@ -111,6 +111,12 @@ void wp_session_item_set_flag (WpSessionItem * self, WpSiFlags flag);
 WP_API
 void wp_session_item_clear_flag (WpSessionItem * self, WpSiFlags flag);
 
+/* associated proxies */
+
+WP_API
+gpointer wp_session_item_get_associated_proxy (WpSessionItem * self,
+    GType proxy_type);
+
 /* configuration */
 
 WP_API
diff --git a/modules/module-si-adapter.c b/modules/module-si-adapter.c
index dc3698d1ac3c46aed548b2d4e58ff297e4d435e3..e715f1724f5520d674bae92ebd2766cacab415b9 100644
--- a/modules/module-si-adapter.c
+++ b/modules/module-si-adapter.c
@@ -79,6 +79,18 @@ si_adapter_reset (WpSessionItem * item)
   WP_SESSION_ITEM_CLASS (si_adapter_parent_class)->reset (item);
 }
 
+static gpointer
+si_adapter_get_associated_proxy (WpSessionItem * item, GType proxy_type)
+{
+  WpSiAdapter *self = WP_SI_ADAPTER (item);
+
+  if (proxy_type == WP_TYPE_NODE)
+    return self->node ? g_object_ref (self->node) : NULL;
+
+  return WP_SESSION_ITEM_CLASS (si_adapter_parent_class)->get_associated_proxy (
+      item, proxy_type);
+}
+
 static GVariant *
 si_adapter_get_configuration (WpSessionItem * item)
 {
@@ -355,6 +367,7 @@ si_adapter_class_init (WpSiAdapterClass * klass)
 
   object_class->finalize = si_adapter_finalize;
 
+  si_class->get_associated_proxy = si_adapter_get_associated_proxy;
   si_class->configure = si_adapter_configure;
   si_class->get_configuration = si_adapter_get_configuration;
   si_class->get_next_step = si_adapter_get_next_step;