Python SDK · Event shape

The event model is a set of dataclasses:

@dataclass
class Event:
    action: str                    # dotted verb, e.g. "user.lock"
    id: str                        # uuid v4; auto-generated
    occurred_at: datetime          # auto-populated (UTC)
    actor: Actor                   # who caused the event
    tenant_id: str                 # optional; set when partitioning by your end-customer
    target: Target                 # what was acted on
    metadata: dict[str, Any]       # freeform context
    origin: Origin                 # IP, user-agent, request ID
    result: Result                 # outcome
    change: Change | None          # before/after for mutations
    idempotency_key: str           # optional dedup key

@dataclass
class Actor:   type: str; id: str; display_name: str; email: str
@dataclass
class Target:  type: str; id: str
@dataclass
class Result:  status: str; code: int; message: Any   # status: "ok" | "error" | "denied"
@dataclass
class Origin:  ip: str; user_agent: str; request_id: str

Fields use plain values with empty defaults ("", 0, empty nested dataclasses) rather than optionals: empty means "not set", and the serializer omits empty fields. Unlike the Node SDK, field names are snake_case everywhere and already match the JSON wire format. result.message accepts any value and special-cases exceptions (an Exception serializes as str(exc)). The JSON payload is byte-compatible with the Go and Node SDKs.