Skip to main content

truagents — Python SDK for the TruAgents Unsubscribe API

PyPI version Python versions CI

Official Python SDK for the TruAgents Unsubscribe API. Handles OAuth token acquisition and refresh, retries, rate limiting, and observability so you can focus on the business logic.

Install

pip install truagents

Requires Python >= 3.11.

Quickstart (sync)

from truagents import Client

with Client(client_id="...", client_secret="...") as client:
    resp = client.list_email_unsubscribes()
    for record in resp.data:
        print(record.email, record.unsubscribed)

Quickstart (async)

import asyncio

from truagents import AsyncClient


async def main() -> None:
    async with AsyncClient(client_id="...", client_secret="...") as client:
        resp = await client.list_email_unsubscribes()
        for record in resp.data:
            print(record.email, record.unsubscribed)


asyncio.run(main())

Writing unsubscribes

Opt identifiers out with add_* and back in with remove_* — the verb is the direction, so items carry only the identifier. Batches are all-or-nothing: any invalid item aborts the whole batch (zero rows written) and raises errors.InvalidItemError.

from truagents import Client
from truagents.generated.models.email_unsubscribe_batch_request import (
    EmailUnsubscribeBatchRequest,
)
from truagents.generated.models.email_unsubscribe_item import EmailUnsubscribeItem

with Client(client_id="...", client_secret="...") as client:
    groups = client.list_unsubscribe_groups()
    group_id = groups.data[0].id

    batch = EmailUnsubscribeBatchRequest(
        items=[EmailUnsubscribeItem(email="john@example.com")],
        group_id=group_id,
    )
    result = client.add_email_unsubscribes(batch)
    print(result.group_id, result.processed)

    client.remove_email_unsubscribes(batch)

Target a batch with either group_id or org_slug, never both — the two are mutually exclusive and the server rejects a request that carries both. Omit both to write to the key's default organization. SMS and voice mirror this shape via add_sms_unsubscribes / remove_sms_unsubscribes and add_voice_unsubscribes / remove_voice_unsubscribes (items carry phone=).

Authentication

TokenManager (owned by the client) mints an access token via client_credentials on the first request and refreshes it via refresh_token refresh_threshold_seconds before the server-side expiry (default: 60s). Concurrent callers are de-duplicated: a thundering herd of sync threads or async coroutines results in a single /oauth/token round-trip.

If the cached refresh token is rejected with invalid_grant (RFC 6749 §5.2 family revoke), the manager transparently drops it and re-mints via client_credentials. Partners never see the intermediate InvalidGrant.

A mid-flight TokenExpired (HTTP 401) on an API endpoint is NOT auto-remedied. It signals that your credentials were revoked externally and you need to investigate. This is a deliberate design decision — silently re-minting on 401 would mask real revocations.

Retries + rate limiting

Every request goes through a shared RetryPolicy:

Failure Retry?
NetworkError Yes — up to max_attempts with exp. back-off
ServerError (5xx) Yes — up to max_attempts with exp. back-off
RateLimited (HTTP 429) Yes — honours Retry-After (capped at 60s)
InvalidRequest (HTTP 400) No
NotFound (HTTP 404) No
TokenExpired (HTTP 401) No

Override defaults by constructing your own RetryPolicy:

from truagents import Client, RetryPolicy

policy = RetryPolicy(max_attempts=5, base_delay=0.1, retry_after_cap=30.0)
client = Client(client_id="...", client_secret="...", retry=policy)

Observability hooks

Hooks fire around every HTTP call (token endpoint and API endpoint alike). Broken hooks never break an SDK call — they are logged and swallowed.

from truagents import Client, Hooks, Request, Response


def log_request(req: Request) -> None:
    print(f"-> {req.method} {req.url}")


def log_response(resp: Response, elapsed_ms: float) -> None:
    print(f"<- {resp.status_code} ({elapsed_ms:.0f}ms)")


hooks = Hooks(on_request=log_request, on_response=log_response)
client = Client(client_id="...", client_secret="...", hooks=hooks)

Error handling

All SDK exceptions inherit from truagents.errors.TruAgentsError.

Exception Raised when
AuthError Base of OAuth token endpoint failures.
InvalidClient Token endpoint returned 401 (bad client_id/secret).
InvalidGrant Token endpoint returned 400 invalid_grant. Almost always caught + recovered internally.
TokenExpired API endpoint returned 401 (external revocation).
APIError Base of API endpoint failures.
InvalidRequest API endpoint returned 400.
InvalidItemError API endpoint returned 400 invalid_item — a batch item failed validation; carries item_index + item_error, zero rows persisted. Subclass of InvalidRequest.
NotFound API endpoint returned 404.
RateLimited API endpoint returned 429. Carries retry_after.
ServerError API endpoint returned 5xx.
NetworkError Transport failure (DNS, connection reset, timeout). Wraps the underlying httpx exception.

Every subclass carries a code class variable (e.g. "INVALID_CLIENT") so you can log it without importing the class.

Version stability

The SDK is at 0.x. Minor bumps may introduce breaking changes. See docs/versioning.md at the repository root for the full policy and the 1.0.0 promotion criteria.

Links

Release files for truagents 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for truagents 0.4.0
File Size Uploaded
truagents-0.4.0.tar.gz 41.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for truagents 0.4.0
File Interpreter ABI Platform
truagents-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 111.7 kB

Release files / truagents-0.4.0.tar.gz

Download URL truagents-0.4.0.tar.gz
Size 41.1 kB
Tags Source
SHA-256 checksum
How to use checksums
34f0e6c38b01973b7860e5251d60bd77b2e95263d9e630d61f7fde452ea4f625
BLAKE2b-256 checksum
How to use checksums
252a67bd021b702fa23c2eb6aeacc25b841885537e62f61de2b9438c7198802e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 27, 2026.

Transparency log

Release files / truagents-0.4.0-py3-none-any.whl

Download URL truagents-0.4.0-py3-none-any.whl
Size 70.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
754153cd975117f26a3cf78934f9977625e0d9fe7d9acfd307ffe0df734ce11f
BLAKE2b-256 checksum
How to use checksums
1680cf425d1623823c44d29d10ec9924481226ad2dc628d02389eef36477a263
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 27, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page