Python Quickstart

Get running with the Python SDK in 5 minutes using direct config (no SDK key needed)

Get the Convert Python SDK running and bucket your first visitor in under 5 minutes. This guide uses direct config (an inline dict, no network call) so it runs anywhere without an SDK key. A remote (sdk_key) variant is shown at the end.

1. Install

pip install convert-python-sdk

Python 3.9 or newer is required. httpx is installed automatically as the only runtime dependency — it is only used when you initialize with an sdk_key. See Installation for uv and Poetry alternatives.

2. Initialize with direct config (offline, no network)

Prefer direct config for local development, tests, and offline tooling. It makes no network call and is ready immediately.

from convert_sdk import Core, SDKConfig

config_data = {
    "account_id": "100123",
    "project": {"id": "200456"},
    "experiences": [
        {
            "id": "e1",
            "key": "checkout-experiment",
            "variations": [
                {"id": "v1", "key": "control",   "traffic_allocation": 50.0},
                {"id": "v2", "key": "treatment", "traffic_allocation": 50.0},
            ],
        }
    ],
    "goals": [
        {"id": "g1", "key": "purchase_completed"},
    ],
}

core = Core(SDKConfig(data=config_data)).initialize()
assert core.is_ready

initialize() is synchronous and returns a ready Core. is_ready is True once the config snapshot is loaded — reaching it already implies success.

3. Create a visitor context

Bind a visitor identity and optional attributes to the current config snapshot:

context = core.create_context(
    "visitor-001",
    visitor_attributes={"country": "US", "plan": "pro"},
)

Attributes are copied defensively. Later mutations to the dict you pass do not affect the context. Keep and reuse the returned context for the same visitor — the SDK does not cache contexts for you.

4. Run an experience

run_experience returns a typed ExperienceResult when the visitor qualifies and buckets into a variation, or None for any normal miss. It never raises for ordinary no-match outcomes and performs no network I/O.

result = context.run_experience("checkout-experiment")
if result is not None:
    print(result.experience_key)   # "checkout-experiment"
    print(result.variation_key)    # "control" or "treatment"
    print(result.variation_id)     # "v1" or "v2"

None is a normal outcome — it means the visitor did not qualify or the experience was not found. No exception is raised.

5. Track a conversion

track_conversion records a goal conversion synchronously. It deduplicates by (visitor_id, goal_id) and appends to an in-process queue. No network call happens on track_conversion.

from convert_sdk import ConversionStatus

result = context.track_conversion("purchase_completed", revenue=49.99)
print(result.tracked)   # True
print(result.status)    # ConversionStatus.QUEUED

# A default duplicate for the same (visitor, goal) is suppressed:
again = context.track_conversion("purchase_completed")
print(again.tracked)    # False
print(again.reason)     # "deduplicated"

# Re-track with force_multiple for repeated revenue / transactions:
context.track_conversion("purchase_completed", revenue=10.0, force_multiple=True)

An unknown goal key is a typed result (ConversionStatus.GOAL_NOT_FOUND), not an exception.

6. Flush and close

Deliver queued events and release transport resources. Call flush() explicitly at the end of a request, before process exit, or in a finally block:

core.flush()   # delivers queued tracking events synchronously
core.close()   # releases transport resources

There is no auto-flush by default — see Configuration for the batch_size and auto_flush_interval_ms options and Tracking Conversions for per-runtime patterns.

Complete direct-config example

from convert_sdk import Core, SDKConfig, ConversionStatus

config_data = {
    "account_id": "100123",
    "project": {"id": "200456"},
    "experiences": [
        {
            "id": "e1",
            "key": "checkout-experiment",
            "variations": [
                {"id": "v1", "key": "control",   "traffic_allocation": 50.0},
                {"id": "v2", "key": "treatment", "traffic_allocation": 50.0},
            ],
        }
    ],
    "goals": [
        {"id": "g1", "key": "purchase_completed"},
    ],
}

core = Core(SDKConfig(data=config_data)).initialize()

context = core.create_context(
    "visitor-001",
    visitor_attributes={"country": "US", "plan": "pro"},
)

result = context.run_experience("checkout-experiment")
if result is not None:
    print("Variation:", result.variation_key)

conversion = context.track_conversion("purchase_completed", revenue=49.99)
assert conversion.status is ConversionStatus.QUEUED

core.flush()
core.close()

Remote initialization (sdk_key)

For production use, fetch live config from the Convert CDN by providing your SDK key. Read the key from the environment — never hard-code credentials:

import os
from convert_sdk import Core, SDKConfig

core = Core(SDKConfig(sdk_key=os.environ["CONVERT_SDK_KEY"])).initialize()
context = core.create_context("visitor-001")

result = context.run_experience("checkout-experiment")
if result is not None:
    print("Variation:", result.variation_key)

core.flush()
core.close()

initialize() fetches config over HTTPS (using the httpx-backed transport) and raises ConfigLoadError if the fetch fails. All subsequent evaluation calls are local and require no network.

Context manager

Core is a context manager. Using it as one is the recommended pattern for scripts and short-lived code because close() is called automatically on exit, even if an exception is raised inside the block:

from convert_sdk import Core, SDKConfig

with Core(SDKConfig(data=config_data)).initialize() as core:
    context = core.create_context("visitor-001")
    result = context.run_experience("checkout-experiment")
    if result is not None:
        print("Variation:", result.variation_key)
# flush() and close() called automatically on exit

Key points

  • initialize() is synchronous. It blocks until the SDK is ready. There is no callback or async/await in the public API.
  • None is a normal outcome. run_experience() and run_feature() return None when a visitor does not qualify — never raise.
  • No auto-flush by default. Queued tracking events are held in-process until you call core.flush(), the batch_size threshold is hit (default 10), or the optional auto_flush_interval_ms timer fires.
  • Direct config = no network. Passing data= to SDKConfig never makes a network call — safe for tests, CI, and offline tooling.
  • One runtime dependency. httpx>=0.28,<1.0 is the only runtime dependency, and it is only used in sdk_key mode.

Next steps