Skip to content

API Reference

Version: 0.1.0

Interactive docs

With the server running, the API is self-documented via OpenAPI:

URLDescription
http://localhost:9481/api/docsSwagger UI: interactive, try-it-out docs
http://localhost:9481/api/redocReDoc: clean, readable reference
http://localhost:9481/api/openapi.jsonRaw OpenAPI schema

Authentication

All endpoints except /api/auth/status, /api/auth/setup, /api/auth/login, and /api/auth/config require a JWT.

Header (preferred):

Authorization: Bearer <token>

Query parameter (required for browser-embedded images and file downloads):

?token=<token>

Tokens are returned by /api/auth/login and expire after 30 days.

Roles

RolePermissions
adminFull access including user management and app settings
gmRead + edit metadata, rescan library, manage campaigns
playerRead-only access

Auth

EndpointMethodAuthDescription
/api/auth/statusGETNoneReturns {"initialized": bool}
/api/auth/configGETNonePublic auth config for the login screen
/api/auth/setupPOSTNoneFirst-run admin account creation. Body: {username, password}
/api/auth/loginPOSTNoneAuthenticate. Body: {username, password}. Returns {token, user}
/api/auth/meGETanyCurrent user details
/api/auth/openid/loginGETNoneStart OIDC login, redirects to IdP
/api/auth/openid/callbackGETNoneOIDC callback
/api/auth/openid/discoverPOSTadminServer-side discovery fetch. Body: {issuer_url}

Users

EndpointMethodAuthDescription
/api/usersGETadminList all users
/api/usersPOSTadminCreate a user. Body: {username, password, role?, email?}
/api/users/:idPATCHadminUpdate role, password, allow_explicit, or email
/api/users/:idDELETEadminDelete a user
/api/users/me/preferencesPATCHanyUpdate own display_name, allow_explicit, or email
/api/users/me/passwordPATCHanyChange own password. Body: {current_password, new_password}
/api/users/meDELETEanyDelete own account

Library

EndpointMethodAuthDescription
/api/statsGETJWT or X-API-KeyCounts, page totals, library size, version
/api/scan-statusGETadminCurrent scan state
/api/rescanPOSTadminTrigger a background rescan and reindex
/api/cancel-scanPOSTadminGracefully stop the running scan

Stats response:

json
{
  "game_systems": 12,
  "books": 340,
  "maps": 1500,
  "tokens": 800,
  "audio": 250,
  "models": 640,
  "indexed_books": 320,
  "total_pages": 45000,
  "total_size_mb": 18240.5,
  "library_size_mb": 41890.2
}

/api/stats is the only endpoint that accepts the X-API-Key header, so it's the safe way to surface library counts on an external dashboard. Generate a key as an admin under Settings → App Settings → Stats API Key (regenerate or revoke it there at any time). See the Homepage Widget guide for a full walkthrough.

Homepage Custom API widget: add this to your Homepage services.yaml (Custom API widget docs):

yaml
- Grimoire:
    href: https://grimoire.example.com
    icon: grimoire.png
    widget:
      type: customapi
      url: https://grimoire.example.com/api/stats
      refreshInterval: 60000
      headers:
        X-API-Key: your-key-here
      mappings:
        - field: books
          label: Books
          format: number
        - field: maps
          label: Maps
          format: number
        - field: tokens
          label: Tokens
          format: number
        - field: library_size_mb
          label: Size
          format: float
          scale: 0.001
          suffix: " GB"

Homepage shows up to four fields per row; pick the counts you care about from the fields below. Use a refreshInterval of 60s or higher, /api/stats is rate limited.

FieldMeaningSuggested format
game_systemsNumber of game systems (top-level library folders)number
booksTotal books (PDFs)number
mapsTotal mapsnumber
tokensTotal tokensnumber
audioTotal audio tracksnumber
modelsTotal 3D modelsnumber
indexed_booksBooks with a searchable full-text indexnumber
total_pagesSum of all book page countsnumber
total_size_mbSize of the books in MB (books only)float, add scale: 0.001 + suffix: " GB" to show GB
library_size_mbSize of the whole library in MB — books plus maps, tokens, audio, and modelsfloat, add scale: 0.001 + suffix: " GB" to show GB

Game Systems

EndpointMethodAuthDescription
/api/systemsGETanyList all systems with book counts
/api/systems/:idGETanySystem detail + full book list
/api/systems/:idPATCHgm/adminUpdate metadata

PATCH fields: name, slug, description, publishers, character_builder_url, cover_image, cover_book_id, tags, genre, is_explicit


Books

EndpointMethodAuthDescription
/api/booksGETanyPaginated book list. Query: system_id, category, limit (max 500), offset
/api/books/:idGETanyBook detail
/api/books/:idPATCHgm/adminUpdate book metadata
/api/books/:id/reindexPOSTgm/adminRe-run OCR on a scanned book. Optional query ocr_dpi (72–600) re-reads it at a higher resolution than the global OCR_DPI. Clears the search index and re-queues the book (OCR runs in the background). 400 for books with an embedded text layer.
/api/books/:id/rescanPOSTgm/adminRe-read a single book from disk and rebuild its search index, for a file edited externally. Works for any PDF: a text-layer book is re-extracted; an image-only book is re-queued for OCR. Refreshes page count and thumbnail. Runs in the background; no-ops if a library scan is already running. 400 for non-PDFs.
/api/books/:id/fileGETanyDownload/stream the file
/api/books/:id/thumbnailGETanyWebP cover thumbnail
/api/books/:id/tocGETanyPDF table of contents
/api/books/:id/page/:numGETanyRender PDF page as WebP. Query: width (default 1200, max 3000)
/api/books/:id/page/:num/textGETanyPlain text of a page
/api/books/:id/page/:num/wordsGETanyWord bounding boxes for text overlay

Maps

EndpointMethodAuthDescription
/api/mapsGETanyPaginated map list. Query: limit, offset, tag
/api/maps/:idGETanyMap detail
/api/maps/:idPATCHgm/adminUpdate description, tags, map_type, grid_size
/api/maps/:id/fileGETanyDownload/stream the original map file
/api/maps/:id/page/:nGETanyDownscaled WebP render. Query: width (default 1600, max 3000)
/api/maps/:id/vtt/imageGETanyBattlemap image decoded out of a .uvtt/.dd2vtt file
/api/maps/:id/vtt/dataGETanyGrid resolution and wall/portal/light counts from a Universal VTT file
/api/maps/:id/thumbnailGETanyWebP thumbnail
/api/map-foldersGETanyList folder tag assignments
/api/map-foldersPATCHgm/adminSet tags on a folder. Body: {path, tags}

Map formats and viewing

Map detail responses carry a media_kind field naming the viewer to use: image, video, vtt, or archive.

Raster maps are not viewed through /file. The viewer requests /page/1, which renders a downscaled WebP — a large battlemap would otherwise have to be transferred in full before anything appeared. /file remains the download endpoint and serves the untouched original.

Animated battlemaps (.webm, .mp4) stream from /file with a real video MIME type and no attachment disposition, so they play in place.

Universal VTT files (.uvtt, .dd2vtt) are JSON envelopes holding the map as base64 plus wall, portal, and light data (format reference). The image is decoded server-side and served by /vtt/image; /vtt/data returns the grid and feature counts with the image omitted, so the base64 payload never reaches the browser.


Tokens

EndpointMethodAuthDescription
/api/tokensGETanyPaginated token list. Query: limit, offset, tag
/api/tokens/:idGETanyToken detail
/api/tokens/:idPATCHgm/adminUpdate description, tags, is_explicit
/api/tokens/:id/fileGETanyDownload the token image
/api/tokens/:id/thumbnailGETanyWebP thumbnail
/api/token-foldersGETanyList folder tag assignments
/api/token-foldersPATCHgm/adminSet tags on a folder. Body: {path, tags}

Models

EndpointMethodAuthDescription
/api/modelsGETanyPaginated 3D model list. Query: limit, offset
/api/models/:idGETanyModel detail, including viewer capability
/api/models/:idPATCHgm/adminUpdate description, tags, is_explicit, is_supported
/api/models/:id/fileGETanyDownload the model file
/api/models/:id/thumbnailGETanyWebP thumbnail (rendered preview)
/api/models/bulkPOSTgm/adminBulk update models
/api/models/bulk/tagsPOSTgm/adminBulk add tags to models
/api/model-foldersGETanyList folder tag assignments
/api/model-foldersPATCHgm/adminSet tags on a folder. Body: {path, tags}
/api/model-folders/bulkPOSTgm/adminSet tags on many folders

The detail response carries three viewer fields. viewer_loader names the client-side loader for the format (stl, gltf, 3mf, ply) and is empty when none applies. viewer_available says whether the browser viewer should load the file on its own. viewer_oversized distinguishes the two reasons it might not: when true, the format is supported and only the size cap held it back, so the client can offer to load it anyway behind a warning; when false alongside a false viewer_available, no loader exists for the format and a download is the only option.

is_supported is a tri-state — true presupported, false unsupported, null when the name says nothing. See the 3D Models guide.


EndpointMethodAuthDescription
/api/search?q=GETanyFTS5 full-text search. Min 2 chars. Optional: book_id, system_id, limit

Response:

json
{
  "query": "fireball",
  "total": 42,
  "results": [{"id": "uuid", "title": "...", "game_system": "...", "page_number": 42, "snippet": "..."}],
  "maps":    [{"id": "uuid", "filename": "...", "relative_path": "...", "tags": []}],
  "tokens":  [{"id": "uuid", "filename": "...", "relative_path": "...", "tags": []}]
}

maps and tokens are empty when book_id or system_id is scoped.


Favorites

EndpointMethodAuthDescription
/api/favoritesGETanyList current user's favorites
/api/favoritesPOSTanyAdd a favorite (idempotent). Body: {item_type, item_id}
/api/favorites/:type/:idDELETEanyRemove a favorite

Item types: book, map, token, system


Bookmarks

EndpointMethodAuthDescription
/api/bookmarks?book_id=GETanyList user's bookmarks for a book
/api/bookmarksPOSTanyCreate a bookmark. Body: {book_id, page_number, label?, notes?, selected_text?}
/api/bookmarks/:idPATCHanyUpdate label or notes
/api/bookmarks/:idDELETEanyDelete a bookmark

Saved audio sets

Named, per-user playlists and soundboards. See Audio Library.

EndpointMethodAuthDescription
/api/audio-setsGETanyList the user's saved sets (names and counts only). Optional ?kind= limits to one kind
/api/audio-setsPOSTanySave a set. Body: {kind, name, entries: [{audio_id, loop?}], layout?: {cols, rows}}
/api/audio-sets/:idGETanyLoad one set, resolved against the current library
/api/audio-sets/:idPATCHanyRename or replace contents. Body: {name?, entries?, layout?}
/api/audio-sets/:idDELETEanyDelete a saved set

Kinds: playlist, soundboard. layout is the soundboard grid and is null for a playlist. Re-saving an existing (kind, name) overwrites that set rather than creating a second one.

Entries store audio ids only, so titles come back resolved against the library as it is now. An entry whose track has been removed is omitted and counted in missing, letting a stale set load with what remains instead of failing.


Campaigns

Campaign CRUD

EndpointMethodAuthDescription
/api/campaignsGETanyList own + invited campaigns
/api/campaignsPOSTanyCreate campaign
/api/campaigns/:idGETowner or memberCampaign detail
/api/campaigns/:idPATCHowner or adminUpdate campaign
/api/campaigns/:idDELETEowner or adminDelete campaign

Members

EndpointMethodAuthDescription
/api/campaigns/:id/invitePOSTowner or adminInvite a user. Body: {user_id}
/api/campaigns/:id/members/:user_idPATCHmember or ownerAccept/decline or set character name
/api/campaigns/:id/members/:user_idDELETEowner, admin, or selfRemove member

Sessions

EndpointMethodAuthDescription
/api/campaigns/:id/sessionsGETmember or ownerList sessions
/api/campaigns/:id/sessionsPOSTmember or ownerCreate session. Body: {session_date, title?}
/api/campaigns/:id/sessions/:sidPATCHowner or adminUpdate title
/api/campaigns/:id/sessions/:sidDELETEowner or adminDelete session
/api/campaigns/:id/sessions/:sid/notes/playerPUTmember or ownerSave player note. Body: {content}
/api/campaigns/:id/sessions/:sid/notes/gmPUTowner or adminSave GM notes. Body: {internal_content?, external_content?}

Schedule

EndpointMethodAuthDescription
/api/campaigns/:id/scheduleGETmember or ownerSchedule + next 10 dates
/api/campaigns/:id/schedulePUTowner or adminCreate or update schedule
/api/campaigns/:id/scheduleDELETEowner or adminRemove schedule

Schedule body:

json
{
  "frequency": "weekly",
  "days": [5],
  "time_utc": "18:00",
  "biweekly_reference": "2026-01-03",
  "monthly_week": null,
  "custom_dates": null
}

frequency values: weekly, biweekly, monthly, custom. Days are 0 (Monday) – 6 (Sunday).

Availability

EndpointMethodAuthDescription
/api/campaigns/:id/availabilityGETmember or ownerAvailability for next 10 scheduled sessions
/api/campaigns/:id/availability/:datePUTmember or ownerSet own availability. Body: {status}
/api/campaigns/:id/availability/:date/cancelPUTowner or adminToggle session cancellation

Statuses: available, tentative, unavailable


Settings (admin only)

EndpointMethodDescription
/api/settingsGETGet all application settings
/api/settingsPATCHUpdate application settings
/api/settings/uiGETUI visibility flags (any authenticated user)
/api/settings/api-key/generatePOSTGenerate a stats API key
/api/settings/api-keyDELETERevoke the stats API key

Maintenance (admin only)

EndpointMethodDescription
/api/maintenance/cleanup-missingPOSTRemove DB records for files no longer present on disk

Backups (admin only)

EndpointMethodDescription
/api/backupsGETList backups, newest first, with created_at, size_bytes, and version
/api/backupsPOSTCreate a backup now
/api/backups/settingsGETRead backup schedule, retention, and storage location
/api/backups/settingsPUTConfigure schedule, retention, and storage location
/api/backups/{backup_id}/downloadGETDownload a backup archive (application/zip)
/api/backups/{backup_id}DELETEDelete a backup archive (204)

A backup is a timestamped .zip holding a consistent snapshot of the database plus the files uploaded through Grimoire. The library is never included. Because the listing carries timestamps, a client can check how stale the newest backup is and take a fresh one before running something destructive.

Creating a backup pauses database writes for the length of the snapshot; a second concurrent create returns 409.

There is no restore endpoint, by design. See Backups for the restore procedure and configuration.


Logs (admin only)

EndpointMethodDescription
/api/logsGETRetrieve recent log entries from the in-memory ring buffer

Query parameters: level (default info), limit (default 200, max 20000), offset, after_seq (cursor for live polling).


Error responses

json
{"detail": "Human-readable error message"}
StatusMeaning
400Bad request / business rule violated
401Not authenticated
403Forbidden, insufficient role
404Not found
409Conflict, duplicate resource
422Request body failed schema validation

Released under the GNU GPL v3.0 License.