API Reference

Entity Graph API

Read and write entities and relationships in your realm's compendium. All members can read; creating, updating, merging, or deleting requires the Collaborator role or above. Any member can update their own private notes on an entity.


POST /api/v1/realms/:realmId/compendium/entities

Creates a new entity in the realm. Requires the Collaborator role or above; members without it receive 404. Returns 201 Created.

Path Parameters

ParameterTypeDescription
realmId*stringThe realm ID.

Request Body

ParameterTypeDescription
name*stringDisplay name of the entity.
type*stringEntity type. Must be a valid type for this realm.
description*string | nullDescription text, or null to leave blank.
accessIds*string[]User IDs to grant viewer access. Pass an empty array for realm-wide access.

Response (201)

ParameterTypeDescription
id*stringID of the newly created entity.
name*stringName of the entity.
type*stringEntity type.
description*string | nullDescription, or null.
accessIds*string[]Granted access IDs.
curl -X POST https://realmsofshod.com/api/v1/realms/clxyz.../compendium/entities \
  -H "Authorization: Bearer ros_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"name":"Elara Dusk","type":"character","description":"A wandering mage","accessIds":[]}'
{
  "id": "ent_abc...",
  "name": "Elara Dusk",
  "type": "character",
  "description": "A wandering mage",
  "accessIds": []
}

PATCH /api/v1/realms/:realmId/compendium/entities/:entityId

Updates one or more fields of an existing entity. All body fields are optional; supply only the fields you want to change. Requires the Collaborator role or above.

Path Parameters

ParameterTypeDescription
realmId*stringThe realm ID.
entityId*stringThe entity ID.

Request Body

ParameterTypeDescription
namestringNew display name. Omit to keep the current value.
typestringNew entity type. Omit to keep the current value.
descriptionstring | nullNew description, null to clear it, or omit to keep the current value.
accessIdsstring[]Replacement access list. Omit to keep the current value.

Returns 200 with the updated entity on success.

curl -X PATCH https://realmsofshod.com/api/v1/realms/clxyz.../compendium/entities/ent_abc... \
  -H "Authorization: Bearer ros_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"description":"Updated description"}'
{
  "id": "ent_abc...",
  "name": "Elara Dusk",
  "type": "character",
  "description": "Updated description",
  "accessIds": [],
  "coverImageUrl": "https://cdn.realmsofshod.com/entity-image/..."
}

DELETE /api/v1/realms/:realmId/compendium/entities/:entityId

Permanently deletes an entity and all of its associated relationships, images, and access records. Requires the Collaborator role or above. Returns 204 No Content on success.

Path Parameters

ParameterTypeDescription
realmId*stringThe realm ID.
entityId*stringThe entity ID to delete.
curl -X DELETE https://realmsofshod.com/api/v1/realms/clxyz.../compendium/entities/ent_abc... \
  -H "Authorization: Bearer ros_your_token_here"

POST /api/v1/realms/:realmId/compendium/entities/merge

Merges one or more source entities into a single surviving entity. The source entities are permanently deleted after the merge. Use the boolean flags to control which data is folded in from the sources. Requires the Collaborator role or above. Returns 201 Created with the surviving entity.

Path Parameters

ParameterTypeDescription
realmId*stringThe realm ID.

Request Body

ParameterTypeDescription
survivingEntityId*stringID of the entity that will remain after the merge. All data from the source entities is folded into this one.
entityIds*string[]IDs of the entities to merge into the surviving entity. Must contain at least one ID. The surviving entity ID does not need to be included.
mergeNotes*booleanWhen true, appends the notes from all source entities onto the surviving entity's notes.
mergeRelationships*booleanWhen true, relationships from source entities are reassigned to the surviving entity. Duplicate relationships are dropped.
mergeAccess*booleanWhen true, access lists from all source entities are unioned into the surviving entity's access list.
mergeImages*booleanWhen true, images from source entities are appended to the surviving entity's image list.

Response (201)

ParameterTypeDescription
id*stringID of the newly created entity.
name*stringName of the entity.
type*stringEntity type.
description*string | nullDescription, or null.
accessIds*string[]Granted access IDs.
curl -X POST https://realmsofshod.com/api/v1/realms/clxyz.../compendium/entities/merge \
  -H "Authorization: Bearer ros_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "survivingEntityId": "ent_abc...",
    "entityIds": ["ent_def...", "ent_ghi..."],
    "mergeNotes": true,
    "mergeRelationships": true,
    "mergeAccess": true,
    "mergeImages": false
  }'
{
  "id": "ent_abc...",
  "name": "Elara Dusk",
  "type": "character",
  "description": "A wandering mage from the eastern provinces.",
  "accessIds": []
}

PATCH /api/v1/realms/:realmId/compendium/entities/:entityId/notes

Replaces the authenticated user's private notes on an entity. Notes are per-user — each realm member maintains their own and cannot see others', including the realm's creator. Any realm member with viewer access to the entity can call this endpoint; no elevated role is required. Returns 204 No Content on success.

Path Parameters

ParameterTypeDescription
realmId*stringThe realm ID.
entityId*stringThe entity ID.

Request Body

ParameterTypeDescription
notes*stringThe full replacement notes string. Pass an empty string to clear notes.
curl -X PATCH https://realmsofshod.com/api/v1/realms/clxyz.../compendium/entities/ent_abc.../notes \
  -H "Authorization: Bearer ros_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"notes":"First appeared in session 3. May be connected to the Silver Covenant."}'

POST /api/v1/realms/:realmId/compendium/entities/:entityId/images

Requests a presigned upload for a new entity image. This is the first of two steps — upload the file to the returned URL, then call Finalize Entity Image Upload below with the returned key. Requires the Collaborator role or above. Returns 201 Created.

Path Parameters

ParameterTypeDescription
realmId*stringThe realm ID.
entityId*stringThe entity ID.

Request Body

ParameterTypeDescription
fileType*stringMIME type of the image to upload (e.g. image/png). Must start with image/.
size*integerSize of the file in bytes. Must not exceed 10,000,000 bytes (10 MB).

Response (201)

ParameterTypeDescription
key*stringOpaque identifier for this upload. Pass it to the finalize call below once the file has been uploaded.
url*stringThe presigned URL to upload the file to.
fields*Record<string, string>Form fields to include alongside the file in the upload request.
curl -X POST https://realmsofshod.com/api/v1/realms/clxyz.../compendium/entities/ent_abc.../images \
  -H "Authorization: Bearer ros_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"fileType":"image/png","size":204800}'
{
  "key": "entity-image/realm_xyz.../ent_abc.../1716000000000-a1b2c3-0",
  "url": "https://storage.example.com/bucket-name",
  "fields": {
    "key": "entity-image/realm_xyz.../ent_abc.../1716000000000-a1b2c3-0",
    "Content-Type": "image/png",
    "bucketName": "bucket-name",
    "acl": "public-read"
  }
}

Upload the file directly to the returned url as a multipart form, including every entry from fields alongside the file:

curl -X POST https://storage.example.com/bucket-name \
  -F key="entity-image/realm_xyz.../ent_abc.../1716000000000-a1b2c3-0" \
  -F Content-Type="image/png" \
  -F bucketName="bucket-name" \
  -F acl="public-read" \
  -F file=@portrait.png

PATCH /api/v1/realms/:realmId/compendium/entities/:entityId/images

Finalizes a previously uploaded image, attaching it to the entity as the last image in its list. Call this only after the file has been uploaded to the URL returned by Request Entity Image Upload above. Requires the Collaborator role or above. Returns 200 with the new image on success.

Returns 400 if the entity already has 10 images, or 409 IMAGE_KEY_ALREADY_USED if the key has already been finalized.

Path Parameters

ParameterTypeDescription
realmId*stringThe realm ID.
entityId*stringThe entity ID.

Request Body

ParameterTypeDescription
key*stringThe key returned from the upload-request call, after the file has been uploaded to it.

Response (200)

ParameterTypeDescription
id*stringUnique image identifier.
order*integerZero-based display order. The image with order 0 is the cover image.
imageUrl*stringPublic URL of the image.
curl -X PATCH https://realmsofshod.com/api/v1/realms/clxyz.../compendium/entities/ent_abc.../images \
  -H "Authorization: Bearer ros_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"key":"entity-image/realm_xyz.../ent_abc.../1716000000000-a1b2c3-0"}'
{
  "id": "img_003...",
  "order": 2,
  "imageUrl": "https://cdn.realmsofshod.com/entity-image/realm_xyz.../ent_abc.../1716000000000-a1b2c3-0"
}

GET /api/v1/realms/:realmId/compendium/entities

Returns a paginated list of entities in the realm, ordered newest first. Visibility is per-entity: every member, including the realm's creator, only sees entities their user ID appears in accessIds (or entities with an empty accessIds).

Path Parameters

ParameterTypeDescription
realmId*stringThe realm ID.

Query Parameters

ParameterTypeDescription
limitintegerNumber of results per page. Default: 50, maximum: 100.
cursorstringOpaque pagination cursor from the previous response. Omit to start from the first page.

Response

ParameterTypeDescription
data*Entity[]Array of entity objects for this page.
nextCursor*string | nullCursor for the next page, or null on the last page.
hasMore*booleanTrue when additional pages exist.

Entity

ParameterTypeDescription
id*stringUnique entity identifier.
name*stringDisplay name of the entity.
type*stringEntity type (e.g. character, location, faction). Varies by realm.
description*string | nullFree-text description of the entity, or null.
accessIds*string[]User IDs that have viewer access to this entity. Empty array means accessible to all realm members.
coverImageUrl*string | nullURL of the first image attached to this entity, or null if the entity has no images.
createdAt*integerUnix timestamp (ms) when the entity was created.
curl https://realmsofshod.com/api/v1/realms/clxyz.../compendium/entities \
  -H "Authorization: Bearer ros_your_token_here"
{
  "data": [
    {
      "id": "ent_abc...",
      "name": "Elara Dusk",
      "type": "character",
      "description": "A wandering mage from the eastern provinces.",
      "accessIds": [],
      "coverImageUrl": "https://cdn.realmsofshod.com/entity-image/...",
      "createdAt": 1716000000000
    }
  ],
  "nextCursor": "eyJjIjox...",
  "hasMore": true
}

GET /api/v1/realms/:realmId/compendium/entities/:entityId

Returns a single entity with its full notes and embedded relationship list. Returns 404 if the entity does not exist or the caller lacks viewer access.

Path Parameters

ParameterTypeDescription
realmId*stringThe realm ID.
entityId*stringThe entity ID.

Response

ParameterTypeDescription
id*stringUnique entity identifier.
name*stringDisplay name of the entity.
type*stringEntity type.
description*string | nullFree-text description, or null.
notes*stringExtended notes for the entity (may be empty string).
accessIds*string[]User IDs with viewer access.
images*EntityImage[]Ordered array of images attached to this entity. Empty array if none.
relationships*Relationship[]Relationships involving this entity. See Relationship below.
createdAt*integerUnix timestamp (ms).
updatedAt*integerUnix timestamp (ms).

EntityImage

ParameterTypeDescription
id*stringUnique image identifier.
order*integerZero-based display order. The image with order 0 is the cover image.
imageUrl*stringPublic URL of the image.

Relationship

ParameterTypeDescription
id*stringRelationship ID.
type*stringRelationship type.
fromEntityId*stringID of the source entity.
toEntityId*stringID of the target entity.
accessIds*string[]User IDs with viewer access.
entity*EntityStubThe other entity in the relationship: { id, type, name }.
curl https://realmsofshod.com/api/v1/realms/clxyz.../compendium/entities/ent_abc... \
  -H "Authorization: Bearer ros_your_token_here"
{
  "id": "ent_abc...",
  "name": "Elara Dusk",
  "type": "character",
  "description": "A wandering mage from the eastern provinces.",
  "notes": "First appeared in session 3. May be connected to the Silver Covenant.",
  "accessIds": [],
  "images": [
    { "id": "img_001...", "order": 0, "imageUrl": "https://cdn.realmsofshod.com/entity-image/..." },
    { "id": "img_002...", "order": 1, "imageUrl": "https://cdn.realmsofshod.com/entity-image/..." }
  ],
  "relationships": [
    {
      "id": "rel_xyz...",
      "type": "allied_with",
      "fromEntityId": "ent_abc...",
      "toEntityId": "ent_def...",
      "accessIds": [],
      "entity": {
        "id": "ent_def...",
        "type": "faction",
        "name": "The Silver Covenant"
      }
    }
  ],
  "createdAt": 1716000000000,
  "updatedAt": 1716500000000
}

POST /api/v1/realms/:realmId/compendium/relationships

Creates a new relationship between two entities. Requires the Collaborator role or above. Returns 201 Created with the new relationship ID.

Returns 409 RELATIONSHIP_EXISTS if an identical relationship already exists, or 400 INVALID_ENTITY if either entity ID does not belong to the realm.

Path Parameters

ParameterTypeDescription
realmId*stringThe realm ID.

Request Body

ParameterTypeDescription
type*stringRelationship type.
fromEntityId*stringID of the source entity. Must belong to the realm.
toEntityId*stringID of the target entity. Must belong to the realm.
accessIds*string[]User IDs to grant viewer access. Pass an empty array for realm-wide access.
curl -X POST https://realmsofshod.com/api/v1/realms/clxyz.../compendium/relationships \
  -H "Authorization: Bearer ros_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"type":"allied_with","fromEntityId":"ent_abc...","toEntityId":"ent_def...","accessIds":[]}'
{ "id": "rel_xyz..." }

DELETE /api/v1/realms/:realmId/compendium/relationships/:relationshipId

Permanently deletes a relationship. Requires the Collaborator role or above. Returns 204 No Content with an empty body on success.

Path Parameters

ParameterTypeDescription
realmId*stringThe realm ID.
relationshipId*stringThe relationship ID to delete.
curl -X DELETE https://realmsofshod.com/api/v1/realms/clxyz.../compendium/relationships/rel_xyz... \
  -H "Authorization: Bearer ros_your_token_here"

GET /api/v1/realms/:realmId/compendium/relationships

Returns a paginated list of relationships in the realm, ordered newest first. Note that relationship rows do not include the names of the connected entities. Use the entity endpoints to resolve IDs to names.

Path Parameters

ParameterTypeDescription
realmId*stringThe realm ID.

Query Parameters

ParameterTypeDescription
limitintegerNumber of results per page. Default: 50, maximum: 100.
cursorstringOpaque pagination cursor from the previous response.

Response

ParameterTypeDescription
data*Relationship[]Array of relationship objects for this page.
nextCursor*string | nullCursor for the next page, or null on the last page.
hasMore*booleanTrue when additional pages exist.

Relationship

ParameterTypeDescription
id*stringUnique relationship identifier.
type*stringRelationship type.
fromEntityId*stringID of the source entity.
toEntityId*stringID of the target entity.
accessIds*string[]User IDs with viewer access.
createdAt*integerUnix timestamp (ms).
curl https://realmsofshod.com/api/v1/realms/clxyz.../compendium/relationships \
  -H "Authorization: Bearer ros_your_token_here"
{
  "data": [
    {
      "id": "rel_xyz...",
      "type": "allied_with",
      "fromEntityId": "ent_abc...",
      "toEntityId": "ent_def...",
      "accessIds": [],
      "createdAt": 1716000000000
    }
  ],
  "nextCursor": null,
  "hasMore": false
}