Skip to content

Error Handling

longwei raises typed exceptions so you can respond precisely to different failure modes. All exceptions inherit from ActivityPubError, making it easy to catch everything at once or be selective.

Exception Hierarchy

ActivityPubError          ← base class for all library exceptions
├── NetworkError          ← connection / transport failure
├── ApiError              ← generic API error (not commonly raised directly)
├── ServerError           ← 5xx responses
└── ClientError           ← 4xx responses (catch-all for unmapped status codes)
    ├── UnauthorizedError     401 — bad or missing access token
    ├── ForbiddenError        403 — token lacks the required scope
    ├── NotFoundError         404 — resource does not exist
    ├── ConflictError         409 — action conflicts with current state
    ├── GoneError             410 — resource has been deleted
    ├── UnprocessedError      422 — server rejected the request parameters
    └── RatelimitError        429 — rate limit exceeded

Import exceptions directly from longwei:

from longwei import (
    ActivityPubError,
    NetworkError,
    ServerError,
    ClientError,
    UnauthorizedError,
    ForbiddenError,
    NotFoundError,
    RatelimitError,
    UnprocessedError,
)

Exception Attributes

Every exception exposes the following attributes:

Attribute Type Description
status_code int \| None HTTP status code (e.g. 401)
reason_phrase str \| None HTTP reason phrase (e.g. "Unauthorized")
message str \| None Error text extracted from the API response body
endpoint str \| None Full URL that was requested (includes query parameters)
method str \| None HTTP verb used (e.g. "GET", "POST")
request_id str \| None Server-assigned request ID from the X-Request-Id response header, if the server included one
response_text str \| None Raw response body, truncated to 500 characters
occurred_at datetime Timestamp (UTC-aware) when the exception was raised

RatelimitError also exposes two additional attributes:

Attribute Type Description
ratelimit_reset datetime \| None When the rate-limit window resets; None if the server did not include the header
ratelimit_limit int \| None Total requests allowed per window; None if the server did not include the header

NetworkError is raised via exception chaining (raise NetworkError from transport_error) so status_code, endpoint, and related attributes will be None; use __cause__ to access the underlying httpx error.

Note on request body parameters: Request body content (status text, media descriptions, etc.) is intentionally not captured in exceptions to avoid logging sensitive or user-authored content. Query parameters are accessible via endpoint.

Common Patterns

Catch specific exceptions

from longwei import APClient, RatelimitError, UnauthorizedError, NetworkError
from longwei import Status

async def post_safely(ap: APClient, text: str) -> "Status | None":
    try:
        return await ap.post_status(text)
    except UnauthorizedError:
        print("Access token is invalid or expired — re-authenticate.")
    except RatelimitError as e:
        print(f"Rate limited at {e.endpoint}. Reset at: {e.ratelimit_reset}")
    except NetworkError as e:
        print(f"Connection failed: {e.__cause__}")
    return None

Inspect error context

from longwei import APClient, ActivityPubError

async def call_with_logging(ap: APClient) -> list:
    try:
        return await ap.get_home_timeline()
    except ActivityPubError as e:
        print(f"[{e.occurred_at}] {e.method} {e.endpoint}{e.status_code} {e.reason_phrase}")
        if e.request_id:
            print(f"  Server request ID: {e.request_id}  (quote this when reporting to instance admin)")
        if e.message:
            print(f"  API message: {e.message}")
        raise

Handle 422 Unprocessable Entity

Mastodon returns 422 when request parameters are rejected — for example posting an empty status or providing an invalid visibility value.

from longwei import UnprocessedError

try:
    await ap.post_status("")          # empty status
except UnprocessedError as e:
    print(f"Rejected: {e.message}")   # e.g. "Validation failed: Text can't be blank"

Handle rate limiting

Rate limit metadata is tracked on the APClient instance after every response, and is also embedded directly in the RatelimitError exception for convenience.

import asyncio
from datetime import datetime, timezone
from longwei import RatelimitError

async def post_with_backoff(ap: "APClient", text: str) -> "Status":
    try:
        return await ap.post_status(text)
    except RatelimitError as e:
        reset = e.ratelimit_reset
        if reset is not None:
            now = datetime.now(tz=timezone.utc)
            wait = max(0, (reset - now).total_seconds())
            print(f"Rate limited. Waiting {wait:.0f}s until reset.")
            await asyncio.sleep(wait + 1)
        return await ap.post_status(text)

Catch all library errors

from longwei import ActivityPubError

try:
    timeline = await ap.get_public_timeline()
except ActivityPubError as e:
    print(f"Error: {e}")   # str(e) → "404 Not Found at https://instance/api/v1/... — Record not found"

Common Error Scenarios

401 Unauthorized

Raised when the access token is missing, expired, or revoked.

from longwei import UnauthorizedError

try:
    await ap.get_home_timeline()
except UnauthorizedError:
    # Re-authenticate and obtain a new token
    new_token = await get_fresh_token()

403 Forbidden

Raised when the token is valid but lacks the required OAuth scope (e.g. trying to write:statuses with a read-only token).

404 Not Found

Raised when the requested resource does not exist — for example, deleting a status that has already been deleted, or looking up a non-existent account ID.

410 Gone

Raised when a resource existed but has been permanently removed. Unlike 404, this signals that retrying will never succeed.

422 Unprocessable Entity

Raised when the server rejects the request parameters. The message attribute contains the server's explanation (e.g. "Validation failed: Visibility is not included in the list").

429 Rate Limited

Raised when the request rate limit is exceeded. Check ap.ratelimit_remaining proactively or handle RatelimitError reactively. The exception carries ratelimit_reset and ratelimit_limit directly so you do not need a reference to the APClient instance to determine when to retry. Pleroma instances may not return rate limit headers; in those cases ratelimit_reset and ratelimit_limit will be None on the exception (though ap.ratelimit_reset is still set to a synthetic 5-minute window).

5xx Server Error

ServerError covers all 5xx responses. These indicate a problem on the instance's side. The response_text attribute contains the raw response body (truncated to 500 characters) which may include a stack trace or error page useful for reporting to instance admins.

Network errors

NetworkError wraps low-level httpx transport errors (connection refused, DNS failure, timeout). Access the original exception via __cause__:

from longwei import NetworkError

try:
    await ap.verify_credentials()
except NetworkError as e:
    original = e.__cause__
    print(f"Transport error: {type(original).__name__}: {original}")