Relations & Graph
Relations connect ideas to each other, forming a navigable graph. Each relation has a source, a
target, a type, an optional note, and a created_by marker recording whether you made it or
NeuralRepo’s similarity pass did.
List Relations for an Idea
Section titled “List Relations for an Idea”GET /api/v1/ideas/:id/relations
Returns relations involving the idea, split by direction. Relations to or from an archived idea are omitted from both lists.
curl https://neuralrepo.com/api/v1/ideas/137/relations \ -H "X-API-Key: nrp_YOUR_KEY"const res = await fetch( "https://neuralrepo.com/api/v1/ideas/137/relations", { headers: { "X-API-Key": "nrp_YOUR_KEY" } });const { outgoing, incoming } = await res.json();Response 200 OK
{ "outgoing": [ { "id": 1, "relation_type": "related", "score": 0.87, "note": "Both deal with theming", "created_by": "system", "created_at": "2026-03-20 10:00:00", "idea_id": 138, "idea_number": 43, "idea_title": "Theme customization options", "idea_status": "building" } ], "incoming": [ { "id": 2, "relation_type": "blocks", "score": null, "note": null, "created_by": "user", "created_at": "2026-03-19 08:00:00", "idea_id": 96, "idea_number": 12, "idea_title": "Implement design tokens", "idea_status": "captured" } ]}The row is flat — the other side of the relation arrives as idea_id, idea_number,
idea_title, and idea_status rather than as a nested object, and it carries no tags. id is
the relation’s own id, which is what PATCH and DELETE take. Rows are ordered by
relation_type, then by descending score.
Create Relation
Section titled “Create Relation”POST /api/v1/map/relations
Pro only. Accepts a single relation, or up to 50 at once.
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
source_idea_id | number | Yes | id of the source idea |
target_idea_id | number | Yes | id of the target idea |
relation_type | string | No | related (default), parent, blocks, inspires, or supersedes |
note | string | No | Free text; anything past 500 characters is silently truncated |
curl -X POST https://neuralrepo.com/api/v1/map/relations \ -H "X-API-Key: nrp_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "source_idea_id": 137, "target_idea_id": 138, "relation_type": "related", "note": "Both deal with theming" }'const res = await fetch("https://neuralrepo.com/api/v1/map/relations", { method: "POST", headers: { "X-API-Key": "nrp_YOUR_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ source_idea_id: 137, target_idea_id: 138, relation_type: "related", note: "Both deal with theming", }),});const { relation } = await res.json();Response 201 Created
{ "relation": { "id": 17, "source_idea_id": 137, "target_idea_id": 138, "relation_type": "related", "note": "Both deal with theming" }}The single-relation response carries no created_at and no score — score is only ever set
by the automatic similarity pass, never on a relation you create. Re-read the relation through
GET /ideas/:id/relations if you need those fields.
Creating many at once
Section titled “Creating many at once”Send a links array instead of a single body. Up to 50 per call.
curl -X POST https://neuralrepo.com/api/v1/map/relations \ -H "X-API-Key: nrp_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "links": [ { "source_idea_id": 137, "target_idea_id": 138, "relation_type": "related" }, { "source_idea_id": 137, "target_idea_id": 96, "relation_type": "blocks", "note": "needs tokens first" } ] }'Response 201 Created
{ "linked": 1, "errors": 1, "results": [ { "source_idea_id": 137, "target_idea_id": 138, "relation_type": "related", "status": "created", "relation_id": 17 }, { "source_idea_id": 137, "target_idea_id": 96, "relation_type": "blocks", "status": "error", "error": "This would create a circular blocking chain: #137 → #96 → #137" } ]}Update Relation
Section titled “Update Relation”PATCH /api/v1/map/relations/:id
Pro only. Changes the type, the note, or both.
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
relation_type | string | No | related, parent, blocks, inspires, or supersedes |
note | string | null | No | Updated note; null clears it, over 500 characters truncates |
curl -X PATCH https://neuralrepo.com/api/v1/map/relations/17 \ -H "X-API-Key: nrp_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"relation_type": "blocks"}'A body with neither field returns 400 Nothing to update.
Response 200 OK — the updated relation wrapped in { "relation": {...} }, or 404 if the
relation id is not yours.
Delete Relation
Section titled “Delete Relation”DELETE /api/v1/map/relations/:id
Pro only.
curl -X DELETE https://neuralrepo.com/api/v1/map/relations/17 \ -H "X-API-Key: nrp_YOUR_KEY"Response 200 OK
{ "success": true}Deleting is idempotent and unverified: an id that does not exist, or belongs to another account,
still returns { "success": true }. It never returns 404.
Duplicate pairs
Section titled “Duplicate pairs”The uniqueness rule is (source_idea_id, target_idea_id, created_by) — it does not include
the relation type. So:
- A second user-created relation from A → B is refused with
Relation already exists, even if you ask for a different type. Change the existing one withPATCHinstead. - A → B and B → A are two different pairs, and both may exist.
- A system-created relation between the same two ideas does not block your user-created one; they coexist, and both appear in the graph.
That refusal is the one error on this page that really does return 409.
Cycle Detection
Section titled “Cycle Detection”Creating a relation runs a breadth-first walk from the proposed target, following edges of the same type, looking for a path back to the source. What happens then depends on the type:
| Types | Cycle behaviour |
|---|---|
blocks, parent | Hard block — always refused; ?force=true does not help |
supersedes | Soft block — refused, but ?force=true creates it anyway |
related, inspires | Not checked at all — the walk is skipped |
The rejection message names the whole chain:
{ "error": "This would create a circular blocking chain: #137 → #96 → #137"}For supersedes the same message ends with Use ?force=true to bypass.
Two more properties of the check:
- The numbers in the message are database ids with a
#prefix, even though#Nmeans the display number everywhere else in NeuralRepo. They are ids. - The walk stops after 50 hops. A cycle that only closes beyond that depth is not detected and the relation is created.
# Soft block: allowed through with ?force=truecurl -X POST "https://neuralrepo.com/api/v1/map/relations?force=true" \ -H "X-API-Key: nrp_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "source_idea_id": 138, "target_idea_id": 137, "relation_type": "supersedes" }'Relation Schema
Section titled “Relation Schema”| Field | Type | Description |
|---|---|---|
id | number | Relation identifier — what PATCH/DELETE take |
source_idea_id | number | Source idea id |
target_idea_id | number | Target idea id |
relation_type | string | related, parent, blocks, inspires, supersedes, or duplicate (system only) |
score | number | null | Similarity score for system-created relations; null for yours |
note | string | null | Optional description, stored truncated at 500 characters |
created_by | string | user or system |
created_at | string | UTC YYYY-MM-DD HH:MM:SS |
Status Codes
Section titled “Status Codes”| Status | Meaning |
|---|---|
200 OK | Read, update, or delete succeeded |
201 Created | Relation created — including bulk calls where every link failed |
400 Bad Request | Validation error, self-link, unknown relation type, or a refused cycle |
401 Unauthorized | Missing or invalid auth |
403 Forbidden | Free plan — writing relations requires Pro |
404 Not Found | An idea id in the request does not exist, or the relation id does not (PATCH only) |
409 Conflict | A user-created relation already exists between that pair, in that direction |