Skip to main content

truthsocial-py

truthsocial-py logo

An unofficial, typed Python client for Truth Social.

truthsocial-py supports OAuth app discovery, manual app configuration, multiple independent user sessions, text and media posts, replies, and structured API errors.

[!NOTE] Truth Social does not publish this interface as a stable public API. Endpoints and payloads may change.

Features

  • Discover the OAuth app identity from the deployed Truth Social web client
  • Configure an OAuth app identity manually
  • Log in multiple users with isolated tokens, HTTP state, and session IDs
  • Rediscover rotated web-app credentials automatically or on demand
  • Publish text and image posts
  • Reply to a status by ID or Status object
  • Answer new-device security-code challenges over email or SMS
  • Complete 2FA logins with an authenticator (TOTP) code
  • Reuse existing access tokens
  • Handle authentication, rate-limit, transport, and protocol errors

Installation

Python 3.10 or newer is required.

python -m pip install truthsocial-py

The distribution is truthsocial-py; the import package is truthsocial_py:

from truthsocial_py import TruthSocialApp

To work on the library itself:

git clone https://github.com/Jxck-S/truthsocial-py.git
cd truthsocial-py
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .

Quick start

Create an app from the current Truth Social web deployment, then log in:

import getpass

from truthsocial_py import TruthSocialApp


app = TruthSocialApp.from_web()

with app.login(
    username=input("Truth Social username: "),
    password=getpass.getpass("Truth Social password: "),
) as client:
    account = client.verify_credentials()
    status = client.post_status("Hello from truthsocial-py!")
    print(f"Posted as @{account.acct}: {status.url}")

App identities

TruthSocialApp represents one OAuth application identity. Load the current identity from the website:

app = TruthSocialApp.from_web()

Or provide one manually:

app = TruthSocialApp(
    client_id="your-client-id",
    client_secret="your-client-secret",
)

Web-loaded apps enable automatic rediscovery by default. If login returns OAuth's invalid_client or unauthorized_client error, the app refreshes its identity and retries once. Refresh it explicitly at any time:

credentials = app.rediscover()

Rediscovery applies to future clients and login attempts. Existing clients keep their current token and app snapshot. A manually configured app can opt in with auto_rediscover=True.

Multiple users

Every login returns a separate TruthSocialClient:

alice = app.login("alice", "alice-password")
bob = app.login("bob", "bob-password")

try:
    alice.post_status("Posted by Alice")
    bob.post_status("Posted by Bob")
finally:
    alice.close()
    bob.close()

Create a client from a previously saved token without logging in again:

alice = app.new_client(access_token=load_alice_token())

User agent

Requests are sent with truthsocial-py/<version> by default, exposed as truthsocial_py.DEFAULT_USER_AGENT. Override it on an app or a client:

from truthsocial_py import DEFAULT_USER_AGENT, TruthSocialApp

app = TruthSocialApp.from_web(user_agent="my-bot/2.0 (+https://example.com)")
client = app.new_client()  # inherits the app's user agent
print(client.user_agent)

TruthSocialClient(..., user_agent=...) works the same way. The value must be a non-empty, header-safe string; anything else raises ConfigurationError.

Media posts

Pass paths through media_files; each file is uploaded before the status:

status = client.post_status(
    "A photo post",
    media_files=["photo.png"],
    idempotency_key="your-stable-unique-key",
)

for attachment in status.media_attachments:
    print(attachment.id, attachment.url)

Upload separately when you need the attachment first:

attachment = client.upload_media("photo.png")
status = client.post_status(
    "Uploaded separately",
    media_ids=[attachment.id],
)

upload_media() also accepts open binary files with optional filename and content_type arguments. Do not combine media_files and media_ids in the same call. Media types and size limits are controlled by Truth Social.

Truth Social currently accepts public visibility for these posting calls.

Replies

Reply using a parent status ID:

reply = client.reply("parent-status-id", "This is a reply")

Or reply to a returned Status, including media:

reply_with_photo = client.reply(
    reply,
    "Replying to my reply",
    media_files=["photo.png"],
)

The lower-level equivalent is post_status(..., in_reply_to_id="parent-status-id").

Low-level client

TruthSocialClient can be used directly:

from truthsocial_py import TruthSocialClient


with TruthSocialClient(
    client_id="your-client-id",
    client_secret="your-client-secret",
) as client:
    client.login("username", "password")

It can also discover the deployed app identity for a single login:

with TruthSocialClient() as client:
    client.login_with_web_app("username", "password")

Use discover_web_app_credentials() to obtain the typed app identity without logging in.

New device verification

Logging in from a device Truth Social has not seen before is rejected with a DeviceChallengeRequired — a subclass of AuthenticationError, so existing handlers still catch it, but it is raised only when the username and password were accepted. Answering it takes two more calls: pick a delivery method to have a 6-digit code sent, then repeat the login with that code.

Own the client yourself so the challenge is answered on the session that raised it (see Challenge handling below):

from truthsocial_py import DeviceChallengeRequired, TruthSocialApp

app = TruthSocialApp.from_web()
username, password = "someone", "hunter2"

with app.new_client() as client:
    try:
        client.login(username, password)
    except DeviceChallengeRequired as exc:
        challenge = exc.challenge
        print(exc.message)  # "New device login detected. Please select a..."
        for option in challenge.delivery_options:
            print(option.kind, option.value)  # e.g. email j***@example.com

        client.send_security_code(challenge, "email")
        client.login_with_security_code(
            username,
            password,
            security_code=input("security code: "),
            challenge=challenge,
        )

    client.post_status("hello from a verified device")

send_security_code may be called again with the same challenge to resend the code. The password is required a second time because /oauth/v2/verify_security_code issues the token itself; the challenge is not a token exchange. TruthSocialApp.login never retries credential rediscovery on this error, since the app credentials were not the problem.

Two-factor accounts

An account with 2FA enabled rejects the password grant with MfaRequired, another AuthenticationError subclass, handing back a short-lived mfa_token. That token stands in for the password — redeeming it with the authenticator code returns the access token directly.

from truthsocial_py import MfaRequired, TruthSocialApp

app = TruthSocialApp.from_web()

with app.new_client() as client:
    try:
        client.login("someone", "hunter2")
    except MfaRequired as exc:
        client.login_with_mfa_code(input("2FA code: "), challenge=exc.challenge)

    client.post_status("hello from a 2FA account")

login_with_mfa_code defaults to challenge_type="totp", the only type Truth Social currently advertises. Note that the 403 body claims "The 2FA code entered is incorrect" even on the first prompt, before any code has been sent; that sentence is kept on exc.challenge.detail rather than used as the exception message, so it cannot be mistaken for a rejected code. A code that really is wrong fails the login_with_mfa_code call with a plain AuthenticationError carrying that same message.

Challenge handling

Both challenge flows are answered on the same client that raised them. Each client carries its own X-Truth-Session-Id and connection, and a challenge belongs to that session, so create the client first and keep it in scope for the handler:

with app.new_client() as client:   # you own the client
    try:
        client.login(username, password)
    except MfaRequired as exc:
        client.login_with_mfa_code(code, challenge=exc.challenge)

TruthSocialApp.login creates a client internally and closes it when login fails, so it cannot be used to answer a challenge — it is the one-shot path for accounts with 2FA disabled logging in from a known device. Owning the client trades away its automatic retry on rotated app credentials; handle that by calling app.rediscover() yourself if a login fails with an invalid_client error.

An account can require both: /oauth/v2/verify_security_code may itself return mfa_required, so the two handlers compose on the one client.

Posting behavior

Status creation is not retried automatically because a transport failure can leave the final outcome unknown. Supply a stable idempotency_key when your application may retry a post.

When media_files is used, a later upload or post failure can leave an uploaded attachment unused.

Errors

All library exceptions inherit from TruthSocialError:

  • ConfigurationError and NotAuthenticatedError
  • CredentialDiscoveryError
  • AuthenticationError, and its DeviceChallengeRequired and MfaRequired subclasses
  • RateLimitError, including an optional retry_after
  • APIError
  • NetworkError and ProtocolError

Development

Run the mocked test suite:

PYTHONPATH=src python -m unittest discover -s tests -v

Live smoke test

Prepare the local settings file:

cp examples/local_credentials.py.example examples/local_credentials.py

Fill in the username and password, then run:

python examples/manual_smoke_test.py

After confirmation, the script creates a public text post, a public reply, and a public image post using examples/truthsocial-py-test.png. It does not delete them afterward.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

truthsocial_py-0.2.0.tar.gz (30.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

truthsocial_py-0.2.0-py3-none-any.whl (21.5 kB view details)

Uploaded Python 3

File details

Details for the file truthsocial_py-0.2.0.tar.gz.

File metadata

  • Download URL: truthsocial_py-0.2.0.tar.gz
  • Upload date:
  • Size: 30.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for truthsocial_py-0.2.0.tar.gz
Algorithm Hash digest
SHA256 4629303f9a975d536c0670301a3a621cf26a5f2c1bc78c4c9eaab4e4d4c50788
MD5 e7745652fed1f3d00d2a27908dd09e5f
BLAKE2b-256 51614dbb7b82e9e900d13c4988645d01bd5be66aa95abea28dc4a08732f281a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for truthsocial_py-0.2.0.tar.gz:

Publisher: release.yml on Jxck-S/truthsocial-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file truthsocial_py-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: truthsocial_py-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 21.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for truthsocial_py-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 652f2fcd9ee06fc4ca73cb24c21c608e111ba0717ae068bb555f37df9144352c
MD5 34b4912e8a3e3693a16f5098527a40d7
BLAKE2b-256 6c6fba5d52a2dff16b32341435b18e36fa98be87236535f513925804693e75e6

See more details on using hashes here.

Provenance

The following attestation bundles were made for truthsocial_py-0.2.0-py3-none-any.whl:

Publisher: release.yml on Jxck-S/truthsocial-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page