Kommo + Klipfolio: sales KPI dashboard from CRM data

Klipfolio is a SaaS platform for business dashboards, popular among SMB teams as an alternative to Tableau and Power BI. Klipfolio supports a Push API: you send data yourself, and the platform stores and visualizes it. There is no native integration with Kommo - we build a Python ETL script with hourly updates.

Architecture: Push datasource

Klipfolio works in two ways: Pull (Klipfolio fetches data on a schedule) and Push (you send data via API). For Kommo we use Push - Kommo has no public URL that Klipfolio could poll without authorization.

Scheme:

  1. Cron every hour -> Python script
  2. Script fetches data from Kommo API
  3. Transforms it into the required format
  4. Sends to Klipfolio Push datasource

Klipfolio authentication

Klipfolio API uses an API Key in the apikey header:

import requests

KLIPFOLIO_API_KEY = "your_klipfolio_api_key"

kf_session = requests.Session()
kf_session.headers.update({
    "apikey": KLIPFOLIO_API_KEY,
    "Content-Type": "application/json",
})

KF_BASE = "https://app.klipfolio.com/api/1.0"

API Key is available in Klipfolio Account -> Profile -> API Keys.

Creating a Push datasource

Created once. The datasource ID is used on every data push.

def create_push_datasource(name: str, columns: list) -> str:
    """Create Klipfolio push datasource. Returns datasource ID."""
    payload = {
        "data": {
            "name": name,
            "type": "push",
            "data_format": "json",
        }
    }
    r = kf_session.post(f"{KF_BASE}/datasources", json=payload)
    r.raise_for_status()
    return r.json()["payload"]["id"]

# Example: datasource for daily pipeline
DATASOURCE_ID = create_push_datasource(
    name="Kommo Daily Pipeline",
    columns=["date", "stage_name", "deals_count", "total_value", "avg_deal_value"],
)
print(f"Datasource ID: {DATASOURCE_ID}")  # save to config

Extracting data from Kommo

from datetime import datetime, timedelta

KOMMO_BASE  = "https://YOUR.kommo.com/api/v4"
KOMMO_TOKEN = "your_bearer_token"

kommo_session = requests.Session()
kommo_session.headers.update({"Authorization": f"Bearer {KOMMO_TOKEN}"})

def get_pipeline_snapshot() -> list:
    """Get current deals by stage from Kommo."""
    r = kommo_session.get(
        f"{KOMMO_BASE}/leads",
        params={"limit": 250, "page": 1},
    )
    r.raise_for_status()
    deals = r.json().get("_embedded", {}).get("leads", [])

    # Group by status
    stage_data = {}
    for deal in deals:
        stage_id   = deal.get("status_id")
        stage_name = get_stage_name(stage_id)  # cache from /pipelines
        value      = deal.get("price", 0)

        if stage_name not in stage_data:
            stage_data[stage_name] = {"count": 0, "total_value": 0}
        stage_data[stage_name]["count"] += 1
        stage_data[stage_name]["total_value"] += value

    today = datetime.now().strftime("%Y-%m-%d")
    rows = []
    for stage_name, data in stage_data.items():
        count = data["count"]
        total = data["total_value"]
        rows.append({
            "date":            today,
            "stage_name":      stage_name,
            "deals_count":     count,
            "total_value":     total,
            "avg_deal_value":  round(total / count, 2) if count else 0,
        })
    return rows

Pushing data to Klipfolio

def push_to_klipfolio(datasource_id: str, rows: list) -> bool:
    """Push data rows to Klipfolio datasource."""
    if not rows:
        return False

    # Klipfolio Push API accepts a JSON array of strings or a JSON array of objects
    payload = {"data": rows}

    r = kf_session.post(
        f"{KF_BASE}/datasources/{datasource_id}/data",
        json=payload,
    )
    if r.status_code not in (200, 201, 204):
        print(f"Klipfolio push failed: {r.status_code} {r.text}")
        return False
    return True

# Main ETL
def run_etl():
    print(f"ETL started at {datetime.now().isoformat()}")
    rows = get_pipeline_snapshot()
    success = push_to_klipfolio(DATASOURCE_ID, rows)
    print(f"Pushed {len(rows)} rows: {'OK' if success else 'FAILED'}")

if __name__ == "__main__":
    run_etl()

Run via cron: 0 * * * * python /path/to/kommo_klipfolio_etl.py

Advanced metrics: stage conversion rates

def get_conversion_metrics(days: int = 30) -> list:
    """Get conversion rates between stages for last N days."""
    since = int((datetime.now() - timedelta(days=days)).timestamp())

    # Won deals for the period
    r_won = kommo_session.get(
        f"{KOMMO_BASE}/leads",
        params={"filter[closed_at][from]": since, "filter[statuses][0]": 142},  # 142 = Won
    )
    won_count = len(r_won.json().get("_embedded", {}).get("leads", []))

    # All deals created in the period
    r_total = kommo_session.get(
        f"{KOMMO_BASE}/leads",
        params={"filter[created_at][from]": since},
    )
    total_count = len(r_total.json().get("_embedded", {}).get("leads", []))

    conversion = round(won_count / total_count * 100, 1) if total_count else 0

    return [{
        "date":             datetime.now().strftime("%Y-%m-%d"),
        "period_days":      days,
        "total_deals":      total_count,
        "won_deals":        won_count,
        "conversion_rate":  conversion,
    }]

Sent to a separate datasource Kommo Conversion.

Date formatting for Klipfolio

Important detail: Klipfolio interprets date format strictly. Accepted formats:

  • YYYY-MM-DD - safe format for dates
  • YYYY-MM-DD HH:MM:SS - for timestamps
  • Unix timestamp (integer) - also works

Send numbers as numbers (not strings): "total_value": 45000, not "total_value": "45000". Klipfolio applies numeric aggregation functions only to numeric fields.

Real case

A SaaS company with a 5-person sales team was doing manual exports from Kommo once a week into Google Sheets, then building charts by hand. The manager received pipeline status with a 3-7 day delay.

After the Kommo -> Klipfolio ETL:

  • Dashboard updates every hour
  • TV screen in the office shows current pipeline value and conversion rate
  • Manager sees the full picture in real time without asking the team

ETL setup took 2 business days, including dashboard design in Klipfolio.

Who this is for

Sales teams of 3-15 people who need a simple live dashboard without heavy BI infrastructure. Klipfolio is cheaper and simpler than Tableau but more powerful than Google Data Studio for Klip-based visualization.

For more powerful BI solutions - Kommo + Metabase (self-hosted, SQL), Kommo + Tableau (enterprise), Kommo + Grafana (open-source).

Frequently asked questions

How does Klipfolio differ from Tableau or Power BI?

Klipfolio is simpler to set up and cheaper ($99-299/month vs $70+/user). Visualizations are called “Klips” - each Klip connects to a separate datasource. There is no SQL editor like in Metabase. Optimal for predefined KPI metrics, not for ad-hoc analysis.

How to update historical data in a datasource?

Klipfolio Push API replaces data by default on every push (replace mode). For append (adding rows), use "mode": "append" in the POST request parameters. For storing history, append mode with a date field is recommended - this allows Klipfolio to show trends.

How many datasources can be created?

Depends on the pricing plan. On the starter plan - up to 20 datasources. For a Kommo integration, 3-5 are typically enough: pipeline by stage, conversion, average time in deal, revenue by manager.

Are there ready-made Klip templates for CRM data?

Klipfolio Gallery contains ready-made Klip templates for sales metrics. When creating a new Klip, choose Gallery -> Sales -> “Sales Pipeline” or “Conversion Funnel” and connect it to your datasource. Field configuration takes 15-20 minutes.

Summary

Kommo -> Klipfolio ETL is the simplest BI solution for small sales teams:

  • Klipfolio API Key in the apikey header
  • Push datasource: created once, updated every hour
  • Data sent as a JSON array of objects
  • Numbers as numbers, dates in YYYY-MM-DD format - otherwise aggregation won’t work

If your sales team needs a live dashboard and has no resources for BI infrastructure - reach out to Exceltic.dev. We’ll set up the ETL and dashboard for your metrics.

More articles

All →