API Reference

Complete REST API documentation for Archivist. All endpoints require the x-api-key header unless noted.

New to Archivist? Start with Core Concepts and the Data Models. See also code examples and the API changelog on GitHub.

Supported Methods

Quick reference for HTTP methods supported by each resource.

ResourceGETPOSTPUTPATCHDELETE
Campaigns✓✓—✓✓
Characters✓✓—✓✓
Sessions✓——✓—
Beats✓✓—✓✓
Moments✓✓—✓✓
Hero Awards✓✓—✓✓
Quotes✓✓—✓✓
Spotlights✓✓—✓✓
Factions✓✓—✓✓
Locations✓✓—✓✓
Items✓✓—✓✓
Quests✓✓—✓✓
Journal Entries✓✓✓—✓
Journal Folders✓✓✓—✓
Links✓✓—✓✓
Entities✓————
Images✓✓——✓
Ask (RAG)✓✓———

Health

GET/health

Health check for the API service (no auth required).

Request Example

curl https://api.myarchivist.ai/health

Responses

200Service is healthy
{
  "status": "healthy",
  "service": "archivist-api",
  "version": "1.0.0",
  "environment": "production",
  "port": 8000,
  "timestamp": "2025-01-15T10:30:00.123456Z"
}

Ask (RAG)

GET/v1/ask/quota

Return remaining Ask token counters for a campaign. When launchPromoActive is true the monthly counter is not enforced; the hourly counter is always enforced. The same counters are included on non-streaming POST /v1/ask responses and as X-Monthly-Remaining-Tokens / X-Hourly-Remaining-Tokens headers on streaming responses.

Headers

x-api-key*— Your API key

Query Params

campaign_id*— Campaign ID

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/ask/quota?campaign_id=camp_abc123"

Responses

200Ask quota remaining
{
  "monthlyTokensRemaining": 12345,
  "hourlyTokensRemaining": 6789,
  "launchPromoActive": false
}
400Missing campaign_id
{
  "detail": "Missing required parameter: campaign_id"
}
POST/v1/ask

RAG chat endpoint for campaign questions. Supports chat history and SSE streaming. Check GET /v1/ask/quota first; streaming responses also send remaining-token headers.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

campaign_id*— ID of your campaign
messages*— Chat messages: [{ role, content }]
stream— Return a streaming response
gm_permissions— When true, grant full journal access for the request (overrides asker_id). Defaults to false.
asker_id— Optional user ID to scope journal access; must be a player/admin/owner of the campaign. Defaults to null.

Request Example

curl -X POST \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "your-campaign-id",
    "messages": [ {"role": "user", "content": "Who is Cassius Traven?"} ],
    "gm_permissions": false,
    "asker_id": null
  }' \
  https://api.myarchivist.ai/v1/ask

Responses

200Successful response (non-streaming)
{
  "answer": "Cassius Traven is a shadowy information broker who operates in the city's underground...",
  "citations": [
    {
      "citation_id": "S1",
      "source_type": "timeline",
      "source_id": "beat-abc123",
      "title": "Session 4 — The Sunken Manor",
      "session_number": 4,
      "excerpt": "Cassius revealed his true identity as..."
    }
  ],
  "monthlyTokensRemaining": 12345,
  "hourlyTokensRemaining": 6789
}
400Bad request - missing required fields
{
  "detail": "messages is required"
}
401Unauthorized - invalid API key
{
  "detail": "Invalid API key"
}
403Forbidden - asker_id lacks campaign access
{
  "detail": "asker_id user does not have access to this world"
}
404Not found - asker_id user missing
{
  "detail": "asker_id user not found"
}
429Rate limit exceeded
{
  "detail": "Monthly token limit exceeded"
}

Campaigns

GET/v1/campaigns

List your campaigns.

Headers

x-api-key*— Your API key

Query Params

page— Page number
size— Page size

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/campaigns?page=1&size=10"

Responses

200List of campaigns
{
  "data": [
    {
      "id": "camp_abc123",
      "title": "Shadows of Elyndor",
      "description": "A dark fantasy campaign",
      "system": "D&D 5e",
      "public": false,
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "total": 1,
  "page": 1,
  "size": 20,
  "pages": 1
}
401Unauthorized - invalid API key
{
  "detail": "Invalid API key"
}
GET/v1/campaigns/{campaign_id}

Get a specific campaign by ID.

Headers

x-api-key*— Your API key

Request Example

curl -H "x-api-key: YOUR_API_KEY" https://api.myarchivist.ai/v1/campaigns/abc123

Responses

200Campaign details
{
  "id": "camp_abc123",
  "title": "Shadows of Elyndor",
  "description": "A dark fantasy campaign",
  "system": "D&D 5e",
  "public": false,
  "created_at": "2024-01-15T10:30:00Z"
}
404Campaign not found
{
  "detail": "Campaign not found"
}
401Unauthorized - invalid API key
{
  "detail": "Invalid API key"
}
POST/v1/campaigns

Create a new campaign. Creation bootstraps the same baseline records as the Archivist web app: the owner is added as a player/admin, a default GM speaker is created, Character Arc defaults are seeded, and default compendium field definitions are created.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

title*— Campaign title
description— Campaign description
system— Game system (e.g. D&D 5e)

Request Example

curl -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "My Epic Campaign",
    "description": "A thrilling adventure",
    "system": "D&D 5e"
  }' \
  https://api.myarchivist.ai/v1/campaigns

Responses

201Campaign created successfully
{
  "id": "camp_new123",
  "title": "My Epic Campaign",
  "description": "A thrilling adventure",
  "system": "D&D 5e",
  "public": false,
  "created_at": "2024-01-25T16:45:00Z"
}
422Validation error - missing required field
{
  "detail": "title is required"
}
401Unauthorized - invalid API key
{
  "detail": "Invalid API key"
}
PATCH/v1/campaigns/{campaign_id}

Update a campaign.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

title— Campaign title
description— Campaign description

Request Example

curl -X PATCH \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Updated Campaign Name"}' \
  https://api.myarchivist.ai/v1/campaigns/abc123

Responses

200Campaign updated successfully
{
  "id": "camp_abc123",
  "title": "Updated Campaign Name",
  "description": "A dark fantasy campaign",
  "system": "D&D 5e",
  "public": false,
  "created_at": "2024-01-15T10:30:00Z"
}
404Campaign not found
{
  "detail": "Campaign not found"
}
401Unauthorized
{
  "detail": "Invalid API key"
}
DELETE/v1/campaigns/{campaign_id}

Permanently delete a campaign. This action cannot be undone.

Headers

x-api-key*— Your API key

Request Example

curl -X DELETE -H "x-api-key: YOUR_API_KEY" https://api.myarchivist.ai/v1/campaigns/abc123

Responses

204Campaign deleted successfully
(no content)
404Campaign not found
{
  "detail": "Campaign not found"
}
401Unauthorized - invalid API key
{
  "detail": "Invalid API key"
}
GET/v1/campaigns/{campaign_id}/stats

Get campaign statistics (character count, session count, etc).

Headers

x-api-key*— Your API key

Request Example

curl -H "x-api-key: YOUR_API_KEY" https://api.myarchivist.ai/v1/campaigns/abc123/stats

Responses

200Campaign statistics
{
  "campaign_id": "camp_abc123",
  "characters": 12,
  "sessions": 24,
  "moments": 156,
  "beats": 48,
  "factions": 5,
  "locations": 18,
  "items": 32
}
404Campaign not found
{
  "detail": "Campaign not found"
}
POST/v1/campaigns/{campaign_id}/links

Create or update a link between two entities. Source and target records must both exist in the same campaign, and aliases are normalized/deduped server-side.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

from_id*— Source entity ID
from_type*— Source entity type: Character, Faction, Location, Item, Moment, GameSession, Beat, Backstory, Journal, or World
to_id*— Target entity ID
to_type*— Target entity type: Character, Faction, Location, Item, or Moment
alias— Relationship label

Responses

201Link created or upserted successfully
{
  "id": "link_new123",
  "campaign_id": "camp_abc123",
  "from_id": "char_456",
  "from_type": "Character",
  "to_id": "loc_789",
  "to_type": "Location",
  "alias": "home",
  "created_at": "2024-01-25T16:45:00Z"
}
404Source or target record was not found in this campaign
{
  "detail": "Target Character not found"
}
422Validation error
{
  "detail": "Link source and target must belong to the same world"
}

Characters

GET/v1/characters

List characters filtered by campaign. The legacy `player_name` field is deprecated; responses may include a nullable read-only `player` object sourced from Speaker.

Headers

x-api-key*— Your API key

Query Params

campaign_id*— Campaign ID
search— Full-text search (name, aliases, player, type) with ILIKE fallback
character_type— PC or NPC
approved_only— Filter approved (default true)
with_links— Preserve wikilink markup
page— Page number
size— Page size (max 100)
fields— Set to card for lightweight list items (id, character_name, type, image)

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/characters?campaign_id=abc123"

Responses

200List of characters
{
  "data": [
    {
      "id": "char_123",
      "campaign_id": "camp_abc123",
      "character_name": "Thorin Ironforge",
      "player_name": null,
      "player": {
        "id": "speaker_123",
        "name": "John Smith",
        "handle": "johnsmith",
        "roles": ["Player"],
        "campaign_id": "camp_abc123",
        "created_at": "2024-01-10T08:00:00Z"
      },
      "description": "A brave dwarf warrior",
      "type": "PC",
      "merge": false,
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "total": 1,
  "page": 1,
  "size": 20,
  "pages": 1
}
422Missing required campaign_id
{
  "detail": "campaign_id is required"
}
401Unauthorized
{
  "detail": "Invalid API key"
}
GET/v1/characters/{character_id}

Get a specific character by ID. The response may include aliases, backstory, speaker linkage, and a nullable read-only `player` object; `player_name` remains legacy.

Headers

x-api-key*— Your API key

Query Params

with_links— Preserve wikilink markup

Request Example

curl -H "x-api-key: YOUR_API_KEY" https://api.myarchivist.ai/v1/characters/char-123

Responses

200Character details
{
  "id": "char_123",
  "campaign_id": "camp_abc123",
  "character_name": "Thorin Ironforge",
  "character_aliases": ["Thorin", "Ironforge"],
  "player_name": null,
  "player": {
    "id": "speaker_123",
    "name": "John Smith",
    "handle": "johnsmith",
    "roles": ["Player"],
    "campaign_id": "camp_abc123",
    "created_at": "2024-01-10T08:00:00Z"
  },
  "description": "A brave dwarf warrior",
  "backstory": "Raised in the halls beneath Emberpeak...",
  "type": "PC",
  "merge": false,
  "created_at": "2024-01-15T10:30:00Z"
}
404Character not found
{
  "detail": "Character not found"
}
POST/v1/characters

Create a new character. `player` is response-only and is not accepted in the request body. Additive parity fields include `character_aliases`/`characterAliases`, `speaker_id`/`speakerId`, and `backstory`; legacy `character_alias` and `player_name` are still accepted.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

character_name*— Character name
campaign_id*— Campaign ID
character_alias— Deprecated single alias field; merged into character_aliases after sanitization
character_aliases— Array of aliases (camelCase `characterAliases` also accepted)
player_name— Deprecated legacy player name field
speaker_id— Linked Speaker ID in the same campaign (camelCase `speakerId` also accepted)
description— Character description
backstory— Character backstory; wikilinks are resolved and stored separately from description links
type— PC or NPC
image— HTTPS image URL (max 2048 chars)

Request Example

curl -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "character_name": "Thorin Ironforge",
    "character_aliases": ["Thorin", "Ironforge"],
    "description": "A brave dwarf warrior",
    "backstory": "Raised in the halls beneath Emberpeak...",
    "speaker_id": "speaker_123",
    "type": "PC",
    "campaign_id": "abc123"
  }' \
  https://api.myarchivist.ai/v1/characters

Responses

201Character created successfully
{
  "id": "char_new456",
  "campaign_id": "camp_abc123",
  "character_name": "Thorin Ironforge",
  "character_aliases": ["Thorin", "Ironforge"],
  "player_name": null,
  "player": null,
  "description": "A brave dwarf warrior",
  "backstory": "Raised in the halls beneath Emberpeak...",
  "type": "PC",
  "merge": false,
  "created_at": "2024-01-25T16:45:00Z"
}
422Validation error - missing required fields or invalid character linkage
{
  "detail": "speaker_id must belong to the same world"
}
422Validation error - HTTP URL not allowed for image
{
  "detail": "image must be an HTTPS URL"
}
404Campaign not found
{
  "detail": "Campaign not found"
}
PATCH/v1/characters/{character_id}

Update a character. `player` is response-only and is not accepted in the request body. PATCH supports the same additive parity fields as POST.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

character_name— Character name
character_alias— Deprecated single alias field
character_aliases— Alias list (camelCase `characterAliases` also accepted)
player_name— Deprecated legacy player name field
speaker_id— Linked Speaker ID (camelCase `speakerId` also accepted)
description— Character description
backstory— Character backstory
type— PC or NPC
image— HTTPS image URL (max 2048 chars)

Responses

200Character updated successfully
{
  "id": "char_123",
  "campaign_id": "camp_abc123",
  "character_name": "Thorin Ironforge the Bold",
  "character_aliases": ["Thorin", "The Bold"],
  "player_name": null,
  "player": {
    "id": "speaker_123",
    "name": "John Smith",
    "handle": "johnsmith",
    "roles": ["Player"],
    "campaign_id": "camp_abc123",
    "created_at": "2024-01-10T08:00:00Z"
  },
  "description": "A brave dwarf warrior with a legendary magic axe",
  "backstory": "Raised in the halls beneath Emberpeak and sworn to the old kings...",
  "type": "PC",
  "merge": false,
  "created_at": "2024-01-15T10:30:00Z"
}
400Character validation failed
{
  "detail": "Character name cannot match the linked speaker name"
}
404Character not found
{
  "detail": "Character not found"
}
DELETE/v1/characters/{character_id}

Delete a character.

Headers

x-api-key*— Your API key

Responses

204Character deleted successfully
(no content)
404Character not found
{
  "detail": "Character not found"
}

Sessions

GET/v1/sessions

List sessions in a campaign. Sessions are system-managed on the developer API: use GET to read and PATCH to update allowed fields. There is no POST or DELETE /v1/sessions on this surface. Each session includes a notes field which is null for most session types. For rawNotes sessions, notes contains the user-supplied session notes (these sessions have no transcript content).

Headers

x-api-key*— Your API key

Query Params

campaign_id*— Campaign ID
page— Page number (default 1)
size— Page size (default 20, max 100)
session_type— audioUpload, playByPost, discordVoice, txtUpload, rawNotes, or other
public_only— Show only public sessions
with_links— Preserve wikilink markup in summary fields

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/sessions?campaign_id=abc123&page=1&size=20"

Responses

200List of game sessions
{
  "data": [
    {
      "id": "session_123",
      "campaign_id": "camp_abc123",
      "type": "audioUpload",
      "title": "The Lost Mines of Phandelver",
      "summary": "The party explored the ancient mines...",
      "notes": null,
      "session_date": "2024-01-20T19:00:00Z",
      "image": null,
      "index": 5,
      "public": false,
      "pbp_start_msg_url": null,
      "pbp_end_msg_url": null,
      "created_at": "2024-01-20T22:30:00Z",
      "updated_at": null
    }
  ],
  "total": 24,
  "page": 1,
  "size": 20,
  "pages": 2
}
422Missing required campaign_id
{
  "detail": "campaign_id is required"
}
GET/v1/sessions/{session_id}

Get a specific session by ID. The notes field is null for most session types. For rawNotes sessions, notes contains the user-supplied session notes; these sessions do not have transcript content. When include_beats or include_moments is true, nested beat descriptions and moment content follow the same with_links behavior as the dedicated beats and moments endpoints.

Headers

x-api-key*— Your API key

Query Params

include_beats— Include related beats
include_moments— Include related moments
with_links— Preserve wikilink markup in summary and, when included, nested beat descriptions and moment content

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/sessions/session-123?include_beats=true"

Responses

200Session details
{
  "id": "session_123",
  "campaign_id": "camp_abc123",
  "type": "audioUpload",
  "title": "The Lost Mines of Phandelver",
  "summary": "The party explored the ancient mines...",
  "notes": null,
  "session_date": "2024-01-20T19:00:00Z",
  "image": null,
  "index": 5,
  "public": false,
  "pbp_start_msg_url": null,
  "pbp_end_msg_url": null,
  "created_at": "2024-01-20T22:30:00Z",
  "updated_at": null,
  "beats": null,
  "moments": null
}
404Session not found
{
  "detail": "Session not found"
}
GET/v1/sessions/{session_id}/cast-analysis

Get stored cast analysis data for a session. pdfSavedAt and pdfPageCount are set when a rendered PDF has been persisted; null means no downloadable PDF exists yet.

Headers

x-api-key*— Your API key

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/sessions/session-123/cast-analysis"

Responses

200Cast analysis payload
{
  "id": "cast_analysis_123",
  "session_id": "session_123",
  "analysis": {
    "coreSessionMetrics": {
      "totalLines": 1284,
      "totalWords": 18201,
      "avgWordsPerLine": 14.18,
      "totalTurns": 1284,
      "avgTurnWords": 14.18
    },
    "talkShare": {
      "GM": 0.42,
      "Player 1": 0.31,
      "Player 2": 0.27
    }
  },
  "pdfPageCount": 4,
  "pdfSavedAt": "2026-08-30T18:12:00Z",
  "created_at": "2025-01-12T20:30:00Z",
  "updated_at": "2025-01-12T20:30:00Z"
}
404Cast analysis not found
{
  "detail": "Cast analysis not found"
}
GET/v1/sessions/{session_id}/handout

Get the session handout for a game session. Returns the structured handout JSON if one has been generated. The handout includes a narrative summary, session outline, encounters, character and entity spotlights, notable items, valuable information, party status, and moments. pdfSavedAt and pdfPageCount are set when a rendered PDF has been persisted; null means no downloadable PDF exists yet.

Headers

x-api-key*— Your API key

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/sessions/session-123/handout"

Responses

200Session handout
{
  "summary": "The party ventured into the **Thornwood** seeking the missing courier...",
  "sessionOutline": "### Entering the Thornwood\n- The party tracked muddy footprints north from Briarton\n- They discovered an abandoned camp with signs of a struggle\n\n### The Ambush\n- Bandits attacked from the tree line\n- The party captured the bandit leader, **Harsk**",
  "encounters": [
    {
      "title": "Ambush at the Thornwood Camp",
      "bullets": [
        "Three bandits attacked from concealed positions in the tree line",
        "The ranger flanked the enemies using the dense underbrush",
        "The bandit leader Harsk was subdued and captured alive",
        "One bandit escaped north toward the river crossing"
      ]
    }
  ],
  "characterSpotlight": [
    {
      "name": "Elara Windsong",
      "description": "Led the interrogation of the captured bandit leader. Negotiated safe passage through the Thornwood in exchange for Harsk's release.",
      "bullets": [
        "Identified the bandit camp through tracking",
        "Secured critical intelligence about the courier's destination"
      ]
    }
  ],
  "otherEntitySpotlight": [
    {
      "name": "Harsk",
      "description": "Bandit leader captured during the Thornwood ambush; revealed the courier was taken to Ashenmere."
    }
  ],
  "items": [
    {
      "name": "Courier's Sealed Letter",
      "description": "A wax-sealed letter bearing the sigil of House Valdris, found discarded at the abandoned camp."
    }
  ],
  "valuableInformation": [
    {
      "info": "The courier was taken alive to Ashenmere by a group working for an unknown patron."
    },
    {
      "info": "Harsk mentioned a 'tall woman in grey' who paid for the abduction."
    }
  ],
  "partyStatusAndNextSteps": {
    "partyStatus": {
      "summary": "The party is camped at the edge of the Thornwood, lightly wounded after the bandit ambush.",
      "bullets": [
        "Harsk has been released per the negotiated agreement",
        "The sealed letter may contain information about the courier's mission"
      ]
    },
    "nextSteps": {
      "summary": "The party intends to travel north to Ashenmere to locate and rescue the missing courier."
    }
  },
  "moments": [
    {
      "label": "A Tense Negotiation",
      "content": "Elara convinced Harsk to reveal the courier's location in exchange for his freedom."
    }
  ],
  "pdfPageCount": 3,
  "pdfSavedAt": "2026-08-30T18:12:00Z"
}
404Handout not found
{
  "detail": "Handout not found for this session"
}
GET/v1/sessions/{session_id}/transcript

Get the cleaned transcript for a game session. Returns the processed transcript with utterances, full text, and aggregate stats. Raw/uncleaned utterances are excluded. Use format=markdown for an agent-friendly plain-text markdown version (returned with Content-Type: text/markdown).

Headers

x-api-key*— Your API key

Query Params

format— Response format: json (default) or markdown. The markdown format returns a plain-text document with metadata, statistics, and speaker-labeled utterances optimised for LLM agent consumption.

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/sessions/session-123/transcript"

Responses

200Session transcript (format=json, default)
{
  "version": 1,
  "created_at": "2025-03-10T06:01:11.195Z",
  "metadata": {
    "session_id": "session-123",
    "session_type": "discordVoice",
    "campaign_id": "camp-abc",
    "world_title": "Adventures in the Realm",
    "world_language": "en",
    "session_date": "2025-03-10T03:28:30.266Z",
    "speakers": [
      "Joshua Perez (GM)",
      "Hannah Gray (Zekaria [PC])"
    ],
    "speaker_count": 2,
    "source_type": "discordVoice",
    "created_at": "2025-03-10T06:01:11.195Z"
  },
  "utterances": [
    {
      "speaker_label": "Joshua Perez (GM)",
      "transcript": "Alright, let's pick up where we left off last time.",
      "start": 0,
      "end": 3.2,
      "said_at": "2025-03-10T03:28:35.170Z"
    },
    {
      "speaker_label": "Hannah Gray (Zekaria [PC])",
      "transcript": "Sounds good. I think we were heading toward the ruins.",
      "start": 3.5,
      "end": 6.8,
      "said_at": "2025-03-10T03:28:38.670Z"
    }
  ],
  "text": "Joshua Perez (GM): Alright, let's pick up where we left off...",
  "stats": {
    "char_count": 143672,
    "tokens": 40104,
    "utterance_count": 1797
  }
}
200Session transcript (format=markdown) — Content-Type: text/markdown
# Transcript

## Metadata

- **Session ID**: session-123
- **Campaign**: Adventures in the Realm
- **Session Date**: 2025-03-10T03:28:30.266Z
- **Session Type**: discordVoice
- **Speakers**: Joshua Perez (GM), Hannah Gray (Zekaria [PC])
- **Speaker Count**: 2

## Statistics

- **Utterances**: 1,797
- **Characters**: 143,672
- **Tokens**: 40,104

## Transcript

**Joshua Perez (GM)** [0:00]: Alright, let's pick up where we left off last time.

**Hannah Gray (Zekaria [PC])** [0:03]: Sounds good. I think we were heading toward the ruins.

...
404Transcript not found
{
  "detail": "Transcript not found for this session"
}
PATCH/v1/sessions/{session_id}

Partially update session metadata (title, summary, notes, type, public, session_date, image). Sessions cannot be created or deleted via the developer API. Omitted fields are left unchanged.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

title— Session title
summary— Session summary
notes— Session notes (meaningful for rawNotes sessions)
type— audioUpload, playByPost, discordVoice, txtUpload, rawNotes, or other
public— Whether the session is public
session_date— ISO datetime
image— HTTPS URL of an image uploaded for this session via the image upload routes (entity_type=session); other URLs are rejected

Responses

200Session updated successfully
{
  "id": "session_123",
  "campaign_id": "camp_abc123",
  "type": "audioUpload",
  "title": "Updated Session Title",
  "summary": "Updated summary...",
  "notes": null,
  "session_date": "2024-01-20T19:00:00Z",
  "image": null,
  "index": 5,
  "public": false,
  "pbp_start_msg_url": null,
  "pbp_end_msg_url": null,
  "created_at": "2024-01-20T22:30:00Z",
  "updated_at": "2024-01-20T23:00:00Z"
}
404Session not found
{
  "detail": "Session not found"
}

Beats

GET/v1/beats

List beats in a campaign (paginated, ordered by index), list beats linked to a session (paginated flat list), or fetch a session beat tree with include_hierarchy=true.

Headers

x-api-key*— Your API key

Query Params

campaign_id— Campaign ID (required for campaign-wide list)
session_id— Session ID — returns a flat paginated list, or a nested tree when include_hierarchy=true
include_hierarchy— With session_id, return nested beat tree instead of paginated flat list
page— Page number (flat lists only)
size— Page size (flat lists only, max 100)
with_links— Preserve wikilink markup

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/beats?campaign_id=abc123"

# Flat session-scoped beat list (paginated)
curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/beats?session_id=session_456&page=1&size=20"

# Session beat hierarchy (nested array, not paginated)
curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/beats?session_id=session_456&include_hierarchy=true"

Responses

200Paginated list of beats (campaign_id or session_id without include_hierarchy)
{
  "data": [
    {
      "id": "beat_123",
      "campaign_id": "camp_abc123",
      "game_session_ids": ["session_456"],
      "game_session_id": "session_456",
      "label": "The Final Battle",
      "type": "major",
      "description": "The party confronts the dark lord",
      "index": 1,
      "parent_id": null,
      "metadata": {},
      "created_at": "2024-01-20T22:30:00Z",
      "updated_at": null
    }
  ],
  "total": 48,
  "page": 1,
  "size": 20,
  "pages": 3
}
200Session beat hierarchy (include_hierarchy=true)
[
  {
    "id": "beat_123",
    "campaign_id": "camp_abc123",
    "label": "Act I",
    "type": "major",
    "index": 0,
    "linkedEntities": {
      "characters": [],
      "factions": [],
      "locations": [],
      "items": []
    },
    "children": [
      {
        "id": "beat_124",
        "label": "The ambush",
        "type": "minor",
        "index": 0,
        "linkedEntities": {
          "characters": ["char_123"],
          "factions": [],
          "locations": ["loc_456"],
          "items": []
        },
        "children": []
      }
    ]
  }
]
422Missing required campaign_id or session_id
{
  "detail": "campaign_id or session_id is required"
}
GET/v1/beats/{beat_id}

Get a specific beat by ID.

Headers

x-api-key*— Your API key

Query Params

with_links— Preserve wikilink markup

Request Example

curl -H "x-api-key: YOUR_API_KEY" https://api.myarchivist.ai/v1/beats/beat-123

Responses

200Beat details
{
  "id": "beat_123",
  "campaign_id": "camp_abc123",
  "game_session_ids": ["session_456"],
  "game_session_id": "session_456",
  "label": "The Final Battle",
  "type": "major",
  "description": "The party confronts the dark lord",
  "index": 1,
  "parent_id": null,
  "metadata": {},
  "created_at": "2024-01-20T22:30:00Z",
  "updated_at": null
}
404Beat not found
{
  "detail": "Beat not found"
}
POST/v1/beats

Create a new beat.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

label*— Beat label
type*— major, minor, or step
campaign_id*— Campaign ID
game_session_id— Session ID
description— Beat description
index— Position within siblings
parent_id— Parent beat ID (for minor/step)

Request Example

curl -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "The Final Battle",
    "type": "major",
    "campaign_id": "abc123",
    "game_session_id": "session-123"
  }' \
  https://api.myarchivist.ai/v1/beats

Responses

201Beat created successfully
{
  "id": "beat_new789",
  "campaign_id": "camp_abc123",
  "game_session_id": "session_456",
  "label": "The Final Battle",
  "type": "major",
  "description": null,
  "index": 1,
  "parent_id": null,
  "created_at": "2024-01-25T16:45:00Z"
}
422Validation error
{
  "detail": "label, type, and campaign_id are required"
}
PATCH/v1/beats/{beat_id}

Update a beat (uses JSON Merge Patch).

Headers

x-api-key*— Your API key
Content-Type*— application/merge-patch+json

Request Body

label— Beat label
description— Beat description
index— Position within siblings
parent_id— Parent beat ID

Responses

200Beat updated successfully
{
  "id": "beat_123",
  "campaign_id": "camp_abc123",
  "game_session_id": "session_456",
  "label": "Updated Beat Label",
  "type": "major",
  "description": "Updated description",
  "index": 2,
  "parent_id": null,
  "created_at": "2024-01-20T22:30:00Z"
}
404Beat not found
{
  "detail": "Beat not found"
}
DELETE/v1/beats/{beat_id}

Delete a beat (with hierarchy rules).

Headers

x-api-key*— Your API key

Responses

204Beat deleted successfully
(no content)
404Beat not found
{
  "detail": "Beat not found"
}

Moments

GET/v1/moments

List moments. A Key Moment card is kind "key-moment" and usually has categories containing key-moment. Rows created without kind stay legacy and are not included in the product key-moment views.

Headers

x-api-key*— Your API key

Query Params

campaign_id— Campaign ID
session_id— Session ID
search— Search by label
page— Page number
size— Page size (max 100)
fields— Set to card for lightweight list items (id, label, image, session_id, created_at, categories)
character_ids— Comma-separated character IDs to filter by linked entities
location_ids— Comma-separated location IDs to filter by linked entities
faction_ids— Comma-separated faction IDs to filter by linked entities
item_ids— Comma-separated item IDs to filter by linked entities
session_ids— Comma-separated session IDs to filter by session
categories— Comma-separated categories (ARRAY overlap). Key Moment cards include key-moment
kind— key-moment for Key Moment cards. Other rows are legacy
with_links— Preserve wikilink markup

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/moments?campaign_id=abc123&categories=key-moment&kind=key-moment"

Responses

200List of moments
{
  "data": [
    {
      "id": "moment_123",
      "campaign_id": "camp_abc123",
      "session_id": "session_456",
      "label": "The stand at the gate",
      "content": "Thorin holds the line as the gate shudders.",
      "image": "https://cdn.example.com/moments/gate.jpg",
      "index": 0,
      "categories": ["key-moment"],
      "kind": "key-moment",
      "metadata": { "style": "verdant", "variant": "landscape" },
      "created_at": "2024-01-20T22:30:00Z",
      "updated_at": null
    }
  ],
  "total": 156,
  "page": 1,
  "size": 20,
  "pages": 8
}
200Card list (fields=card)
{
  "data": [
    {
      "id": "moment_123",
      "label": "The stand at the gate",
      "image": "https://cdn.example.com/moments/gate.jpg",
      "session_id": "session_456",
      "created_at": "2024-01-20T22:30:00Z",
      "categories": ["key-moment"]
    }
  ],
  "total": 156,
  "page": 1,
  "size": 20,
  "pages": 8
}
GET/v1/moments/{moment_id}

Get a moment by ID, including image, categories, kind, and presentation metadata. Key Moment cards use kind "key-moment".

Headers

x-api-key*— Your API key

Query Params

with_links— Preserve wikilink markup

Responses

200Moment details
{
  "id": "moment_123",
  "campaign_id": "camp_abc123",
  "session_id": "session_456",
  "label": "The stand at the gate",
  "content": "Thorin holds the line as the gate shudders.",
  "image": "https://cdn.example.com/moments/gate.jpg",
  "index": 0,
  "categories": ["key-moment"],
  "kind": "key-moment",
  "metadata": { "style": "verdant", "variant": "landscape" },
  "created_at": "2024-01-20T22:30:00Z",
  "updated_at": "2024-01-21T10:00:00Z"
}
404Moment not found
{
  "detail": "Moment not found"
}
POST/v1/moments

Create a moment attached to a session. For a Key Moment card set kind to "key-moment" and categories to ["key-moment"]. Omitting kind stores legacy, which the product key-moment views do not include. Attach art with the image upload routes (entity_type=moment) or an HTTPS image URL.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

campaign_id*— Campaign ID
session_id*— Session ID
label— Moment label
content— Moment content
image— HTTPS image URL (max 2048 chars)
index— Display order
categories— Category tags. Include "key-moment" for a Key Moment card
kind— key-moment for a Key Moment card. Defaults to legacy
metadata— Presentation JSON. style: parchment, azurelight, nightwire, verdant, infernalink, or custom. Key Moment cards use variant "landscape"

Responses

201Moment created successfully
{
  "id": "moment_new999",
  "campaign_id": "camp_abc123",
  "session_id": "session_456",
  "label": "The stand at the gate",
  "content": "Thorin holds the line.",
  "image": null,
  "index": 0,
  "categories": ["key-moment"],
  "kind": "key-moment",
  "metadata": { "style": "verdant", "variant": "landscape" },
  "created_at": "2024-01-25T16:45:00Z",
  "updated_at": null
}
422Validation error
{
  "detail": "campaign_id and session_id are required"
}
PATCH/v1/moments/{moment_id}

Update a Key Moment. metadata is merged into the existing JSON object (same as the product app).

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

label— Moment label
content— Moment content
image— HTTPS image URL (max 2048 chars)
index— Display order
categories— Replaces the full category list when sent
kind— key-moment for a Key Moment card, or legacy
metadata— Merged into existing presentation JSON. style: parchment, azurelight, nightwire, verdant, infernalink, or custom

Responses

200Moment updated successfully
{
  "id": "moment_123",
  "campaign_id": "camp_abc123",
  "session_id": "session_456",
  "label": "Updated Label",
  "content": "Updated content...",
  "image": null,
  "index": 0,
  "categories": ["key-moment"],
  "kind": "key-moment",
  "metadata": { "style": "verdant" },
  "created_at": "2024-01-20T22:30:00Z",
  "updated_at": "2024-01-25T16:45:00Z"
}
404Moment not found
{
  "detail": "Moment not found"
}
DELETE/v1/moments/{moment_id}

Delete a Key Moment.

Headers

x-api-key*— Your API key

Responses

204Moment deleted successfully
(no content)
404Moment not found
{
  "detail": "Moment not found"
}

Hero Awards

GET/v1/hero-awards

List hero awards in a campaign or session. Requires campaign_id or session_id. tone_mode, award_mode, and variant are server-set and are not accepted on write.

Headers

x-api-key*— Your API key

Query Params

campaign_id— Campaign ID (required if session_id is omitted)
session_id— Session ID (required if campaign_id is omitted)
search— Search title, description, character name, or player name
page— Page number
size— Page size (max 100)

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/hero-awards?campaign_id=abc123"

Responses

200List of hero awards
{
  "data": [
    {
      "id": "award_123",
      "campaign_id": "camp_abc123",
      "session_id": "session_456",
      "title": "MVP of the Gate",
      "description": "Held the line while the party regrouped.",
      "character_name": "Thorin",
      "player_name": "Alex",
      "tone_mode": "default",
      "award_mode": "moment",
      "style": "verdant",
      "variant": "square-v2",
      "custom_theme": null,
      "order_index": 1,
      "created_at": "2024-01-20T22:30:00Z",
      "updated_at": null
    }
  ],
  "total": 3,
  "page": 1,
  "size": 20,
  "pages": 1
}
400Missing campaign_id and session_id
{
  "detail": "campaign_id or session_id is required"
}
GET/v1/hero-awards/{award_id}

Get a specific hero award by ID.

Headers

x-api-key*— Your API key

Responses

200Hero award details
{
  "id": "award_123",
  "campaign_id": "camp_abc123",
  "session_id": "session_456",
  "title": "MVP of the Gate",
  "description": "Held the line while the party regrouped.",
  "character_name": "Thorin",
  "player_name": "Alex",
  "tone_mode": "default",
  "award_mode": "moment",
  "style": "verdant",
  "variant": "square-v2",
  "custom_theme": null,
  "order_index": 1,
  "created_at": "2024-01-20T22:30:00Z",
  "updated_at": null
}
404Hero award not found
{
  "detail": "Hero award not found"
}
POST/v1/hero-awards

Create a hero award attached to a session. Card images are rendered by the app, not uploaded via the image routes. Does not use wikilinks. The server sets tone_mode to default, award_mode to moment, and variant to square-v2. Those fields are returned on read and ignored if sent.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

session_id*— Session ID
title*— Award title (max 200)
description*— Award description (max 4000)
character_name— Character name (max 120)
player_name— Player name (max 120)
style— parchment, azurelight, nightwire, verdant (default), infernalink, or custom (uses custom_theme)
custom_theme— Color tokens used when style is custom
order_index— Display order; defaults to next index in the session

Responses

201Hero award created successfully
{
  "id": "award_new999",
  "campaign_id": "camp_abc123",
  "session_id": "session_456",
  "title": "MVP of the Gate",
  "description": "Held the line while the party regrouped.",
  "character_name": "Thorin",
  "player_name": "Alex",
  "tone_mode": "default",
  "award_mode": "moment",
  "style": "verdant",
  "variant": "square-v2",
  "custom_theme": null,
  "order_index": 1,
  "created_at": "2024-01-25T16:45:00Z",
  "updated_at": null
}
400Missing session_id
{
  "detail": "session_id is required"
}
PATCH/v1/hero-awards/{award_id}

Update a hero award. Session cannot be changed after create. tone_mode, award_mode, and variant cannot be changed.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

title— Award title
description— Award description
character_name— Character name
player_name— Player name
style— parchment, azurelight, nightwire, verdant, infernalink, or custom (uses custom_theme)
custom_theme— Color tokens used when style is custom
order_index— Display order

Responses

200Hero award updated successfully
{
  "id": "award_123",
  "campaign_id": "camp_abc123",
  "session_id": "session_456",
  "title": "Updated Award",
  "description": "Updated description",
  "character_name": "Thorin",
  "player_name": "Alex",
  "tone_mode": "default",
  "award_mode": "moment",
  "style": "verdant",
  "variant": "square-v2",
  "custom_theme": null,
  "order_index": 1,
  "created_at": "2024-01-20T22:30:00Z",
  "updated_at": "2024-01-25T16:45:00Z"
}
404Hero award not found
{
  "detail": "Hero award not found"
}
DELETE/v1/hero-awards/{award_id}

Delete a hero award.

Headers

x-api-key*— Your API key

Responses

204Hero award deleted successfully
(no content)
404Hero award not found
{
  "detail": "Hero award not found"
}

Quotes

GET/v1/quotes

List memorable session quotes in a campaign or session. Requires campaign_id or session_id.

Headers

x-api-key*— Your API key

Query Params

campaign_id— Campaign ID (required if session_id is omitted)
session_id— Session ID (required if campaign_id is omitted)
search— Search quote text, speaker, or context
page— Page number
size— Page size (max 100)

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/quotes?session_id=session_456"

Responses

200List of quotes
{
  "data": [
    {
      "id": "quote_123",
      "campaign_id": "camp_abc123",
      "session_id": "session_456",
      "text": "I am Thorin, son of Thrain.",
      "speaker": "Thorin",
      "character_name": "Thorin",
      "context": "Standing at the shattered gate.",
      "style": "verdant",
      "custom_theme": null,
      "order_index": 0,
      "pipeline_id": "pipeline_789",
      "recap_set_id": null,
      "created_at": "2024-01-20T22:30:00Z",
      "updated_at": null
    }
  ],
  "total": 12,
  "page": 1,
  "size": 20,
  "pages": 1
}
400Missing campaign_id and session_id
{
  "detail": "campaign_id or session_id is required"
}
GET/v1/quotes/{quote_id}

Get a specific quote by ID.

Headers

x-api-key*— Your API key

Responses

200Quote details
{
  "id": "quote_123",
  "campaign_id": "camp_abc123",
  "session_id": "session_456",
  "text": "I am Thorin, son of Thrain.",
  "speaker": "Thorin",
  "character_name": "Thorin",
  "context": "Standing at the shattered gate.",
  "style": "verdant",
  "custom_theme": null,
  "order_index": 0,
  "pipeline_id": "pipeline_789",
  "recap_set_id": null,
  "created_at": "2024-01-20T22:30:00Z",
  "updated_at": null
}
404Quote not found
{
  "detail": "Quote not found"
}
POST/v1/quotes

Create a quote attached to a session. Finds or creates a recap pipeline (source=api) so the card appears in the artifacts catalog. Does not use wikilinks.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

session_id*— Session ID
text*— Quote text (max 8000)
speaker— Speaker label (max 200)
character_name— Character name (max 120)
context— Optional context (max 2000)
style— parchment, azurelight, nightwire, verdant (default), infernalink, or custom (uses custom_theme)
custom_theme— Color tokens used when style is custom
order_index— Display order

Responses

201Quote created successfully
{
  "id": "quote_new999",
  "campaign_id": "camp_abc123",
  "session_id": "session_456",
  "text": "I am Thorin, son of Thrain.",
  "speaker": "Thorin",
  "character_name": "Thorin",
  "context": "Standing at the shattered gate.",
  "style": "verdant",
  "custom_theme": null,
  "order_index": 0,
  "pipeline_id": "pipeline_789",
  "recap_set_id": null,
  "created_at": "2024-01-25T16:45:00Z",
  "updated_at": null
}
422Validation error
{
  "detail": "session_id and text are required"
}
PATCH/v1/quotes/{quote_id}

Update a quote. Session cannot be changed after create.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

text— Quote text
speaker— Speaker label
character_name— Character name
context— Optional context
style— parchment, azurelight, nightwire, verdant, infernalink, or custom (uses custom_theme)
custom_theme— Color tokens used when style is custom
order_index— Display order

Responses

200Quote updated successfully
{
  "id": "quote_123",
  "campaign_id": "camp_abc123",
  "session_id": "session_456",
  "text": "Updated quote text.",
  "speaker": "Thorin",
  "character_name": "Thorin",
  "context": "Updated context",
  "style": "verdant",
  "custom_theme": null,
  "order_index": 0,
  "pipeline_id": "pipeline_789",
  "recap_set_id": null,
  "created_at": "2024-01-20T22:30:00Z",
  "updated_at": null
}
404Quote not found
{
  "detail": "Quote not found"
}
DELETE/v1/quotes/{quote_id}

Delete a quote.

Headers

x-api-key*— Your API key

Responses

204Quote deleted successfully
(no content)
404Quote not found
{
  "detail": "Quote not found"
}

Spotlights

GET/v1/spotlights

List recap spotlights (GEMS cards). This is not the session-handout spotlight list. Requires campaign_id or session_id. Results are filtered to entry_type=spotlight.

Headers

x-api-key*— Your API key

Query Params

campaign_id— Campaign ID (required if session_id is omitted)
session_id— Session ID (required if campaign_id is omitted)
search— Search title, description, or character name
page— Page number
size— Page size (max 100)

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/spotlights?campaign_id=abc123"

Responses

200List of spotlights
{
  "data": [
    {
      "id": "spotlight_123",
      "campaign_id": "camp_abc123",
      "session_id": "session_456",
      "title": "Thorin at the Gate",
      "description": "A last stand that bought the party time.",
      "character_name": "Thorin",
      "player_name": "Alex",
      "style": "verdant",
      "custom_theme": null,
      "order_index": 0,
      "entry_type": "spotlight",
      "source_moment_id": "moment_123",
      "pipeline_id": "pipeline_789",
      "recap_set_id": null,
      "created_at": "2024-01-20T22:30:00Z",
      "updated_at": null
    }
  ],
  "total": 4,
  "page": 1,
  "size": 20,
  "pages": 1
}
400Missing campaign_id and session_id
{
  "detail": "campaign_id or session_id is required"
}
GET/v1/spotlights/{spotlight_id}

Get a specific recap spotlight by ID.

Headers

x-api-key*— Your API key

Responses

200Spotlight details
{
  "id": "spotlight_123",
  "campaign_id": "camp_abc123",
  "session_id": "session_456",
  "title": "Thorin at the Gate",
  "description": "A last stand that bought the party time.",
  "character_name": "Thorin",
  "player_name": "Alex",
  "style": "verdant",
  "custom_theme": null,
  "order_index": 0,
  "entry_type": "spotlight",
  "source_moment_id": "moment_123",
  "pipeline_id": "pipeline_789",
  "recap_set_id": null,
  "created_at": "2024-01-20T22:30:00Z",
  "updated_at": null
}
404Spotlight not found
{
  "detail": "Spotlight not found"
}
POST/v1/spotlights

Create a recap spotlight attached to a session. Finds or creates a recap pipeline (source=api). This is a GEMS card, not a session-handout spotlight. Does not use wikilinks.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

session_id*— Session ID
title*— Spotlight title (max 200). Also accepts spotlight_title
description— Spotlight description (max 4000)
character_name— Character name (max 120)
player_name— Player name (max 120)
style— parchment, azurelight, nightwire, verdant (default), infernalink, or custom (uses custom_theme)
custom_theme— Color tokens used when style is custom
order_index— Display order
source_moment_id— Optional source Key Moment ID

Responses

201Spotlight created successfully
{
  "id": "spotlight_new999",
  "campaign_id": "camp_abc123",
  "session_id": "session_456",
  "title": "Thorin at the Gate",
  "description": "A last stand that bought the party time.",
  "character_name": "Thorin",
  "player_name": "Alex",
  "style": "verdant",
  "custom_theme": null,
  "order_index": 0,
  "entry_type": "spotlight",
  "source_moment_id": null,
  "pipeline_id": "pipeline_789",
  "recap_set_id": null,
  "created_at": "2024-01-25T16:45:00Z",
  "updated_at": null
}
422Validation error
{
  "detail": "session_id and title are required"
}
PATCH/v1/spotlights/{spotlight_id}

Update a recap spotlight. Session cannot be changed after create.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

title— Spotlight title. Also accepts spotlight_title
description— Spotlight description
character_name— Character name
player_name— Player name
style— parchment, azurelight, nightwire, verdant, infernalink, or custom (uses custom_theme)
custom_theme— Color tokens used when style is custom
order_index— Display order
source_moment_id— Optional source Key Moment ID

Responses

200Spotlight updated successfully
{
  "id": "spotlight_123",
  "campaign_id": "camp_abc123",
  "session_id": "session_456",
  "title": "Updated Spotlight",
  "description": "Updated description",
  "character_name": "Thorin",
  "player_name": "Alex",
  "style": "verdant",
  "custom_theme": null,
  "order_index": 0,
  "entry_type": "spotlight",
  "source_moment_id": "moment_123",
  "pipeline_id": "pipeline_789",
  "recap_set_id": null,
  "created_at": "2024-01-20T22:30:00Z",
  "updated_at": null
}
404Spotlight not found
{
  "detail": "Spotlight not found"
}
DELETE/v1/spotlights/{spotlight_id}

Delete a recap spotlight.

Headers

x-api-key*— Your API key

Responses

204Spotlight deleted successfully
(no content)
404Spotlight not found
{
  "detail": "Spotlight not found"
}

Factions

GET/v1/factions

List factions in a campaign.

Headers

x-api-key*— Your API key

Query Params

campaign_id*— Campaign ID
search— Full-text search (name, aliases, type) with ILIKE fallback
with_links— Preserve wikilink markup

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/factions?campaign_id=abc123&search=thieves"

Responses

200List of factions
{
  "data": [
    {
      "id": "faction_123",
      "campaign_id": "camp_abc123",
      "name": "The Shadow Thieves",
      "description": "A guild of thieves operating in the shadows",
      "type": "guild",
      "merge": false,
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "total": 5,
  "page": 1,
  "size": 20,
  "pages": 1
}
422Missing required campaign_id
{
  "detail": "campaign_id is required"
}
GET/v1/factions/{faction_id}

Get a specific faction by ID.

Headers

x-api-key*— Your API key

Query Params

with_links— Preserve wikilink markup

Responses

200Faction details
{
  "id": "faction_123",
  "campaign_id": "camp_abc123",
  "name": "The Shadow Thieves",
  "description": "A guild of thieves operating in the shadows",
  "type": "guild",
  "merge": false,
  "created_at": "2024-01-15T10:30:00Z"
}
404Faction not found
{
  "detail": "Faction not found"
}
POST/v1/factions

Create a new faction.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

name*— Faction name
campaign_id*— Campaign ID
description— Faction description
type— Faction type
image— HTTPS image URL (max 2048 chars)

Responses

201Faction created successfully
{
  "id": "faction_new456",
  "campaign_id": "camp_abc123",
  "name": "The Iron Brotherhood",
  "description": "A mercenary company",
  "type": "guild",
  "merge": false,
  "created_at": "2024-01-25T16:45:00Z"
}
422Validation error
{
  "detail": "name and campaign_id are required"
}
PATCH/v1/factions/{faction_id}

Update a faction.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

name— Faction name
description— Faction description
type— Faction type
image— HTTPS image URL (max 2048 chars)

Responses

200Faction updated successfully
{
  "id": "faction_123",
  "campaign_id": "camp_abc123",
  "name": "Updated Faction Name",
  "description": "Updated description",
  "type": "guild",
  "merge": false,
  "created_at": "2024-01-15T10:30:00Z"
}
404Faction not found
{
  "detail": "Faction not found"
}
DELETE/v1/factions/{faction_id}

Delete a faction.

Headers

x-api-key*— Your API key

Responses

204Faction deleted successfully
(no content)
404Faction not found
{
  "detail": "Faction not found"
}

Locations

GET/v1/locations

List locations in a campaign.

Headers

x-api-key*— Your API key

Query Params

campaign_id*— Campaign ID
search— Full-text search (name, aliases, type) with ILIKE fallback
with_links— Preserve wikilink markup

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/locations?campaign_id=abc123&search=tavern"

Responses

200List of locations
{
  "data": [
    {
      "id": "loc_123",
      "campaign_id": "camp_abc123",
      "name": "The Prancing Pony",
      "description": "A cozy inn in Bree",
      "type": "tavern",
      "parent_id": "loc_456",
      "merge": false,
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "total": 18,
  "page": 1,
  "size": 20,
  "pages": 1
}
422Missing required campaign_id
{
  "detail": "campaign_id is required"
}
GET/v1/locations/{location_id}

Get a specific location by ID.

Headers

x-api-key*— Your API key

Query Params

with_links— Preserve wikilink markup

Responses

200Location details
{
  "id": "loc_123",
  "campaign_id": "camp_abc123",
  "name": "The Prancing Pony",
  "description": "A cozy inn in Bree",
  "type": "tavern",
  "parent_id": "loc_456",
  "merge": false,
  "created_at": "2024-01-15T10:30:00Z"
}
404Location not found
{
  "detail": "Location not found"
}
POST/v1/locations

Create a new location.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

name*— Location name
campaign_id*— Campaign ID
description— Location description
type— Location type
parent_id— Parent location ID
image— HTTPS image URL (max 2048 chars)

Responses

201Location created successfully
{
  "id": "loc_new789",
  "campaign_id": "camp_abc123",
  "name": "Rivendell",
  "description": "The Last Homely House",
  "type": "city",
  "parent_id": null,
  "merge": false,
  "created_at": "2024-01-25T16:45:00Z"
}
422Validation error
{
  "detail": "name and campaign_id are required"
}
PATCH/v1/locations/{location_id}

Update a location.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

name— Location name
description— Location description
type— Location type
parent_id— Parent location ID
image— HTTPS image URL (max 2048 chars)

Responses

200Location updated successfully
{
  "id": "loc_123",
  "campaign_id": "camp_abc123",
  "name": "Updated Location Name",
  "description": "Updated description",
  "type": "tavern",
  "parent_id": "loc_456",
  "merge": false,
  "created_at": "2024-01-15T10:30:00Z"
}
404Location not found
{
  "detail": "Location not found"
}
DELETE/v1/locations/{location_id}

Delete a location.

Headers

x-api-key*— Your API key

Responses

204Location deleted successfully
(no content)
404Location not found
{
  "detail": "Location not found"
}

Items

GET/v1/items

List items in a campaign.

Headers

x-api-key*— Your API key

Query Params

campaign_id*— Campaign ID
search— Full-text search (name, aliases, type) with ILIKE fallback
with_links— Preserve wikilink markup

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/items?campaign_id=abc123&search=sword"

Responses

200List of items
{
  "data": [
    {
      "id": "item_123",
      "campaign_id": "camp_abc123",
      "name": "Sting",
      "description": "An elven blade that glows blue near orcs",
      "type": "weapon",
      "merge": false,
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "total": 32,
  "page": 1,
  "size": 20,
  "pages": 2
}
422Missing required campaign_id
{
  "detail": "campaign_id is required"
}
GET/v1/items/{item_id}

Get a specific item by ID.

Headers

x-api-key*— Your API key

Query Params

with_links— Preserve wikilink markup

Responses

200Item details
{
  "id": "item_123",
  "campaign_id": "camp_abc123",
  "name": "Sting",
  "description": "An elven blade that glows blue near orcs",
  "type": "weapon",
  "merge": false,
  "created_at": "2024-01-15T10:30:00Z"
}
404Item not found
{
  "detail": "Item not found"
}
POST/v1/items

Create a new item.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

name*— Item name
campaign_id*— Campaign ID
description— Item description
type— Item type (weapon, armor, etc)
image— HTTPS image URL (max 2048 chars)

Responses

201Item created successfully
{
  "id": "item_new456",
  "campaign_id": "camp_abc123",
  "name": "Anduril",
  "description": "The Flame of the West",
  "type": "weapon",
  "merge": false,
  "created_at": "2024-01-25T16:45:00Z"
}
422Validation error
{
  "detail": "name and campaign_id are required"
}
PATCH/v1/items/{item_id}

Update an item.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

name— Item name
description— Item description
type— Item type (weapon, armor, etc)
image— HTTPS image URL (max 2048 chars)

Responses

200Item updated successfully
{
  "id": "item_123",
  "campaign_id": "camp_abc123",
  "name": "Updated Item Name",
  "description": "Updated description",
  "type": "weapon",
  "merge": false,
  "created_at": "2024-01-15T10:30:00Z"
}
404Item not found
{
  "detail": "Item not found"
}
DELETE/v1/items/{item_id}

Delete an item.

Headers

x-api-key*— Your API key

Responses

204Item deleted successfully
(no content)
404Item not found
{
  "detail": "Item not found"
}

Quests

GET/v1/quests

List canonical quests in a campaign. Results are paginated and include summary counts, quest status/category, and first/last session provenance when known.

Headers

x-api-key*— Your API key

Query Params

campaign_id*— Campaign ID
page— Page number
size— Page size
search— Search by quest name, giver, or narrative fields
status— Filter by planned | in-progress | blocked | failed | done | n/a
quest_category— Filter by main | side | faction | personal | n/a

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/quests?campaign_id=abc123&status=in-progress"

Responses

200Paginated quest summaries
{
  "data": [
    {
      "id": "quest_123",
      "campaign_id": "camp_abc123",
      "order_index": 2,
      "quest_name": "Recover the Azure Sigil",
      "quest_giver": "Aria Voss",
      "quest_giver_id": "char_123",
      "quest_category": "main",
      "status": "in-progress",
      "next_action": "Question the ferryman at Blackwake.",
      "resolution": null,
      "objective_count": 3,
      "completed_objective_count": 1,
      "progress_entry_count": 2,
      "related_entity_count": 3,
      "first_session": {
        "id": "sess_001",
        "number": 4,
        "title": "Ashes on the Water",
        "session_date": "2025-02-10T02:30:00Z"
      },
      "last_session": {
        "id": "sess_003",
        "number": 6,
        "title": "The Ferryman's Price",
        "session_date": "2025-02-24T02:30:00Z"
      },
      "created_at": "2025-02-10T03:00:00Z",
      "updated_at": "2025-02-24T04:15:00Z"
    }
  ],
  "total": 1,
  "page": 1,
  "size": 20,
  "pages": 1
}
401Unauthorized
{
  "detail": "Invalid API key"
}
403Forbidden
{
  "detail": "Access denied to this campaign"
}
GET/v1/quests/{quest_id}

Get a fully expanded quest by ID, including objectives, progress log entries, related entity refs, and session provenance.

Headers

x-api-key*— Your API key

Request Example

curl -H "x-api-key: YOUR_API_KEY" https://api.myarchivist.ai/v1/quests/quest_123

Responses

200Quest details
{
  "id": "quest_123",
  "campaign_id": "camp_abc123",
  "order_index": 2,
  "quest_name": "Recover the Azure Sigil",
  "quest_giver": "Aria Voss",
  "quest_giver_id": "char_123",
  "quest_category": "main",
  "status": "in-progress",
  "success_definition": "Return the sigil to Aria Voss.",
  "failure_conditions": "The sigil is destroyed or delivered to House Merrow.",
  "next_action": "Question the ferryman at Blackwake.",
  "resolution": null,
  "objective_count": 3,
  "completed_objective_count": 1,
  "progress_entry_count": 2,
  "objectives": [
    { "id": "obj_1", "text": "Reach Blackwake alive", "status": "completed", "order": 1 },
    { "id": "obj_2", "text": "Find who bought the sigil", "status": "in-progress", "order": 2 },
    { "id": "obj_3", "text": "Recover the sigil", "status": "pending", "order": 3 }
  ],
  "progress_log": [
    "The party tracked the smugglers to Blackwake.",
    "A ferryman claimed House Merrow paid for a sealed relic case."
  ],
  "progress_log_entries": [
    {
      "id": "prog_1",
      "text": "The party tracked the smugglers to Blackwake.",
      "order": 1,
      "session_id": "sess_002",
      "session_number": 5,
      "session_title": "Salt and Smoke",
      "session_date": "2025-02-17T02:30:00Z"
    }
  ],
  "related_characters": ["Captain Nera", "Dovan Pike — suspect buyer"],
  "related_factions": ["House Merrow"],
  "related_locations": ["Blackwake Docks"],
  "related_items": ["Azure Sigil"],
  "related_entity_refs": [
    {
      "id": "qe_1",
      "entity_type": "character",
      "entity_id": "char_456",
      "entity_name_snapshot": "Captain Nera",
      "label": "Captain Nera",
      "order": 1
    },
    {
      "id": "qe_2",
      "entity_type": "item",
      "entity_id": "item_123",
      "entity_name_snapshot": "Azure Sigil",
      "label": "Azure Sigil",
      "order": 4
    }
  ],
  "first_session": {
    "id": "sess_001",
    "number": 4,
    "title": "Ashes on the Water",
    "session_date": "2025-02-10T02:30:00Z"
  },
  "last_session": {
    "id": "sess_003",
    "number": 6,
    "title": "The Ferryman's Price",
    "session_date": "2025-02-24T02:30:00Z"
  },
  "created_at": "2025-02-10T03:00:00Z",
  "updated_at": "2025-02-24T04:15:00Z"
}
404Quest not found
{
  "detail": "Quest not found"
}
POST/v1/quests

Create a manual quest. `quest_giver` must match an approved character or faction in the same campaign. If `objectives` are supplied, quest status is recomputed from objective statuses. `related_entity_refs` accepts explicit entity IDs; the convenience arrays (`related_characters`, `related_factions`, `related_locations`, `related_items`) are also accepted.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

campaign_id*— Campaign ID
quest_name*— Quest title
quest_giver— Must resolve to an approved character or faction in this campaign
quest_category— main | side | faction | personal | n/a
status— planned | in-progress | blocked | failed | done | n/a; ignored when objectives are supplied
success_definition— How the quest succeeds
failure_conditions— How the quest fails
next_action— Immediate next step
resolution— Outcome or conclusion text
objectives— Array of strings or { text, status } objects
progress_log— Array of progress strings
related_characters— Convenience array of related character labels
related_factions— Convenience array of related faction labels
related_locations— Convenience array of related location labels
related_items— Convenience array of related item labels
related_entity_refs— Array of { entity_type, entity_id?, entity_name_snapshot?, label? } refs

Request Example

curl -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "camp_abc123",
    "quest_name": "Recover the Azure Sigil",
    "quest_giver": "Aria Voss",
    "quest_category": "main",
    "objectives": [
      { "text": "Find who bought the sigil", "status": "in-progress" },
      { "text": "Recover the sigil", "status": "pending" }
    ],
    "progress_log": [
      "The party tracked the smugglers to Blackwake."
    ],
    "related_entity_refs": [
      { "entity_type": "item", "entity_id": "item_123", "label": "Azure Sigil" },
      { "entity_type": "location", "entity_name_snapshot": "Blackwake Docks", "label": "Blackwake Docks" }
    ]
  }' \
  https://api.myarchivist.ai/v1/quests

Responses

201Quest created successfully
{
  "id": "quest_new456",
  "campaign_id": "camp_abc123",
  "order_index": 7,
  "quest_name": "Recover the Azure Sigil",
  "quest_giver": "Aria Voss",
  "quest_giver_id": "char_123",
  "quest_category": "main",
  "status": "in-progress",
  "success_definition": null,
  "failure_conditions": null,
  "next_action": null,
  "resolution": null,
  "objectives": [
    { "id": "obj_new1", "text": "Find who bought the sigil", "status": "in-progress", "order": 1 },
    { "id": "obj_new2", "text": "Recover the sigil", "status": "pending", "order": 2 }
  ],
  "progress_log": ["The party tracked the smugglers to Blackwake."],
  "progress_log_entries": [
    { "id": "prog_new1", "text": "The party tracked the smugglers to Blackwake.", "order": 1, "session_id": null, "session_number": null, "session_title": null, "session_date": null }
  ],
  "related_characters": [],
  "related_factions": [],
  "related_locations": ["Blackwake Docks"],
  "related_items": ["Azure Sigil"],
  "related_entity_refs": [
    { "id": "qe_new1", "entity_type": "item", "entity_id": "item_123", "entity_name_snapshot": "Azure Sigil", "label": "Azure Sigil", "order": 1 },
    { "id": "qe_new2", "entity_type": "location", "entity_id": null, "entity_name_snapshot": "Blackwake Docks", "label": "Blackwake Docks", "order": 2 }
  ],
  "first_session": null,
  "last_session": null,
  "created_at": "2025-03-01T05:00:00Z",
  "updated_at": "2025-03-01T05:00:00Z"
}
400Invalid quest giver or related entity reference
{
  "detail": "Quest giver must match an approved character or faction in this campaign"
}
403Forbidden
{
  "detail": "Write access denied to this campaign"
}
PATCH/v1/quests/{quest_id}

Partially update a quest. Omitted fields are left untouched. Supplying `objectives` replaces the full objective list and recomputes quest status from those objective statuses. Supplying `related_entity_refs` replaces the full related-entity set; the type-specific arrays replace only their matching type bucket.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

quest_name— Updated quest title
quest_giver— Updated quest giver; must still resolve to an approved character or faction
quest_category— Updated category
status— Updated status when objectives are omitted
success_definition— Updated success definition
failure_conditions— Updated failure conditions
next_action— Updated next action
resolution— Updated resolution
objectives— Replacement objective list
progress_log— Replacement progress-log list
related_characters— Replacement related character list
related_factions— Replacement related faction list
related_locations— Replacement related location list
related_items— Replacement related item list
related_entity_refs— Replacement full related-entity ref list

Request Example

curl -X PATCH \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "next_action": "Break into House Merrow's archive vault.",
    "status": "blocked",
    "related_factions": ["House Merrow", "Blackwake Dock Union"]
  }' \
  https://api.myarchivist.ai/v1/quests/quest_123

Responses

200Quest updated successfully
{
  "id": "quest_123",
  "campaign_id": "camp_abc123",
  "order_index": 2,
  "quest_name": "Recover the Azure Sigil",
  "quest_giver": "Aria Voss",
  "quest_giver_id": "char_123",
  "quest_category": "main",
  "status": "blocked",
  "success_definition": "Return the sigil to Aria Voss.",
  "failure_conditions": "The sigil is destroyed or delivered to House Merrow.",
  "next_action": "Break into House Merrow's archive vault.",
  "resolution": null,
  "objectives": [
    { "id": "obj_1", "text": "Reach Blackwake alive", "status": "completed", "order": 1 },
    { "id": "obj_2", "text": "Find who bought the sigil", "status": "in-progress", "order": 2 },
    { "id": "obj_3", "text": "Recover the sigil", "status": "pending", "order": 3 }
  ],
  "progress_log": [
    "The party tracked the smugglers to Blackwake.",
    "A ferryman claimed House Merrow paid for a sealed relic case."
  ],
  "progress_log_entries": [
    { "id": "prog_1", "text": "The party tracked the smugglers to Blackwake.", "order": 1, "session_id": "sess_002", "session_number": 5, "session_title": "Salt and Smoke", "session_date": "2025-02-17T02:30:00Z" }
  ],
  "related_characters": ["Captain Nera"],
  "related_factions": ["House Merrow", "Blackwake Dock Union"],
  "related_locations": ["Blackwake Docks"],
  "related_items": ["Azure Sigil"],
  "related_entity_refs": [
    { "id": "qe_1", "entity_type": "character", "entity_id": "char_456", "entity_name_snapshot": "Captain Nera", "label": "Captain Nera", "order": 1 },
    { "id": "qe_2", "entity_type": "faction", "entity_id": null, "entity_name_snapshot": "House Merrow", "label": "House Merrow", "order": 2 },
    { "id": "qe_3", "entity_type": "faction", "entity_id": null, "entity_name_snapshot": "Blackwake Dock Union", "label": "Blackwake Dock Union", "order": 3 },
    { "id": "qe_4", "entity_type": "location", "entity_id": null, "entity_name_snapshot": "Blackwake Docks", "label": "Blackwake Docks", "order": 4 },
    { "id": "qe_5", "entity_type": "item", "entity_id": "item_123", "entity_name_snapshot": "Azure Sigil", "label": "Azure Sigil", "order": 5 }
  ],
  "first_session": {
    "id": "sess_001",
    "number": 4,
    "title": "Ashes on the Water",
    "session_date": "2025-02-10T02:30:00Z"
  },
  "last_session": {
    "id": "sess_003",
    "number": 6,
    "title": "The Ferryman's Price",
    "session_date": "2025-02-24T02:30:00Z"
  },
  "created_at": "2025-02-10T03:00:00Z",
  "updated_at": "2025-03-02T01:15:00Z"
}
400Invalid payload
{
  "detail": "quest_name cannot be empty"
}
404Quest not found
{
  "detail": "Quest not found"
}
DELETE/v1/quests/{quest_id}

Delete a quest.

Headers

x-api-key*— Your API key

Request Example

curl -X DELETE -H "x-api-key: YOUR_API_KEY" https://api.myarchivist.ai/v1/quests/quest_123

Responses

204Quest deleted successfully
(no content)
404Quest not found
{
  "detail": "Quest not found"
}

Entities

GET/v1/entities

Lightweight compendium picker across characters, factions, locations, or items. Returns card-shaped results with pagination via limit (not size).

Headers

x-api-key*— Your API key

Query Params

campaign_id*— Campaign ID
type*— characters, factions, locations, or items
search— Full-text search (name, aliases, type) with ILIKE fallback
page— Page number (default 1)
limit— Page size (default 20, max 100)

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/entities?campaign_id=abc123&type=characters&search=thor&limit=20"

Responses

200Paginated entity picker results
{
  "results": [
    {
      "id": "char_123",
      "name": "Thorin Ironforge",
      "type": "PC",
      "image": null
    }
  ],
  "hasMore": false,
  "page": 1,
  "pages": 1,
  "total": 1
}

Journal Entries

GET/v1/journals

List journal entries in a campaign. Results are filtered to entries the caller can see: public entries, entries authored by the caller, or entries explicitly shared with the caller. Use fields=card for lightweight items (id, title, summary, updated_at, folder_id).

Headers

x-api-key*— Your API key

Query Params

campaign_id*— Campaign ID (world_id)
page— Page number
size— Page size
fields— Set to card for lightweight list items (id, title, summary, updated_at, folder_id)
with_links— Preserve wikilink markup in content

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/journals?campaign_id=abc123"

Responses

200List of journal entries (full list includes content unless fields=card)
{
  "data": [
    {
      "id": "journal_123",
      "campaign_id": "camp_abc123",
      "title": "World History",
      "summary": "A high-level overview of the first age...",
      "token_count": 3245,
      "is_public": false,
      "status": "draft",
      "tags": ["history"],
      "folder_id": "folder_789",
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-01-16T10:30:00Z"
    }
  ],
  "total": 8,
  "page": 1,
  "size": 20,
  "pages": 1
}
422Missing required campaign_id
{
  "detail": "campaign_id is required"
}
401Unauthorized
{
  "detail": "Invalid API key"
}
GET/v1/journals/{entry_id}

Get a specific journal entry by ID including full content. Responses include the caller’s effective permission level for the entry.

Headers

x-api-key*— Your API key

Request Example

curl -H "x-api-key: YOUR_API_KEY" https://api.myarchivist.ai/v1/journals/journal_123

Responses

200Journal entry with full content
{
  "id": "journal_123",
  "campaign_id": "camp_abc123",
  "title": "World History",
  "summary": "A high-level overview of the first age...",
  "content": "In the beginning, the gods created the world of Aetheria...",
  "content_rich": { "root": { "type": "root", "version": 1, "children": [] } },
  "tags": ["history"],
  "token_count": 3245,
  "is_public": false,
  "status": "draft",
  "folder_id": "folder_789",
  "permission_level": "manage",
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-16T10:30:00Z"
}
404Journal entry not found
{
  "detail": "Journal entry not found"
}
403Forbidden
{
  "detail": "Insufficient permissions"
}
POST/v1/journals

Create a new journal entry. `content_rich` is canonical when both rich and plain content are supplied, folder IDs must belong to the same world, creator-level manage permission is granted automatically, and wikilinks are extracted/synced from the saved content. Maximum 2,000,000 characters per request. Worlds are capped at 1,000,000 journal tokens; individual entries are capped at 50,000 tokens.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

world_id*— Campaign ID
title*— Entry title
content— Plain text content (max 2M chars)
content_rich— Lexical editor state JSON; takes precedence over content when both are present
content_metadata— Structured content metadata
summary— Short summary
tags— Array of tag strings
cover_image— Optional journal cover image URL
is_pinned— Pin this entry in the UI
is_public— Publicly visible entry
status— draft | published | archived
published_at— Explicit published timestamp; otherwise set automatically when status becomes published
archived_at— Explicit archived timestamp; otherwise set automatically when status becomes archived
folder_id— Folder ID

Request Example

curl -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "world_id": "camp_abc123",
    "title": "World History",
    "content": "In the beginning, the gods created the world of Aetheria...",
    "tags": ["history"],
    "status": "draft"
  }' \
  https://api.myarchivist.ai/v1/journals

Responses

201Journal entry created successfully
{
  "success": true,
  "id": "journal_new456"
}
400Token limits exceeded
{
  "detail": "This journal entry exceeds the per-entry token limit"
}
403Forbidden
{
  "detail": "Write access denied to this world"
}
404Referenced journal folder was not found in this world
{
  "detail": "Journal folder not found"
}
400Content too large
{
  "detail": "Content too large; max 2000000 characters"
}
PUT/v1/journals

Update an existing journal entry. `content_rich` remains canonical, embeddings are regenerated when saved content changes, permissions can be added or removed, ownership can be reassigned with `author_id`, and journal wikilinks are re-synced from the saved content.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

id*— Journal entry ID
title— Updated title
summary— Updated summary
content— Updated content (triggers re-embedding)
content_rich— Updated Lexical JSON (takes precedence over content)
content_metadata— Updated metadata object
tags— Updated tags
cover_image— Updated cover image URL
is_pinned— Updated pinned state
is_public— Updated visibility
status— Updated status
published_at— Updated published timestamp
archived_at— Updated archived timestamp
folder_id— Updated folder
author_id— Transfer authorship to another user with world access
permissions— Permission mutation object: { add: [{ user_id, level }], remove: [{ user_id }] }

Request Example

curl -X PUT \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "journal_123",
    "content": "Updated journal content with [[Cassius Traven]].",
    "status": "published",
    "permissions": {
      "add": [{ "user_id": "user_456", "level": "comment" }],
      "remove": []
    }
  }' \
  https://api.myarchivist.ai/v1/journals

Responses

200Journal entry updated successfully
{
  "success": true,
  "id": "journal_123"
}
403Forbidden
{
  "detail": "Insufficient permissions"
}
404Journal entry or referenced folder/user not found
{
  "detail": "Journal entry not found"
}
400Validation failed
{
  "detail": "Cannot remove the current owner from this journal entry."
}
DELETE/v1/journals

Delete a journal entry and its associated embeddings. Requires manage permission on the entry.

Headers

x-api-key*— Your API key

Query Params

id*— Journal entry ID

Request Example

curl -X DELETE -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/journals?id=journal_123"

Responses

200Journal entry deleted successfully
{
  "success": true
}
403Forbidden
{
  "detail": "Insufficient permissions"
}
404Journal entry not found
{
  "detail": "Journal entry not found"
}

Journal Folders

GET/v1/journal-folders

List journal folders for a campaign. Ordered by path and position for tree rendering.

Headers

x-api-key*— Your API key

Query Params

campaign_id*— Campaign ID (world_id)

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/journal-folders?campaign_id=abc123"

Responses

200List of journal folders
{
  "data": [
    {
      "id": "folder_123",
      "campaign_id": "camp_abc123",
      "parent_id": null,
      "name": "Lore",
      "path": "lore",
      "description": null,
      "position": 0,
      "metadata": null,
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-01-16T10:30:00Z"
    }
  ]
}
403Forbidden
{
  "detail": "Access denied to this world"
}
404World not found
{
  "detail": "World not found"
}
GET/v1/journal-folders/{folder_id}

Get a specific journal folder by ID.

Headers

x-api-key*— Your API key

Request Example

curl -H "x-api-key: YOUR_API_KEY" https://api.myarchivist.ai/v1/journal-folders/folder_123

Responses

200Journal folder
{
  "id": "folder_123",
  "campaign_id": "camp_abc123",
  "parent_id": null,
  "name": "Lore",
  "path": "lore",
  "description": null,
  "position": 0,
  "metadata": null,
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-16T10:30:00Z"
}
403Forbidden
{
  "detail": "Access denied to this world"
}
404World not found
{
  "detail": "World not found"
}
404Journal folder not found
{
  "detail": "Journal folder not found"
}
POST/v1/journal-folders

Create a new journal folder.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

world_id*— Campaign ID
name*— Folder name
path*— Folder path (unique per campaign; normalized, no leading/trailing slashes)
parent_id— Parent folder ID (must be in same campaign)
description— Folder description
position— Sort position within parent
metadata— Optional metadata JSON

Request Example

curl -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "world_id": "camp_abc123",
    "name": "Lore",
    "path": "lore",
    "position": 0
  }' \
  https://api.myarchivist.ai/v1/journal-folders

Responses

201Journal folder created successfully
{
  "success": true,
  "id": "folder_456"
}
400Invalid parent folder
{
  "detail": "Invalid parent folder"
}
400Duplicate folder path
{
  "detail": "Folder path already exists for this campaign"
}
403Forbidden
{
  "detail": "Write access denied to this world"
}
404World not found
{
  "detail": "World not found"
}
PUT/v1/journal-folders

Update an existing journal folder.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

id*— Journal folder ID
name— Updated name
path— Updated path (unique per campaign; normalized, no leading/trailing slashes)
parent_id— Updated parent folder ID (must be in same campaign)
description— Updated description
position— Updated position
metadata— Updated metadata JSON

Request Example

curl -X PUT \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "folder_456",
    "name": "Lore Docs",
    "path": "lore/docs"
  }' \
  https://api.myarchivist.ai/v1/journal-folders

Responses

200Journal folder updated successfully
{
  "success": true,
  "id": "folder_456"
}
400Folder cannot be its own parent
{
  "detail": "Folder cannot be its own parent"
}
400Invalid parent folder
{
  "detail": "Invalid parent folder"
}
400Duplicate folder path
{
  "detail": "Folder path already exists for this campaign"
}
403Forbidden
{
  "detail": "Write access denied to this world"
}
404World not found
{
  "detail": "World not found"
}
404Journal folder not found
{
  "detail": "Journal folder not found"
}
DELETE/v1/journal-folders

Delete a journal folder.

Headers

x-api-key*— Your API key

Query Params

id*— Journal folder ID

Request Example

curl -X DELETE -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/journal-folders?id=folder_456"

Responses

200Journal folder deleted successfully
{
  "success": true
}
403Forbidden
{
  "detail": "Write access denied to this world"
}
404World not found
{
  "detail": "World not found"
}
404Journal folder not found
{
  "detail": "Journal folder not found"
}

Images

GET/v1/images/usage

Return the account image quota for a campaign (used/limit, tier, billing cycle). Requires campaign read access.

Headers

x-api-key*— Your API key

Query Params

campaign_id*— Campaign ID

Request Example

curl -H "x-api-key: YOUR_API_KEY" "https://api.myarchivist.ai/v1/images/usage?campaign_id=camp_abc123"

Responses

200Quota snapshot
{
  "used": 3,
  "limit": 50,
  "can_access": true,
  "tier": "legend",
  "billing_cycle_start": "2026-07-01T00:00:00Z",
  "billing_cycle_end": "2026-08-01T00:00:00Z"
}
POST/v1/images/generate

Server-side AI image generation for character, faction, location, item, or world. Returns a public URL; does not auto-attach. Consumes quota. Requires campaign manage access.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

campaign_id*— Campaign ID
type*— character | faction | location | item | world
entity_id— Target entity (required except for world)
user_input— Optional prompt guidance

Request Example

curl -X POST -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"campaign_id":"camp_abc123","type":"character","entity_id":"char_123"}' \
  https://api.myarchivist.ai/v1/images/generate

Responses

200Generated image URL
{ "url": "https://pub-....r2.dev/characters/char_123/....png" }
403Quota exceeded or no manage access
{ "detail": "Image generation limit reached for this billing cycle" }
POST/v1/campaigns/{campaign_id}/images/init

Step 1 of direct upload: reserve an object key and return a presigned PUT URL. Client uploads bytes to upload_url before calling complete.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

entity_type*— campaign | character | faction | location | item | moment | session
entity_id*— Target entity ID
file_name*— Original filename
content_type*— Must be image/*

Request Example

curl -X POST -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"entity_type":"character","entity_id":"char_123","file_name":"portrait.png","content_type":"image/png"}' \
  https://api.myarchivist.ai/v1/campaigns/camp_abc123/images/init

Responses

200Presigned upload details
{
  "object_key": "characters/char_123/abc.png",
  "upload_url": "https://...",
  "public_url": "https://pub-....r2.dev/characters/char_123/abc.png",
  "expires_in_seconds": 900
}
POST/v1/campaigns/{campaign_id}/images/complete

Step 2 of direct upload: validate the uploaded object, run moderation, and optionally attach the image to an entity. Safe to retry with the same object_key after a successful upload — a repeat complete does not re-moderate or consume extra quota.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

object_key*— From init response
entity_type*— Same as init
entity_id*— Same as init
attach— Attach to entity (default true)

Request Example

curl -X POST -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"object_key":"characters/char_123/abc.png","entity_type":"character","entity_id":"char_123","attach":true}' \
  https://api.myarchivist.ai/v1/campaigns/camp_abc123/images/complete

Responses

200Validated image
{ "url": "https://pub-....r2.dev/...", "attached": true }
DELETE/v1/campaigns/{campaign_id}/images

Remove an entity image (detach + delete object) or delete a managed object by URL.

Headers

x-api-key*— Your API key
Content-Type*— application/json

Request Body

entity_type— With entity_id — detach and delete
entity_id— With entity_type
image_url— Alternative: delete object by URL

Request Example

curl -X DELETE -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"entity_type":"character","entity_id":"char_123"}' \
  https://api.myarchivist.ai/v1/campaigns/camp_abc123/images

Responses

200Removal result
{ "removed": true }