diff --git a/src/polymesh/algorithms/properties.hh b/src/polymesh/algorithms/properties.hh
index df2a8151303d5879cec6ac70b96ebf48f7ff99be..7b6988bec9182fcd2d4e7fdd07b3467b26c959d8 100644
--- a/src/polymesh/algorithms/properties.hh
+++ b/src/polymesh/algorithms/properties.hh
@@ -196,6 +196,12 @@ bool can_rotate_next(halfedge_handle h);
 /// returns true if m.halfedges().rotate_prev(h) would work
 bool can_rotate_prev(halfedge_handle h);
 
+/// returns true if m.edges().add_or_get(v_from, v_to) would work
+bool can_add_or_get_edge(vertex_handle v_from, vertex_handle v_to);
+
+/// returns true if m.edges().add_or_get(h_from, h_to) would work
+bool can_add_or_get_edge(halfedge_handle h_from, halfedge_handle h_to);
+
 /// ======== IMPLEMENTATION ========
 
 inline bool is_boundary(vertex_handle v) { return v.is_boundary(); }
@@ -741,4 +747,48 @@ inline bool can_rotate_prev(halfedge_handle h)
     return true;
 }
 
+inline bool can_add_or_get_edge(vertex_handle v_from, vertex_handle v_to)
+{
+    POLYMESH_ASSERT(v_from.mesh == v_to.mesh);
+    auto ll = low_level_api(v_from.mesh);
+
+    if (v_from == v_to)
+        return false; // no self-loops
+
+    if (ll.find_halfedge(v_from, v_to).is_valid())
+        return true; // existing
+
+    if (!v_from.is_isolated() && ll.find_free_incident(v_from).is_invalid())
+        return false; // from already full
+
+    if (!v_to.is_isolated() && ll.find_free_incident(v_to).is_invalid())
+        return false; // to already full
+
+    return true;
+}
+
+inline bool can_add_or_get_edge(halfedge_handle h_from, halfedge_handle h_to)
+{
+    POLYMESH_ASSERT(h_from.mesh == h_to.mesh);
+    auto ll = low_level_api(h_from.mesh);
+
+    auto v_from = h_from.vertex_to();
+    auto v_to = h_to.vertex_to();
+
+    if (v_from == v_to)
+        return false; // no self-loops
+
+    auto ex_he = ll.find_halfedge(v_from, v_to);
+    if (ex_he.is_valid())
+        return true; // existing
+
+    if (!ll.is_free(h_from))
+        return false; // cannot insert into face
+
+    if (!ll.is_free(h_to))
+        return false; // cannot insert into face
+
+    return true;
+}
+
 }
diff --git a/src/polymesh/impl/impl_low_level_api_mutable.hh b/src/polymesh/impl/impl_low_level_api_mutable.hh
index 27fb5bbf352a9fd3797840cda6cdd208fdbca5c8..612af0e19d8026135fac0629ec5609534e97f7c0 100644
--- a/src/polymesh/impl/impl_low_level_api_mutable.hh
+++ b/src/polymesh/impl/impl_low_level_api_mutable.hh
@@ -172,15 +172,16 @@ inline halfedge_index low_level_api_mutable::add_or_get_halfedge(vertex_index v_
 
 inline edge_index low_level_api_mutable::add_or_get_edge(halfedge_index h_from, halfedge_index h_to) const
 {
-    POLYMESH_ASSERT(h_from != h_to);
-
     auto v_from = to_vertex_of(h_from);
     auto v_to = to_vertex_of(h_to);
 
+    POLYMESH_ASSERT(v_from != v_to);
+
     auto ex_he = find_halfedge(v_from, v_to);
     if (ex_he.is_valid())
     {
-        POLYMESH_ASSERT(prev_halfedge_of(ex_he) == h_from && prev_halfedge_of(opposite(ex_he)) == h_to);
+        // TODO: is this really required?
+        // POLYMESH_ASSERT(prev_halfedge_of(ex_he) == h_from && prev_halfedge_of(opposite(ex_he)) == h_to);
 
         // TODO: Maybe try rewriting an existing halfedge that does NOT yet have the right connection.
         return edge_of(ex_he);
diff --git a/src/polymesh/low_level_api.hh b/src/polymesh/low_level_api.hh
index aebcc64f74ab743701ffe0abfa7a1754087acbd6..54f7b67f8fb73b2410a2103c19eb81f52329b335 100644
--- a/src/polymesh/low_level_api.hh
+++ b/src/polymesh/low_level_api.hh
@@ -236,12 +236,12 @@ public:
     /// if edge already exists, returns it
     edge_index add_or_get_edge(halfedge_index h_from, halfedge_index h_to) const;
 
-    /// same as add_or_get_edge but returns the apattrriate half-edge
+    /// same as add_or_get_edge but returns the appropriate half-edge
     /// Assures:
     ///     return_value.from_vertex == v_from
     ///     return_value.to_vertex == v_to
     halfedge_index add_or_get_halfedge(vertex_index v_from, vertex_index v_to) const;
-    /// same as add_or_get_edge but returns the apattrriate half-edge
+    /// same as add_or_get_edge but returns the appropriate half-edge
     /// Assures:
     ///     return_value.from_vertex == h_from.to_vertex
     ///     return_value.to_vertex == h_to.to_vertex