Skip to content

Data Models

All objects returned by the API are Pydantic BaseModel instances. Fields use attribute access (status.id) rather than dict-style access (status["id"]).

Every field is optional (None by default) except where noted, because server implementations (Mastodon, Pleroma, GotoSocial) vary in what they return. Guard optional fields with is not None before use.

Unknown fields from the server response are silently accepted and accessible via model.model_extra. This is a deliberate design decision: Mastodon-compatible servers (Pleroma, GotoSocial, Akkoma, Pixelfed, and others) frequently return fields that are not part of the Mastodon spec. Accepting extras prevents ValidationError on valid server responses and keeps the library forward-compatible as the API evolves. Extra fields cannot shadow declared fields (Pydantic v2 guarantees declared fields take precedence).

Raw HTML fields

The fields Status.content, Account.note, Card.html, and Field.value contain raw, server-provided HTML. Sanitise them with a library such as nh3 or bleach before rendering in any web or UI context. Card.html is particularly sensitive as it is intended for iframe/embed rendering.

Import

from longwei import Account, Status, Conversation, Marker
from longwei import EmojiReaction
from longwei.models import Chat, ChatMessage

Nested types are not exported from the top-level package but can be imported from longwei.models if needed for type annotations:

from longwei.models import (
    Emoji, Field, Mention, Tag, Application,
    MediaAttachment, Card, PollOption, Poll,
)

Status

A status (post/toot) returned by timeline, search, or posting methods.

from longwei import Status
Field Type Description
id str \| None The status ID
created_at str \| None ISO 8601 creation timestamp (format varies by server)
in_reply_to_id str \| None ID of the status being replied to
in_reply_to_account_id str \| None ID of the account being replied to
sensitive bool \| None Whether the content is marked sensitive
spoiler_text str \| None Content warning text shown before expanding
visibility str \| None "public", "unlisted", "private", or "direct"
language str \| None ISO 639-1 language code
uri str \| None Canonical URI of the status
url str \| None URL of the status HTML page
replies_count int \| None Number of replies
reblogs_count int \| None Number of reblogs/boosts
favourites_count int \| None Number of favourites/likes
favourited bool \| None Whether the current user has favourited this status
reblogged bool \| None Whether the current user has reblogged this status
muted bool \| None Whether the current user has muted this conversation
bookmarked bool \| None Whether the current user has bookmarked this status
pinned bool \| None Whether this status is pinned to the account profile
text str \| None Plain-text source (when available; typically on own statuses)
content str \| None HTML body of the status. Raw HTML — sanitise before rendering.
reblog Status \| None The original status if this is a reblog/boost
application Application \| None App that posted this status
account Account \| None Account that posted this status
media_attachments list[MediaAttachment] \| None Attached media files
mentions list[Mention] \| None Accounts mentioned in this status
tags list[Tag] \| None Hashtags used in this status
emojis list[Emoji] \| None Custom emojis used in this status
card Card \| None Link preview card
poll Poll \| None Attached poll

Example

timeline = await ap.get_public_timeline(limit=5)
for status in timeline:
    author = status.account.username if status.account else "unknown"
    content = (status.content or "")[:80]
    print(f"@{author}: {content}")

    if status.media_attachments:
        for media in status.media_attachments:
            print(f"  Media: {media.type}{media.url}")

    if status.poll:
        for option in status.poll.options:
            print(f"  Poll option: {option.title} ({option.votes_count} votes)")

Account

A user profile on an ActivityPub-compatible server.

from longwei import Account
Field Type Description
id str \| None The account ID
username str \| None Account username (local part only, no domain)
acct str \| None username@domain for remote; username for local accounts
display_name str \| None Display name
note str \| None Profile bio. Raw HTML — sanitise before rendering.
url str \| None URL of the account's profile page
avatar str \| None URL of the avatar image
avatar_static str \| None URL of a static (non-animated) avatar
header str \| None URL of the header/banner image
header_static str \| None URL of a static (non-animated) header
locked bool \| None Whether the account requires follow approval
bot bool \| None Whether the account is a bot
created_at str \| None ISO 8601 account creation timestamp
followers_count int \| None Number of followers
following_count int \| None Number of accounts followed
statuses_count int \| None Number of statuses posted
last_status_at str \| None Date of most recent status
emojis list[Emoji] \| None Custom emojis used in display name or bio
fields list[Field] \| None Profile metadata fields

Example

account = await ap.verify_credentials()
print(f"@{account.acct} ({account.display_name})")
print(f"Followers: {account.followers_count}")
if account.fields:
    for field in account.fields:
        print(f"  {field.name}: {field.value}")

Nested types

MediaAttachment

A media file attached to a status.

Field Type Description
id str \| None Attachment ID (used in media_ids when posting)
type str \| None "image", "video", "gifv", "audio", "unknown"
url str \| None URL of the media file
preview_url str \| None URL of a preview/thumbnail
remote_url str \| None URL of the original remote media (for remote statuses)
description str \| None Alt-text description
blurhash str \| None BlurHash string for placeholder display

Poll and PollOption

Poll is attached to a status when the status includes a poll.

Field Type Description
id str \| None Poll ID
expires_at str \| None ISO 8601 timestamp when the poll closes
expired bool \| None Whether the poll has closed
multiple bool \| None Whether multiple choices are allowed
votes_count int \| None Total votes cast
voters_count int \| None Unique accounts that voted
options list[PollOption] Poll options (defaults to [])
voted bool \| None Whether the current user has voted

Each PollOption has:

Field Type Description
title str \| None Option text
votes_count int \| None Votes for this option (None if results are hidden)

Card

A rich-preview card for a link embedded in a status.

Field Type Description
url str \| None URL of the linked resource
title str \| None Title of the linked page
description str \| None Description of the linked page
type str \| None "link", "photo", "video", "rich"
author_name str \| None Author of the linked page
author_url str \| None Author URL
provider_name str \| None Provider of the linked page
provider_url str \| None Provider URL
html str \| None Embed HTML (for video and rich types). Raw HTML — sanitise before rendering.
width int \| None Embed width in pixels
height int \| None Embed height in pixels
image str \| None Preview thumbnail URL

Mention

A user mentioned within a status.

Field Type Description
id str \| None Account ID of the mentioned user
username str \| None Username of the mentioned user
acct str \| None Webfinger acct: of the mentioned user
url str \| None Profile URL of the mentioned user

Tag

A hashtag used in a status.

Field Type Description
name str \| None Hashtag text (without the leading #)
url str \| None URL of the tag's timeline page

FeaturedTag

A hashtag featured on an account's profile. Returned by get_account_featured_tags().

from longwei import FeaturedTag
Field Type Description
id str \| None Internal ID of the featured tag
name str \| None Hashtag name (without the leading #)
url str \| None URL of the account's tagged statuses page
statuses_count int \| None Number of statuses with this tag by the account
last_status_at str \| None Date of the most recent status using this tag (YYYY-MM-DD)

Emoji

A custom emoji used in a status or account display name.

Field Type Description
shortcode str \| None Shortcode, e.g. "blobcat"
url str \| None URL to the full emoji image
static_url str \| None URL to a static (non-animated) version
visible_in_picker bool \| None Whether shown in the emoji picker

Field

A profile metadata field (name/value pair) on an account.

Field Type Description
name str \| None Field label
value str \| None Field content. May be raw HTML — sanitise before rendering.
verified_at str \| None ISO 8601 timestamp if the URL was verified; None otherwise

Application

The application that created a status.

Field Type Description
name str \| None Application name
website str \| None Application website URL

Conversation

A direct-message conversation thread returned by get_conversations() and read_conversation().

from longwei import Conversation
Field Type Description
id str \| None The conversation ID
unread bool \| None Whether the conversation has unread messages
accounts list[Account] Accounts participating in the conversation
last_status Status \| None The most recent status in the conversation

Example

conversations = await ap.get_conversations()
for conv in conversations:
    if conv.unread:
        participants = ", ".join(a.acct for a in conv.accounts if a.acct)
        print(f"Unread DM with {participants}")
        if conv.last_status:
            print(f"  Last message: {conv.last_status.content}")
        conv = await ap.read_conversation(conv.id)

Marker

A saved timeline position marker returned by get_markers() and save_markers().

from longwei import Marker
Field Type Description
last_read_id str \| None ID of the last status read in the timeline
version int \| None Monotonically-incrementing version counter
updated_at str \| None ISO 8601 timestamp of when the marker was last updated

Example

markers = await ap.get_markers(timelines=["home", "notifications"])
if "home" in markers:
    print(f"Last read home status: {markers['home'].last_read_id}")

# Save position after reading
await ap.save_markers(home_last_read_id=statuses[-1].id)

UserList

A user-defined list for organising followed accounts (Mastodon only).

from longwei import UserList
Field Type Description
id str \| None The list ID
title str \| None The user-defined title of the list
replies_policy str \| None Which replies to show in the list timeline: "followed", "list", or "none"
exclusive bool \| None If True, listed accounts are excluded from the home timeline (Mastodon 4.2+)

Example

lists = await ap.get_lists()
for lst in lists:
    print(f"{lst.id}: {lst.title} (replies: {lst.replies_policy})")

SearchResults

Returned by search().

Field Type Description
accounts list[Account] Matching accounts
statuses list[Status] Matching statuses
hashtags list[Tag] Matching hashtags
from longwei import SearchResults

results = await ap.search(query="cats", query_type=SearchType.STATUSES)
for status in results.statuses:
    print(status.id, status.content)

Relationship

Returned by block_account(), follow_account(), and other account-relationship operations.

Field Type Description
id str \| None The account ID
following bool \| None Whether you are following this account
showing_reblogs bool \| None Whether boosts from this account appear in your home timeline
notifying bool \| None Whether you receive notifications for this account's posts
followed_by bool \| None Whether this account follows you
blocking bool \| None Whether you have blocked this account
blocked_by bool \| None Whether this account has blocked you
muting bool \| None Whether you have muted this account
muting_notifications bool \| None Whether notifications from this account are also muted
requested bool \| None Whether you have sent a follow request
requested_by bool \| None Whether this account has sent you a follow request
domain_blocking bool \| None Whether you are blocking this account's domain
endorsed bool \| None Whether you have endorsed (featured) this account
note str \| None Your personal note about this account

FamiliarFollowers

Returned by get_familiar_followers(). Each entry corresponds to one of the requested target account IDs.

from longwei import FamiliarFollowers
Field Type Description
id str \| None The target account ID
accounts list[Account] Accounts that follow both you and the target

Filter

Returned by get_filters(), create_filter(), get_filter(), and update_filter().

GotoSocial uses v1 filters only.

Field Type Description
id str \| None The filter ID
phrase str \| None The keyword or phrase being filtered
context list[str] \| None Contexts where the filter applies (e.g. ["home", "notifications"])
expires_at str \| None ISO 8601 expiry timestamp, or None if permanent
irreversible bool \| None Whether matching statuses are permanently dropped rather than collapsed
whole_word bool \| None Whether the filter matches whole words only

FilterV2

A v2 filter grouping keywords and/or statuses under a named rule. Returned by get_filters_v2(), create_filter_v2(), get_filter_v2(), and update_filter_v2().

Mastodon only. Pleroma/Akkoma and GotoSocial do not support v2 filters.

from longwei import FilterV2
Field Type Description
id str \| None The filter ID
title str \| None User-defined name of the filter
context list[str] \| None Contexts where the filter applies (e.g. ["home", "notifications"])
expires_at str \| None ISO 8601 expiry timestamp, or None if permanent
filter_action str \| None "warn" (collapse) or "hide" (drop matching content)
keywords list[FilterKeyword] Keyword entries that trigger this filter (defaults to [])
statuses list[FilterStatus] Specific statuses that trigger this filter (defaults to [])

Example

filters = await ap.get_filters_v2()
for f in filters:
    print(f"{f.title}: action={f.filter_action}, keywords={[kw.keyword for kw in f.keywords]}")

FilterKeyword

A keyword entry within a v2 filter. Returned (in lists) by get_filter_keywords() and add_filter_keyword(). Also embedded in FilterV2.keywords.

from longwei import FilterKeyword
Field Type Description
id str \| None The keyword entry ID
keyword str \| None The keyword or phrase being filtered
whole_word bool \| None Whether the filter matches whole words only

FilterStatus

A specific status attached to a v2 filter. Returned (in lists) by get_filter_statuses() and add_filter_status(). Also embedded in FilterV2.statuses.

from longwei import FilterStatus
Field Type Description
id str \| None The filter-status entry ID
status_id str \| None The ID of the status being filtered

Report

Returned by get_reports().

Field Type Description
id str \| None The report ID
action_taken bool \| None Whether a moderator has acted on the report
action_taken_at str \| None ISO 8601 timestamp when action was taken
category str \| None Report category (e.g. "spam", "violation")
comment str \| None Additional context provided when filing
forwarded bool \| None Whether the report was forwarded to the remote instance
created_at str \| None ISO 8601 timestamp when filed
status_ids list[str] \| None IDs of statuses included in the report
rule_ids list[str] \| None IDs of instance rules cited
target_account Account \| None The account the report is against

ScheduledStatus

Returned by get_scheduled_statuses(), get_scheduled_status(), and update_scheduled_status().

from longwei import ScheduledStatus
Field Type Description
id str \| None The scheduled status ID
scheduled_at str \| None ISO 8601 timestamp when the status will be posted
params ScheduledStatusParams \| None The status content and settings
media_attachments list[MediaAttachment] Media files that will be attached

ScheduledStatusParams fields (accessible as s.params):

Field Type Description
text str \| None Plain-text content of the status
visibility str \| None Visibility level
sensitive bool \| None Whether the status is marked sensitive
spoiler_text str \| None Content warning text
in_reply_to_id str \| None ID of the status being replied to
media_ids list[str] \| None IDs of attached media
application_id int \| None ID of the application that created the status
idempotency str \| None Idempotency key used when posting
with_rate_limit bool \| None Whether rate limiting applies

InstanceInfo

Returned by get_instance_info().

Field Type Description
domain str \| None The instance domain
title str \| None The instance title
version str \| None Software version string
source_url str \| None URL to the instance's source code repository
description str \| None Short description of the instance
usage dict \| None Usage statistics (e.g. monthly active users)
thumbnail dict \| None Thumbnail image metadata
languages list[str] \| None ISO 639-1 language codes preferred by the instance
contact dict \| None Contact information (email, account)
rules list[dict] \| None List of instance rules
info = await ap.get_instance_info()
print(info.domain, info.version)
if info.rules:
    for rule in info.rules:
        print(rule.get("text"))

Rule

Returned by get_instance_rules().

Field Type Description
id str \| None Numeric string ID of the rule
text str \| None The rule text
hint str \| None Extended description (Mastodon 4.x)
rules = await ap.get_instance_rules()
for rule in rules:
    print(f"{rule.id}: {rule.text}")

DomainBlock

Returned by get_instance_domain_blocks().

Unauthenticated requests may return a partially obfuscated domain value.

Field Type Description
domain str \| None The blocked domain (may be obfuscated)
digest str \| None SHA-256 hash of the domain name
severity str \| None One of silence, suspend, or noop
comment str \| None Publicly visible reason for the block
blocks = await ap.get_instance_domain_blocks()
for block in blocks:
    print(block.domain, block.severity)

Activity

Returned by get_instance_activity().

All numeric fields are returned as strings in the Mastodon API response.

Field Type Description
week str \| None Unix timestamp (as string) of the week start
statuses str \| None Number of statuses created that week
logins str \| None Number of user logins that week
activity = await ap.get_instance_activity()
for week in activity:
    print(week.week, week.statuses, week.logins)

AnnouncementReaction

Nested inside Announcement. Represents one emoji reaction on an announcement.

Field Type Description
name str \| None The emoji character or shortcode
count int \| None Total number of users who reacted with this emoji
me bool \| None Whether the authenticated user added this reaction
url str \| None URL to the custom emoji image (only for custom emojis)
static_url str \| None URL to a static version of the custom emoji

Announcement

Returned by get_announcements().

Field Type Description
id str \| None The announcement ID
content str \| None Announcement text (HTML)
starts_at str \| None Optional ISO 8601 start timestamp
ends_at str \| None Optional ISO 8601 end timestamp
all_day bool \| None Whether the announcement covers a whole day
published_at str \| None ISO 8601 timestamp of publication
updated_at str \| None ISO 8601 timestamp of last update
read bool \| None Whether the authenticated user has dismissed this announcement
mentions list[Account] \| None Accounts mentioned in the announcement
statuses list[Status] \| None Statuses linked in the announcement
tags list[Tag] \| None Hashtags used in the announcement
emojis list[Emoji] \| None Custom emojis used in the announcement
reactions list[AnnouncementReaction] \| None Emoji reactions on this announcement
announcements = await ap.get_announcements()
for ann in announcements:
    print(ann.content)
    if ann.reactions:
        for r in ann.reactions:
            print(f"  {r.name}: {r.count}")
    if not ann.read:
        await ap.dismiss_announcement(ann.id)

Notification

Returned by get_notifications() and get_notification().

from longwei import Notification
Field Type Description
id str \| None The notification ID
type str \| None Notification type: mention, status, reblog, follow, follow_request, favourite, poll, update, admin.sign_up, admin.report. Pleroma/Akkoma also emits pleroma:emoji_reaction, pleroma:chat_mention, pleroma:report
created_at str \| None ISO 8601 timestamp when the notification was created
account Account\| None The account that triggered the notification
status Status\| None The relevant status, if applicable (mention, reblog, favourite, etc.)
report Report\| None The relevant report; only present for admin.report notifications
notifications = await ap.get_notifications(types=["mention", "follow"])
for n in notifications:
    if n.type == "mention" and n.status:
        print(f"@{n.account.username} mentioned you: {n.status.id}")
    elif n.type == "follow":
        print(f"@{n.account.username} followed you")

NotificationGroup

An entry in the grouped notifications response returned by get_notifications_v2() (Mastodon 4.3+ only).

from longwei import NotificationGroup
Field Type Description
group_key str \| None Opaque server-assigned key identifying this group
notifications_count int \| None Total number of notifications in the group
type str \| None Notification type shared by all members of the group
most_recent_notification_id str \| None ID of the most recent notification in the group
page_min_id str \| None Oldest notification ID on the current page within this group
page_max_id str \| None Newest notification ID on the current page within this group
latest_page_notification_at str \| None ISO 8601 timestamp of the most recent notification on the current page
sample_account_ids list[str] Sample of account IDs that triggered notifications in this group

GroupedNotificationsResults

Result envelope returned by get_notifications_v2() (Mastodon 4.3+ only). Pleroma/Akkoma and GotoSocial do not support this endpoint.

from longwei import GroupedNotificationsResults
Field Type Description
accounts list[Account] Accounts referenced by notification groups on this page
statuses list[Status] Statuses referenced by notification groups on this page
notification_groups list[NotificationGroup] The grouped notification entries
from longwei.exceptions import NotFoundError

try:
    result = await ap.get_notifications_v2(grouped_types=["mention", "favourite"])
    for group in result.notification_groups:
        print(f"{group.type}: {group.notifications_count} notifications")
except NotFoundError:
    # Pleroma/Akkoma and GotoSocial do not support /api/v2/notifications
    notifications = await ap.get_notifications()

Context

Returned by get_status_context().

from longwei import Context
Field Type Description
ancestors list[Status] Statuses appearing before the requested status in the thread
descendants list[Status] Statuses appearing after the requested status in the thread
ctx = await ap.get_status_context("123456")
for s in ctx.ancestors:
    print(f"Before: @{s.account.acct if s.account else '?'}: {s.content}")
for s in ctx.descendants:
    print(f"After:  @{s.account.acct if s.account else '?'}: {s.content}")

StatusEdit

A single entry in the edit history of a status. Returned (in a list) by get_status_history().

from longwei import StatusEdit
Field Type Description
content str \| None HTML content at this point in history
spoiler_text str \| None Content warning text at this point in history
sensitive bool \| None Whether the status was marked sensitive at this point
created_at str \| None ISO 8601 timestamp of this edit
account Account \| None Account that made this edit
media_attachments list[MediaAttachment] Media at this point in history
emojis list[Emoji] Custom emojis at this point in history
poll Poll \| None Poll at this point in history

StatusSource

The unrendered plain-text source of a status, returned by get_status_source(). Use this to pre-fill an edit form.

from longwei import StatusSource
Field Type Description
id str \| None The status ID
text str \| None Plain-text body of the status
spoiler_text str \| None Plain-text content warning
source = await ap.get_status_source("123456")
updated = await ap.edit_status("123456", status=source.text + " (edited)")

Translation

A machine-translated version of a status, returned by translate_status().

from longwei import Translation
Field Type Description
content str \| None HTML content of the translated status body
spoiler_text str \| None Translated content warning text
poll Poll \| None Translated poll options
media_attachments list[MediaAttachment] Media with translated descriptions
detected_source_language str \| None BCP 47 tag of the detected source language
provider str \| None The translation provider (e.g. "DeepL")

EmojiReaction

An emoji reaction on a status, returned by get_pleroma_reactions() (Pleroma/Akkoma).

from longwei import EmojiReaction
Field Type Description
name str \| None The emoji character or shortcode
count int \| None Total number of accounts that reacted with this emoji
me bool \| None Whether the authenticated account has added this reaction
accounts list[Account] \| None Accounts that reacted (may be omitted by server)

ChatMessage

A message in a Pleroma/Akkoma direct-message chat, returned by get_pleroma_chat_messages() and post_pleroma_chat_message().

from longwei.models import ChatMessage
Field Type Description
id str \| None The message ID
content str \| None HTML content of the message
chat_id str \| None ID of the chat this message belongs to
account_id str \| None ID of the account that sent the message
created_at str \| None ISO 8601 creation timestamp
emojis list[Emoji] \| None Custom emojis used in the message
attachment MediaAttachment \| None A single media attachment (Pleroma supports one per message)

Chat

A direct-message chat thread, returned by get_pleroma_chats(), open_pleroma_chat(), and get_pleroma_chat() (Pleroma/Akkoma only).

from longwei.models import Chat
Field Type Description
id str \| None The chat ID
account Account \| None The other account in the chat
unread int \| None Number of unread messages
last_message ChatMessage \| None The most recent message in the chat

Working with Pydantic models

All models support standard Pydantic operations:

# Serialise to dict
status_dict = status.model_dump()

# Access server-specific extra fields not in the model definition
pleroma_data = status.model_extra.get("pleroma")

# Check which fields are defined in the model
print(Status.model_fields.keys())