Python SDK


Official Python SDK for the GrowPanel REST API. Published as growpanel on PyPI. Source: growpanel/growpanel-sdk-python.


Installation

pip install growpanel

Requires 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
raise

err.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 growpanel

Changelog on GitHub Releases.