Archivist Developer Docs

Build on the Archivist platform. This guide covers authentication, key concepts, and common workflows.

Quick Start

Make your first request in minutes.

Authentication

Use your API key with headers.

API Reference

Endpoints, parameters, and responses.

Code Examples

Python, TypeScript, and cURL samples on GitHub.

API Changelog

Breaking changes and release notes.

Quick Start

  1. Sign in to Archivist and create an API key in Account Settings → API Keys.
  2. Save your key securely as an environment variable.
  3. Ping the health endpoint to verify connectivity.
curl https://api.myarchivist.ai/health

Expected response:

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

Authentication

Send your API key with every request using the x-api-key header. OAuth Bearer tokens are also accepted on the same REST routes (used by MCP and other third-party integrations).

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

Do not embed keys in client-side code. Rotate compromised keys immediately.

Core Concepts

Campaign — The top-level container for your game. All data (characters, sessions, quests, beats, key moments, hero awards, quotes, spotlights, factions, locations, items) is scoped to a single campaign. API-created campaigns are bootstrapped with the owner as player/admin, a default GM speaker, Character Arc defaults, and default compendium field definitions.

Session — A play event in time (e.g., a table session, Discord call, play‑by‑post thread, or audio upload). Sessions have a session_date, optional title/summary, and are the primary anchor for time‑based content. On the developer and MCP surfaces, sessions are system-managed: you can list, read, and update metadata with PATCH or PUT, but not create or delete them. Session creation and deletion are reserved for first-party product clients (e.g. the mobile app).

Cast Analysis — Structured analytics generated from a session transcript (talk share, pacing, dominance, questions, etc.). Stored per session and retrievable via the Cast Analysis endpoint. When a rendered PDF has been saved, the payload includes pdfSavedAt and pdfPageCount; both are null until then. The same PDF timestamps appear on session handouts.

Beat — A narrative unit that structures a session or storyline. Beats are hierarchical and ordered (type: "major" | "minor" | "step") with optional parent_id and index. Use beats to outline arcs and attach summaries, decisions, and consequences. GET /v1/beats?session_id= returns a paginated flat list of beats linked to that session; add include_hierarchy=true for a nested beat tree with a children array on each node (not paginated). Hierarchy nodes also include linkedEntities — ID arrays of characters, factions, locations, and items tagged on that beat.

Key Moment — The product name for a Moment record: a session-attached highlight with optional image and presentation metadata (style, customTheme, variant). Most rows stay kind: "legacy". A Key Moment card is identified by categories containing key-moment, not by kind=key-moment alone. Attach art with the image upload routes (entity_type=moment) or an HTTPS image URL. pending/approved/discovered are product-view fields and are not on the developer Moment read shape.

Hero Award — A session-attached highlight card (title, description, optional character/player names, style/variant). Create requires session_id, title, and description. Card images are rendered by the app; they are not uploaded via init_image_upload. Hero awards do not use wikilinks.

Quote — A memorable line attached to a session. Create requires session_id and text. Create finds or creates a recap pipeline (source="api") so the card appears in the artifacts catalog. Quotes do not use wikilinks.

Spotlight — A GEMS recap card attached to a session (title, optional description and source moment). Distinct from character/entity spotlights on a session handout. Create requires session_id and title, and finds or creates a recap pipeline. List results are filtered to entry_type=spotlight. Spotlights do not use wikilinks.

Character — A person in the world. Can be a PC, NPC, or other subtype. Characters support alias lists, optional backstory, and speaker_id linkage. The legacy player_name field is deprecated; responses may also include a nullable, read-only player object sourced from the linked Speaker record.

Faction — An organization or group (guilds, cults, companies). Useful for tracking influence, relationships, and agendas.

Location — A place in the world, optionally hierarchical via parent_id (e.g., City → District → Tavern).

Item — A notable object (weapons, artifacts, clues) with optional type and description.

Quest — A canonical quest-log entry for a campaign. Quests track a name, optional quest giver, category, status, structured objectives, narrative progress, and related compendium entities. The public API exposes the persisted quest state directly; session-by-session reconciliation remains an internal Archivist workflow.

Link — A relationship between two entities with optional alias/label. Sources (from_type) can be Character, Faction, Location, Item, Moment, GameSession, Beat, Backstory, Journal, or World. Targets (to_type) can be Character, Faction, Location, Item, or Moment. Link create/update normalizes aliases and validates that both records exist in the same campaign. Links power Archivist's wikilinks and graph views.

Journal Entries — Rich, permissioned campaign notes stored per campaign. Entries support Lexical JSON plus extracted plain text, are chunked/embedded, and are included in RAG retrieval for the Ask endpoint. When both content and content_rich are supplied, rich content is canonical. Creator manage permission is granted automatically, while other users require public visibility or explicit shares. Limits: 50,000 tokens per entry and 1,000,000 tokens per world.

Journal Folders — Hierarchical containers for journal entries. Folders include id, name, path, parent_id, and position for tree rendering.

Ask (RAG) — Question‑answering over your campaign's knowledge base. Send chat messages and optionally stream responses. Retrieval includes characters, beats, moments, sessions, and journal entries. Check remaining tokens with GET /v1/ask/quota before a long conversation; the same counters are returned on non-streaming Ask responses and as X-Monthly-Remaining-Tokens / X-Hourly-Remaining-Tokens headers when streaming.

Data Models

Representative TypeScript interfaces for common resources. Fields may be extended over time; unknown fields should be ignored by clients.

// Common primitives
export type ID = string; // stable, opaque identifier
export type ISODate = string; // ISO 8601 timestamp

// Campaign
export interface Campaign {
  id: ID;
  title: string;
  description?: string;
  system: string;
  summary?: string;
  language: string;
  image?: string;
  public: boolean;
  mature: boolean;
  owner_id: ID;
  created_at: ISODate;
  updated_at?: ISODate;
}

export interface CampaignStats {
  campaign_id: ID;
  title: string;
  characters: number;
  sessions: number;
  moments: number;
  public: boolean;
  created_at: ISODate;
}

// Session
export type SessionType = 'audioUpload' | 'playByPost' | 'discordVoice' | 'txtUpload' | 'rawNotes' | 'other';
export interface GameSession {
  id: ID;
  campaign_id: ID;
  type: SessionType;
  title: string;
  summary?: string;
  notes?: string;
  session_date: ISODate;
  image?: string;
  index?: number;
  public: boolean;
  pbp_start_msg_url?: string;
  pbp_end_msg_url?: string;
  created_at: ISODate;
  updated_at?: ISODate;
}

export interface GameSessionDetail extends GameSession {
  beats?: Beat[];
  moments?: Moment[];
}

// Beat
export type BeatType = 'major' | 'minor' | 'step';
export interface Beat {
  id: ID;
  campaign_id: ID;
  label: string;
  type: BeatType;
  description?: string;
  index: number;
  parent_id?: ID;
  metadata?: Record<string, unknown>;
  game_session_ids: ID[];
  game_session_id?: ID;
  created_at: ISODate;
  updated_at?: ISODate;
}

export interface BeatLinkedEntities {
  characters: ID[];
  factions: ID[];
  locations: ID[];
  items: ID[];
}

export interface BeatHierarchyRead extends Beat {
  children: BeatHierarchyRead[];
  linkedEntities: BeatLinkedEntities;
}

// Key Moment (Moment table)
export interface Moment {
  id: ID;
  campaign_id: ID;
  session_id: ID;
  label?: string;
  content?: string;
  image?: string;
  index?: number;
  categories?: string[];
  kind: string; // defaults to "legacy"
  metadata?: Record<string, unknown> | null;
  created_at: ISODate;
  updated_at?: ISODate;
}

// Hero Award
export interface HeroAward {
  id: ID;
  campaign_id: ID;
  session_id: ID;
  title: string;
  description: string;
  character_name: string;
  player_name?: string;
  tone_mode: string;
  award_mode: string; // "moment" | "theme"
  style: string;
  variant: string;
  custom_theme?: Record<string, unknown> | null;
  order_index: number;
  created_at: ISODate;
  updated_at?: ISODate;
}

// Quote
export interface Quote {
  id: ID;
  campaign_id: ID;
  session_id: ID;
  text: string;
  speaker: string;
  character_name: string;
  context: string;
  style: string;
  custom_theme?: Record<string, unknown> | null;
  order_index: number;
  pipeline_id?: ID;
  recap_set_id?: ID;
  created_at: ISODate;
  updated_at?: ISODate;
}

// Spotlight (GEMS recap card; not a session-handout spotlight)
export interface Spotlight {
  id: ID;
  campaign_id: ID;
  session_id: ID;
  title: string;
  description: string;
  character_name: string;
  player_name: string;
  style: string;
  custom_theme?: Record<string, unknown> | null;
  order_index: number;
  entry_type: string;
  source_moment_id?: ID;
  pipeline_id?: ID;
  recap_set_id?: ID;
  created_at: ISODate;
  updated_at?: ISODate;
}

// Card shapes (fields=card list endpoints)
export interface CharacterCard {
  id: ID;
  character_name: string;
  type: CharacterType;
  image?: string;
}

export interface MomentCard {
  id: ID;
  label?: string;
  image?: string;
  session_id: ID;
  created_at: ISODate;
  categories: string[];
}

export interface JournalCard {
  id: ID;
  title: string;
  summary?: string;
  updated_at?: ISODate;
  folder_id?: ID;
}

export interface EntityCard {
  id: ID;
  name: string;
  type?: string;
  image?: string;
}

export interface EntitiesListResponse {
  results: EntityCard[];
  hasMore: boolean;
  page: number;
  pages: number;
  total: number;
}

// Character
export type CharacterType = 'PC' | 'NPC' | 'Other';
export interface CharacterPlayer {
  id: ID;
  name?: string;
  handle?: string;
  roles: string[];
  campaign_id: ID;
  created_at: ISODate;
}

export interface Character {
  id: ID;
  campaign_id: ID;
  character_name: string;
  character_alias?: string;
  character_aliases: string[];
  player_name?: string;
  player_handle?: string;
  player?: CharacterPlayer | null;
  description?: string;
  backstory?: string;
  type: CharacterType;
  image?: string;
  tcg_image?: string;
  merge: boolean;
  created_at: ISODate;
  updated_at?: ISODate;
}

// Faction
export interface Faction {
  id: ID;
  campaign_id: ID;
  name: string;
  aliases: string[];
  description?: string;
  type?: string;
  image?: string;
  tcg_image?: string;
  merge: boolean;
  created_at: ISODate;
  updated_at?: ISODate;
}

// Location
export interface Location {
  id: ID;
  campaign_id: ID;
  name: string;
  aliases: string[];
  description?: string;
  type?: string;
  parent_id?: ID;
  image?: string;
  tcg_image?: string;
  merge: boolean;
  created_at: ISODate;
  updated_at?: ISODate;
}

// Item
export interface Item {
  id: ID;
  campaign_id: ID;
  name: string;
  aliases: string[];
  description?: string;
  type?: string;
  image?: string;
  tcg_image?: string;
  merge: boolean;
  created_at: ISODate;
  updated_at?: ISODate;
}

// Cast Analysis
export interface CastAnalysis {
  id: ID;
  session_id: ID;
  analysis: Record<string, unknown>;
  created_at: ISODate;
  updated_at?: ISODate;
}

// Session Handout (note: camelCase keys in response)
export interface SessionHandout {
  summary: string;
  sessionOutline: string | Array<{ title: string }>;
  encounters: Array<{ title: string; bullets: string[] }>;
  characterSpotlight: Array<{ name: string; description: string; bullets: string[] }>;
  otherEntitySpotlight: Array<{ name: string; description: string }>;
  items: Array<{ name: string; description: string }>;
  valuableInformation: Array<{ info: string }>;
  partyStatusAndNextSteps: {
    partyStatus: { summary: string; bullets: string[] };
    nextSteps: { summary: string };
  };
  moments: Array<{ label: string; content: string }>;
}

// Quest
export type QuestCategory = 'main' | 'side' | 'faction' | 'personal' | 'n/a';
export type QuestStatus = 'planned' | 'in-progress' | 'blocked' | 'failed' | 'done' | 'n/a';
export type ObjectiveStatus = 'pending' | 'in-progress' | 'completed' | 'failed' | 'blocked';
export type QuestEntityType = 'character' | 'faction' | 'location' | 'item';

export interface QuestObjective {
  id: ID;
  text: string;
  status: ObjectiveStatus;
  order: number;
}

export interface QuestProgressEntry {
  id: ID;
  text: string;
  order: number;
  session_id?: ID | null;
  session_number?: number | null;
  session_title?: string | null;
  session_date?: ISODate | null;
}

export interface QuestRelatedEntityRef {
  id: ID;
  entity_type: QuestEntityType;
  entity_id?: ID | null;
  entity_name_snapshot?: string | null;
  label?: string | null;
  order: number;
}

export interface QuestSessionRef {
  id: ID;
  number?: number | null;
  title?: string | null;
  session_date?: ISODate | null;
}

export interface Quest {
  id: ID;
  campaign_id: ID;
  order_index: number;
  quest_name: string;
  quest_giver?: string | null;
  quest_giver_id?: ID | null; // resolved Character/Faction id when available
  quest_category: QuestCategory;
  status: QuestStatus;
  success_definition?: string | null;
  failure_conditions?: string | null;
  next_action?: string | null;
  resolution?: string | null;
  objectives: QuestObjective[];
  progress_log: string[];
  progress_log_entries: QuestProgressEntry[];
  related_characters: string[];
  related_factions: string[];
  related_locations: string[];
  related_items: string[];
  related_entity_refs: QuestRelatedEntityRef[];
  first_session?: QuestSessionRef | null;
  last_session?: QuestSessionRef | null;
  created_at: ISODate;
  updated_at?: ISODate | null;
}

// Link (relationship)
export type EntityType = 'Character' | 'Faction' | 'Location' | 'Item' | 'Beat' | 'Moment';
export interface Link {
  id: ID;
  campaign_id: ID;
  from_id: ID;
  from_type: EntityType;
  to_id: ID;
  to_type: EntityType;
  alias: string;
  created_at: ISODate;
}

// Journal Entry
export interface JournalEntry {
  id: ID;
  campaign_id: ID;
  title: string;
  summary?: string;
  content?: string; // included on detail GET, omitted from list
  content_rich?: unknown; // Lexical editor state JSON (detail only)
  content_metadata?: unknown; // detail only
  tags: string[];
  token_count: number;
  cover_image?: string;
  is_pinned: boolean;
  is_public: boolean;
  status?: 'draft' | 'published' | 'archived';
  published_at?: ISODate;
  archived_at?: ISODate;
  folder_id?: ID;
  author_id?: ID;
  last_edited_by_id?: ID;
  permission_level?: 'view' | 'comment' | 'edit' | 'manage'; // detail only
  created_at: ISODate;
  updated_at?: ISODate;
}

// Journal Folder
export interface JournalFolder {
  id: ID;
  campaign_id: ID;
  parent_id?: ID;
  name: string;
  path: string;
  description?: string;
  position: number;
  metadata?: unknown;
  created_at: ISODate;
  updated_at?: ISODate;
}

// Ask (RAG)
export interface ChatMessage { role: 'system' | 'user' | 'assistant'; content: string; }
export interface AskRequest { campaign_id: ID; messages: ChatMessage[]; stream?: boolean; }
export interface AskCitation {
  citation_id: string;
  source_type: string;
  source_id?: ID;
  title?: string;
  session_number?: number;
  excerpt?: string;
}
export interface AskQuota {
  monthlyTokensRemaining: number;
  hourlyTokensRemaining: number;
  launchPromoActive: boolean;
}
export interface AskResponse {
  answer: string;
  citations: AskCitation[];
  monthlyTokensRemaining: number;
  hourlyTokensRemaining: number;
}
export interface AskChunk { id: string; type: 'delta' | 'done'; content?: string; }

Conventions

  • Base URL: https://api.myarchivist.ai
  • Authentication: All endpoints require x-api-key unless otherwise noted. Authorization: Bearer tokens work on the same routes.
  • Pagination: list endpoints accept page and size. The default size is server‑defined; clients should not assume a maximum.
  • Rate limits: Requests are limited per API key. 429 responses include a Retry-After header. Ask also has separate monthly and hourly token counters — see GET /v1/ask/quota.
  • Timestamps: All dates are returned in ISO 8601 (UTC). Client UIs should localize for display.
  • Idempotency: POST generally creates, PATCH updates via JSON Merge Patch where specified.
  • Errors: Error responses use FastAPI-style JSON bodies with a top-level detail field.
  • Filtering: List endpoints support filters such as campaign_id, character_type, and search. Compendium and journal search uses Postgres full-text search (searchVector) across names, aliases, tags, and related indexed fields, with ILIKE fallback. Moments and quests still use substring matching on label/name fields. For a single cross-type lookup use GET /v1/campaigns/{id}/search?q= (optional types comma-list; max 10 hits per type).
  • Card lists: Pass fields=card on characters, moments, and journals list endpoints for lightweight picker items.
  • Entity picker: GET /v1/entities uses limit (not size) and returns a {results, hasMore} envelope.
  • Moment filters: Moment list supports character_ids, location_ids, faction_ids, item_ids, and session_ids comma-separated ID filters, plus categories (ARRAY overlap) and kind. For Key Moment cards filter categories=key-moment; do not rely on kind=key-moment.
  • Highlight lists: /v1/hero-awards, /v1/quotes, and /v1/spotlights require campaign_id or session_id.
  • Image URLs: The image field for Characters, Items, Factions, Locations, Moments, Sessions, and Campaigns must be an HTTPS URL (max 2048 characters). HTTP URLs are rejected with a validation error.

Entity Images

Attach images via the dedicated image routes (developer API) or set an HTTPS URL on entity update. Two upload paths are supported:

  • AI generationPOST /v1/images/generate returns a public URL from entity context. Attach it with a PATCH on the target entity. Consumes per-cycle quota; check GET /v1/images/usage first.
  • Direct uploadPOST /v1/campaigns/{id}/images/init returns a presigned PUT URL; upload bytes client-side, then POST .../images/complete to validate, moderate, and attach. Repeating complete with the same object_key is idempotent.
  • RemovalDELETE /v1/campaigns/{id}/images by entity or managed URL.

The MCP server exposes the upload and removal tools (init_image_upload, complete_image_upload, delete_entity_image, get_image_usage); direct upload step 2 requires an HTTP PUT outside MCP. AI generation is REST-only — there is no generate_image MCP tool. See the API reference and MCP docs.

Read-Only Properties

Certain properties are system-managed and cannot be set through POST or PATCH requests. These fields appear in GET responses but are ignored if included in create/update operations.

  • Campaigns: public, created_at
  • Sessions: public, created_at
  • Characters, Locations, Factions, Items: merge, created_at
  • All entities: id, created_at

These properties are managed by the system based on application logic, user permissions, and moderation workflows.

Resources

Typical Workflow

A simple sequence: create a campaign, rely on bootstrap defaults, add a character, then list sessions.

API_KEY="your-api-key"
BASE_URL="https://api.myarchivist.ai"

# 1) Create a campaign
curl -s -X POST \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "My First API Campaign",
    "description": "Created via API",
    "system": "D&D 5e"
  }' \
  "$BASE_URL/v1/campaigns"