https://github.com/toddsundsted/ktistec
Single user ActivityPub (https://www.w3.org/TR/activitypub/) server.
https://github.com/toddsundsted/ktistec
activitypub crystal
Last synced: 8 days ago
JSON representation
Single user ActivityPub (https://www.w3.org/TR/activitypub/) server.
- Host: GitHub
- URL: https://github.com/toddsundsted/ktistec
- Owner: toddsundsted
- License: agpl-3.0
- Created: 2020-03-31T16:54:22.000Z (almost 6 years ago)
- Default Branch: main
- Last Pushed: 2026-03-01T11:29:53.000Z (10 days ago)
- Last Synced: 2026-03-02T08:42:51.650Z (9 days ago)
- Topics: activitypub, crystal
- Language: Crystal
- Homepage:
- Size: 18.7 MB
- Stars: 424
- Watchers: 11
- Forks: 21
- Open Issues: 31
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: COPYING
Awesome Lists containing this project
- my-awesome-github-stars - toddsundsted/ktistec - Single user ActivityPub (https://www.w3.org/TR/activitypub/) server. (Crystal)
README
# 
- [Features](#features)
- [Text and images](#text-and-images)
- [Draft posts](#draft-posts)
- [Threaded replies](#threaded-replies)
- [Thread Analysis](#thread-analysis)
- [Translations](#translations)
- [@-mention and #-hashtag autocomplete](#-mention-and--hashtag-autocomplete)
- [Custom emoji](#custom-emoji)
- [Polls](#polls)
- [Control over comment visibility](#control-over-comment-visibility)
- [Pretty URLs](#pretty-urls)
- [Open Graph metadata](#open-graph-metadata)
- [Followers/following](#followersfollowing)
- [Content discovery](#content-discovery)
- [Content filtering](#content-filtering)
- [Blocking](#blocking)
- [Bookmarks](#bookmarks)
- [Pinned posts](#pinned-posts)
- [Quote posts](#quote-posts)
- [RSS feeds](#rss-feeds)
- [X-Ray Mode](#x-ray-mode)
- [Metrics](#metrics)
- [Tasks](#tasks)
- [Scripts](#scripts)
- [Theming](#theming)
- [API](#api)
- [A Note on ActivityPub](#a-note-on-activitypub)
- [Publishing an Object](#publishing-an-object)
- [Sharing an Object (ActivityPub `Announce`)](#sharing-an-object-activitypub-announce)
- [Liking an Object](#liking-an-object)
- [Following an Actor](#following-an-actor)
- [Undoing an Activity](#undoing-an-activity)
- [Deleting](#deleting)
- [MCP Support](#mcp-support)
- [OAuth](#oauth)
- [Prerequisites](#prerequisites)
- [Building](#building)
- [SQLite3 Compatibility](#sqlite3-compatibility)
- [Running Tests](#running-tests)
- [Setup, Configuration, and Usage](#setup-configuration-and-usage)
- [Command Line Options](#command-line-options)
- [User Settings](#user-settings)
- [Site Settings](#site-settings)
- [Contributors](#contributors)
- [Copyright and License](#copyright-and-license)
**Ktistec** is an [ActivityPub](https://www.w3.org/TR/activitypub/)
server. It is intended for small numbers of trusted users (everyone is
an administrator). It is designed to have few runtime dependencies --
for example, it uses SQLite as its database, instead of PostgreSQL +
Redis + etc. It is licensed under the AGPLv3.
Ktistec powers [Epiktistes](https://epiktistes.com/), my low-volume
home in the Fediverse. If you want to talk to me, I'm
[@toddsundsted@epiktistes.com](https://epiktistes.com/@toddsundsted).
## Features
Ktistec is intended for writing and scripting.
### Text and images
Ktistec supports two editing modes: a rich text editor and a Markdown
editor. Choose your preferred editor in your account settings.
The **rich text editor** provides text formatting options including
*bold*, *italic*, *strikethrough*, *code* (inline and block),
*superscript*/*subscript*, *headers*, *blockquotes* with nested
indentation, and both *bullet* and *numeric* lists.
The rich text editor also supports adding and editing alt text for
images to improve accessibility. Alt text describes images for screen
readers and displays when images fail to load.

The **Markdown editor** lets you write posts in Markdown syntax.
Ktistec automatically converts Markdown to HTML when you publish. In
addition to CommonMark syntax, Ktistec supports hashtags and mentions.
Ktistec supports inline placement of images, with ActivityPub image
attachments used for compatibility with non-Ktistec servers. Images
support focal point positioning to ensure the most important part of
an image is always visible in thumbnails. Focal point positioning
currently only works with federated attachments. There is no means to
edit an image's focal point locally at this time.

### Draft posts
Meaningful writing is an iterative process so Ktistec supports draft
posts. Draft posts aren't visible until you publish them. Draft posts,
including draft replies, are automatically saved as you type.

With Ktistec, you can focus on creating without worrying about losing
your work.
### Threaded replies
Threaded replies make it easier to follow discussions with many
posts. To keep the author's first posts together, the author's
self-replies are prioritized in the thread view.

### Thread Analysis
Ktistec can analyze large conversation threads to help you navigate
complex discussions. Thread analysis provides:
- **Key Participants**: Identify the most active contributors
- **Timeline Histogram**: Visualize conversation activity over time
- **Notable Branches**: Discover important sub-conversations within threads
Access thread analysis from the thread view page for any conversation.
### Translations
Integrate Ktistec with a translation service like DeepL or
LibreTranslate (or host your own) and translate posts in other
languages into your own language.

See [Configuring Translation](#configuring-translation) for details on
how to set up the integration.
### @-mention and #-hashtag autocomplete
Ktistec automatically converts @-mentions and #-hashtags into links,
and to encourage hands-on-the-keyboard composition, Ktistec supports
autocompletion.

### Custom emoji
Ktistec supports viewing custom emoji in posts and on actor
profiles. Custom emoji are federated from remote servers and
automatically rendered when present.
### Polls
Ktistec supports creating, viewing, and voting on polls. When
composing a post, you can add a poll with multiple options and set an
expiration time. Create polls to gather feedback from your
followers. When a post includes a poll, you can see the available
options and vote directly from your timeline. Poll results are updated
in real-time as votes are federated across the network. Poll authors
receive notifications when their polls expire.
### Control over comment visibility
Ktistec promotes healthy dialog and gives you comprehensive control
over content visibility.

Control which replies to your posts are public and visible to
anonymous users, and which are private.
Mark posts as sensitive using the content warning checkbox in the
editor. Sensitive posts are hidden behind a summary that readers can
click to reveal the content.
Send private messages directly to specific users with proper
visibility controls.
### Pretty URLs
Assign pretty (canonical) URLs to posts, both for SEO and as helpful
mnemonics for users (and yourself).
### Open Graph metadata
Ktistec includes Open Graph metadata support for actor and posts.
When you share a link to your profile or a post on social media
platforms, rich previews with images, titles, and descriptions are
automatically generated. Configure a site image in the site settings
to customize how your instance appears when shared.
### Followers/following
The Fediverse is a distributed social network. You can follow other
users on other servers from your timeline or by searching for them by
name. You can also approve or deny follow requests and manually
refresh an actor's profile directly from their profile page. Ktistec
is also compatible with the "remote follow" protocol used by Mastodon
and other servers.

### Content discovery
In addition to following other users, you can follow threads, hashtags
and even mentions. When posts arrive for content you follow, a
notification is added to your notifications. Because running a single
user instance can be lonely, Ktistec also proactively (and gently)
fetches relevant content from other servers.

To make navigation and discovery easier, post details pages now have
labels with links to internal hashtag and mention index pages.
### Content filtering
Content filters prevent undesirable content from appearing in your
timeline and notifications. Filter terms match on the text of a post
(ignoring any markup). Filters support wildcards.

### Blocking
Ktistec gives you control over what you see. Blocking authors removes
them and their posts from your timeline, and obscures their handle and
display name. Blocking posts removes individual posts. The user
interface makes it clear when content is unavailable because it has
been deleted or blocked.


### Bookmarks
Save posts for later with bookmarks. Bookmark any post to add it to
your personal bookmarks collection for easy access.
### Pinned posts
Pin important posts to the top of your profile. Pinned posts appear
prominently at the top of your profile page and are also exposed via
the Mastodon "featured posts" collection for compatibility with
Mastodon and other Fediverse servers.
### Quote posts
Ktistec supports consent-respecting quote posts (FEP-044f), allowing
you to quote another user's post in your own post. You can also
configure whether to automatically approve requests to quote your
posts or to require manual approval.
### RSS feeds
Ktistec provides RSS feeds for easy content syndication even without
an account in the Fediverse. RSS feeds are available on the *home
page* and the *account pages*.
### X-Ray Mode
X-Ray Mode is a developer and power-user tool for inspecting
ActivityPub JSON-LD representations of actors, objects, and other
content. Press `Ctrl+Shift+X` on any page to open X-Ray Mode:
- **Cached Version**: View the local JSON-LD representation stored in the Ktistec database
- **Remote Version**: Fetch and view the original JSON-LD representation from the source server
- **Navigation**: Click on any ActivityPub IRI to navigate to that object
- **History**: Use Alt+Left and Alt+Right to navigate through your viewing history
This feature is useful for debugging federation issues, understanding ActivityPub
structures, and verifying how content is stored and represented.
### Metrics
Ktistec tracks metrics about how the instance is performing. Metrics
include inbox and outbox activity, as well as memory usage; and the
machinery is in place to do much more.

### Tasks
View currently running tasks. Tasks, in Ktistec, are background jobs
that deliver content, fetch content, and perform other housekeeping
chores.

### Scripts
The Ktistec server will periodically and in sequential order run all
**executable** files in the `etc/scripts` directory.
When running scripts, the Ktistec server will set the following
variables in the child process's environment:
* API\_KEY - authenticates the script with the server
* KTISTEC\_HOST - the base URL of the server instance
* KTISTEC\_NAME - the name of the server instance
* USERNAME - the username of the account the script is run as
Output to `STDOUT` and `STDERR` will show up in the Ktistec server
logs. Output to `STDOUT` will be emitted as severity `INFO` and
output to `STDERR` will be emitted as severity `WARN`.
Scripts can use the [API](#api) to communicate with the Ktistec
server.
## Theming
Ktistec includes built-in theming support that allows for custom theme
creation. The theming system uses a hierarchy of CSS custom
properties and computed fallback values to provide consistent
styling across all elements with a minimum of effort.
Theme authors can customize at multiple levels:
### Level 1 - Base Colors
Define only base colors like `--text-primary`, `--bg-primary`,
`--bg-input`, `--semantic-primary`, etc. Variations are
automatically generated using `color-mix` formulas.
Example:
```css
:root { --semantic-primary: #7277cf; }
```
Result: `--bg-accent-code`, `--anchor-color`, etc. auto-generate using
`--semantic-primary` as the base color.
### Level 2 - Full Control
Define base colors and derived colors. Defined colors are used as
specified. The rest are automatically generated.
Example:
```css
:root {
--text-primary: #333;
--text-primary-2: #ff0000;
}
```
Result: `--text-primary-1/-3/-4` auto-generate; `--text-primary-2` is
red.
The full set of CSS custom color properties is found in
[variables.less](src/assets/css/themes/variables.less).
### CSS Classes and Data Attributes
Ktistec exposes rich metadata through CSS classes and data attributes
that you can target in your themes. In the descriptions below, **author**
refers to the original creator of the content, while **actor** refers
to the user performing the activity (e.g., the person who boosted the
post).
#### CSS Classes
##### ActivityPub Object Types
- `object-note`, `object-question`, etc.
##### ActivityPub Actor Types
- `actor-person`, `actor-group`, etc.
##### ActivityPub Activity Types
- `activity-create`, `activity-announce`, `activity-like`
##### Object States
- `is-draft` - Post is a draft
- `is-deleted` - Post or author is deleted
- `is-blocked` - Post or author is blocked
- `is-sensitive` - Post is marked sensitive
- `has-replies` - Post has at least one reply
- `has-media` - Post has media attachments
##### Visibility States
- `visibility-public` - Posted to public timeline
- `visibility-private` - Posted to followers only
- `visibility-direct` - Direct mention
##### Relationship States (when logged in)
- `author-followed-by-me` - You follow the original author
- `actor-followed-by-me` - You follow the announcer/liker
##### Mention States
- `mentions-me` - You are mentioned in this post
- `mentions-only-me` - You are the only actor mentioned
##### Visual States
- `highlighted` - Post is highlighted in the UI
- `depth-0`, `depth-1`, `depth-2`, etc. - Thread depth levels
#### Data Attributes
Data attributes allow targeting specific identities and content identifiers.
##### Author/Actor Identifiers
- `data-author-handle` - Author's full handle (e.g., `@alice@example.com`)
- `data-author-iri` - Author's full IRI (e.g., `https://example.com/actors/alice`)
- `data-actor-handle` - Actor's handle (when different from author, e.g., in boosts)
- `data-actor-iri` - Actor's IRI (when different from author)
##### Content Tags
- `data-followed-hashtags` - Space-separated hashtags you follow that appear in this post
- `data-followed-mentions` - Space-separated mentions you follow that appear in this post
- `data-hashtags` - Up to 10 space-separated hashtags that appear in the post
- `data-mentions` - Up to 10 space-separated mentions that appear in the post
##### Metadata
- `data-object-id` - Database ID of the object
#### Example Selectors
```css
/* Highlight posts from a specific author */
.ui.feed [data-author-handle="@alice@example.com"] {
border-left: 4px solid purple;
background: #f5f0ff;
}
/* Style posts about specific topics */
.ui.feed [data-followed-hashtags~="activitypub"] {
border-left-color: #6b4fbb;
}
/* Followed author shared by followed actor */
.ui.feed .activity-announce.author-followed-by-me.actor-followed-by-me {
background: linear-gradient(to right, #e8f5e9, #e1f5fe);
border-left: 4px solid #4caf50;
border-top: 3px solid #00bcd4;
}
/* Polls from groups */
.ui.feed .object-question.actor-group {
border-left: 5px solid #ff9800;
}
/* Direct messages from followed authors */
.ui.feed .visibility-direct.author-followed-by-me {
border: 3px solid #4caf50;
}
```
### Custom Themes
You can create custom themes by adding CSS and/or JavaScript files to
the `public/themes/` directory. These files are automatically added to
every page's HTML `head` element.
#### Creating a Custom Theme
1. Add `.css` and `.js` files to `public/themes/`
2. Use CSS custom properties to override theme variables
3. Restart the server—theme files are discovered at startup
A simple custom theme that supports both light and dark modes:
```css
:root {
--text-primary: #2c3e50;
--bg-primary: #ecf0f1;
--semantic-primary: #e74c3c;
--anchor-color: #e67e22;
}
@media (prefers-color-scheme: dark) {
:root {
--text-primary: #ecf0f1;
--bg-primary: #2c3e50;
--semantic-primary: #e74c3c;
--anchor-color: #f39c12;
}
}
```
### Light/Dark Mode Support
If themes are properly designed, Ktistec automatically adapts to your
browser's light/dark mode preference.
## API
Given a valid `API_KEY`, scripts and external tools have full access
to the Ktistec server's API. This allows them to automate actions on
the server such as posting, following, sharing, liking, etc.
The table below contains a list of supported endpoints:
| Method | Path | Notes |
|-|-|-|
| GET | /actors/:username/inbox | Retrieves a page of `activities` in your inbox as an ActivityPub collection. |
| GET | /actors/:username/outbox | Retrieves a page of `activities` in your outbox as an ActivityPub collection. |
| POST | /actors/:username/outbox | Puts an `activity` in your outbox for delivery. |
| GET | /actors/:username/posts | Retrieves a page of `objects` you've published. |
| GET | /actors/:username/drafts | Retrieves a page of `objects` you've saved as drafts. |
| GET | /actors/:username/followers | Retrieves a page of `actors` following you. |
| GET | /actors/:username/following | Retrieves a page of `actors` you're following. |
| GET | /admin/accounts | Retrieves all user accounts. |
| GET | /admin/accounts/new | Gets a representation of an account. |
| POST | /admin/accounts | Creates a new user account. |
| POST | /settings/actor | Updates account settings for the authenticated user. |
| GET | /lookup/activity?iri=:iri | Looks up the `activity` in the server cache identified by `iri`. |
| GET | /lookup/actor?iri=:iri | Looks up the `actor` in the server cache identified by `iri`. |
| GET | /lookup/object?iri=:iri | Looks up the `object` in the server cache identified by `iri`. |
| GET | /sessions | Gets a representation of an authentication attempt with unset `username` and `password`. |
| POST | /sessions | Authenticates using the supplied `username` and `password`. |
| DELETE | /sessions | Destroys the current session. |
The `/sessions` endpoint is useful when doing script development
outside of the server environment. The scripts the Ktistec server
runs directly do not need to create their own session -- the supplied
`API_KEY` is sufficient.
### Initial Server Configuration
You can configure a new Ktistec server entirely via the API.
#### Configure Server Settings
First, configure the server host name and site name (no authentication
required):
```bash
curl -s \
-X POST \
-H "Content-Type: application/json" \
-d '{"host":"https://your-domain.com","site":"Your Site Name"}' \
"$KTISTEC_HOST/"
```
#### Create Primary User Account
Next, create the primary user account (again, no authentication
required).
| Name | Notes |
|-|-|
| username | The username for the primary account. |
| password | The password for the primary account. |
| name | Optional. Display name for the account. |
| summary | Optional. Biography/description for the account. |
| language | IETF BCP 47 language tag (e.g., "en", "en-US"). |
| timezone | IANA timezone (e.g., "UTC", "America/New_York"). |
| auto_approve_followers | Optional. When `true`, follow requests are automatically approved. Defaults to `false`. |
| auto_follow_back | Optional. When `true`, automatically follows back accounts that follow you. Defaults to `false`. |
Example:
```bash
curl -s \
-X POST \
-H "Content-Type: application/json" \
-d '{"username":"your_username","password":"YourSecurePassword123@","name":"Your Display Name","summary":"Your summary","language":"en","timezone":"UTC","auto_approve_followers":true,"auto_follow_back":false}' \
"$KTISTEC_HOST/"
```
### Admin Account Management
#### List All Accounts
Retrieve all user accounts:
```bash
curl -s \
-X GET \
-H "Authorization: Bearer $API_KEY" \
-H "Accept: application/json" \
"$KTISTEC_HOST/admin/accounts"
```
#### Create New Account
Create a new user account. Supports the same parameters as creating the primary account.
Example:
```bash
curl -s \
-X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"username":"another_user","password":"AnotherPassword123@","name":"Another User","summary":"Another user account","language":"en","timezone":"UTC","auto_approve_followers":false,"auto_follow_back":true}' \
"$KTISTEC_HOST/admin/accounts"
```
### Account Settings
#### Update Actor Account Settings
Update account settings for the authenticated user. Requires authentication.
| Name | Notes |
|-|-|
| name | Optional. Display name for the account. |
| summary | Optional. Biography/description for the account. |
| language | Optional. IETF BCP 47 language tag (e.g., "en", "en-US"). |
| timezone | Optional. IANA timezone (e.g., "UTC", "America/New_York"). |
| password | Optional. New password for the account. |
| auto_approve_followers | Optional. When `true`, follow requests are automatically approved. When `false`, requests require manual approval. |
| auto_follow_back | Optional. When `true`, automatically follows back accounts that follow you. |
| image | Optional. Full URI of the account's profile image. |
| icon | Optional. Full URI of the account's avatar icon. |
| attachments | Optional. Array of attachment objects. |
Example:
```bash
curl -s \
-X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Updated Display Name","summary":"Updated summary","language":"en","timezone":"UTC"}' \
"$KTISTEC_HOST/settings/actor"
```
### A Note on ActivityPub
At its core, Ktistec is an ActivityPub server and some things about
its API make more sense if you understand a little bit about
ActivityPub.
ActivityPub is a protocol for sending and receiving messages.
Entities called `actors` send and receive `activities`. `Activities`
represent the various social actions that define interaction on the
Fediverse: posting, sharing and liking posts, following other actors,
etc.
Every `actor` has an `inbox` for receiving `activities` from the
`actors` they follow, and an `outbox` for sending `activities` to the
`actors` that follow them.
A Fediverse "post", "status", "note", "toot", etc. are all names for
an ActivityPub `object`. ActivityPub `activities` are typically about
`objects`.
ActivityPub `actors`, `activities`, and `objects` are described using
a flavor of JSON known as JSON-LD. Every `actor`, `activity`, and
`object` is identified by a unique `IRI`.
Ktistec allows a JSON shorthand (described below) to be used when
publishing, which greatly simplifies the development of scripts.
* [ActivityPub W3C Recommendation](https://www.w3.org/TR/activitypub/)
### Publishing an Object
To publish an `object`, `POST` a JSON `activity` to the `outbox`
endpoint (examples assume scripts use `curl`). The JSON `activity` may
include the following fields:
| Name | Notes |
|-|-|
| type | Must be "Publish". |
| content | An HTML formatted string. |
| name | Optional. A plain text string often displayed as the title of a post. |
| summary | Optional. A plain text string often used as a summary of a longer post. |
| object | Optional. The IRI of the `object` being updated (instead of created). |
| in-reply-to | Optional. The IRI of the `object` being replied to. |
| to | Optional. A comma-separate list of `actors` to address. Specified as IRIs. |
| cc | Optional. A comma-separate list of `actors` to CC. Specified as IRIs. |
| audience | Optional. A comma-separate list of `actors` in the audience. Specified as IRIs. |
| visibility | Optional. Controls post visibility. Values: "public", "private", "direct". |
| sensitive | Optional. May be `true` or `false`. Marks content as sensitive. |
By default, `cc` includes the publishing `actor`'s followers collection.
Example:
```bash
curl -s \
-X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"Publish","content":"this is a test","visibility":"public"}' \
"$KTISTEC_HOST/actors/$USERNAME/outbox"
```
Note: "publishing" an `object` encompasses both creating a new
`object` (an ActivityPub `Create` activity) and updating an existing
`object` (an ActivityPub `Update` activity).
### Sharing an Object (ActivityPub `Announce`)
To share an `object`, `POST` a JSON `activity` to the `outbox`
endpoint. The JSON `activity` may include the following fields:
| Name | Notes |
|-|-|
| type | Must be "Announce". |
| object | The IRI of the `object` being shared. |
| to | Optional. A comma-separate list of `actors` to address. Specified as IRIs. |
| cc | Optional. A comma-separate list of `actors` to CC. Specified as IRIs. |
| audience | Optional. A comma-separate list of `actors` in the audience. Specified as IRIs. |
| visibility | Optional. Controls post visibility. Values: "public", "private", "direct". |
By default, `to` includes the actor to which the `object` is
attributed, and `cc` includes the publishing `actor`'s followers
collection.
Example:
```bash
curl -s \
-X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"Announce","object":"https://example.com/objects/123","visibility":"public"}' \
"$KTISTEC_HOST/actors/$USERNAME/outbox"
```
### Liking an Object
To like an `object`, `POST` a JSON `activity` to the `outbox`
endpoint. The JSON `activity` may include the following fields:
| Name | Notes |
|-|-|
| type | Must be "Like". |
| object | The IRI of the `object` being liked. |
| to | Optional. A comma-separate list of `actors` to address. Specified as IRIs. |
| cc | Optional. A comma-separate list of `actors` to CC. Specified as IRIs. |
| audience | Optional. A comma-separate list of `actors` in the audience. Specified as IRIs. |
| visibility | Optional. Controls post visibility. Values: "public", "private", "direct". |
By default, `to` includes the actor to which the `object` is
attributed, and `cc` includes the publishing `actor`'s followers
collection.
Example:
```bash
curl -s \
-X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"Like","object":"https://example.com/objects/123","visibility":"public"}' \
"$KTISTEC_HOST/actors/$USERNAME/outbox"
```
### Disliking an Object
To dislike an `object`, `POST` a JSON `activity` to the `outbox`
endpoint. The JSON `activity` may include the following fields:
| Name | Notes |
|-|-|
| type | Must be "Dislike". |
| object | The IRI of the `object` being disliked. |
| to | Optional. A comma-separate list of `actors` to address. Specified as IRIs. |
| cc | Optional. A comma-separate list of `actors` to CC. Specified as IRIs. |
| audience | Optional. A comma-separate list of `actors` in the audience. Specified as IRIs. |
| visibility | Optional. Controls post visibility. Values: "public", "private", "direct". |
By default, `to` includes the actor to which the `object` is
attributed, and `cc` includes the publishing `actor`'s followers
collection.
Example:
```bash
curl -s \
-X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"Dislike","object":"https://example.com/objects/123","visibility":"public"}' \
"$KTISTEC_HOST/actors/$USERNAME/outbox"
```
### Following an Actor
To follow an actor, `POST` a JSON `activity` to the `outbox`
endpoint. The JSON `activity` may include the following fields:
| Name | Notes |
|-|-|
| type | Must be "Follow". |
| object | The IRI of the `actor` being followed. |
Example:
```bash
curl -s \
-X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"Follow","object":"https://example.com/actors/alice"}' \
"$KTISTEC_HOST/actors/$USERNAME/outbox"
```
### Undoing an Activity
You can undo a previously published `activity` by `POST`ing an `Undo`
`activity` to the `outbox` endpoint. The JSON `activity` may include
the following fields:
| Name | Notes |
|-|-|
| type | Must be "Undo". |
| object | The IRI of the `activity` being undone. |
Example:
```bash
curl -s \
-X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"Undo","object":"https://example.com/activities/123"}' \
"$KTISTEC_HOST/actors/$USERNAME/outbox"
```
### Deleting
You can delete an `actor` or `object` by `POST`ing a `Delete`
`activity` to the `outbox` endpoint. The JSON `activity` may include
the following fields:
| Name | Notes |
|-|-|
| type | Must be "Delete". |
| object | The IRI of the `actor` or `object` being deleted. |
Example:
```bash
curl -s \
-X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"Delete","object":"https://example.com/123"}' \
"$KTISTEC_HOST/actors/$USERNAME/outbox"
```
## MCP Support
Ktistec provides Model Context Protocol (MCP) server support for
integrating Ktistec with MCP clients. Compatible clients should "just
work" with Ktistec.
**Important:** *Content generation is not currently supported.* There
are technical issues to resolve, but for the most part I currently
prioritize accessibility and analytics use cases.
### OAuth
OAuth2 authentication is required to access the MCP server from a
compatible MCP client.
### Prompts
#### server_info
Demonstrates how to use the MCP server to retrieve and present
detailed information about the Ktistec server.
#### whats_new
Demonstrates how to use the MCP server to retrieve and summarize
recent posts and social activity.
### Resources
#### ktistec://information
Ktistec server instance information including version, collections
supported, statistics, etc.
#### ktistec://users/{id}
User account information for registered users. Returns account details
including username, language, timezone, and related actor information.
Supports a single ID (`ktistec://users/123`).
#### ktistec://actors/{id*}
ActivityPub actor profiles including name, summary, icon, image,
attachments/metadata, and URLs. Supports single ID (`ktistec://actors/123`) or
comma-separated IDs for batch retrieval (`ktistec://actors/123,456,789`).
#### ktistec://objects/{id*}
ActivityPub objects (posts) including name, summary, content,
attachments, and relationships. Supports single ID (`ktistec://objects/123`) or
comma-separated IDs for batch retrieval (`ktistec://objects/123,456,789`).
### Tools
#### count\_collection\_since(name, since)
Count items in ActivityPub collections since a given time. Use this
tool when you want to know if new items have been added in the last
day/week/month.
**Parameters:**
- `name` (string, required) - Name of the collection to count.
- `since` (string, required) - RFC3339 timestamp to count from (e.g., `2024-01-01T00:00:00Z`)
#### paginate\_collection(name, page, size)
Paginate through collections of ActivityPub items. Use this tool when
you want to retrieve the items in a collection.
**Parameters:**
- `name` (string, required) - Name of the collection to paginate
- `page` (integer, optional) - Page number (defaults to 1, minimum 1)
- `size` (integer, optional) - Number of items per page (defaults to 10, minimum 1, maximum 20)
#### read\_resources(uris)
Read one or more resources by URI. Supports all resource types
including templated resources (actors, objects) and static resources
(information, users). Supports batched reads with comma-separated
IDs.
Use this tool as a universal fallback when resources are not supported
by an MCP client.
**Parameters:**
- `uris` (array, required) - Resource URIs to read (e.g., `['ktistec://actors/123,456', 'ktistec://objects/456,789']`)
#### analyze\_thread(object\_id)
Analyze a conversation thread to understand its structure and activity.
Returns key participants, timeline histogram, and notable branches.
**Parameters:**
- `object_id` (integer, required) - Database ID of any object in the thread
#### get\_thread(object\_id, projection, page\_size) or get\_thread(cursor)
Retrieve thread structure, metadata, and summary data for a
conversation thread with pagination support. Supports two modes of
operation: initial query mode and pagination mode.
**Initial query mode** Return summary data and the first page of
objects. If there are more pages, the results include a `cursor`.
**Pagination mode**: Provide only the `cursor` to fetch the next page.
Returns the next page of objects and a new `cursor` if more pages
remain.
**Parameters:**
- `object_id` (integer) - Database ID of any object in the thread
- `projection` (string) - Data fields to include: 'minimal' (IDs and structure only) or 'metadata' (adds authors, timestamps)
- `page_size` (integer) - Number of objects per page
- `cursor` (string) - Opaque pagination cursor
### Supported Collections
- Standard: `timeline`, `notifications`, `posts`, `drafts`, `likes`, `announces`, `bookmarks`, `pins`, `followers`, `following`
- Dynamic: `hashtag#` (e.g., `hashtag#technology`), `mention@` (e.g., `mention@euripides`)
### Compatibility
The Ktistec MCP server works well with Claude Desktop and Claude
Code. I have also tested it with Gemini CLI and Cursor. It also works
with open source tools like Goose and Ollama.
## Prerequisites
To run an instance of Ktistec as part of the Fediverse, you'll need a
server with a fixed hostname. In the Fediverse, users are identified
by (and content is addressed to) a username and a hostname.
## Building
You must compile the Ktistec server executable from its source code.
You will need to install a recent release of the [Crystal programming
language](https://crystal-lang.org/install/). Ktistec requires at least
SQLite3 version 3.35.0 (but see notes on [Sqlite3 compatibility](#sqlite3-compatibility)).
To obtain the source code, clone the [Ktistec Github
repo](https://github.com/toddsundsted/ktistec).
If you intend to do *development* on the server, check out the
**main** branch. In addition to Crystal, you'll also need Node.js and
Webpack to build the JS and CSS assets from source.
If you just want to build and run the server, check out the **dist**
branch.
To compile the server:
`$ crystal build src/ktistec/server.cr`
If you're developing on the **main** branch, build the assets next
(skip this step if you're on the **dist** branch--the latest JS and
CSS assets are already built for you):
`$ npm run build`
Run the compiled executable:
`$ LOG_LEVEL=INFO ./server`
The first time the server runs it will run the database migrations
necessary to create the database. This should only take a few seconds,
maximum. When the server is ready to accept connections you will see
something like:
`Ktistec is ready to lead at http://0.0.0.0:3000`
You can now connect to and configure the server.
### SQLite3 Compatibility
The following SQLite3 versions are known to have bugs that cause
problems for Ktistec:
| SQLite Version | Issue |
|--|--|
| 3.39.x | problems with bloom filters and recursive queries [link](https://sqlite.org/forum/forumpost/56de336385) |
| 3.40.x | problems with bloom filters and recursive queries [link](https://sqlite.org/forum/forumpost/56de336385) |
### Running Tests
If you change the code, you should run the tests:
`$ crystal spec`
## Setup, Configuration, and Usage
The server runs on port 3000. If you're planning on running it in
production, you should put Nginx or Apache in front of it. Don't
forget an SSL certificate!
When you run Ktistec for the first time, you'll need to name the
server and create the primary user.
Ktistec needs to know the *host name* of its home on the internet.
The server host name is a part of every users's identity in the
Fediverse. My identity is "toddsundsted@epiktistes.com". The host name
is "epiktistes.com" and other federated servers, and users, know to
send posts and other content to me there.
Give the server a *site name*, too.

After you name the server, you create the primary user account. The
primary user can create additional user accounts via the admin
interface at `/admin/accounts`. All additional users are also
administrators.
At a minimum, you need to specify the user's *username*, *password*,
*language*, and *timezone*. You can use a single character for the
*username* if you want, but you'll need six characters, including
letters, numbers and symbols, for the *password* (but more than six
characters is better). *language* is any valid IETF BCP 47 language
tag (e.g. "en-US" or "en"). *timezone* is any valid IANA time zone
database string (e.g. "America/New_York").
You can select an ActivityPub actor type. "Person" is the
default. Other options are "Application", "Group", "Organization",
and "Service".
*Display name* and *summary* are optional.

Once these steps are done, you're running!

### Command Line Options
The Ktistec server supports several command line options:
#### `--full-garbage-collection-on-startup`
**⚠️ WARNING: This operation can take significant time and delete large amounts of data.**
Garbage collection removes old ActivityPub objects that are not
connected to your user through:
- **Attribution**: Objects attributed to you or actors you follow
- **Activities**: Objects referenced by your activities or activities of actors you follow
- **Collections**: Objects in your timeline, notifications, or outbox
- **Content**: Objects with hashtags, mentions, or in threads you follow
Note that garbage collection preserves:
- Complete threads if any object in a thread is preserved
- Objects newer than the configured age limit (default: 365 days)
It also:
- Performs database optimization (VACUUM and PRAGMA optimize) after cleanup
- May take minutes to hours, depending on database size
- Should not be interrupted once started
**Consider backing up your database before using this option!**
### User Settings
Ktistec provides comprehensive user/account settings that you can
access through the web interface. Navigate to the settings page to
configure your settings.
**Available Settings:**
- **Display Name**: Your public display name shown on your profile
- **Summary**: Your biography or profile description
- **Language**: IETF BCP 47 language tag (e.g., "en-US", "de", "ja") used for your posts and UI preferences
- **Timezone**: IANA timezone (e.g., "America/New_York", "UTC") for displaying timestamps and aggregating metrics
- **Password**: When supplied, updates your account password
- **Auto-approve Followers**: When enabled, follow requests are automatically approved without requiring manual review
- **Auto-follow Back**: When enabled, automatically follows back actors that follow you
- **Manually Approve Quote Requests**: When enabled, quote requests require your approval before approval to quote to granted
- **Default Editor**: Choose between the rich text editor or Markdown editor for composing posts
- **Background Image**: Your profile banner/background image
- **Profile Image**: Your profile avatar icon
- **Metadata**: Up to four custom profile metadata fields with name/value pairs
These settings can also be configured via the API (see [Account Settings](#account-settings)
in the API documentation).
### Site Settings
Site settings control server-wide configuration. You can access these
from the settings page.
**Available Settings:**
- **Site Name**: The name of your Ktistec instance
- **Site Image**: An image used for Open Graph metadata when sharing your instance on social media
- **Description**: A customizable description displayed on your home page
- **Footer**: A customizable footer displayed at the bottom of every page
- **Translator Service**: Choose translation service (DeepL or LibreTranslate) if API keys are configured
- **Translator Service URL**: API endpoint for the selected translation service
#### Site Description
Ktistec supports a customizable site description on your home
page.

Edit your site description using the same rich text editor you use for
authoring posts:
1. Visit Site Settings on the settings page
2. Use the rich text editor to change the site description
3. Press Update
#### Translation
First, ensure you've set your language in Account Settings on
the settings page. Your language must be a valid [IETF BCP 47
language tag](https://en.wikipedia.org/wiki/IETF_language_tag) like
"en-US" for US English, "de" for German (Deutsch), or "ja" for
Japanese (日本語).
In order to enable translation, you need an *API key* for either
[DeepL](https://www.deepl.com/) or [LibreTranslate](https://libretranslate.com/).
These are the only services Ktistec supports at this time.
Depending on the service you use, you need to set either the
environment variable `DEEPL_API_KEY` or `LIBRETRANSLATE_API_KEY`
to the API key and restart Ktistec.
Once the API key is set correctly, Site Settings on the settings page
will show an input for the translator service API endpoint. Currently,
supported values are:
https://api.deepl.com/v2/translate (for the DeepL paid service) or
https://api-free.deepl.com/v2/translate (for the DeepL free service), or
https://libretranslate.com/translate (for the LibreTranslate paid
service).

*If you change the service API or change the service you are using, you
will need to restart Ktistec.*
Posts from properly configured accounts on supported servers, like
Mastodon, include the language the content is written in. On these
posts, Ktistec will display a button to translate the content if the
language differs from your default language.
A few caveats:
* Some ActivityPub servers don't explicitly support language.
* Some users don't correctly set their posts' language.
TL;DR It's a big Fediverse so translation won't always work.
## Contributors
- [Todd Sundsted](https://github.com/toddsundsted) - creator and maintainer
## Copyright and License
Ktistec ActivityPub Server
Copyright (C) 2021, 2022, 2023, 2024, 2025, 2026 Todd Sundsted
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public
License along with this program. If not, see
(https://www.gnu.org/licenses/).
### LibreJS Compliance
Ktistec is compliant with GNU LibreJS. All JavaScript bundled with
Ktistec is free software. A complete list of JavaScript and Crystal
dependencies with their licenses is available at `/license` on any
running Ktistec instance.