Python sample

A minimal, copy-pasteable Python client for the BottleCRM API. Uses only the standard library plus requests.

Setup

pip install requests

A tiny client

import os
import requests

BASE = os.environ["BOTTLECRM_URL"].rstrip("/")          # e.g. https://crm.acme.com
TOKEN = os.environ["BOTTLECRM_TOKEN"]                    # JWT access token

session = requests.Session()
session.headers.update({
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
})


def list_leads(**filters):
    """Yield every lead matching the filters, paginating automatically."""
    url = f"{BASE}/api/leads/"
    params = filters
    while url:
        r = session.get(url, params=params)
        r.raise_for_status()
        body = r.json()
        yield from body["results"]
        url = body["next"]
        params = None  # next URL already has the params encoded


def create_lead(**fields):
    r = session.post(f"{BASE}/api/leads/", json=fields)
    r.raise_for_status()
    return r.json()


def update_lead(lead_id, **fields):
    r = session.patch(f"{BASE}/api/leads/{lead_id}/", json=fields)
    r.raise_for_status()
    return r.json()

Example: sync website signups into BottleCRM

def on_signup(form_data):
    create_lead(
        title=f"Website signup: {form_data['company']}",
        first_name=form_data["first_name"],
        last_name=form_data["last_name"],
        email=form_data["email"],
        source="website_form",
        status="new",
        custom_fields={
            "industry": form_data.get("industry"),
            "company_size": form_data.get("company_size"),
        },
    )

Example: weekly hot-leads report

from datetime import datetime, timedelta, timezone

since = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat()
hot = list(list_leads(
    status="qualified",
    cf_bant_score__gte=70,
    created_at__gte=since,
))

print(f"{len(hot)} qualified leads in the last 7 days")
for lead in hot[:20]:
    print(f"  {lead['title']:40s} {lead['email']}")

Refreshing the token

Once TOKEN expires you'll get a 401. Wrap the calls in a small retry helper:

def refresh():
    r = requests.post(
        f"{BASE}/api/auth/refresh/",
        json={"refresh": os.environ["BOTTLECRM_REFRESH_TOKEN"]},
    )
    r.raise_for_status()
    return r.json()["access"]

In a long-running process, refresh proactively a minute before expiry rather than waiting for the 401.