Skip to content

Getting Started

Installation

pip install longwei
# or with uv
uv add longwei

Quick example

import asyncio
import httpx
from longwei import APClient

async def main():
    async with httpx.AsyncClient() as client:
        ap = await APClient.create(
            instance="mastodon.social",
            client=client,
            access_token="your_access_token",
        )
        status = await ap.post_status("Hello, fediverse!")
        print(f"Posted: {status.url}")

asyncio.run(main())

Creating an APClient

APClient.create() is the recommended entry point. It calls determine_instance_type() automatically, configuring server-specific parameters (status length limit, attachment limits, supported MIME types) before any API call is made.

async with httpx.AsyncClient() as client:
    ap = await APClient.create(
        instance="mastodon.social",      # domain or full URL; trailing slash is stripped
        client=client,                   # httpx AsyncClient
        access_token="your_token",       # optional — omit for public endpoints
        timeout=30,                      # optional — seconds; defaults to 120
    )

Direct instantiation

You can also instantiate directly. The instance type defaults to INSTANCE_TYPE_MASTODON until determine_instance_type() is called manually.

ap = APClient(
    instance="mastodon.social",
    client=client,
    access_token="your_token",
)
await ap.determine_instance_type()   # optional — only needed for accurate limits

Authentication

To authenticate, you need an access token. See the Authentication guide for how to obtain one using the password grant or the OAuth authorization code flow.

If you are building a tool that only reads public data (public timelines, public statuses), you can omit the access_token parameter.


Basic operations

Read the home timeline

statuses = await ap.get_home_timeline(limit=20)
for status in statuses:
    author = status.account.username if status.account else "?"
    print(f"@{author}: {(status.content or '')[:80]}")

Post a status

from longwei import Visibility

status = await ap.post_status(
    "Hello, fediverse!",
    visibility=Visibility.PUBLIC,   # default; see Enums for all options
)
print(f"Posted: {status.url}")

Delete a status

deleted = await ap.delete_status(status=status)
from longwei import SearchType

results = await ap.search(query="python", query_type=SearchType.HASHTAGS)
for tag in results.get("hashtags", []):
    print(tag["name"])

Pagination

Timeline methods that support pagination store the next/previous page cursors on the client instance after each call:

first_page = await ap.get_public_timeline(limit=20)

# Fetch the next page using max_id from the stored pagination state
next_max_id = ap.pagination["next"]["max_id"]
if next_max_id:
    second_page = await ap.get_public_timeline(limit=20, max_id=next_max_id)

ap.pagination structure:

{
    "next": {"max_id": "...", "min_id": "..."},
    "prev": {"max_id": "...", "min_id": "..."},
}

Values are None when there is no further page in that direction.


Rate limits

After each successful response the client tracks the server's rate limit headers:

Attribute Type Description
ap.ratelimit_limit int Total requests allowed per window (default 300)
ap.ratelimit_remaining int Requests remaining in the current window
ap.ratelimit_reset datetime When the window resets

Pleroma instances do not return rate limit headers; for those, ratelimit_reset is set to a synthetic 5-minute window.

When the limit is exhausted, the next API call raises RatelimitError before contacting the server. See Error Handling for how to handle it.


Context manager pattern

httpx.AsyncClient works as a context manager. The recommended pattern is:

async with httpx.AsyncClient() as client:
    ap = await APClient.create(instance="mastodon.social", client=client, access_token="token")
    # all API calls inside this block
    statuses = await ap.get_home_timeline()

The client is closed automatically when the async with block exits.


Instance type detection

After APClient.create() (or after calling determine_instance_type() manually), the following attributes are populated from the server's /api/v1/instance response:

Attribute Type Description
ap.instance_type str "Mastodon" or "Pleroma"
ap.max_status_len int Maximum status length in characters
ap.max_attachments int Maximum number of media attachments per status
ap.max_att_size int Maximum attachment size in bytes
ap.supported_mime_types list[str] MIME types accepted for media uploads
from longwei import INSTANCE_TYPE_MASTODON, INSTANCE_TYPE_PLEROMA

if ap.instance_type == INSTANCE_TYPE_PLEROMA:
    print("Connected to a Pleroma instance")

print(f"Max status length: {ap.max_status_len} chars")