Sessions¶
Sessions are the ### YYYY-MM-DD, Title, Narrator blocks that chronicle play. This page
covers listing, fetching, the participation graph, integrity, and the write workflows
(create, edit, open, close, distribute, apply transfers). The examples use the fixture
session 2026-07-01, Eraster rozmawia z Tussalem,
Anward — narrated by Anward, with the characters Eraster and Lord Tussal on
its @PU.
Reads need session.read, writes need session.write; distribution needs
session.distribute and applying transfers needs currency.write. Headers are
URL-encoded in the path (,→,, space→%20, ó→%C3%B3). Every example runs against a
live daemon — see how the reference is tested.
Routes¶
| Method | Path | Cmdlet | Cap | Write |
|---|---|---|---|---|
| GET | /sessions |
Find-NerthusSession |
session.read |
— |
| GET | /sessions/integrity |
Test-NerthusSessionIntegrity |
session.read |
— |
| GET | /sessions/graph |
Get-NerthusSessionGraph |
session.read |
— |
| POST | /sessions/graph |
Set-NerthusSessionGraph |
session.write |
— |
| POST | /sessions/compare |
Compare-NerthusSessionParticipation |
session.read |
— |
| POST | /sessions/hash |
Set-NerthusSessionHash |
session.write |
✓ |
| GET | /sessions/narrator/{nick}/profile |
Get-NerthusNarratorSessionProfile |
session.read |
— |
| GET | /sessions/{header} |
Get-NerthusSession |
session.read |
— |
| GET | /sessions/{header}/logs |
Get-NerthusSessionLog |
session.read |
— |
| POST | /sessions |
Add-NerthusSession (New-NerthusSession synonym) |
session.write |
✓ |
| PATCH | /sessions/{header} |
Set-NerthusSession |
session.write |
✓ |
| POST | /workflows/open-session |
Open-NerthusSession |
session.write |
✓ |
| POST | /workflows/close-session |
Close-NerthusSession |
session.write |
✓ |
| POST | /workflows/distribute-session |
Invoke-NerthusSessionDistribution |
session.distribute |
✓ |
| POST | /workflows/apply-transfers |
Invoke-NerthusSessionTransfers |
currency.write |
✓ |
Cap is the required capability (— = public, no token); Write (✓) marks routes that pass the write gate; a — in Cmdlet is reached directly, no wrapper. Paths are relative to /v1/api; the cross-cutting contract — middleware, envelopes, status codes — is on the API reference index.
POST /workflows/close-sessionneeds onlysession.write, yet closing distributes the session, applies its@Transferdirectives (currency), and delivers its@Intelover Discord — the Narrator's authority over the coin is the session they ran (Sessions).Add-NerthusSessionrequires-File(a repo-relative target); the daemon computes only the chronological position within it.POST /sessions/graphonly rebuilds the in-memory graph — nothing touches disk, so it carries no Write flag.
List sessions¶
GET /v1/api/sessions returns the { count, items } envelope, each item the session
record. Filter with ?narrator=, ?from=, ?to=, ?entity=. After import there is one.
Response 200 (trimmed):
{ "count": 1, "items": [
{ "Header": "2026-07-01, Eraster rozmawia z Tussalem, Anward", "Date": "2026-07-01",
"Title": "Eraster rozmawia z Tussalem", "Narrator": "Anward", "Generation": "Gen3" }
] }
Fetch one session¶
GET /v1/api/sessions/{header} resolves the header and returns the full projection —
locations, PU, transfers, logs, generation.
Response 200 (trimmed):
{ "Header": "2026-07-01, Eraster rozmawia z Tussalem, Anward", "Date": "2026-07-01",
"Title": "Eraster rozmawia z Tussalem", "Narrator": "Anward", "Generation": "Gen3",
"Locations": ["Thuzal", "Thuzal/Rezydencja Tussal"],
"PU": { "Eraster": "0.2", "Lord Tussal": "0.2" } }
Sessions by narrator¶
GET /v1/api/sessions/narrator/{nick}/profile lists every session a narrator ran.
Response 200:
{ "narrator": "Anward", "count": 1, "sessions": [
{ "header": "2026-07-01, Eraster rozmawia z Tussalem, Anward", "date": "2026-07-01" }
] }
Integrity check¶
GET /v1/api/sessions/integrity compares each session against its stored content hash and
reports tiered findings ({ count, total, items }). Before any hash is baselined the one
fixture session shows as Unhashed.
Response 200:
{ "count": 1, "total": 1, "items": [
{ "severity": "Medium", "header": "2026-07-01, Eraster rozmawia z Tussalem, Anward", "issue": "Unhashed" }
] }
The participation graph¶
GET /v1/api/sessions/graph returns the session⇄entity edges, tiered by how the entity was
found (file placement vs @PU/@Uczestnicy/@Transfer).
Response 200 (trimmed):
{ "Sessions": 1, "Entities": 2, "EdgeCount": 2, "Edges": [
{ "Session": "2026-07-01, Eraster rozmawia z Tussalem, Anward", "Entity": "Eraster", "Tier": 1 }
] }
Rebuild the graph¶
POST /v1/api/sessions/graph refreshes the in-memory model and returns the recomputed
counts. It mutates no lore — a model refresh only — needs session.write.
Response 200:
Compare participation¶
POST /v1/api/sessions/compare with { entities: [...] } returns each entity's session set
and their intersection. Both characters share the one fixture session.
Response 200 (trimmed):
{ "Entities": [
{ "Entity": "Eraster", "Count": 1, "Sessions": ["2026-07-01, Eraster rozmawia z Tussalem, Anward"] },
{ "Entity": "Lord Tussal", "Count": 1, "Sessions": ["2026-07-01, Eraster rozmawia z Tussalem, Anward"] }
], "Shared": ["2026-07-01, Eraster rozmawia z Tussalem, Anward"], "SharedCount": 1 }
A session's logs¶
GET /v1/api/sessions/{header}/logs joins the session's @Logi URLs to their archived
transcripts. This one has none, so the list is empty — a plain read never touches the
network.
Response 200:
Baseline the content hashes¶
POST /v1/api/sessions/hash re-baselines the content-hash sidecar from the current model
and returns how many sessions it stamped — one here.
Response 200:
Preview a create without writing¶
POST /v1/api/sessions?dryRun=true returns 200 { Applied: false, Preview } and writes
nothing — proven by the follow-up 404. The new session is a follow-up meeting in the
Gildia Teologów, scene-derived from Tussal's world.
POST /v1/api/sessions?dryRun=true
Content-Type: application/json
Authorization: Bearer <token>
{ "file": "Świat gry/Thuzal/Sesje lokalne.md", "date": "2026-07-02",
"title": "Narada w Gildii Teologów", "narrator": "Anward",
"locations": ["Thuzal", "Thuzal/Gildia Teologów"], "pu": { "Eraster": "0,1" } }
await fetch("https://evocation.nerthus.pl/v1/api/sessions?dryRun=true", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
body: JSON.stringify({
file: "Świat gry/Thuzal/Sesje lokalne.md", date: "2026-07-02",
title: "Narada w Gildii Teologów", narrator: "Anward",
locations: ["Thuzal", "Thuzal/Gildia Teologów"], pu: { Eraster: "0,1" },
}),
});
Response 200 (trimmed):
{ "Applied": false, "Header": "2026-07-02, Narada w Gildii Teologów, Anward",
"File": "Świat gry/Thuzal/Sesje lokalne.md", "Preview": "### 2026-07-02, Narada w Gildii Teologów, Anward\n..." }
Create a session¶
POST /v1/api/sessions authors the block into the named @plik file and returns 201. The
follow-up GET proves it landed.
await fetch("https://evocation.nerthus.pl/v1/api/sessions", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
body: JSON.stringify({
file: "Świat gry/Thuzal/Sesje lokalne.md", date: "2026-07-02",
title: "Narada w Gildii Teologów", narrator: "Anward",
locations: ["Thuzal", "Thuzal/Gildia Teologów"], pu: { Eraster: "0,1" },
}),
});
Response 201:
{ "Applied": true, "Header": "2026-07-02, Narada w Gildii Teologów, Anward",
"File": "Świat gry/Thuzal/Sesje lokalne.md", "Action": "Appended" }
Edit a session¶
PATCH /v1/api/sessions/{header} splices only the managed sections you pass — here, adding a
@Logi entry to the session just created. Prose and unknown tags stay byte-for-byte.
const header = "2026-07-02, Narada w Gildii Teologów, Anward";
await fetch(`https://evocation.nerthus.pl/v1/api/sessions/${encodeURIComponent(header)}`, {
method: "PATCH",
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
body: JSON.stringify({ logs: ["https://www.margonem.pl/dziennik/2026-07-02-narada"] }),
}).then((r) => r.json());
Response 200:
{ "Applied": true, "Header": "2026-07-02, Narada w Gildii Teologów, Anward",
"File": "Świat gry/Thuzal/Sesje lokalne.md" }
Open a session (draft)¶
POST /v1/api/workflows/open-session begins an in-progress session — a header + prose draft.
Previewed with ?dryRun=true; the returned Preview writes nothing. The scene is a supper
at Tussal's, scene-derived from the prior session.
await fetch("https://evocation.nerthus.pl/v1/api/workflows/open-session?dryRun=true", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
body: JSON.stringify({
file: "Świat gry/Thuzal/Sesje lokalne.md", date: "2026-07-03",
title: "Wieczerza u Tussala", narrator: "Anward",
}),
});
Response 200 (trimmed):
{ "Applied": false, "Header": "2026-07-03, Wieczerza u Tussala, Anward",
"File": "Świat gry/Thuzal/Sesje lokalne.md", "Preview": "### 2026-07-03, Wieczerza u Tussala, Anward" }
Close a session (preview)¶
POST /v1/api/workflows/close-session enacts a session — distribute, transfers, intel, then
re-hash. Previewed here with ?dryRun=true, which enacts nothing.
await fetch("https://evocation.nerthus.pl/v1/api/workflows/close-session?dryRun=true", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
body: JSON.stringify({ header: "2026-07-01, Eraster rozmawia z Tussalem, Anward" }),
}).then((r) => r.json());
Response 200:
Distribute a session (preview)¶
POST /v1/api/workflows/distribute-session fans the block out to every participant's file.
?dryRun=true returns the target list with applied: false and writes nothing — needs
session.distribute.
await fetch("https://evocation.nerthus.pl/v1/api/workflows/distribute-session?dryRun=true", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
body: JSON.stringify({ header: "2026-07-01, Eraster rozmawia z Tussalem, Anward" }),
}).then((r) => r.json());
Response 200 (trimmed):
{ "header": "2026-07-01, Eraster rozmawia z Tussalem, Anward",
"distributedTo": ["Świat gry/Thuzal/Sesje lokalne.md"], "skipped": [], "applied": false }
Apply a session's transfers (preview)¶
POST /v1/api/workflows/apply-transfers folds the session's @Transfer directives into
holdings, dedup-guarded by header. This session has no transfers, so ?dryRun=true moves
nothing — needs currency.write.
await fetch("https://evocation.nerthus.pl/v1/api/workflows/apply-transfers?dryRun=true", {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
body: JSON.stringify({ header: "2026-07-01, Eraster rozmawia z Tussalem, Anward" }),
}).then((r) => r.json());
Response 200: