Openstatus Python SDK
Official Python SDK for Openstatus — the open-source status page and uptime monitoring platform.
Status: pre-alpha. APIs may change without notice until v1.0.
Table of contents
- Features
- Installation
- Quick start
- Authentication
- Sync and async
- Custom HTTP client
- Services
- Reference
- Error handling
- Recipes
- Migration from the Node SDK
- Development
- License
Features
- Sync and async clients —
OpenstatusClientandAsyncOpenstatusClient, both backed byhttpx. - Typed messages — request and response types are generated from the
upstream protobuf schema, with
.pyistubs for editor and pyright support. - All services covered — Health, Monitor, StatusReport, StatusPage, Maintenance, and Notification.
- JSON over HTTP — uses Connect-RPC's JSON mode; no binary protobuf required at runtime.
- Predictable errors — Connect-style error envelopes are mapped to a typed exception hierarchy.
Installation
pip install openstatus
# or
uv add openstatus
Requires Python 3.10 or newer.
Quick start
from openstatus import OpenstatusClient
from openstatus._gen.openstatus.monitor.v1.assertions_pb2 import (
NumberComparator,
StatusCodeAssertion,
)
from openstatus._gen.openstatus.monitor.v1.http_monitor_pb2 import (
HTTPMethod,
HTTPMonitor,
)
from openstatus._gen.openstatus.monitor.v1.monitor_pb2 import Periodicity, Region
from openstatus._gen.openstatus.monitor.v1.service_pb2 import (
CreateHTTPMonitorRequest,
ListMonitorsRequest,
)
with OpenstatusClient() as client:
created = client.monitor.v1.MonitorService.create_http_monitor(
CreateHTTPMonitorRequest(
monitor=HTTPMonitor(
name="My API",
url="https://api.example.com/health",
method=HTTPMethod.HTTP_METHOD_GET,
periodicity=Periodicity.PERIODICITY_1M,
regions=[Region.REGION_FLY_AMS, Region.REGION_FLY_IAD],
active=True,
status_code_assertions=[
StatusCodeAssertion(
comparator=NumberComparator.NUMBER_COMPARATOR_EQUAL,
target=200,
)
],
)
)
)
print(f"Created monitor id={created.monitor.id}")
monitors = client.monitor.v1.MonitorService.list_monitors(ListMonitorsRequest())
print(f"Total monitors: {monitors.total_size}")
Authentication
The SDK reads OPENSTATUS_API_KEY from the environment by default:
export OPENSTATUS_API_KEY="..."
Or pass it explicitly:
from openstatus import ClientOptions, OpenstatusClient
client = OpenstatusClient(ClientOptions(api_key="..."))
Override the base URL with OPENSTATUS_API_URL or ClientOptions(base_url=...).
The default is https://api.openstatus.dev.
Sync and async
Both clients expose the same nested namespace path
(client.<service>.v1.<ServiceName>.<method>):
from openstatus import OpenstatusClient
from openstatus._gen.openstatus.health.v1.health_pb2 import CheckRequest
with OpenstatusClient() as client:
health = client.health.v1.HealthService.check(CheckRequest())
import asyncio
from openstatus import AsyncOpenstatusClient
from openstatus._gen.openstatus.health.v1.health_pb2 import CheckRequest
async def main() -> None:
async with AsyncOpenstatusClient() as client:
health = await client.health.v1.HealthService.check(CheckRequest())
print(health.status)
asyncio.run(main())
Custom HTTP client
Supply your own httpx.Client to control timeouts, transports, proxies, or
TLS configuration:
import httpx
from openstatus import ClientOptions, OpenstatusClient
http = httpx.Client(
timeout=httpx.Timeout(connect=2.0, read=10.0, write=10.0, pool=10.0),
transport=httpx.HTTPTransport(retries=3),
)
client = OpenstatusClient(ClientOptions(http_client=http))
When you pass a client in, the SDK does not close it; manage its lifetime yourself.
Services
Each method takes a typed protobuf request and returns a typed protobuf
response. Pass per-call headers via the keyword-only headers= argument.
Health
from openstatus._gen.openstatus.health.v1.health_pb2 import CheckRequest
health = client.health.v1.HealthService.check(CheckRequest())
Monitor
from openstatus._gen.openstatus.monitor.v1.service_pb2 import (
GetMonitorRequest,
ListMonitorsRequest,
TriggerMonitorRequest,
)
client.monitor.v1.MonitorService.list_monitors(ListMonitorsRequest())
client.monitor.v1.MonitorService.get_monitor(GetMonitorRequest(id="abc"))
client.monitor.v1.MonitorService.trigger_monitor(TriggerMonitorRequest(id="abc"))
Status report
from openstatus._gen.openstatus.status_report.v1.service_pb2 import ListStatusReportsRequest
client.status_report.v1.StatusReportService.list_status_reports(ListStatusReportsRequest())
Status page
from openstatus._gen.openstatus.status_page.v1.service_pb2 import ListStatusPagesRequest
client.status_page.v1.StatusPageService.list_status_pages(ListStatusPagesRequest())
Maintenance
from openstatus._gen.openstatus.maintenance.v1.service_pb2 import ListMaintenancesRequest
client.maintenance.v1.MaintenanceService.list_maintenances(ListMaintenancesRequest())
Notification
from openstatus._gen.openstatus.notification.v1.service_pb2 import ListNotificationsRequest
client.notification.v1.NotificationService.list_notifications(ListNotificationsRequest())
Reference
Regions
Region constants live in openstatus._gen.openstatus.monitor.v1.monitor_pb2
as Region. Use the descriptor API to enumerate them:
from openstatus._gen.openstatus.monitor.v1.monitor_pb2 import Region
print(list(Region.keys())) # ["REGION_UNSPECIFIED", "REGION_FLY_AMS", ...]
print(Region.Name(1)) # "REGION_FLY_AMS"
print(Region.Value("REGION_FLY_AMS"))
Enums
Common enums:
HTTPMethod(openstatus.monitor.v1.http_monitor_pb2) — request method for HTTP monitors.Periodicity(openstatus.monitor.v1.monitor_pb2) — check frequency.MonitorStatus,Region(openstatus.monitor.v1.monitor_pb2).NumberComparator,StringComparator,RecordComparator(openstatus.monitor.v1.assertions_pb2) — assertion operators.HTTPResponseLogRequestStatus,HTTPResponseLogTrigger,TimeRange(openstatus.monitor.v1.service_pb2).
Error handling
All transport errors derive from OpenstatusError:
from openstatus import (
AuthenticationError,
NotFoundError,
OpenstatusClient,
OpenstatusError,
)
from openstatus._gen.openstatus.monitor.v1.service_pb2 import GetMonitorRequest
with OpenstatusClient() as client:
try:
client.monitor.v1.MonitorService.get_monitor(GetMonitorRequest(id="missing"))
except NotFoundError as err:
print("monitor not found:", err.connect_code, err.http_status)
except AuthenticationError:
print("check OPENSTATUS_API_KEY")
except OpenstatusError as err:
print("openstatus failed:", err.connect_code, err.details)
Each exception carries connect_code, http_status, details, and
raw_body attributes for inspection.
| Connect code | Exception |
|---|---|
unauthenticated |
AuthenticationError |
not_found |
NotFoundError |
invalid_argument |
InvalidArgumentError |
permission_denied |
PermissionDeniedError |
resource_exhausted |
RateLimitError |
unavailable |
ServiceUnavailableError |
| anything else | OpenstatusError |
When the server response is not a Connect error envelope (e.g. a 502 HTML
body from a proxy), ServiceUnavailableError is raised with the raw body
preserved on .raw_body.
Recipes
FastAPI
from fastapi import Depends, FastAPI
from openstatus import OpenstatusClient
app = FastAPI()
_client = OpenstatusClient()
def get_client() -> OpenstatusClient:
return _client
@app.on_event("shutdown")
def shutdown() -> None:
_client.close()
@app.get("/monitors")
def monitors(client: OpenstatusClient = Depends(get_client)):
from openstatus._gen.openstatus.monitor.v1.service_pb2 import ListMonitorsRequest
res = client.monitor.v1.MonitorService.list_monitors(ListMonitorsRequest())
return {"total": res.total_size}
Django
# myapp/openstatus.py
from django.conf import settings
from openstatus import ClientOptions, OpenstatusClient
_singleton: OpenstatusClient | None = None
def client() -> OpenstatusClient:
global _singleton
if _singleton is None:
_singleton = OpenstatusClient(ClientOptions(api_key=settings.OPENSTATUS_API_KEY))
return _singleton
Migration from the Node SDK
- Method names:
MonitorService.listMonitors(...)→MonitorService.list_monitors(...). - Message field access:
monitor.fooBar→monitor.foo_bar. - Enum lookups:
Region[value](Node) →Region.Name(value)(Python). - Headers per call: pass as keyword argument
headers={...}. - Authentication: same
OPENSTATUS_API_KEYenv var andClientOptions(api_key=...)parameter.
Development
uv sync
uv run pytest tests/unit
uv run pyright
uv run ruff check .
Integration tests against the live API:
OPENSTATUS_API_KEY=... uv run pytest tests/integration
Regenerating the protobuf message classes:
bash scripts/regen.sh
See CONTRIBUTING.md for details.
For the design rationale (why JSON over HTTP, why a pinned Buf archive, why sync+async from day one), see docs/decisions.md.
License
MIT — see LICENSE.
Metadata
Release files for openstatus 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| openstatus-0.1.0.tar.gz | 92.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| openstatus-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 224.5 kB
Release files / openstatus-0.1.0.tar.gz
| Download URL | openstatus-0.1.0.tar.gz |
|---|---|
| Size | 92.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
84cfc6c8efa3a0713c0f2b168e0558a5a4e1d3efb1ee8db45c1b5581e89e105b
|
|
BLAKE2b-256 checksum How to use checksums |
03524e632260ed22177fc72402ccf82157fb726a6ea4338b0fea17c601ee40c0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 29, 2026.
Transparency logRelease files / openstatus-0.1.0-py3-none-any.whl
| Download URL | openstatus-0.1.0-py3-none-any.whl |
|---|---|
| Size | 132.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e3de81ef0c9507d982f846464b6f47474e692133699bc303a354b6267342182c
|
|
BLAKE2b-256 checksum How to use checksums |
5370a9c10d1509f192a39725aad4cefe15da5e64dbb97cf5cbea23a37c90bd5d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 29, 2026.
Transparency log