GET /v1/graph/*
Stream the graph skeleton at canvas scale, fetch rich per-node detail on demand.
The graph endpoints split the team's memory graph into two layers:
- A light skeleton that paints the canvas — every node, every edge, but only the fields a renderer needs (uuid, name, labels, degree, cognitive pattern).
- A rich per-node detail payload — summary, knot narrative, neighbours with full
intent_meta, mentioning episodes — fetched lazily when the user focuses a node.
The list endpoints stream as NDJSON so the canvas paints incrementally as rows arrive; the per-node endpoint returns plain JSON.
Scope required: read (implicit on every key)
GET /v1/graph/nodes
NDJSON stream of light node skeletons. One JSON object per line, sentinel as the last line.
GET /v1/graph/nodes?query=&limit=2000&offset=0
Authorization: Bearer ck_live_...| Query param | Type | Default | Notes |
|---|---|---|---|
query | string | "" | Case-insensitive substring filter on name. |
limit | int, 1–50000 | 2000 | Hard cap is 50K rows per request. Page with offset for larger graphs. |
offset | int, ≥ 0 | 0 | Skip the first N rows (sorted by degree descending). |
Response — 200 OK · application/x-ndjson
Each line is one of:
{"u":"a7b1…","n":"Sridhar","l":["Person"],"d":12,"k":1,"ks":47.3}
{"u":"d04e…","n":"Rust","l":["Tech"],"d":8,"k":0,"ks":null}
…
{"_end": 38}| Field | Meaning |
|---|---|
u | Entity uuid. |
n | Entity name (canonical id for force-graph rendering). |
l | Labels, excluding the base Entity label. |
d | Degree — total connected edges (in + out, undirected). |
k | 1 if the entity is a knot (has been through synthesis), else 0. |
ks | Knot score (null for non-knots). |
_end | Sentinel on the final line. Carries the row count. |
GET /v1/graph/links
NDJSON stream of light edges.
GET /v1/graph/links?include_retracted=false&limit=5000&offset=0
Authorization: Bearer ck_live_...| Query param | Type | Default | Notes |
|---|---|---|---|
include_retracted | bool | false | Set true to include edges with retracted_at IS NOT NULL. |
limit | int, 1–100000 | 5000 | Hard cap 100K per request. |
offset | int, ≥ 0 | 0 | Page through larger result sets. |
Response — 200 OK · application/x-ndjson
{"u":"e1c8…","s":"Sridhar","t":"Rust","p":"preference","r":0}
{"u":"f7a2…","s":"Sridhar","t":"Go","p":"preference","r":0}
…
{"_end": 412}| Field | Meaning |
|---|---|
u | Edge uuid. |
s | Source entity name. |
t | Target entity name. |
p | cognitive_pattern from the edge's intent_meta (null if the edge wasn't intent-annotated). |
r | 1 if retracted, else 0. |
_end | Sentinel — row count. |
why_connected, director_vision, and edge_kind are not in this payload — fetch them via /v1/graph/nodes/{name}/details when the user focuses a node.
GET /v1/graph/nodes/{name}/details
Rich payload for a single entity: the entity itself, every neighbour with full intent_meta, and the mentioning episodes. Fired on canvas-node click.
GET /v1/graph/nodes/Sridhar/details
Authorization: Bearer ck_live_...Name is URL-encoded. Names are unique within (team, project), so this is a single-row lookup.
Response — 200 OK · application/json
{
"entity": {
"uuid": "a7b1…",
"name": "Sridhar",
"labels": ["Person"],
"summary": "Founder of Breeth. Backend leaning, ships fast.",
"edge_count": 12,
"episode_count": 24,
"space": "individual",
"member_id": null,
"created_at": "2025-12-04T18:22:10Z",
"knot_narrative": "Sridhar is the founder-engineer hub …",
"knot_score": 47.3,
"knot_synthesized_at": 1746012345.1,
"knot_model": "anthropic/claude-haiku-4.5"
},
"neighbors": [
{
"peer": "Rust",
"direction": "out",
"fact": "Sridhar prefers async Rust over Go for IO-heavy services",
"cognitive_pattern": "preference",
"intent_meta": {
"edge_kind": "preference",
"cognitive_pattern": "preference",
"why_connected": "Tail latency matters more than ramp-up time.",
"director_vision": "Bet on languages with strong async stories for v1."
},
"edge_uuid": "e1c8…"
}
],
"episodes": [
{ "uuid": "ep_…", "name": "api_1778775104758", "valid_at": "2026-05-13T22:15:04Z" }
]
}| Field | Meaning |
|---|---|
entity.knot_* | Populated only when this entity is a knot. knot_narrative is the pre-synthesized one-paragraph framing — rendered inline; zero live LLM cost. |
neighbors[].direction | "out" means entity → peer; "in" means peer → entity. |
neighbors[].intent_meta | All four intent fields are present when the edge was written with intent extraction. Older / un-annotated edges return null. |
episodes[] | The top mentioning episodes (capped at ~20). |
Errors
| HTTP | Slug | When |
|---|---|---|
| 404 | not_found | No entity with this name in the caller's (team, project). |
Streaming clients
curl — print rows as they land
curl -sN https://api.thebreeth.com/v1/graph/nodes \
-H "Authorization: Bearer $KEY"-N disables curl's output buffering; otherwise you only see the stream after the connection closes.
Python — incremental parse
import httpx, json
with httpx.stream(
"GET",
"https://api.thebreeth.com/v1/graph/nodes",
headers={"Authorization": f"Bearer {KEY}"},
timeout=None,
) as r:
for line in r.iter_lines():
if not line:
continue
row = json.loads(line)
if "_end" in row:
print(f"done — {row['_end']} nodes")
break
print(row["n"], "deg", row["d"])Node / browser — Web Streams API
const res = await fetch("/api/graph/nodes", {
headers: { Authorization: `Bearer ${KEY}` },
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buf = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
let idx: number;
while ((idx = buf.indexOf("\n")) >= 0) {
const line = buf.slice(0, idx).trim();
buf = buf.slice(idx + 1);
if (!line) continue;
const row = JSON.parse(line);
if ("_end" in row) continue;
onNode(row); // paint immediately
}
}Scaling notes
- Compact field names (
u/n/l/d/k/ks) cut wire size ~25% vs the legacy/v1/graph/entitiespayload. Drop the prose (summary,knot_narrative,intent_meta.why_connected) from the list to keep that budget intact at canvas scale. - No layout precompute — clients run their own force-directed simulation. For graphs > 1.5K nodes, switch to a WebGL renderer (Cosmograph etc.); the wire format is unchanged.
- Per-node detail is cached in the dashboard via an in-memory
Map<name, NodeDetails>so the inspector's "Show details" expansion never re-fetches the same node.
Legacy endpoints
GET /v1/graph/{entities,edges,episodes} (regular JSON arrays, no streaming) remain available. They predate the v2 split and ship the full prose payload. Prefer the streamed endpoints above for any new integration; the legacy routes will receive no further enhancements.
Related
- Concepts → Knots — how
knot_*fields are produced - Concepts → Intents — what
intent_metacarries POST /v1/search— hybrid retrieval (returns edges withintent_metainline)