Python SDK
Official Python SDK for the GrowPanel REST API. Published as growpanel on PyPI. Source: growpanel/growpanel-sdk-python.
Installation
pip install growpanelRequires Python 3.9+. Built on top of httpx and attrs — both are installed automatically.
Authentication
Get an API key from app.growpanel.io → Account → API keys, then pass it as api_key. See Authentication for key scoping and rotation.
import os
from growpanel import GrowPanel
gp = GrowPanel(api_key=os.environ["GROWPANEL_API_KEY"])Override the base URL for staging:
gp = GrowPanel(
api_key=os.environ["GROWPANEL_API_KEY"],
base_url="https://api-dev.growpanel.io"
)The client supports the context-manager protocol so HTTP connections are cleaned up properly:
with GrowPanel(api_key=...) as gp:
summary = gp.reports.get_summary()Quick start
# Top-level KPIs
summary = gp.reports.get_summary()
print(summary.summary.mrr_current)
# MRR time series
mrr = gp.reports.get_mrr(date="20260101-20260531", interval="month")
for period in mrr.result:
print(period.date, period.total_mrr)
# List customers
customers = gp.customers.list(limit=50)
for c in customers.result.list:
print(c.name, c.current_mrr_base_currency)Surfaces
Analytics:
gp.reports.get_summary()
gp.reports.get_mrr(date=..., interval=..., breakdown=...)
gp.reports.get_cohort(date=..., interval=...)
gp.reports.get_retention(date=..., interval=...)
gp.reports.get_cashflow_failed_payments(date=...)
# ... 22 report endpoints in total
gp.customers.list(limit=50, search="acme")
gp.customers.detail(id="cus_xxx")
gp.plans.list()Account & integrations:
gp.profile.get()
gp.profile.update(body={"first_name": "Lasse", "timezone": "Europe/Copenhagen"})
gp.notifications.get()
gp.notifications.update(body={"daily_report": True})
gp.webhooks.list()
gp.webhooks.create(body={"url": "https://hooks.zapier.com/...", "event_type": "movement.churn"})
gp.webhooks.delete(id="wh_xxx")Ingestion (nested under data.*):
gp.data.customers.create(body={
"id": "cus_xxx",
"name": "Acme Inc",
"email": "[email protected]",
"created_date": "2025-01-15",
"country": "us",
"data_source": "ds_xxx"
})
gp.data.customers.update(id="cus_xxx", body={"name": "Acme Corporation"})
gp.data.customers.delete(id="cus_xxx")
gp.data.plans.create(body={
"id": "price_pro", "name": "Pro",
"billing_freq": "month", "currency": "usd",
"data_source": "ds_xxx"
})
gp.data.invoices.create(body={
"id": "in_xxx", "customer_id": "cus_xxx",
"date": "2025-01-15", "amount": 9900,
"currency": "usd", "data_source": "ds_xxx"
})
gp.data.plan_groups.create(body={"name": "Pro tier", "plans": ["price_a", "price_b"]})
gp.data.segments.create(body={"name": "EU Enterprise", "filters": {"region": "eu"}})
gp.data.sources.list()
gp.data.sources.full_import(id="ds_xxx")
gp.data.sources.get_progress(id="ds_xxx")Bulk-friendly endpoints (data.customers.create, data.invoices.create, data.plans.create) accept either a dict or a list of dicts — the response is always a list of per-row import results.
For anything not in a named namespace, gp.raw is the generated module tree — every endpoint is reachable from there.
Errors
Every operation raises GrowPanelError on non-2xx responses.
from growpanel import GrowPanel, GrowPanelError
try:
gp.customers.detail(id="cus_doesnotexist")
except GrowPanelError as err:
if err.status == 404:
... # customer doesn't exist
elif err.status == 429:
... # rate-limited
raiseerr.status, err.status_text, and err.body carry the full context. See error codes for what each status means.
Updating
The SDK is regenerated on every API surface change. To pull the latest:
pip install --upgrade growpanelChangelog on GitHub Releases.