Skip to main content
trevosdk runs experiments and records conversions from your Python backend. Zero dependencies, Python 3.10+, and assignment is byte-identical to every other Trevo SDK — the same user gets the same variant in the browser and on the backend.

Create a client

You need a server key: tsk_secret_…, from Settings → SDK keys with platform Server. It is shown once. Never put it in client code.
Create the client once at module scope — one per process, reused across requests. Config refresh (60-second poll) and event delivery run on background daemon threads; events flush on interpreter exit, or deterministically via trevo.flush() / trevo.close(). Assignment needs config, so wait for the first fetch once at startup:
Without this, requests in the first moments after a deploy fall back to control.

Read a variant

get_variant reads an immutable in-memory config snapshot — no network call, nothing that blocks — so it is safe anywhere: Django and Flask views, FastAPI handlers, Celery tasks, cron scripts. Each call records an exposure (deduplicated per identity and variant within a 10-minute window). To read without recording one, pass track_exposure=False.

Identity is passed per call

A server holds no ambient user state, so every call takes the identity explicitly:
Send both ids whenever you have them. user_id wins for bucketing when present; an event carrying both is what links a user’s anonymous browsing to their conversions. With no identity at all, get_variant returns "control" and records nothing. Bucket with the identity the browser is using at that moment — the user id when identified, otherwise the trevo_id cookie. Anything else assigns the same person different variants on the client and the server.

Track conversions

Payment providers replay webhooks, and a conversion counted twice inflates whichever variant that user was in. Pass a stable insert_id derived from the thing that happened, and Trevo records the event once no matter how many deliveries arrive:
Webhooks and cron jobs have no cookie to read. State the link directly:
Idempotent and safe to retry. An anonymous_id belongs to one user — pointing it at a different user raises and changes nothing.

Framework notes

  • Django / Flask — instantiate once at module scope (a settings or extensions file) and call from views directly.
  • FastAPI / async — the sync client is safe in async handlers (nothing blocks on the read path). For an awaitable lifecycle, wrap it:
  • Pre-fork servers (gunicorn --preload, uWSGI, Celery prefork) — a client built before the fork keeps working in the child. Its worker threads do not survive a fork, so the SDK re-initialises them and its locks via os.register_at_fork; the first get_variant or track in the child restarts delivery. No post_fork hook is needed.
  • Serverless and short-lived scripts — background timers do not get a chance to fire, so call trevo.flush() before returning. The client is also a context manager: with TrevoClient(...) as trevo: flushes and closes on exit.

Local development

Pin variants without touching Trevo:
Or in code with force_variants={"checkout-cta": "treatment"}. Forced reads never record an exposure, so local testing never pollutes results. The SDK’s own test suite deletes the variable, so exporting it cannot turn a conformance run red.

Errors

Everything raised or passed to on_error is typed, so you can branch on what happened: TrevoConfigError, TrevoDeliveryError (carries .status), TrevoSerializationError, TrevoQueueFullError and TrevoEventRejectedError, all under TrevoError.

Options

API summary

AsyncTrevoClient mirrors the same surface with await-able ready(), alias(), flush(), and aclose(), plus async with support.