truagents — Python SDK for the TruAgents Unsubscribe API
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
- Homepage: https://github.com/ablt-ai/truagents-sdk
- Docs: https://docs.truagents.com
- Issues: https://github.com/ablt-ai/truagents-sdk/issues
- License: Apache 2.0 (see
LICENSE)
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)
| File | Size | Uploaded | |
|---|---|---|---|
| truagents-0.4.0.tar.gz | 41.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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