SDK reference · v0.1
Growth SDK
A small, structured API your code (or Claude Code) drops in. Three packages — JS/TS for browser & node, Python for server. All emit the same wire format.
Install
JavaScript / TypeScript (Next.js, Vite, Express, Node):
pnpm add @growth-loop/sdk # or npm i @growth-loop/sdk
Python (FastAPI, Django, Flask):
pip install growth-loop-sdk # or uv add growth-loop-sdk
1 · Construct a client
The browser variant batches with sendBeacon, the node variant with fetch. Both share the same surface — only the transport differs.
// src/lib/growth.ts (Next.js client component)
'use client';
import { instrument } from '@growth-loop/sdk';
import { createBrowserClient } from '@growth-loop/sdk/browser';
export const growth = instrument(
createBrowserClient({
apiKey: process.env.NEXT_PUBLIC_GROWTH_KEY!,
host: 'https://api.growth-loop.dev',
}),
);# growth_setup.py
from growth import Growth, GrowthOptions
growth = Growth(GrowthOptions(
api_key=os.environ["GROWTH_KEY"],
host="https://api.growth-loop.dev",
environment=os.environ.get("APP_ENV", "production"),
))2 · track / identify / group
The low-level API. Use this for one-off events that don’t fit a span/step/button pattern. Properties are JSON; never put PII (email, payment) here — use distinct_id for identity.
growth.track('signup_completed', { plan: 'pro' });
growth.identify({ distinctId: user.id, properties: { email_domain: 'acme.com' } });
growth.group('account', account.id, { tier: 'enterprise' });
growth.alias(user.id, anonId); // map old anonymous id → real user3 · Decorators (preferred)
Higher-level helpers Claude Code applies automatically. Each maps to one or more track() calls under the hood.
growth.span(name, fn, opts?)
Wraps a sync or async function. Emits name.started, name.completed (with duration_ms), and name.failed on throw with error_message / error_name.
const checkout = growth.span(
'checkout',
async (items: Item[]) => stripe.charge(items),
{
properties: { source: 'web' },
enrich: (args, phase, result) => phase === 'success' && result
? { amount_cents: (result as Charge).amountCents }
: undefined,
},
);growth.button(name, handler, props?)
Wraps an event handler so the click is logged before the handler runs. Returns a function with the same signature.
<button onClick={growth.button(
'cta_clicked',
signUp,
{ location: 'hero' },
)}>
Sign up
</button>growth.step(name, props?)
One-shot funnel-step event. Cheaper than span (no timing, no failure tracking).
growth.step('activation.created_first_project', { template: 'next.js' });@growth_span (Python)
Decorator. Sync and async functions. distinct_id accepts a string or callable for dynamic resolution.
from growth import growth_span
from growth_setup import growth
@router.post("/checkout")
@growth_span(growth, "checkout",
distinct_id=lambda body: body.user_id)
async def checkout(body: CheckoutBody):
return await stripe.charge(body.items)4 · Wire format
The SDK POSTs batches to /v1/ingest. You can call it directly:
curl -X POST https://api.growth-loop.dev/v1/ingest \
-H 'content-type: application/json' \
-H 'x-growth-api-key: pk_live_…' \
-d '{
"api_key": "pk_live_…",
"sdk": { "name": "curl", "version": "1.0" },
"sent_at": "2026-04-27T17:00:00Z",
"events": [{
"name": "signup_completed",
"distinct_id": "u_42",
"source": "web",
"properties": { "plan": "pro" }
}]
}'5 · Auto-instrument with Claude Code
Drop the growth-init skill into your repo’s .claude/skills/, then run:
claude mcp add growth-loop \ -e GROWTH_API_KEY=pk_live_… \ -e GROWTH_HOST=https://api.growth-loop.dev \ -- npx -y @growth-loop/mcp-server
The skill scans your stack, picks 15-30 high-signal call sites (signup, checkout, key clicks, async flows), wraps them with the decorators above, and opens a PR. See the skill marketplace for the source.
Rate limits
Ingest is rate-limited per API key: 1000 events/sec by default. Burst over the limit returns 429 with a retry-after header. Increase via env on self-host or contact us for cloud.