Skip to content

Players & characters

A Gracz (player) owns one or more Postać (character) blocks in nerthus.entities.md. This page covers reading a player and their roster, creating and retiring players and characters, the player's Discord webhook, and the dated per-character info entries. The examples use the canonical session: the Gracz Stefan owns the character Eraster; Roman owns Lord Tussal.

Reads need player.read, writes need player.write. Names are URL-encoded in the path (ł%C5%82, space → %20). Every example runs against a live daemon — see how the reference is tested. The create-success cases use a scratch player (Anward, the session's Narrator, not yet a Gracz) and a scene-derived courier (Posłaniec Stefana), so they never collide with fixture data.

Routes

Method Path Cmdlet Cap Write
GET /people/{name} Get-NerthusPlayer player.read
GET /people/{name}/characters Get-NerthusPlayerCharacter player.read
GET /people/{name}/webhook Get-NerthusPlayerWebhook player.read
PUT /people/{name}/webhook Set-NerthusPlayerWebhook player.write
POST /people New-NerthusPlayer player.write
PATCH /people/{name} Set-NerthusPlayer player.write
DELETE /people/{name} Remove-NerthusPlayer player.write
POST /characters New-NerthusPlayerCharacter player.write
PATCH /characters/{name} Set-NerthusPlayerCharacter player.write
DELETE /characters/{name} Remove-NerthusPlayerCharacter player.write
GET /characters/{name}/info Get-NerthusCharacterInfo player.read
POST /characters/{name}/info Add-NerthusCharacterInfo player.write
PUT /characters/{name}/info Set-NerthusCharacterInfo player.write
DELETE /characters/{name}/info Remove-NerthusCharacterInfo player.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.

  • The /people and /characters routes are the Gracz/Postać CRUD surface (player.write), distinct from generic /entities + entity.write. The gracz role holds player.write.own, scoped to the caller's own Gracz and Postacie.
  • PUT /people/{name}/webhook (body { webhook }) writes the Gracz block's @prfwebhook; an empty value clears it to BRAK.
  • The /characters/{name}/info routes manage the charfile's **Dodatkowe informacje:** entries; PUT/DELETE require match to equal the entry's current text, so a stale match is refused 409.

Fetch a player

GET /v1/api/people/{name} resolves a Gracz name and returns its projection.

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

Response 200 (trimmed):

{ "Name": "Stefan", "Type": "Gracz", "MargonemId": "100001" }

A player's characters

GET /v1/api/people/{name}/characters returns the { count, items } envelope of every Postać that @należy_do the player. Stefan owns one: Eraster.

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

Response 200 (trimmed):

{ "count": 1, "items": [{ "Name": "Eraster", "Type": "Postać", "Owner": "Stefan" }] }

Read a player's webhook

GET /v1/api/people/{name}/webhook returns the Gracz's Discord @prfwebhook (or null when set to BRAK).

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

Response 200:

{ "name": "Stefan", "webhook": "https://discord.com/api/webhooks/1001/PLACEHOLDER" }

Create a player

POST /v1/api/people mints a new Gracz — the handler forces type: Gracz. Anward, the session's Narrator, is not yet a player.

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

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

Response 201:

{ "created": "Anward", "type": "Gracz" }

Update a player tag

PATCH /v1/api/people/{name} sets one tag from the closed schema — here Anward's Margonem id.

PATCH /v1/api/people/Anward
Content-Type: application/json
Authorization: Bearer <token>

{ "tag": "margonemid", "value": "100003" }
await fetch("https://evocation.nerthus.pl/v1/api/people/Anward", {
  method: "PATCH",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ tag: "margonemid", value: "100003" }),
});

Response 200:

{ "updated": "Anward", "tag": "margonemid", "changed": true }

Set a player's webhook

PUT /v1/api/people/{name}/webhook replaces the Gracz's @prfwebhook; an empty value clears it to BRAK.

PUT /v1/api/people/Stefan/webhook
Content-Type: application/json
Authorization: Bearer <token>

{ "webhook": "https://discord.com/api/webhooks/1001/NOWY" }
await fetch("https://evocation.nerthus.pl/v1/api/people/Stefan/webhook", {
  method: "PUT",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ webhook: "https://discord.com/api/webhooks/1001/NOWY" }),
});

Response 200:

{ "updated": "Stefan", "field": "prfwebhook" }

Retire a player (soft delete)

DELETE /v1/api/people/{name} marks the Gracz block @status: Usunięty — never physical removal. Here the scratch player Anward.

DELETE /v1/api/people/Anward
Authorization: Bearer <token>
await fetch("https://evocation.nerthus.pl/v1/api/people/Anward", {
  method: "DELETE",
  headers: { "Authorization": `Bearer ${token}` },
});

Response 200:

{ "softDeleted": "Anward", "status": "Usunięty" }

Create a character

POST /v1/api/characters mints a new Postać — the handler forces type: Postać. Pass the owning Gracz as the należy_do seed tag. The name is a scene-derived courier serving Stefan.

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

{ "name": "Posłaniec Stefana", "tags": { "należy_do": "Stefan" } }
await fetch("https://evocation.nerthus.pl/v1/api/characters", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ name: "Posłaniec Stefana", tags: { "należy_do": "Stefan" } }),
});

Response 201:

{ "created": "Posłaniec Stefana", "type": "Postać" }

Update a character tag

PATCH /v1/api/characters/{name} sets one tag. Promoting a character to Aktywny also demotes the owner's previously active one, so a Gracz never has two active characters. Here a benign location tag on the courier.

PATCH /v1/api/characters/Pos%C5%82aniec%20Stefana
Content-Type: application/json
Authorization: Bearer <token>

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

Response 200:

{ "updated": "Posłaniec Stefana", "tag": "lokacja", "changed": true }

Retire a character (soft delete)

DELETE /v1/api/characters/{name} marks the Postać block @status: Usunięty. Here the scratch courier.

DELETE /v1/api/characters/Pos%C5%82aniec%20Stefana
Authorization: Bearer <token>
await fetch("https://evocation.nerthus.pl/v1/api/characters/Pos%C5%82aniec%20Stefana", {
  method: "DELETE",
  headers: { "Authorization": `Bearer ${token}` },
});

Response 200:

{ "softDeleted": "Posłaniec Stefana", "status": "Usunięty" }

Add a character info entry

POST /v1/api/characters/{name}/info appends one dated entry to the character's info section; from/to are YYYY-MM bounds. The response added is the formatted bullet line.

POST /v1/api/characters/Eraster/info
Content-Type: application/json
Authorization: Bearer <token>

{ "text": "Gość w rezydencji Tussala", "from": "2026-07", "to": "2026-08" }
await fetch("https://evocation.nerthus.pl/v1/api/characters/Eraster/info", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ text: "Gość w rezydencji Tussala", from: "2026-07", to: "2026-08" }),
});

Response 201:

{ "added": "- Gość w rezydencji Tussala (2026-07 - 2026-08)", "name": "Eraster" }

List character info entries

GET /v1/api/characters/{name}/info returns the { name, count, items } envelope; each item carries its index, text, and optional from/to. Add ?activeOn=YYYY-MM-DD to filter to the entries covering that date.

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

Response 200 (trimmed):

{ "name": "Eraster", "count": 1, "items": [{ "index": 0, "text": "Gość w rezydencji Tussala", "from": "2026-07", "to": "2026-08" }] }

Replace a character info entry

PUT /v1/api/characters/{name}/info replaces the entry at index; match (the current raw text) is the optimistic-concurrency guard. Both are required — an empty body is 400, nothing written. To edit for real, read the entry with the GET above, then send its index plus its exact text as match.

PUT /v1/api/characters/Eraster/info
Content-Type: application/json
Authorization: Bearer <token>

{}
await fetch("https://evocation.nerthus.pl/v1/api/characters/Eraster/info", {
  method: "PUT",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({}),
});

Response 400:

{ "error": "index and match required" }

Remove a character info entry

DELETE /v1/api/characters/{name}/info removes the entry at index, guarded by the same match. Both are required — an empty body is 400.

DELETE /v1/api/characters/Eraster/info
Content-Type: application/json
Authorization: Bearer <token>

{}
await fetch("https://evocation.nerthus.pl/v1/api/characters/Eraster/info", {
  method: "DELETE",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({}),
});

Response 400:

{ "error": "index and match required" }