Python API
The stage-events-client package provides Pydantic event models and synchronous
and asynchronous HTTPX-based clients for sending structured CloudEvents to the
Stage Events API.
Installation
Install the core Python library with:
python -m pip install stage-events-client
Python 3.10 or newer is required. The optional [cli] extra is only needed for
the send-stage-event command and is not required when using the
Python API.
Create a client
Use AuthenticatedClient when the endpoint requires a bearer token:
from stage_events_client import AuthenticatedClient
client = AuthenticatedClient(
base_url="https://events.example.com",
token="your-bearer-token",
)
The default authentication header is:
Authorization: Bearer your-bearer-token
Use Client for an endpoint that does not require authentication:
from stage_events_client import Client
client = Client(base_url="https://events.example.com")
The endpoint functions use /cloud-events as their default request path, so
base_url normally contains only the scheme, host, and any common API prefix.
Construct an event
Every supported event has a CloudEvent model and a corresponding data model in
stage_events_client.models. The following example constructs a submitted
event by instantiating both models and setting their fields directly:
from datetime import datetime, timezone
from stage_events_client.models import SubmittedCloudEvent, SubmittedData
subject = "workflows:2f660c57:example-workflow"
event_data = SubmittedData(
namespace="workflows",
time=datetime(2026, 7, 18, 12, 0, tzinfo=timezone.utc),
)
event = SubmittedCloudEvent(
source="workflows:example-process:submit",
subject=subject,
partitionkey=subject,
data=event_data,
)
type does not need to be supplied because each event model defines its own
value. In this example it defaults to submitted.
Models validate event-specific fields before a request is sent. This includes timezone-aware timestamps, semantic versions, status values, and GeoJSON structures where applicable.
The supported event models are:
| Event type | CloudEvent model | Data model |
|---|---|---|
calendar-event |
CalendarCloudEvent |
CalendarData |
submitted |
SubmittedCloudEvent |
SubmittedData |
dismissed |
DismissedCloudEvent |
DismissedData |
prepared |
PreparedCloudEvent |
PreparedData |
completed |
CompletedCloudEvent |
CompletedData |
failed |
FailedCloudEvent |
FailedData |
piped |
PipedCloudEvent |
PipedData |
staged |
StagedCloudEvent |
StagedData |
ordered |
OrderedCloudEvent |
OrderedData |
Send an event synchronously
Import the endpoint module and call sync:
from stage_events_client.api.default import send_cloud_event
with client:
result = send_cloud_event.sync(
client=client,
body=event,
x_kafka_topic="workflows.2f660c57.submitted",
)
print(result)
The context manager closes the underlying httpx.Client after the request.
Create a client once and keep its context open when sending multiple events:
with client:
for event in events:
send_cloud_event.sync(client=client, body=event)
Optional Kafka topic
x_kafka_topic is optional. When it is omitted or set to None, the client
does not add an X-Kafka-Topic header:
result = send_cloud_event.sync(
client=client,
body=event,
)
When supplied, topic names accepted by the API follow the form
{namespace}.{workflow-uid}.{event-suffix}. Calendar topics use a duration and
.calendar, such as workflows.2f660c57.10m.calendar.
Inspect the complete response
sync returns only the parsed response. Use sync_detailed to inspect the
status, headers, raw content, and parsed value:
response = send_cloud_event.sync_detailed(
client=client,
body=event,
x_kafka_topic="workflows.2f660c57.submitted",
)
print(response.status_code)
print(response.headers)
print(response.content)
print(response.parsed)
The returned stage_events_client.types.Response has these attributes:
| Attribute | Description |
|---|---|
status_code |
Response status as an http.HTTPStatus. |
headers |
HTTP response headers. |
content |
Raw response bytes. |
parsed |
Parsed success value, problem model, or None. |
A successful 200 response is parsed as a string. A documented 400 response
is parsed into the corresponding problem-details model.
Custom endpoint path
The detailed functions accept a custom relative path when the service does not
expose the default /cloud-events endpoint:
response = send_cloud_event.sync_detailed(
client=client,
body=event,
path="/hooks/cloud-events",
)
Async usage
Use asyncio to return the parsed value or asyncio_detailed to return the
complete response:
import asyncio
from stage_events_client import AuthenticatedClient
from stage_events_client.api.default import send_cloud_event
async def main() -> None:
client = AuthenticatedClient(
base_url="https://events.example.com",
token="your-bearer-token",
)
async with client:
result = await send_cloud_event.asyncio(
client=client,
body=event,
x_kafka_topic="workflows.2f660c57.submitted",
)
print(result)
asyncio.run(main())
Do not use the same client instance in synchronous and asynchronous context managers at the same time.
Response and error handling
Set raise_on_unexpected_status=True to raise UnexpectedStatus when the API
returns a status that is not documented by the OpenAPI definition:
from stage_events_client import AuthenticatedClient
from stage_events_client.errors import UnexpectedStatus
client = AuthenticatedClient(
base_url="https://events.example.com",
token="your-bearer-token",
raise_on_unexpected_status=True,
)
try:
result = send_cloud_event.sync(client=client, body=event)
except UnexpectedStatus as exc:
print(exc.status_code)
print(exc.content)
When this option is False, an unexpected status is parsed as None.
Documented API errors are returned as problem models rather than raised as
UnexpectedStatus:
For example, a missing header response can be handled with its concrete problem type:
from eoap_problems_registry import MissingRequestHeader
result = send_cloud_event.sync(client=client, body=event)
if isinstance(result, MissingRequestHeader):
print(result.model_dump_json(indent=2))
HTTPX exceptions such as httpx.TimeoutException and
httpx.RequestError can still be raised for transport failures.
Client configuration
Both client classes support shared HTTP configuration:
import httpx
from stage_events_client import AuthenticatedClient
client = AuthenticatedClient(
base_url="https://events.example.com",
token="your-bearer-token",
headers={"X-Correlation-ID": "request-id"},
cookies={"session": "value"},
timeout=httpx.Timeout(30.0),
verify_ssl="/path/to/ca-bundle.pem",
follow_redirects=True,
httpx_args={"proxy": "http://proxy.example.com:8080"},
)
| Argument | Description |
|---|---|
base_url |
Base URL used for relative endpoint paths. |
headers |
Headers included with every request. |
cookies |
Cookies included with every request. |
timeout |
HTTPX timeout configuration. |
verify_ssl |
Boolean, CA bundle path, or ssl.SSLContext. |
follow_redirects |
Whether HTTP redirects are followed. |
httpx_args |
Additional httpx.Client and httpx.AsyncClient arguments. |
raise_on_unexpected_status |
Raise instead of returning None for undocumented statuses. |
TLS certificate verification is enabled by default. Setting verify_ssl=False
disables server verification and should only be used in a controlled
development environment.
Derive an updated client
The with_* methods return an updated client:
client = client.with_headers({"X-Correlation-ID": "new-request-id"})
client = client.with_cookies({"session": "new-value"})
client = client.with_timeout(httpx.Timeout(10.0))
Supply an HTTPX client
Use set_httpx_client or set_async_httpx_client to provide an existing HTTPX
client:
http_client = httpx.Client(
base_url="https://events.example.com",
timeout=30.0,
)
client.set_httpx_client(http_client)
Supplying an HTTPX client overrides the generated client's base URL, headers, cookies, timeout, and TLS settings. Configure those values directly on the HTTPX instance.