A modern Python client for the Kanka API, the collaborative worldbuilding and campaign management platform.
Originally inspired by/forked from Kathrin Weihe's python-kanka. Thank you to Kathrin for the foundation and inspiration.
- Entity Support: Support for 12 core Kanka entity types:
- Characters, Locations, Organisations, Families
- Calendars, Events, Quests, Journals
- Notes, Tags, Races, Creatures
- Type Safety: Built with Pydantic v2 for data validation and type hints
- Python 3.9+: Full typing support for modern Python versions
- Pythonic API: Consistent interface patterns across all entity types
- Error Handling: Specific exception types for different API errors
- Rate Limit Handling: Automatic retry with exponential backoff
- Entity Posts: Support for entity posts/comments management
- Filtering and Search: Filter entities by various criteria and search across types
- Pagination: Built-in pagination support for large result sets
Install from PyPI:
pip install python-kanka
git clone https://github.com/ervwalter/python-kanka.git
cd python-kanka
uv sync --all-groups
uv pip install -e .
git clone https://github.com/ervwalter/python-kanka.git
cd python-kanka
pip install -r requirements.txt
pip install -r dev-requirements.txt
pip install -e .
from kanka import KankaClient
# Initialize the client
client = KankaClient(
token="your-api-token", # Get from https://app.kanka.io/settings/api
campaign_id=12345 # Your campaign ID
)
# Create a character
gandalf = client.characters.create(
name="Gandalf the Grey",
title="Wizard",
type="Istari",
age="2000+ years",
is_private=False
)
# Update the character
gandalf = client.characters.update(
gandalf,
name="Gandalf the White"
)
# Search across all entities
results = client.search("Dragon")
for result in results:
print(f"{result.name} ({result.type})")
# List characters with filters
wizards = client.characters.list(
type="Wizard",
is_private=False
)
# Delete when done
client.characters.delete(gandalf)
# Get a character
character = client.characters.get(character_id)
# Create a new post/note (pass the entity object, not just ID)
post = client.characters.create_post(
character, # Pass the full entity object
name="Background",
entry="*Born in the ancient times...*",
is_private=False
)
# List all posts for an entity
posts = client.characters.list_posts(character)
for post in posts:
print(f"{post.name}: {post.entry[:50]}...")
# Update a post (name field is required even if not changing)
update = client.characters.update_post(
character,
post.id,
name=post.name, # Required by API
entry="Updated content..."
)
# Filter by multiple criteria
results = client.characters.list(
name="Gandalf", # Partial name match
8000
span>
type="Wizard", # Exact type match
is_private=False, # Only public entities
tags=[15, 23], # Has specific tags
page=1, # Pagination
limit=30 # Results per page
)
# Access the generic entities endpoint
entities = client.entities(
types=["character", "location"], # Multiple entity types
name="Dragon", # Name filter
tags=[15, 23], # Tag filter
is_private=False
)
All entity types follow the same pattern:
# Locations
rivendell = client.locations.create(
name="Rivendell",
type="City",
parent_location_id=middle_earth.id
)
# Organizations
council = client.organisations.create(
name="The White Council",
type="Council"
)
# Journals
journal = client.journals.create(
name="Campaign Log",
date="3019-03-25"
)
# Notes (for DM/private notes)
note = client.notes.create(
name="DM Notes",
entry="Remember: Gandalf knows about the ring",
is_private=True
)
# Tags
tag = client.tags.create(
name="Important NPC",
colour="#ff0000"
)
from kanka.exceptions import (
NotFoundError,
ValidationError,
RateLimitError,
AuthenticationError
)
try:
character = client.characters.get(99999)
except NotFoundError:
print("Character not found")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds")
except ValidationError as e:
print(f"Invalid data: {e.errors}")
except AuthenticationError:
print("Invalid API token")
The client automatically handles API rate limits by retrying requests with exponential backoff:
# Default behavior - automatic retry on rate limits
client = KankaClient(token, campaign_id)
# Disable automatic retry
client = KankaClient(
token,
campaign_id,
enable_rate_limit_retry=False
)
# Customize retry behavior
client = KankaClient(
token,
campaign_id,
max_retries=5, # Try up to 5 times (default: 3)
retry_delay=2.0, # Initial delay in seconds (default: 1.0)
max_retry_delay=120.0 # Maximum delay between retries (default: 60.0)
)
The client parses rate limit headers from the API to determine retry delays and respects the API's rate limits.
The v2.0 release introduces a new API design with Pydantic models and type safety. Here's how to migrate:
# Old way - procedural API
import kanka
client = kanka.KankaClient(token)
campaign = client.campaign(campaign_id)
char = campaign.character(char_id)
char.name = "New Name"
char.update()
# New way - object-oriented with managers
from kanka import KankaClient
client = KankaClient(token, campaign_id)
char = client.characters.get(char_id)
char = client.characters.update(char, name="New Name")
- Single Client: No more separate campaign object - everything through
KankaClient
- Entity Managers: Each entity type has a dedicated manager (
client.characters
,client.locations
, etc.) - Immutable Models: Models are Pydantic objects - use manager methods to update
- Better Types: Full typing support with IDE autocomplete
- Consistent API: All entities follow the same CRUD pattern
For development, install additional dependencies:
# Clone the repository
git clone https://github.com/ervwalter/python-kanka.git
cd python-kanka
# Install dev dependencies
pip install -r dev-requirements.txt
pip install -e . # Install in editable mode
This project uses several tools to maintain code quality:
- black - Code formatter
- isort - Import sorter
- ruff - Fast Python linter
- pytest - Testing framework
- mypy - Static type checker
Use the Makefile for common development tasks:
make install # Install all dependencies
make format # Format code with black and isort
make lint # Run linting checks
make test # Run all tests
make coverage # Run tests with coverage report
make check # Run all checks (lint + test)
make clean # Clean up temporary files
To automatically run formatting and linting before each commit:
pre-commit install
See the API Reference for detailed documentation of all classes and methods.
Check out the examples/ directory for more detailed examples:
quickstart.py
- Basic usage tutorialcrud_operations.py
- Full CRUD examples for all entity typesfiltering.py
- Advanced filtering and searchposts.py
- Working with entity postserror_handling.py
- Proper error handling patternsmigration.py
- Migrating from the old API
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License - see the LICENSE file for details.
- Kanka.io - The Kanka platform
- Kanka API Documentation - Official API docs
- GitHub Repository - Source code
- Issue Tracker - Report bugs or request features