traceten (Python)
Server-side SDK for Traceten. Send AI-traffic and revenue events to Traceten from your backend, over authenticated HTTP that ad-blockers and privacy browsers cannot strip.
Zero runtime dependencies (standard library only). Python 3.9+.
Install
pip install traceten
Quickstart
import os
import traceten
client = traceten.Client(
"ttid_7Rb4TrC1dTbnD8w3s1TS12", # your site key, from the dashboard's install page
"https://ingest.traceten.com",
# Required. A secret: load it from your environment, never hardcode it.
api_key=os.environ["TRACETEN_API_KEY"],
)
# A pageview / traffic event -> POST /v1/server/events
client.page(
url="https://shop.example.com/pricing",
visitor_id="123e4567-e89b-42d3-a456-426614174000",
referrer="https://chatgpt.com/",
)
# A revenue / custom event -> POST /v1/server/conversions
client.track(
"subscription_started",
visitor_id="123e4567-e89b-42d3-a456-426614174000",
value_cents=4900, # minor units (cents), never dollars
currency="usd",
)
# A goal completion -> the same endpoint, under the name the goal is counted by
client.goal("demo_booked", visitor_id="123e4567-e89b-42d3-a456-426614174000",
properties={"plan": "pro"})
client.close() # flushes anything still queued and stops the background thread
page(), track() and goal() return immediately. Events are buffered and delivered in
the background with batching and retry. Call flush() to force delivery now, or
close() on shutdown to drain the queues.
goal(name, *, visitor_id, ...)
Same arguments and same endpoint as track(), with one difference: the reserved
names below raise TracetenError. A goal IS a custom event, so
goal("demo_booked", ...) and track("demo_booked", ...) send exactly the same
payload. Use goal() for something you want to count and put in a funnel, and
track() when you are recording revenue.
Reserved names. These belong to the Stripe and Shopify integrations, which emit them for real subscription and payment events, so a goal may not use one:
payment, free_trial, trial_started, trial_converted,
subscription_started, subscription_upgraded, subscription_downgraded,
subscription_renewed, subscription_cancel_scheduled,
subscription_reactivated, subscription_ended.
track() still accepts them, because that is how those events are legitimately
sent.
Whitespace is not trimmed. goal(" signup ") throws. The browser snippet
trims a name read from an HTML attribute, because attribute values pick up
whitespace from how the page is formatted; a name written in server code does
not, so a stray space is a bug worth surfacing rather than quietly fixing.
Property keys and values are both stored, and both are readable back.
GET /v1/goals/{name}/properties returns every property key sent with a goal
and that key's most common values. Do not put an email address, a person's name,
or a postal address in either half of a property.
Ingestion drops a property whose key is exactly email, phone, name,
password, token, ssn, credit_card or card_number, and redacts email,
phone, card and national-ID patterns inside string values. It has no pattern
for a personal name or a street address, and it does not scan keys at all, so
{"full_name": "Alice Chen"} is stored and returned exactly as sent.
page(), track() and goal() return immediately. Events are buffered and delivered in
the background with batching and retry. Call flush() to force delivery now, or
close() on shutdown to drain the queues.
payment(*, transaction_id, amount, currency, ...)
Records a payment from ANY payment processor (POST /v1/server/payments). The
only method here that blocks and that raises on a delivery failure: a dropped
pageview is a dropped pageview, a dropped payment is missing revenue.
result = client.payment(
transaction_id="pay_9fK2mQ", # required — the processor's id. Idempotency key.
amount=49.99, # required — MAJOR unit, not cents
currency="USD", # required — ISO-4217, sent uppercase
provider="dodo", # optional — your label. Defaults to "api".
email="ada@example.com", # optional — hashed server-side, never stored
visitor_id=vid, # optional — a stronger match than the email
renewal=False, # optional
refunded=False, # optional — never send a negative amount
is_free_trial=False, # optional — implied by amount=0
settlement_amount=48.50, # optional — provider's own conversion, fallback only
settlement_currency="USD", # optional — must be set together with settlement_amount
)
result["status"] # "recorded" | "trial" | "refunded" | "duplicate"
amount is the MAJOR unit, the opposite of track()'s value_cents: 49.99
for $49.99, 5000 for ¥5000.
settlement_amount/settlement_currency are a fallback for when currency is
not one Traceten can price on its own: the processor's own conversion of the
payment into a currency it always settles in (e.g. Dodo always settles in
USD/GBP/EUR). Send them only together — setting just one omits both from the
request rather than sending a partial pair.
Re-posting the same transaction_id returns "duplicate" and creates nothing,
which is why a 5xx is retried here. It raises TracetenError on an invalid
field and DeliveryError after every retry is exhausted.
⚠️ Do NOT send payments here for a processor you have also connected natively. Traceten would record the payment twice and overstate your revenue.
The API key
api_key is required. Create a key in the dashboard under Settings -> API
keys, or use the key shown once when you created the site.
Keep it on your server. It is a secret: never put it in client-side code, a mobile app, or a public repository. It is not the same value as the site id, which is public and already embedded in your pages.
The key does two things:
- Gets you in. The SDK posts to the authenticated ingestion endpoints, which
return
401without a valid key. - Gets you your own quota. Authenticated traffic is rate-limited on a bucket tied to the key, separate from the shared per-site bucket. The site id is public, so anyone who can read your page source can send events under it. With a key, that traffic cannot exhaust your allowance and 429 your conversion calls.
The constructor validates the key's shape and raises TracetenError if it is
malformed. Omitting it entirely is a TypeError: api_key is a required
keyword argument, so Python refuses the call.
Permissions
A key carries a set of permissions that decide which endpoints it can reach.
This SDK sends to /v1/server/*, which requires ingest:write. Tick that
permission when you create the key.
The constructor cannot check this for you. Permissions live on the server and
the key looks identical either way, so a key without ingest:write is rejected
with the same 401 as an invalid one.
Grant only what you need. A key used solely for server-side ingestion does not need permission to read your analytics or erase visitor data, and if it leaks it cannot do either.
To rotate a key: create the new one, deploy it, then revoke the old one. Revocation normally takes effect within about a minute. If our database is unreachable at that moment, an edge location that was already using the key may keep honouring it for up to about fifteen minutes more, so that a database blip cannot silently drop your events.
Identifiers
Server-side there is no cookie and no DOM, so the SDK never fabricates a
visitor. You supply visitor_id (and optionally session_id) from your own
request context. A visitor_id is a UUID or the identify-hash form
h:<64 hex chars>. track() requires one; page() does not.
The robust way to get this value is window.traceten.getVisitorId(), called
client-side and forwarded to your backend (a form field, a fetch body, a
header) — it always resolves the current cookie, so it keeps working if a
customer turns cross-subdomain cookies on or off later. If you read the
cookie by name instead, its name depends on the site's cookie scope:
cross-subdomain cookies are off by default, giving plain _traceten_vid;
once a customer enables it, the cookie becomes _traceten_vid_ followed by
eight characters of the site key. The install page shows the exact current
name. Read that name exactly, never by prefix: two Traceten sites under one
registered domain each set their own cookie, and a prefix match picks
whichever the browser happens to list first, which merges two visitors the
suffix exists to keep apart. If a request carries no such value, send the
event without a visitor_id rather than inventing one.
User agent
The same reasoning applies to user_agent. Pass the end user's User-Agent if
you know it — that is what gets recorded as the visitor's user agent. Omit it
and the event falls back to the User-Agent this SDK's HTTP client sent,
traceten-python/<version>, which describes your server, not the visitor.
User agent is an input to Traceten's traffic classification, so passing the
real one materially improves your results.
Full documentation
See API.md for the complete API
reference, configuration options, retry semantics, and error handling.
Development
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
ruff check . && black --check . && pytest
Versioning
This package follows Semantic Versioning. Before 1.0.0,
minor versions may include breaking changes — pin an exact version in production
until then. See CHANGELOG.md for release history.
Contributing
Issues and pull requests are welcome. For anything beyond a small fix, please open an issue first to discuss the change. Run the checks in Development above before submitting a PR — CI enforces the same steps on every pull request.
License
MIT © Traceten — see LICENSE.
Links
- Documentation
- Traceten — AI traffic attribution for the AI search era
- Issues
- Changelog
- Node SDK · Go SDK · AI crawler tracking
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 traceten-1.0.0.tar.gz.
File metadata
- Download URL: traceten-1.0.0.tar.gz
- Upload date:
- Size: 36.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
966b75be40d83e6303739bccd3a8a6d50bd846565d5a8fa1619e8be2e3bac909
|
|
| MD5 |
aead11c825f9f27ee7472d4b830a36a2
|
|
| BLAKE2b-256 |
953d10343fb236fe4e0596a1de32d180c41d860d74521255001e9bf9afef39ca
|
Provenance
The following attestation bundles were made for traceten-1.0.0.tar.gz:
Publisher:
release.yml on traceten/sdk-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
traceten-1.0.0.tar.gz -
Subject digest:
966b75be40d83e6303739bccd3a8a6d50bd846565d5a8fa1619e8be2e3bac909 - Sigstore transparency entry: 2821581603
- Sigstore integration time:
-
Permalink:
traceten/sdk-python@56d1cd2077f4dac9b011ddf126ae7da44478147a -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/traceten
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@56d1cd2077f4dac9b011ddf126ae7da44478147a -
Trigger Event:
push
-
Statement type:
File details
Details for the file traceten-1.0.0-py3-none-any.whl.
File metadata
- Download URL: traceten-1.0.0-py3-none-any.whl
- Upload date:
- Size: 26.2 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 |
e00b16a780e178d18274f467f6b2556429a84ce87efe644e5485447f915a274c
|
|
| MD5 |
06c30926d2dc66ae4a3a46d12c7210c6
|
|
| BLAKE2b-256 |
3a90f11a0aa1f5ac419d4a319bd0ca9efb8483c491e5b7fe6b5ad93111bf35bc
|
Provenance
The following attestation bundles were made for traceten-1.0.0-py3-none-any.whl:
Publisher:
release.yml on traceten/sdk-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
traceten-1.0.0-py3-none-any.whl -
Subject digest:
e00b16a780e178d18274f467f6b2556429a84ce87efe644e5485447f915a274c - Sigstore transparency entry: 2821581715
- Sigstore integration time:
-
Permalink:
traceten/sdk-python@56d1cd2077f4dac9b011ddf126ae7da44478147a -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/traceten
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@56d1cd2077f4dac9b011ddf126ae7da44478147a -
Trigger Event:
push
-
Statement type: