Skip to content

Locations & maps

Locations (Lokacja) and maps (Mapa) are entities too, but they carry a geography the plain entity routes ignore: a containment tree (@lokacja), authored passages (@drzwi), exterior/interior links (@outerior), and the Margonem map catalogue. This page covers the location tree, the connectivity graph, the map-traversal edges inferred from sessions and logs, the claim-ledger lint, and creating or editing locations and maps. Names are URL-encoded in the path; the reads use the fixture geography around Thuzal — its child Gildia Teologów, plus Ithan, Nithal, Karka-han.

Reads need entity.read, writes need entity.write (a reindex needs location.override). Every example runs against a live daemon — see how the reference is tested.

Routes

Method Path Cmdlet Cap Write
GET /locations Find-NerthusLocation entity.read
GET /locations/graph Get-NerthusLocationGraph entity.read
GET /claims/lint Test-NerthusClaims entity.read
POST /locations/reindex Invoke-NerthusReindex location.override
POST /locations New-NerthusLocationEntity entity.write
PATCH /locations/{name} Set-NerthusLocationEntity entity.write
GET /locations/{name} Get-NerthusLocation entity.read
GET /maps/traversal Get-NerthusMapTraversalGraph entity.read
POST /maps/traversal Set-NerthusTraversalEntities entity.write
POST /maps New-NerthusMapEntity entity.write
PATCH /maps/{name} Set-NerthusMapEntity entity.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.

List locations

GET /v1/api/locations returns the { count, items } envelope of every Lokacja projection. Filter with ?parent= (@lokacja) or ?outerior=. Here, the seven fixtures.

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

Response 200 (trimmed):

{ "count": 7, "items": [
  { "Name": "Thuzal", "Type": "Lokacja", "Status": "Aktywny" },
  { "Name": "Gildia Teologów", "Type": "Lokacja", "Location": "Thuzal" }
] }

Fetch one location

GET /v1/api/locations/{name} resolves the name as a Lokacja and enriches the projection with its child locations. An unknown name is 404.

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

Response 200 (trimmed):

{ "Name": "Thuzal", "Type": "Lokacja", "Status": "Aktywny", "Children": ["Gildia Teologów"] }

The location graph

GET /v1/api/locations/graph returns the containment (@lokacja) plus door (@drzwi) edges across every Lokacja, with the node and edge counts. The seven fixtures give seven nodes.

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

Response 200 (trimmed):

{ "NodeCount": 7, "EdgeCount": 2, "Edges": [
  { "Source": "Thuzal", "Target": "Gildia Teologów", "Type": "Containment" }
] }

Lint the claim ledger

GET /v1/api/claims/lint checks the @forma_sesyjna routing ledger against the live session model — duplicate claims, claims on soft-deleted blocks, duplicate headings, and operator claims matching no observed session form. It returns { file, findingCount, findings, stats }; stats carries the ledger tallies (claims, blocks, operator, …).

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

Response 200 (trimmed):

{ "findingCount": 0, "findings": [], "stats": { "claims": 0, "blocks": 26, "operator": 0, "auto": 0 } }

Map traversal edges

GET /v1/api/maps/traversal merges movement edges from session @Lokacje route sequences and cached log transcripts. Each edge carries a Weight and an Origins set; a move between two non-adjacent locations is typed Teleport — the cue for an undocumented passage.

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

Response 200 (trimmed):

{ "EdgeCount": 1, "Edges": [
  { "Source": "Thuzal", "Target": "Rezydencja Tussal", "Type": "Containment", "Weight": 1, "Origins": ["session"] }
] }

Preview a reindex

POST /v1/api/locations/reindex re-runs the full import so edited @forma_sesyjna claims re-route. With ?dryRun=true it writes nothing and returns { wouldReindex, findingCount, findings, stats } — the claim lint, so a conflict never takes effect silently.

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

Response 200:

{ "wouldReindex": true, "findingCount": 0, "findings": [], "stats": { "blocks": 26 } }

Preview a location create

POST /v1/api/locations forces type = Lokacja, so the body needs only a name. ?dryRun=true returns 200 { wouldCreate, type } and touches no disk — proven by the follow-up 404. The scratch location is a tavern in Thuzal, drawn from the session's world.

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

{ "name": "Karczma w Thuzal" }
await fetch("https://evocation.nerthus.pl/v1/api/locations?dryRun=true", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ name: "Karczma w Thuzal" }),
});

Response 200:

{ "wouldCreate": "Karczma w Thuzal", "type": "Lokacja" }

Create a location

POST /v1/api/locations for real writes the Lokacja block and returns 201 { created, type }. The tavern in Thuzal is scene-derived.

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

{ "name": "Karczma w Thuzal" }
await fetch("https://evocation.nerthus.pl/v1/api/locations", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ name: "Karczma w Thuzal" }),
});

Response 201:

{ "created": "Karczma w Thuzal", "type": "Lokacja" }

Update a location tag

PATCH /v1/api/locations/{name} sets one tag from the closed schema. Here the tavern gets its @lokacja parent — placing it under Thuzal in the containment tree. An unknown tag is 422.

PATCH /v1/api/locations/Karczma%20w%20Thuzal
Content-Type: application/json
Authorization: Bearer <token>

{ "tag": "lokacja", "value": "Thuzal" }
await fetch("https://evocation.nerthus.pl/v1/api/locations/Karczma%20w%20Thuzal", {
  method: "PATCH",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ tag: "lokacja", value: "Thuzal" }),
});

Response 200:

{ "updated": "Karczma w Thuzal", "tag": "lokacja", "changed": true }

Preview a map create

POST /v1/api/maps forces type = Mapa and accepts the map metadata keys (margonemid, url, wymiary, koordynaty, slug, …). ?dryRun=true returns 200 { wouldCreate, type } and writes nothing — proven by the follow-up 404. The scratch map is the ground-floor map of that Thuzal tavern, scene-derived.

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

{ "name": "Karczma w Thuzal - parter" }
await fetch("https://evocation.nerthus.pl/v1/api/maps?dryRun=true", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ name: "Karczma w Thuzal - parter" }),
});

Response 200:

{ "wouldCreate": "Karczma w Thuzal - parter", "type": "Mapa" }

Create a map

POST /v1/api/maps for real writes the Mapa block (seeded @status: Aktywny plus any metadata keys supplied) and returns 201 { created, type }.

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

{ "name": "Karczma w Thuzal - parter" }
await fetch("https://evocation.nerthus.pl/v1/api/maps", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ name: "Karczma w Thuzal - parter" }),
});

Response 201:

{ "created": "Karczma w Thuzal - parter", "type": "Mapa" }

Update a map tag

PATCH /v1/api/maps/{name} sets one schema tag on a Mapa — here the Margonem @margonemid.

PATCH /v1/api/maps/Karczma%20w%20Thuzal%20-%20parter
Content-Type: application/json
Authorization: Bearer <token>

{ "tag": "margonemid", "value": "5099" }
await fetch("https://evocation.nerthus.pl/v1/api/maps/Karczma%20w%20Thuzal%20-%20parter", {
  method: "PATCH",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ tag: "margonemid", value: "5099" }),
});

Response 200:

{ "updated": "Karczma w Thuzal - parter", "tag": "margonemid", "changed": true }

Promote an inferred passage

POST /v1/api/maps/traversal turns an inferred movement into an authored @drzwi passage on the source location, returning { source, target, linked }. Here Thuzal gains a door to Ithan. Both must be existing locations; ?dryRun=true returns { wouldLink } instead.

POST /v1/api/maps/traversal
Content-Type: application/json
Authorization: Bearer <token>

{ "source": "Thuzal", "target": "Ithan" }
await fetch("https://evocation.nerthus.pl/v1/api/maps/traversal", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ source: "Thuzal", target: "Ithan" }),
});

Response 200:

{ "source": "Thuzal", "target": "Ithan", "linked": true }