Twin Brain
Read-model endpoints for the AI Twin Brain Simulator: your twin's neural map (neurons, synapses, regions), its wiki-style memory graph, live activity, and an optional wallet-billed AI narration.
All graph data is computed server-side from what your twin has already learned. These endpoints never modify the twin.
Endpoints
| Method | Path | Description | Auth |
|---|---|---|---|
GET | /agents/twins/resolve | Resolve a subject to its twin | JWT + Entitlement |
GET | /agents/twins/{twinId}/brain/graph | Brain graph (nodes, edges, clusters, overlay) | JWT + Entitlement |
GET | /agents/twins/{twinId}/brain/memory-graph | Wiki-style memory graph | JWT + Entitlement |
GET | /agents/twins/{twinId}/brain/memories/{memoryId} | Memory detail with provenance and evidence | JWT + Entitlement |
GET | /agents/twins/{twinId}/brain/activity | Live activity levels for the brain | JWT + Entitlement |
GET | /agents/twins/{twinId}/brain/chain-impact/{chainId} | How a chain has shaped the brain | JWT + Entitlement |
POST | /agents/twins/{twinId}/brain/sample | Consume the one-time free brain preview | JWT + Entitlement |
GET | /agents/twins/{twinId}/brain/sample-status | Whether the one-time preview is still available | JWT + Entitlement |
POST | /agents/twins/{twinId}/brain/narrate | AI narration of a brain region (wallet-billed) | JWT + Entitlement |
GET | /public/twins/{username}/brain | Public brain showcase (opt-in only) | None (30/min) |
Plan access
| Feature | Endpoints | Free | Bitter | Chainer | Architect |
|---|---|---|---|---|---|
Brain Simulator (ai.twin.brain_simulator) | resolve, graph, activity, chain-impact | — | ✓ | ✓ | ✓ |
Memory Graph (ai.twin.memory_graph) | memory-graph, memories/ | — | ✓ | ✓ | ✓ |
One-time preview (ai.twin.brain_sample) | sample, sample-status | ✓ (once) | — | — | — |
AI Narration (ai.twin.brain_narrate) | narrate | — | 5/day | 25/day | 100/day |
Calling an endpoint your plan does not include returns 403 with a message naming the missing feature key. See the feature guide for the full plan matrix.
GET /agents/twins/resolve
Description
Resolves a subject (you, a workspace, a team, or an agent) to its twin so you can call the brain endpoints. Twins are system-managed — this is how you discover the twinId.
Request
- Headers:
Authorization: Bearer <token> - Query params:
| Parameter | Type | Required | Description |
|---|---|---|---|
subjectType | string | Yes | One of chainer, workspace, team, agent |
subjectId | string (max 64) | Yes | Entity id, or me when subjectType=chainer |
You can only resolve your own personal twin, and twins that belong to your account (workspace twins additionally require membership in that workspace). Anything else returns 404.
Response
200 OKjson{ "data": { "twinId": "3f7c2a90-5b1e-4d2a-8c4e-1a9d6b3f5e72", "name": "My Twin", "scopeKind": "chainer", "lifecycleState": "active" } }
Code Example
curl "https://api.chainabit.com/api/v1/agents/twins/resolve?subjectType=chainer&subjectId=me" \
-H "Authorization: Bearer $TOKEN"GET /agents/twins/{twinId}/brain/graph
Description
Returns the strongest window of your twin's brain graph: neurons (nodes), synapses (edges), kind clusters, and optionally a productivity overlay that maps your chainies to regions and your chains to synapses. The strongest connections are returned first; truncated tells you when the full brain is larger than the returned window.
Request
- Headers:
Authorization: Bearer <token> - Path params:
twinId(UUID) - Query params:
| Parameter | Type | Required | Description |
|---|---|---|---|
maxNodes | integer | No | Node budget. Default 150, maximum 300 (higher values are clamped) |
kinds | string (CSV) | No | Node kinds to include: topic, person, tool, trait, memory — e.g. kinds=topic,tool |
overlay | string | No | productivity to include the regions/synapses overlay |
Response
200 OKjson{ "data": { "nodes": [ { "id": "n-typescript", "kind": "topic", "label": "TypeScript", "weight": 4.6, "degree": 2, "lastSeen": "2026-06-10T09:12:41.000Z" }, { "id": "n-testing", "kind": "topic", "label": "Testing", "weight": 2.9, "degree": 1, "lastSeen": "2026-06-09T18:02:10.000Z" }, { "id": "n-deep-focus", "kind": "trait", "label": "Deep focus", "weight": 1.7, "degree": 1, "lastSeen": "2026-06-08T07:45:00.000Z" } ], "edges": [ { "src": "n-typescript", "dst": "n-testing", "weight": 2.9, "releaseProb": 0.74, "sourceCount": 12 }, { "src": "n-typescript", "dst": "n-deep-focus", "weight": 1.7, "releaseProb": 0.52, "sourceCount": 5 } ], "clusters": [ { "kind": "topic", "label": "topic", "nodeCount": 2 }, { "kind": "trait", "label": "trait", "nodeCount": 1 } ], "stats": { "nodeCount": 3, "edgeCount": 2, "totalNodes": 184, "totalEdges": 512 }, "overlay": { "regions": [ { "chainyId": "5c8e1f2a-9b4d-4c6e-8a3f-7d2b6e9c1a45", "title": "Product Launch", "colorHex": "#7C5CFF", "bitCount": 42, "completedBitCount": 31 } ], "synapses": [ { "chainId": "9a1d4f6b-3c2e-4b7a-8d5c-6e9f2b1a7c34", "title": "Daily writing", "colorHex": "#19B26B", "protocolType": "daily", "currentStreak": 9, "totalCompletions": 124, "signedPeriodCount": 87, "chainyIds": ["5c8e1f2a-9b4d-4c6e-8a3f-7d2b6e9c1a45"] } ] }, "truncated": true, "generatedAt": "2026-06-10T09:30:00.000Z" } }
weight and degree are computed from the returned edges, so they describe the window you received. overlay is present only when requested.
Code Example
Use data.twinId from GET /agents/twins/resolve as $TWIN_ID.
curl "https://api.chainabit.com/api/v1/agents/twins/$TWIN_ID/brain/graph?maxNodes=150&kinds=topic,tool,trait&overlay=productivity" \
-H "Authorization: Bearer $TOKEN"GET /agents/twins/{twinId}/brain/memory-graph
Description
Returns the wiki-style memory map: the most relevant memories as nodes, consolidation links between them, and tag clusters for grouping. Memory content is excerpted to 240 characters — use the memory detail endpoint for the full entry.
Request
- Headers:
Authorization: Bearer <token> - Path params:
twinId(UUID) - Query params:
| Parameter | Type | Required | Description |
|---|---|---|---|
maxNodes | integer | No | Memory budget. Default 100, maximum 200 (higher values are clamped) |
Response
200 OKjson{ "data": { "nodes": [ { "id": "b2e6c1d4-7a3f-4e8b-9c5d-2f1a8e6b4c90", "memoryType": "preference", "excerpt": "Prefers concise bullet-point summaries over long prose.", "importance": 8, "relevanceScore": 0.91, "tags": ["writing", "style"], "isConsolidated": false, "flaggedWrongCount": 0, "createdAt": "2026-05-28T10:15:00.000Z" }, { "id": "d4f8a2b6-1c5e-4a9d-8b3f-6e2c9a5d7b18", "memoryType": "preference", "excerpt": "Likes short summaries.", "importance": 5, "relevanceScore": 0.62, "tags": ["writing"], "isConsolidated": true, "flaggedWrongCount": 0, "createdAt": "2026-04-12T08:00:00.000Z" } ], "consolidationEdges": [ { "fromId": "d4f8a2b6-1c5e-4a9d-8b3f-6e2c9a5d7b18", "toId": "b2e6c1d4-7a3f-4e8b-9c5d-2f1a8e6b4c90" } ], "tagClusters": [ { "tag": "writing", "memoryIds": ["b2e6c1d4-7a3f-4e8b-9c5d-2f1a8e6b4c90", "d4f8a2b6-1c5e-4a9d-8b3f-6e2c9a5d7b18"] }, { "tag": "style", "memoryIds": ["b2e6c1d4-7a3f-4e8b-9c5d-2f1a8e6b4c90"] } ], "stats": { "nodeCount": 2, "consolidationEdgeCount": 1, "tagCount": 2, "totalMemories": 57 }, "truncated": true, "generatedAt": "2026-06-10T09:30:00.000Z" } }
tagClusters are groupings, not pairwise edges — render them as hulls or clusters around the listed memories.
Code Example
curl "https://api.chainabit.com/api/v1/agents/twins/$TWIN_ID/brain/memory-graph?maxNodes=100" \
-H "Authorization: Bearer $TOKEN"GET /agents/twins/{twinId}/brain/memories/
Description
Returns one memory in full — the wiki page for that memory: complete content, provenance (where it came from), consolidation relationships, and recent behavioural evidence recorded for the twin.
Request
- Headers:
Authorization: Bearer <token> - Path params:
twinId(UUID),memoryId(UUID)
Response
200 OKjson{ "data": { "id": "b2e6c1d4-7a3f-4e8b-9c5d-2f1a8e6b4c90", "memoryType": "preference", "content": "Prefers concise bullet-point summaries over long prose, especially in status updates.", "importance": 8, "relevanceScore": 0.91, "tags": ["writing", "style"], "isConsolidated": false, "flaggedWrongCount": 0, "createdAt": "2026-05-28T10:15:00.000Z", "source": "session", "sourceId": "a7c3e9f1-2b6d-4e8a-9c4f-5d1b8e3a6c72", "consolidatedInto": null, "consolidatedChildren": [ { "id": "d4f8a2b6-1c5e-4a9d-8b3f-6e2c9a5d7b18", "excerpt": "Likes short summaries." } ], "evidence": [ { "type": "chain_completed", "claim": "Keeps a daily writing habit", "confidence": 0.84, "recordedAt": "2026-06-09T20:00:00.000Z" } ] } }
To remove or correct a memory, use the forget and flag endpoints in the Twins API.
Code Example
Use a memory id from GET /agents/twins/{twinId}/brain/memory-graph's data.nodes[] as $MEMORY_ID.
curl "https://api.chainabit.com/api/v1/agents/twins/$TWIN_ID/brain/memories/$MEMORY_ID" \
-H "Authorization: Bearer $TOKEN"GET /agents/twins/{twinId}/brain/activity
Description
Returns live activity for the brain over a recent window — this is what makes the brain "shine" while your twin produces. Activation levels are 0..1: a node or chain that was just active is near 1, one at the edge of the window is near 0.
Request
- Headers:
Authorization: Bearer <token> - Path params:
twinId(UUID) - Query params:
| Parameter | Type | Required | Description |
|---|---|---|---|
windowMinutes | integer | No | Activity window. Default 60, minimum 5, maximum 1440 (out-of-range values are clamped) |
Response
200 OKjson{ "data": { "totals": { "runs": 14, "aiSessions": 6, "signedPeriods": 2 }, "chainActivations": [ { "chainId": "9a1d4f6b-3c2e-4b7a-8d5c-6e9f2b1a7c34", "level": 1, "lastRunAt": "2026-06-10T09:28:12.000Z", "running": true } ], "nodeActivations": [ { "nodeId": "n-typescript", "level": 0.93 }, { "nodeId": "n-testing", "level": 0.41 } ], "windowMinutes": 60, "cacheTtlSeconds": 30, "generatedAt": "2026-06-10T09:30:00.000Z" } }
cacheTtlSeconds is the snapshot freshness — polling faster than this returns the same data, so use it as your minimum refresh interval. signedPeriods counts your own signed chain periods within the window.
Code Example
curl "https://api.chainabit.com/api/v1/agents/twins/$TWIN_ID/brain/activity?windowMinutes=60" \
-H "Authorization: Bearer $TOKEN"GET /agents/twins/{twinId}/brain/chain-impact/
Description
Summarises how one of your chains has shaped the brain over the last 30 days: how many pieces of behavioural evidence the chain produced and when it last evolved the twin.
Request
- Headers:
Authorization: Bearer <token> - Path params:
twinId(UUID),chainId(UUID)
Response
200 OKjson{ "data": { "chainId": "9a1d4f6b-3c2e-4b7a-8d5c-6e9f2b1a7c34", "twinId": "3f7c2a90-5b1e-4d2a-8c4e-1a9d6b3f5e72", "strengthenedNodes": [], "evidenceCount": 6, "lastEvolvedAt": "2026-06-09T20:00:00.000Z" } }
strengthenedNodes entries have the shape { nodeId, label, kind, weightDelta? }; the array may be empty when the impact cannot be attributed to specific concepts — evidenceCount and lastEvolvedAt always summarise the chain's contribution.
Code Example
Use one of your own chain ids from the Chains API as $CHAIN_ID.
curl "https://api.chainabit.com/api/v1/agents/twins/$TWIN_ID/brain/chain-impact/$CHAIN_ID" \
-H "Authorization: Bearer $TOKEN"POST /agents/twins/{twinId}/brain/sample
Description
Consumes the one-time free brain preview (Free plan). Returns a real brain graph — your actual data, capped at 80 nodes — exactly once per account. A second call returns 403 brain_sample_already_used. Check availability first with sample-status.
Request
- Headers:
Authorization: Bearer <token> - Path params:
twinId(UUID) - Body: none
Response
200 OK— same shape asGET .../brain/graph(withoutoverlay), capped at 80 nodes:json{ "data": { "nodes": [ { "id": "n-typescript", "kind": "topic", "label": "TypeScript", "weight": 4.6, "degree": 2, "lastSeen": "2026-06-10T09:12:41.000Z" } ], "edges": [ { "src": "n-typescript", "dst": "n-testing", "weight": 2.9, "releaseProb": 0.74, "sourceCount": 12 } ], "clusters": [{ "kind": "topic", "label": "topic", "nodeCount": 1 }], "stats": { "nodeCount": 1, "edgeCount": 1, "totalNodes": 184, "totalEdges": 512 }, "truncated": true, "generatedAt": "2026-06-10T09:30:00.000Z" } }403 Forbidden— preview already used:json{ "error": { "code": "brain_sample_already_used", "message": "The one-time brain preview has already been used on this account. Upgrade to unlock the full Brain Simulator." } }
Code Example
curl -X POST "https://api.chainabit.com/api/v1/agents/twins/$TWIN_ID/brain/sample" \
-H "Authorization: Bearer $TOKEN"GET /agents/twins/{twinId}/brain/sample-status
Description
Tells you whether the one-time preview is still available on this account, so your UI can show the right call to action before posting to sample.
Request
- Headers:
Authorization: Bearer <token> - Path params:
twinId(UUID)
Response
200 OKjson{ "data": { "available": true } }
Code Example
curl "https://api.chainabit.com/api/v1/agents/twins/$TWIN_ID/brain/sample-status" \
-H "Authorization: Bearer $TOKEN"POST /agents/twins/{twinId}/brain/narrate
Description
Generates a short AI narration of a region of your brain graph — a focus concept, a path of concepts, or an activation wave. This is the only billed endpoint in the Brain Simulator: usage is charged to your wallet, and each plan has a daily narration budget (Bitter 5/day, Chainer 25/day, Architect 100/day).
The narration is grounded in your graph only — node labels and their connections. Raw memories are never sent to the model.
Request
- Headers:
Authorization: Bearer <token>,Content-Type: application/json - Path params:
twinId(UUID) - Body (provide at least one of
focusNodeId,pathNodeIds,waveSeedId— if several are set,focusNodeIdwins, thenpathNodeIds, thenwaveSeedId):
| Field | Type | Required | Description |
|---|---|---|---|
focusNodeId | string (max 256) | No* | Narrate around a single node |
pathNodeIds | string[] (max 10 items, each max 256) | No* | Narrate a path of nodes in order |
waveSeedId | string (max 256) | No* | Narrate the activation wave seeded at this node |
locale | string | No | en, tr, es, fr, ar, de, pt — default en |
- Example body:json
{ "focusNodeId": "n-typescript", "locale": "en" }
Response
200 OKjson{ "data": { "narration": "You keep circling back to TypeScript — it is the hub your testing habits and deep-focus sessions connect through...", "model": "gemini-3.5-flash", "creditsCharged": 286 } }
| Field | Type | Description |
|---|---|---|
narration | string | The generated narration text |
model | string | The model that produced the narration |
creditsCharged | number | Billed usage committed to your wallet for this narration, measured in model tokens |
400 Bad Request—narrate_target_requiredwhen no target is provided402 Payment Required—insufficient_creditwhen your wallet cannot cover the narration (top up via Wallet)404 Not Found—narrate_target_not_foundwhen the node ids do not exist in this twin brain429 Too Many Requests— daily narration budget for your plan is exhausted; resets the next day
Code Example
curl -X POST "https://api.chainabit.com/api/v1/agents/twins/$TWIN_ID/brain/narrate" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "pathNodeIds": ["n-typescript", "n-testing"], "locale": "en" }'GET /public/twins/{username}/brain
Description
Public, unauthenticated showcase of a brain — available only when the owner has a public profile and has opted in via the publicBrainShowcase twin preference (off by default; see Twins API). The payload is reduced: top topic, tool, and trait nodes only (up to 60 nodes and 180 edges) — never memories, never people. Everything else returns 404.
Changes — including turning the showcase off — can take a few minutes to propagate.
Request
- Path params:
username(Chainer username) - Rate limit: 30/min
Response
200 OKjson{ "data": { "username": "ada", "nodes": [ { "id": "n-typescript", "kind": "topic", "label": "TypeScript", "weight": 4.6 }, { "id": "n-vitepress", "kind": "tool", "label": "VitePress", "weight": 2.1 }, { "id": "n-deep-focus", "kind": "trait", "label": "Deep focus", "weight": 1.7 } ], "edges": [ { "src": "n-typescript", "dst": "n-vitepress", "weight": 2.1 } ], "stats": { "nodeCount": 3, "edgeCount": 1 } } }404 Not Found— the user does not exist, has a non-public profile, or has not opted in
Code Example
curl "https://api.chainabit.com/api/v1/public/twins/ada/brain"Errors
| Status | Code | When | What to do |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Malformed parameters (non-UUID twinId, unknown kinds value, too many pathNodeIds, …) | Fix the request |
| 400 | narrate_target_required | Narrate body has none of focusNodeId, pathNodeIds, waveSeedId | Provide a target |
| 401 | UNAUTHORIZED | Missing or expired token | Re-authenticate |
| 402 | insufficient_credit | Wallet balance cannot cover the narration | Top up your wallet |
| 403 | FORBIDDEN | Your plan does not include the feature — the message names the missing feature key | Upgrade your plan |
| 403 | brain_sample_already_used | The one-time free preview was already consumed on this account | Upgrade to unlock the full simulator |
| 404 | NOT_FOUND | Twin, memory, or chain not found or not accessible to you; public brain not available | Check the id / opt-in state |
| 404 | narrate_target_not_found | No matching nodes for the narration target in this twin brain | Use node ids from the brain graph |
| 429 | RATE_LIMIT_EXCEEDED | Endpoint rate limit hit, or the daily narration budget is exhausted | Retry later; narration budgets reset daily |
Errors use the standard response envelope. See also the global error codes reference.
Notes
- Replace
https://api.chainabit.com/api/v1with your base URL, e.g.https://api.chainabit.com/api/v1. - All
twinId,memoryId, andchainIdpath parameters are UUIDs. Discover yourtwinIdwithGET /agents/twins/resolve. - Account, workspace, and identity scope are derived from your authentication token — request bodies never include them.
- Simulating thinking and tracing decision paths happen in the app on top of the graph data — deterministic and free. Only
narratespends from your wallet.