Graph view endpoints
Graph view endpoints return typed projections of the course universe. There is no GraphQL in v1 (ADR 0009); the seven named views below are the v1 graph contract.
Each is POST, accepts a request describing the projection bounds, and
returns a typed graph_view payload inside the standard
envelope.
The list lives at GET /api/v1/graph/views for client discovery; the
projections themselves are:
| Endpoint | Purpose |
|---|---|
POST /api/v1/graph/views/course-neighborhood | Local neighborhood around one course. |
POST /api/v1/graph/views/course-universe | The full universal projection used by the Canva. |
POST /api/v1/graph/views/course-pathways | Paths from one course (or set of courses) to a target. |
POST /api/v1/graph/views/credential-requirements | Tree projection of a credential’s requirements as a graph. |
POST /api/v1/graph/views/unlock-overlay | Overlay of unlocked / taken / planned statuses on a given graph view. State-bearing. |
POST /api/v1/graph/views/target-relevance | Filter a graph view to only nodes relevant to a target credential or course. State-bearing. |
POST /api/v1/graph/views/expand-node | Expand one node’s full neighborhood at higher detail without changing the global layout. |
Bounds
Every graph view request takes optional bound parameters; defaults and hard maxima:
| Bound | Default | Hard max |
|---|---|---|
max_nodes | 250 | 2,500 |
max_edges | 600 | 7,500 |
max_depth | 2 | 4 |
max_courses | 100 | 2,500 |
max_credentials | 5 | 25 |
These exist because uwwoe runs on a single Fly machine; producing unbounded responses would be a DoS vector. Bounds are also a UX feature — a 50,000-edge response is unreadable.
Truncation, not error
- Request bound ≤ default → response is complete.
- Request bound > default but ≤ hard max → response includes
graph_view_truncatedinwarnings, with anomittedcount inmeta. Status200 OK. - Request bound > hard max →
400 bad_requestwith codebound_exceeds_hard_max.
Request shape (shared)
{ "bounds": { "max_nodes": 500, "max_depth": 3 }, "filter": { ... }, // endpoint-specific "include_layout": true // whether to bundle precomputed positions}Including layout is recommended for full-universe views — server-side layout is precomputed at index build time per ADR 0026, so requesting it adds no compute cost.
Response shape (shared)
{ "data": { "nodes": [ { "id": "course:MATH 247", "kind": "course", "...": "..." } ], "edges": [ { "from": "course:MATH 137", "to": "course:MATH 247", "relation": "prerequisite" } ], "layout": { ... }, // present if requested and available "view_meta": { "omitted_nodes": 12, "omitted_edges": 45 } }, "meta": { ... }, "warnings": [ { "code": "graph_view_truncated", "message": "Truncated to max_nodes=500" } ], "unknowns": [], "source_references": [ ... ]}State-bearing views
unlock-overlay and target-relevance require an Authorization
header. They combine the graph projection with the user’s state to
overlay statuses (taken / planned / unlocked) or to filter to
target-relevant nodes.
Atlas worker payloads do not carry the state token — the worker receives only the resulting filtered/overlaid nodes and edges, not the state itself. Gate 4 enforces this.
Want more?
- Graph view response spec — full grammar of each projection.
- Decision: REST + typed graph views — why not GraphQL.
- Decision: Server-side course universe projection — where layout actually runs.
- The Canva — user-facing view of these endpoints.