Graph · live examples
Relations are first-class rows.
Edges live in the same substrate as the rows they connect — declared in the schema, written atomically with the data, traversed with prefix scans. Below: the traversal toolkit, a nine-motif library covering the structural patterns AML teams actually hunt, and the planner's guaranteed cardinality upper bounds (LP-based) pricing every pattern before a single edge is walked.
Neighbors
easyAccounts owned by one customer — forward edges of `owns`.
GET /v1/tenants/:t/graph/bank/neighbors?rel=owns&node=CUST-0042&limit=10
Reverse edges
easyWho sent money INTO this account — reverse direction, same index, no extra write.
GET /v1/tenants/:t/graph/bank/neighbors?rel=txn_to&node=ACC-1877&direction=reverse&limit=10
BFS — blast radius
easyEverything within 3 hops of a flagged account. The first question every investigator asks.
GET /v1/tenants/:t/graph/bank/bfs?rel=txn_to&start=ACC-0954&depth=3
Shortest path
easyAre these two suspect customers connected at all? Path through owns + txn_to.
GET /v1/tenants/:t/graph/bank/path?from=CUST-0042&to=CUST-0163&rels=owns,txn_to&max_depth=6
Dijkstra — cheapest money route
intermediateWeighted shortest path: the route between two accounts that moves money at the lowest total transfer cost.
GET /v1/tenants/:t/graph/bank/dijkstra?rel=txn_to&src=ACC-2201&dst=ACC-1120&weights_json=%7B%22field%22%3A%22fee_inr%22%7D
All simple paths — every route
intermediateNot just one path: EVERY loop-free route money can take between two suspect accounts within 5 hops.
GET /v1/tenants/:t/graph/bank/all_simple_paths?rel=txn_to&src=ACC-2201&dst=ACC-1120&max_depth=5
PageRank — find the hub
intermediateRank accounts by transaction-graph centrality. The planted fraud ring's mule account surfaces on top.
GET /v1/tenants/:t/graph/bank/pagerank?rel=txn_to&limit=5
Motif 1/9 — Fan-out (smurfing)
easyOne account spraying funds to many receivers in a window — structuring deposits below reporting thresholds.
POST /v1/tenants/:t/cypher { "query": "MATCH (a)-[:txn_to]->(b) WHERE a.out_degree_7d >= 15 RETURN a.account_id, a.out_degree_7d ORDER BY a.out_degree_7d DESC LIMIT 5", "default_schema": "bank" }
Motif 2/9 — Fan-in (collection mule)
easyMany senders converging on one receiving account — the collection side of a mule operation.
POST /v1/tenants/:t/cypher { "query": "MATCH (a)-[:txn_to]->(m) WHERE m.in_degree_7d >= 15 RETURN m.account_id, m.in_degree_7d ORDER BY m.in_degree_7d DESC LIMIT 5", "default_schema": "bank" }
Motif 3/9 — Reciprocal pair (ping-pong)
easyA → B and B → A within days — value bounced to fake activity or reset trails.
POST /v1/tenants/:t/cypher { "query": "MATCH (a)-[:txn_to]->(b)-[:txn_to]->(a) WHERE a.account_id < b.account_id RETURN a.account_id, b.account_id LIMIT 5", "default_schema": "bank" }
Motif 4/9 — Wedge (one-step layering)
easyA → B → C where A never touches C directly — the minimal layering hop that breaks naive pair-matching.
POST /v1/tenants/:t/cypher { "query": "MATCH (a {flagged: true})-[:txn_to]->(b)-[:txn_to]->(c) RETURN a.account_id, b.account_id, c.account_id LIMIT 5", "default_schema": "bank" }
Motif 5/9 — Triangle (round-trip)
intermediateA → B → C → A. Money that comes home in three hops — the classic wash cycle.
POST /v1/tenants/:t/cypher { "query": "MATCH (a)-[:txn_to]->(b)-[:txn_to]->(c)-[:txn_to]->(a) WHERE a.flagged = true RETURN a.account_id, b.account_id, c.account_id LIMIT 5", "default_schema": "bank" }
Motif 6/9 — 4-cycle (extended round-trip)
intermediateFour hops back to the origin — one more layer than the triangle, much harder to eyeball in statements.
POST /v1/tenants/:t/cypher { "query": "MATCH (a)-[:txn_to]->(b)-[:txn_to]->(c)-[:txn_to]->(d)-[:txn_to]->(a) WHERE a.flagged = true RETURN a.account_id, b.account_id, c.account_id, d.account_id LIMIT 5", "default_schema": "bank" }
Motif 7/9 — Scatter-gather
intermediateSource fans out to intermediaries which all converge on a different destination — split, launder, reassemble.
POST /v1/tenants/:t/cypher { "query": "MATCH (src)-[:txn_to]->(mid)-[:txn_to]->(dst) WHERE src.out_degree_7d >= 10 AND dst.in_degree_7d >= 10 AND src.account_id <> dst.account_id RETURN src.account_id, dst.account_id, count_distinct(mid) AS lanes LIMIT 5", "default_schema": "bank" }
Motif 8/9 — Gather-scatter
intermediateThe mirror image: many sources pool into one account which then redistributes — pooling before dispersal.
POST /v1/tenants/:t/cypher { "query": "MATCH (src)-[:txn_to]->(hub)-[:txn_to]->(dst) WHERE hub.in_degree_7d >= 10 AND hub.out_degree_7d >= 10 RETURN hub.account_id, hub.in_degree_7d, hub.out_degree_7d LIMIT 5", "default_schema": "bank" }
Motif 9/9 — Mule chain (deep layering)
intermediateA 4+ hop chain through low-activity accounts — each intermediary used once or twice, exactly why chains beat pair rules.
POST /v1/tenants/:t/cypher { "query": "MATCH (a {flagged: true})-[:txn_to]->(m1)-[:txn_to]->(m2)-[:txn_to]->(m3)-[:txn_to]->(dst) WHERE m1.txn_count_30d <= 3 AND m2.txn_count_30d <= 3 AND m3.txn_count_30d <= 3 RETURN a.account_id, m1.account_id, m2.account_id, m3.account_id, dst.account_id LIMIT 5", "default_schema": "bank" }
Guaranteed upper bound vs naive estimate
intermediateBefore executing the triangle motif, the planner computes a GUARANTEED cardinality upper bound (LP-based — it can never lie low). Compare bound vs naive estimate vs what actually came back.
POST /v1/tenants/:t/cypher?explain=1 { "query": "MATCH (a)-[:txn_to]->(b)-[:txn_to]->(c)-[:txn_to]->(a) RETURN a.account_id LIMIT 100", "default_schema": "bank" }
Bound-driven join order — 4-hop chain
intermediateA 4-hop money-flow chain has many join orders. The planner prices each with guaranteed bounds and picks the cheapest — shown with the losing orders' bounds.
POST /v1/tenants/:t/cypher?explain=1 { "query": "MATCH (c)-[:owns]->(a)-[:txn_to]->(m)-[:txn_to]->(x)-[:txn_to]->(dst {flagged: true}) RETURN c.customer_id, dst.account_id LIMIT 50", "default_schema": "bank" }