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
    )

The instance parameter is validated before any HTTP call:

  • A bare hostname (e.g. "mastodon.social") is automatically normalised to "https://mastodon.social".
  • Plain http:// URLs are rejected — HTTPS is required to protect credentials in transit.
  • Private, loopback, link-local, and reserved IP ranges are rejected (RFC 1918, 169.254.0.0/16, ::1, etc.).
  • Blocked hostnames (localhost, metadata.google.internal, metadata.internal) are rejected.

A ClientError is raised immediately if validation fails — no HTTP request is made.

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 and snac 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/v2/instance or /api/v1/instance response:

Attribute Type Description
ap.instance_type str "Mastodon", "Pleroma", or "snac"
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

Note — server-reported values, not security guarantees

These attributes are populated from the server's instance configuration response. They are clamped to reasonable bounds (status length ≤ 100 000 chars; attachment count ≤ 100; attachment size ≤ 100 MB; MIME type list capped at 200 entries), but they must not be used as hard security limits in your application. A server you do not control can return any value within those bounds. Use them only for UI hints (e.g. character counters) or soft pre-flight checks.

from longwei import INSTANCE_TYPE_MASTODON, INSTANCE_TYPE_PLEROMA, INSTANCE_TYPE_SNAC

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

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

Note — snac does not support status deletion

snac implements no DELETE endpoints. Calling delete_status() against a snac instance raises ClientError immediately with a descriptive message.


Dependency security

longwei uses uv audit to check all resolved packages for known CVEs before each release.

requests is not a direct runtime dependency of longwei but may be pulled in as a transitive dependency by development or documentation tooling. The pyproject.toml constraint

[tool.uv]
constraint-dependencies = [
    "requests>=2.33.0",  # Addresses GHSA-gc5v-m9x4-r6x2
]

ensures that uv-managed environments always resolve a patched version of requests. GHSA-gc5v-m9x4-r6x2 describes credential leakage when following redirects to a different host, patched in requests 2.32.0.

Note — scope of this constraint

This constraint is enforced only in uv-managed environments. If you install longwei's development dependencies via pip into an environment where requests is already pinned below 2.32.0 by another package, this constraint will not apply. In that case, pin requests>=2.33.0 in your own environment to ensure the fix is in effect.