Skip to main content
Traceten

traceten (Python)

PyPI version License: MIT 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 401 without 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

Download files

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

Source Distribution

traceten-1.0.0.tar.gz (36.8 kB view details)

Uploaded Source

Built Distribution

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

traceten-1.0.0-py3-none-any.whl (26.2 kB view details)

Uploaded Python 3

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

Hashes for traceten-1.0.0.tar.gz
Algorithm Hash digest
SHA256 966b75be40d83e6303739bccd3a8a6d50bd846565d5a8fa1619e8be2e3bac909
MD5 aead11c825f9f27ee7472d4b830a36a2
BLAKE2b-256 953d10343fb236fe4e0596a1de32d180c41d860d74521255001e9bf9afef39ca

See more details on using hashes here.

Provenance

The following attestation bundles were made for traceten-1.0.0.tar.gz:

Publisher: release.yml on traceten/sdk-python

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

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

Hashes for traceten-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e00b16a780e178d18274f467f6b2556429a84ce87efe644e5485447f915a274c
MD5 06c30926d2dc66ae4a3a46d12c7210c6
BLAKE2b-256 3a90f11a0aa1f5ac419d4a319bd0ca9efb8483c491e5b7fe6b5ad93111bf35bc

See more details on using hashes here.

Provenance

The following attestation bundles were made for traceten-1.0.0-py3-none-any.whl:

Publisher: release.yml on traceten/sdk-python

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

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 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