Tyxter Python SDK
Typed, synchronous Python client for the Tyxter Messaging API. The package uses
httpx, supports Python 3.10–3.13, and covers every SDK-callable route in the
public launch manifest.
The SDK is alpha software. Additive response fields are compatible and are
tolerated at runtime. Removing or renaming a public method, field, or stable
error.code requires a deprecation cycle.
Install
pip install tyxter
For repository development:
uv sync --locked --extra dev
uv run ruff format --check .
uv run ruff check .
uv run mypy
uv run pytest
First message
import os
from tyxter import Tyxter
with Tyxter(api_key=os.environ["TYXTER_API_KEY"]) as client:
account = client.account.retrieve()
sender_id = client.sandbox.quickstart()["sender"]["default_sender_id"]
if sender_id is None:
raise RuntimeError("sandbox sender is not configured")
# A customer inbound opens WhatsApp's 24-hour free-form service window.
client.sandbox.inbound_messages.create(
{
"from": "+15555550100",
"to": sender_id,
"type": "text",
"text": {"body": "ping"},
},
idempotency_key="idem_python_open_window_001",
)
message = client.whatsapp.send_text(
{
"from": sender_id,
"to": "+15555550100",
"body": "Hello from Tyxter Python.",
},
idempotency_key="idem_python_first_message_001",
)
detail = client.messages.retrieve(message["id"])
print(account["environment"]["kind"], detail["status"], message["trace_id"])
Set base_url="http://localhost:3001" when using the local stack.
The complete deterministic example at
examples/sandbox_send_and_verify.py sends a sandbox message, retrieves it,
polls the public webhook-listen API, and verifies the returned raw-body
signature preview without using dashboard or internal routes.
TYXTER_API_KEY=tx_sandbox_... \
TYXTER_WEBHOOK_SECRET=... \
python examples/sandbox_send_and_verify.py
Resources
The client exposes snake-case resource namespaces:
account,api_keys,ai_agents,agentic_payments, andaudiencesautomations,automation_runs, andautomation_webhooksbatches,billing,contacts,data_retention,feedback,fiscal, andflowsllm,llm_routes,media,messages,meta_signup_sessions, andpaymentsphone_numbers,provider_connections, andprovider_credential_setup_sessionsrate_cards,sandbox,templates,usage,webhook_endpoints, andwebhook_events- channel-native conveniences:
whatsapp,instagram, andwhatsapp_channels
Request and response dictionaries are TypedDict contracts exported from
tyxter.types. Write methods expose idempotency_key and trace_id only where
the canonical endpoint manifest supports those headers.
Pagination
List methods return cursor pages. Continue with next_cursor only when
has_more is true:
cursor = None
while True:
page = client.messages.list(limit=100, starting_after=cursor)
for message in page["data"]:
print(message["id"], message["status"])
if not page["has_more"]:
break
cursor = page["next_cursor"]
Idempotency and errors
Tyxter does not implicitly retry writes. Reuse one idempotency key for retries of the same logical operation and choose your retry policy from the stable error fields:
Flow creation, LLM route upsert/delete, AI Agent completion, and automation
webhook-secret rotation all accept the idempotency_key keyword argument.
from tyxter import TyxterAPIError, TyxterConnectionError
try:
message = client.messages.create(payload, idempotency_key="order_123_send_1")
except TyxterAPIError as error:
print(error.status_code, error.code, error.request_id, error.trace_id)
if error.retry_after_ms is not None:
print("retry after", error.retry_after_ms, "ms")
except TyxterConnectionError as error:
print("request did not receive an API response", error)
TyxterAPIError.body preserves the original response. Internal errors may also
include error.feedback, which points to the public feedback endpoint.
Webhook verification
Tyxter signs the exact raw request body with
HMAC-SHA256(secret, "{timestamp}.{raw_body}"). Never parse and re-serialize
the body before verification.
from tyxter import WebhookSignatureVerifier
verifier = WebhookSignatureVerifier(signing_secret)
if not verifier.verify(raw_body=raw_body_bytes, headers=request_headers):
raise PermissionError("invalid Tyxter webhook signature")
FastAPI:
from fastapi import HTTPException, Request
@app.post("/webhooks/tyxter")
async def tyxter_webhook(request: Request) -> dict[str, bool]:
raw_body = await request.body()
if not verifier.verify(raw_body=raw_body, headers=request.headers):
raise HTTPException(status_code=400, detail="invalid signature")
return {"received": True}
Django:
from django.http import HttpRequest, JsonResponse
def tyxter_webhook(request: HttpRequest) -> JsonResponse:
headers = {key: value for key, value in request.headers.items()}
if not verifier.verify(raw_body=request.body, headers=headers):
return JsonResponse({"error": "invalid signature"}, status=400)
return JsonResponse({"received": True})
Header names are case-insensitive. The default replay tolerance is 300 seconds.
Media capability URLs
client.media.upload(...) performs create → capability upload → complete. The
SDK deliberately does not attach the Tyxter bearer token to the returned upload
URL. Capability failures raise TyxterMediaUploadError.
Client ownership and cleanup
Use the context manager when the SDK creates its own httpx.Client. If you pass
http_client=, the caller retains ownership and the SDK will not close it.
import httpx
http_client = httpx.Client()
client = Tyxter(api_key="tx_sandbox_...", http_client=http_client)
client.close() # does not close http_client
http_client.close() # caller-owned cleanup
TyxterBootstrap is a separate unauthenticated client for agent API-key device
authorization. It never sends a bearer token.
Broadcast example
examples/broadcast_customer_list.py validates a CSV of E.164 phone numbers and
sends an approved template through client.batches.create.
python examples/broadcast_customer_list.py \
--customers examples/customers.csv \
--from pn_123 \
--template-name promo_april \
--template-language en_US \
--name "April promo" \
--idempotency-key idem_april_promo_001
Route conformance
conformance/ holds a vendored copy of the canonical public-API manifest:
public-api-launch-endpoints.json— every launch route, its scope, query schema, and whether it honorsIdempotency-Key/Trace-Id.public-api-query-params.json— the Zod-derived query-parameter names.SOURCE_COMMIT— the Tyxter Messaging commit these copies were taken from.
tests/test_route_conformance.py pins this package's resource surface to that
manifest: a route in the manifest with no typed SDK method fails, an SDK method
that hits a route the manifest does not define fails, and a query parameter or
Idempotency-Key header that drifts from the contract fails.
Do not hand-edit these files to make a test pass. They are generated in the
Tyxter Messaging repo, where they are verified against the mounted v1
controllers, and are updated here only by the automated sync PR. Editing them
locally silences the gate instead of fixing the drift — if a sync PR turns the
suite red, the correct fix is to add or correct the SDK method.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tyxter-0.4.0.tar.gz.
File metadata
- Download URL: tyxter-0.4.0.tar.gz
- Upload date:
- Size: 91.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
56b181bbe48dd10ac7880efa8639e4a2ad9f286dc9045a879708b4934351f390
|
|
| MD5 |
7a5f2f42ac5b4cfbc4ee57d21e777d2b
|
|
| BLAKE2b-256 |
10be13f3c4f0a7a3886462816622e6c44b6583a3401d6b8ba3ee4b871d953e08
|
Provenance
The following attestation bundles were made for tyxter-0.4.0.tar.gz:
Publisher:
publish.yml on tyxter-dev/tyxter-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tyxter-0.4.0.tar.gz -
Subject digest:
56b181bbe48dd10ac7880efa8639e4a2ad9f286dc9045a879708b4934351f390 - Sigstore transparency entry: 2338196335
- Sigstore integration time:
-
Permalink:
tyxter-dev/tyxter-python@e3b35bc51136450330c69ef6e374352f0369ba51 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/tyxter-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e3b35bc51136450330c69ef6e374352f0369ba51 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tyxter-0.4.0-py3-none-any.whl.
File metadata
- Download URL: tyxter-0.4.0-py3-none-any.whl
- Upload date:
- Size: 61.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e8c62a31c839aabd9958d2f6a075c35a069a9ae5a800ad8cda94ab8baf837bf
|
|
| MD5 |
6a9fea82074ff8a5e619984e2dd9359d
|
|
| BLAKE2b-256 |
52c3ad3ddca949aa9fad9be7c762f58c9b9f55bfdaa524d9b19c51d4a8f575e4
|
Provenance
The following attestation bundles were made for tyxter-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on tyxter-dev/tyxter-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tyxter-0.4.0-py3-none-any.whl -
Subject digest:
1e8c62a31c839aabd9958d2f6a075c35a069a9ae5a800ad8cda94ab8baf837bf - Sigstore transparency entry: 2338196340
- Sigstore integration time:
-
Permalink:
tyxter-dev/tyxter-python@e3b35bc51136450330c69ef6e374352f0369ba51 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/tyxter-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e3b35bc51136450330c69ef6e374352f0369ba51 -
Trigger Event:
push
-
Statement type: