nozle.track() submits events to base_url/api/v1/events, defaulting to https://api.nozle.app/engine/api/v1/events. Subscription discovery uses events_url/api/v1/subscriptions.
Signature
A naive datetime without a timezone is rejected. datetime.now().isoformat() raises NozleValidationError: track timestamp must be RFC3339 or Unix seconds before any request. Use an aware value, for example datetime.now(timezone.utc).isoformat().
Subscription resolution
When subscription_id is omitted, the SDK queries the Events API for active subscriptions:
- one active subscription is cached and reused until the client changes that customer’s subscriptions (see below);
- no active subscription raises
NozleAPIError (no active subscription for customer '…'); and
- multiple active subscriptions raise
NozleAPIError (has 2 active subscriptions; specify subscription_id) and require an explicit ID.
From 0.10.0, subscribe(), checkout(), cancel_subscription() and apply_subscription_transition() clear that customer’s cached subscription, even when the call fails, so the next track() looks it up again. Earlier versions never refreshed the cache. checkout_many and the Entity calls do not clear it, because they act on Entity subscriptions. A change made outside this client instance still needs an explicit subscription_id. A second subscribe adds a subscription; it does not replace the first, and with two active subscriptions track() raises until you pass subscription_id.
The lookup errors carry status_code 200. The SDK detects the failure, not an HTTP error. Do not treat 200 as success when you check status_code.
Supply the subscription explicitly for customers with multiple subscriptions and latency-sensitive paths.
Idempotency
Use a deterministic transaction ID and stable event timestamp for each logical event. Reuse both on retries.
A timeout, a network error or a 5xx can arrive after Nozle recorded the event. Nozle records an event once per transaction_id, so a retry with the same ID, the same event and the same timestamp does not bill it twice; a new ID is a new event. From nozle-sdk 0.13.3, the error track() raises carries the ID it sent as error.transaction_id (on NozleAPIError, and on NozleTransportError for a network error or a timeout), also when the SDK generated it. Earlier versions lose a generated ID when the call raises, so pass your own transaction_id there. Pass a timestamp and keep it whenever you may retry.
FastAPI worker pattern
For high-volume workloads, enqueue your business event and call track() from a durable worker. Use usage.track() instead when the operation must atomically enforce product credits.