---
title: "Read Graph"
method: POST
path: "/api/v1/memory/graph"
tags: ["memory"]
---

# Read Graph

`POST /api/v1/memory/graph`

## Request

- Base URL: `https://core.mnemoverse.com`
- URL: `https://core.mnemoverse.com/api/v1/memory/graph`
- Auth: one of:
  - API key in header `X-Api-Key`
  - HTTP bearer

Bounded read of the concept-association graph around `seeds` (core#630).

Distinct from `/memory/read`'s `expanded_concepts` (names only, discarded
weights): this returns the actual `hebbian_edges` rows — weight, valence,
count — for a caller-supplied neighbourhood, never the whole org's graph
(`PostgresStorage.load_hebbian_edges` stays reserved for the single-tenant
WS viz snapshot it was built for). Same org resolution and auth/scope as
`/memory/read` — a read-only op, `require_write=False`.

An unknown seed (or one whose edges all fall below `min_weight`) simply
contributes nothing: an all-unknown `seeds` list is a 200 empty graph, not
a 404 — same "absence is not an error" contract as `/memory/recent`.

`req.domain` reaches `_resolve_target_org` exactly like `/memory/read`'s
does: an `xroom:<room_id>` value resolves to that room's OWN storage
bucket (after the membership/scope check) — a real change of which org's
edges get read, not a filter within one org. Any other value is inert
(`hebbian_edges` has no `domain` column), same as passing none.

## Request body

- GraphRequestSchema: Request body for the bounded concept-association-graph read. New endpoint, no back-compat surface yet, so `extra="forbid"` rejects an unknown field outright rather than silently dropping it (unlike `ReadRequestSchema`, which predates that convention and cannot adopt it now without breaking older clients — see the note on `include_history`).
  - `seeds` string[], required: Concept names to center the graph on. 1-20 items, each at most 200 characters (the `VARCHAR(200)` width of hebbian_edges.source_concept/target_concept — a longer string can never match a stored concept).
  - `depth` integer: Hop count from the seeds. depth=1 returns only edges touching a seed directly; depth=2/3 walk further, one hop per level. Each hop is independently bounded server-side by a per-hop row cap (see `truncated`). When `min_weight` is null AND this REQUESTED depth is >= 2, EVERY hop of the walk — including the first, hop 0 — floors the edge weight at 0.05 (reported back as `min_weight_applied`), so an unfiltered multi-hop walk from a hub concept cannot fan out across an org's whole graph before `limit` ever applies, and a depth=2 request cannot return a direct seed edge the floor would otherwise have excluded one hop later. An EXPLICIT `min_weight` (including `0.0`) is honoured at every hop instead, with no server override.
  - `domain` string, nullable: Same mechanism as /memory/read, not a filter over one org's edges: an `xroom:<room_id>` value resolves (after the membership/scope check) to that ROOM's own storage bucket, so the graph is read from the room's `hebbian_edges` rows instead of the caller's own — this is NOT inert. Any other value (or null) IS inert: `hebbian_edges` has no `domain` column, so a plain domain string does not narrow which edges/atoms count — it has the same effect as omitting the field entirely.
  - `min_weight` number, nullable: Drop edges below this weight. null = no caller-supplied floor at depth 1; when the REQUESTED depth is >= 2, the server applies its own floor (0.05, see `depth`) at every hop of the walk, including hop 0 — reported back as `min_weight_applied`. An EXPLICIT value (including `0.0`) is honoured at EVERY hop with no server override, so `min_weight=0.0` at depth>=2 really does return every edge down to weight 0.
  - `limit` integer: Maximum number of edges to return. Mirrors ReadRequestSchema.top_k's bounds.

## Response `200`

Successful Response

- GraphResponseSchema: Response from the bounded concept-association-graph read.
  - `nodes` GraphNodeSchema[], required: Concepts touched by `edges` below. A seed with no surviving edge (unknown concept, or every edge fell below `min_weight`) is NOT listed — nodes are derived from the returned edges, not echoed from the request.
    - `concept` string, required: The concept name.
    - `degree` integer, required: Number of returned edges touching this concept — a property of THIS response's edge set, not the concept's total degree across the org's whole graph (which `limit`/`truncated` may cut short).
  - `edges` GraphEdgeSchema[], required: Association edges found within `depth` hops.
    - `source` string, required: Source concept (storage order, not learn order).
    - `target` string, required: Target concept (storage order, not learn order).
    - `weight` number, required: Co-activation strength, [0, max_weight].
    - `valence` number, required: Outcome polarity, [-1, +1].
    - `count` integer, required: Co-activation count.
    - `updated_at` string, date-time, required: UTC instant this edge was last reinforced.
  - `truncated` boolean, required: True when either a per-hop server-side cap or the request's `limit` cut the walk short — the graph may hold more edges than are reported here.
  - `min_weight_applied` number, required: The weight floor actually used at EVERY hop of the walk. Equal to the request's `min_weight` when it was set (an explicit value, including `0.0`, is never overridden); equal to the server floor 0.05 when the request left `min_weight` null and the REQUESTED `depth` was >= 2 (applied from hop 0 onward, not just hop 1 onward); equal to `0.0` when it was null and `depth == 1`.

## Other responses

- `400`: Validation error
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not found
- `422`: Validation Error
- `429`: Rate limited
- `500`: Internal server error
- `503`: Service unavailable

---

[API](https://skmtc.dev/mnemoverse/apis/mnemoverse-core.md) · [All operations](https://skmtc.dev/mnemoverse/apis/mnemoverse-core/llms.txt) · [OpenAPI document](https://skmtc.dev/mnemoverse/apis/mnemoverse-core/revisions/1f5e0048f017?raw)
