8. triangles - every closed triple in a relation
← Graph exampleswhat this does
triangles enumerates every closed triple in one table's own relation graph. GET /v1/tenants/:t/graph/:schema/triangles takes a single rel parameter, symmetrises the relation so an edge counts in both directions, and returns one row per triangle with the three primary keys in canonical order.
when to use it
- Mutual-connection detection. Three accounts that all reference each other is a far stronger signal than three that merely share a neighbour.
- Local density analytics. Triangle count over a node's degree is the standard clustering-coefficient measure.
- Ring detection in payments, referrals and reciprocal-link networks, where the closed triple is exactly the shape you are hunting for.
schema requirement
The relation must be declared in the schema's [[relations]] block and must target the table it is declared on - triangle enumeration walks one table's own edge set, and a relation pointing at another table is refused. See schemas/reference#relations.
the request
rel is the only parameter this endpoint accepts. There is no depth, no start node and no cap: the answer is every triangle in the relation.
curl -G "https://$OC_HOST/v1/tenants/$OC_TENANT/graph/social.users/triangles" \
--data-urlencode "rel=follows" \
-H "Authorization: Bearer $OC_TOKEN"# The Python SDK does not wrap this endpoint - call it directly.
import httpx
r = httpx.get(
f"https://{OC_HOST}/v1/tenants/{OC_TENANT}/graph/social.users/triangles",
params={"rel": "follows"},
headers={"Authorization": f"Bearer {OC_TOKEN}"},
)
triangles = r.json()// The TypeScript SDK does not wrap the graph algorithms - use fetch.
const qs = new URLSearchParams({ rel: "follows" });
const res = await fetch(
`https://${OC_HOST}/v1/tenants/${OC_TENANT}/graph/social.users/triangles?${qs}`,
{ headers: { Authorization: `Bearer ${OC_TOKEN}` } },
);
const triangles = await res.json();// The Go SDK does not wrap the graph algorithms - use net/http.
url := "https://" + OC_HOST + "/v1/tenants/" + OC_TENANT +
"/graph/social.users/triangles?rel=follows"
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
req.Header.Set("Authorization", "Bearer "+OC_TOKEN)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var triangles []map[string][]string
json.NewDecoder(resp.Body).Decode(&triangles)what you get back
[
{ "a": ["alice"], "b": ["bob"], "c": ["carol"] },
{ "a": ["heidi"], "b": ["ivan"], "c": ["judy"] }
]
One object per triangle. a, b and c are primary-key arrays, not strings - the array is what a composite primary key needs, so a single-column key arrives as a one-element array. The three keys come back in canonical order, so the same triangle is always reported the same way and you can deduplicate on the triple.
how it works
- The relation is symmetrised before enumeration, so a triangle is found whether its three edges point around the ring or at each other.
- Enumeration runs inside the query executor rather than as a standalone library call, so it inherits the same cancellation and result-budget handling as the rest of the read path.
- A self-loop is not an edge for this purpose and contributes no triangle.
- On a graph big enough to blow the scan budget the request is refused with a tagged
graph_work_budget_exceeded400 instead of being allowed to exhaust memory.
common mistakes
- Expecting direction to matter. The relation is walked as undirected.
alicepointing atbobandbobpointing ataliceare the same edge here. If direction is the question, triangles is the wrong endpoint. - Reading
aas a string. Each ofa,bandcis an array. For a single-column primary key you wantrow.a[0]. - Pointing
relat another table. The relation has to target the table it is declared on. A cross-table relation is refused rather than quietly returning nothing.