Skip to content

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-session needs only session.write, yet closing distributes the session, applies its @Transfer directives (currency), and delivers its @Intel over Discord — the Narrator's authority over the coin is the session they ran (Sessions).
  • Add-NerthusSession requires -File (a repo-relative target); the daemon computes only the chronological position within it.
  • POST /sessions/graph only 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.

GET /v1/api/sessions
Authorization: Bearer <token>
await fetch("https://evocation.nerthus.pl/v1/api/sessions", {
  headers: { "Authorization": `Bearer ${token}` },
}).then((r) => r.json());

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.

GET /v1/api/sessions/2026-07-01,%20Eraster%20rozmawia%20z%20Tussalem,%20Anward
Authorization: Bearer <token>
const header = "2026-07-01, Eraster rozmawia z Tussalem, Anward";
await fetch(`https://evocation.nerthus.pl/v1/api/sessions/${encodeURIComponent(header)}`, {
  headers: { "Authorization": `Bearer ${token}` },
}).then((r) => r.json());

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.

GET /v1/api/sessions/narrator/Anward/profile
Authorization: Bearer <token>
await fetch("https://evocation.nerthus.pl/v1/api/sessions/narrator/Anward/profile", {
  headers: { "Authorization": `Bearer ${token}` },
}).then((r) => r.json());

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.

GET /v1/api/sessions/integrity
Authorization: Bearer <token>
await fetch("https://evocation.nerthus.pl/v1/api/sessions/integrity", {
  headers: { "Authorization": `Bearer ${token}` },
}).then((r) => r.json());

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).

GET /v1/api/sessions/graph
Authorization: Bearer <token>
await fetch("https://evocation.nerthus.pl/v1/api/sessions/graph", {
  headers: { "Authorization": `Bearer ${token}` },
}).then((r) => r.json());

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.

POST /v1/api/sessions/graph
Authorization: Bearer <token>
await fetch("https://evocation.nerthus.pl/v1/api/sessions/graph", {
  method: "POST",
  headers: { "Authorization": `Bearer ${token}` },
}).then((r) => r.json());

Response 200:

{ "rebuilt": true, "sessions": 1, "entities": 2, "edges": 2 }

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.

POST /v1/api/sessions/compare
Content-Type: application/json
Authorization: Bearer <token>

{ "entities": ["Eraster", "Lord Tussal"] }
await fetch("https://evocation.nerthus.pl/v1/api/sessions/compare", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ entities: ["Eraster", "Lord Tussal"] }),
}).then((r) => r.json());

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.

GET /v1/api/sessions/2026-07-01,%20Eraster%20rozmawia%20z%20Tussalem,%20Anward/logs
Authorization: Bearer <token>
const header = "2026-07-01, Eraster rozmawia z Tussalem, Anward";
await fetch(`https://evocation.nerthus.pl/v1/api/sessions/${encodeURIComponent(header)}/logs`, {
  headers: { "Authorization": `Bearer ${token}` },
}).then((r) => r.json());

Response 200:

{ "Header": "2026-07-01, Eraster rozmawia z Tussalem, Anward", "Logs": [] }

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.

POST /v1/api/sessions/hash
Authorization: Bearer <token>
await fetch("https://evocation.nerthus.pl/v1/api/sessions/hash", {
  method: "POST",
  headers: { "Authorization": `Bearer ${token}` },
}).then((r) => r.json());

Response 200:

{ "hashed": 1 }

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.

POST /v1/api/sessions
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", {
  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.

PATCH /v1/api/sessions/2026-07-02,%20Narada%20w%20Gildii%20Teolog%C3%B3w,%20Anward
Content-Type: application/json
Authorization: Bearer <token>

{ "logs": ["https://www.margonem.pl/dziennik/2026-07-02-narada"] }
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.

POST /v1/api/workflows/open-session?dryRun=true
Content-Type: application/json
Authorization: Bearer <token>

{ "file": "Świat gry/Thuzal/Sesje lokalne.md", "date": "2026-07-03",
  "title": "Wieczerza u Tussala", "narrator": "Anward" }
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.

POST /v1/api/workflows/close-session?dryRun=true
Content-Type: application/json
Authorization: Bearer <token>

{ "header": "2026-07-01, Eraster rozmawia z Tussalem, Anward" }
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:

{ "Applied": false, "Closed": "2026-07-01, Eraster rozmawia z Tussalem, Anward" }

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.

POST /v1/api/workflows/distribute-session?dryRun=true
Content-Type: application/json
Authorization: Bearer <token>

{ "header": "2026-07-01, Eraster rozmawia z Tussalem, Anward" }
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.

POST /v1/api/workflows/apply-transfers?dryRun=true
Content-Type: application/json
Authorization: Bearer <token>

{ "header": "2026-07-01, Eraster rozmawia z Tussalem, Anward" }
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:

{ "Applied": false, "Header": "2026-07-01, Eraster rozmawia z Tussalem, Anward",
  "Moves": [], "Warnings": [], "AlreadyApplied": false }