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_reports_summary()

Naming convention

v0.1 uses the full operation name from the OpenAPI spec for each method — so GET /reports/summary is reached via gp.reports.get_reports_summary(), not gp.reports.summary() or gp.reports.get_summary(). This keeps the SDK names stable and predictable as long as the spec's operationIds are stable, but it does mean the prefix repeats inside namespaces (e.g. gp.reports.get_reports_mrr()). A future v0.2 will add short aliases (gp.reports.summary() etc.) — the long forms will keep working.


Quick start

# Top-level KPIs
summary = gp.reports.get_reports_summary()
print(summary.summary.mrr_current)

# MRR time series
mrr = gp.reports.get_reports_mrr(date="20260101-20260531", interval="month")
for period in mrr.result:
print(period.date, period.total_mrr)

# List customers
customers = gp.customers.get_customers(limit="50")
for c in customers.result.list_:
print(c.name, c.mrr)

Note: list is a reserved word in Python, so openapi-python-client renames the response field to list_ (trailing underscore). You'll see that in customers, plans, and any other list-shaped response.


Surfaces

Analytics — reports:

gp.reports.get_reports_summary()
gp.reports.get_reports_mrr(date="...", interval="...", breakdown="...")
gp.reports.get_reports_mrr_growth(date="...")
gp.reports.get_reports_cmrr_summary()
gp.reports.get_reports_leads(date="...", interval="...")
gp.reports.get_reports_leads_summary(date="...")
gp.reports.get_reports_cohort(date="...", interval="...")
gp.reports.get_reports_retention(date="...", interval="...")
gp.reports.get_reports_transactions(date="...", interval="...")
gp.reports.get_reports_transactions_summary(date="...")
gp.reports.get_reports_cashflow_failed_payments(date="...")
gp.reports.get_reports_cashflow_failed_payments_summary(date="...")
gp.reports.get_reports_cashflow_refunds(date="...")
gp.reports.get_reports_cashflow_failure_rate(date="...")
gp.reports.get_reports_cashflow_failure_rate_summary(date="...")
gp.reports.get_reports_cashflow_outstanding_unpaid()
gp.reports.get_reports_churn_scheduled(date="...")
gp.reports.get_reports_churn_reasons_summary(date="...")
gp.reports.get_reports_map()
gp.reports.get_reports_latest_activity(limit="10")
gp.reports.get_reports_customer_concentration()
gp.reports.get_reports_custom_variables(key="industry")

Customers (analytics view):

gp.customers.get_customers(limit="50", search="acme")
gp.customers.get_customers_id(id="cus_xxx")

Plans (analytics view):

gp.plans.get_plans()

Account & integrations:

gp.profile.get_profile()
gp.profile.put_profile(body={"first_name": "Lasse", "timezone": "Europe/Copenhagen"})

gp.notifications.get_settings_notifications()
gp.notifications.put_settings_notifications(body={"daily_report": True})

gp.webhooks.get_integrations_webhooks()
gp.webhooks.post_integrations_webhooks(body={"url": "https://hooks.zapier.com/...", "event_type": "movement.churn"})
gp.webhooks.delete_integrations_webhooks_id(id="wh_xxx")

Data ingestion (gp.data.*):

gp.data.customers.get_data_customers(source="ds_xxx", limit="50")
gp.data.customers.get_data_customers_id(id="cus_xxx", source="ds_xxx")
gp.data.customers.post_data_customers(body={
"id": "cus_xxx",
"name": "Acme Inc",
"email": "billing@acme.com",
"created_date": "2025-01-15",
"country": "us",
"data_source": "ds_xxx"
})
gp.data.customers.put_data_customers_id(id="cus_xxx", body={"name": "Acme Corporation"})
gp.data.customers.delete_data_customers_id(id="cus_xxx")

gp.data.plans.get_data_plans(source="ds_xxx")
gp.data.plans.post_data_plans(body={
"id": "price_pro",
"name": "Pro",
"billing_freq": "month",
"currency": "usd",
"data_source": "ds_xxx"
})

gp.data.invoices.get_data_invoices(source="ds_xxx")
gp.data.invoices.post_data_invoices(body={
"id": "in_xxx", "customer_id": "cus_xxx",
"date": "2025-01-15", "amount": 9900,
"currency": "usd", "data_source": "ds_xxx"
})

gp.data.plan_groups.get_data_plan_groups()
gp.data.plan_groups.post_data_plan_groups(body={"name": "Pro tier", "plans": ["price_a", "price_b"]})

gp.data.segments.get_data_segments()
gp.data.segments.post_data_segments(body={"name": "EU Enterprise", "filters": {"region": "eu"}})

gp.data.sources.get_data_data_sources()
gp.data.sources.post_data_data_sources(body={"name": "My Stripe", "type": "stripe"})
gp.data.sources.post_data_data_sources_id_full_import(id="ds_xxx")
gp.data.sources.get_data_data_sources_id_progress(id="ds_xxx")

Bulk-friendly endpoints (post_data_customers, post_data_invoices, post_data_plans) 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.get_customers_id(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.