> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.jelliu.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.jelliu.co/_mcp/server.

# Retrieve the workspace KPI summary

GET https://api.jelliu.co/api/analytics/kpi/tenant-summary

Call totals for the workspace plus the category KPIs of every campaign that recorded KPI data in
the window. KPIs are computed per call from the data the agent extracts (see
`GET /api/analytics/kpi/templates/{category}`) and are voice-only.

* `totalCalls` counts every non-deleted call created in the window (any status, with or without a campaign).
* `totalSuccessful` counts those whose outcome is a success outcome for any category (e.g. `sale_closed`, `appointment_booked`, `payment_promised`).
* `successRate` is a percentage 0-100 with two decimals.
* `categorySummaries` has ONE ENTRY PER CAMPAIGN with KPI rows in the window — two sales campaigns
  produce two `sales` entries — in no guaranteed order, and without the campaign id. Use
  `GET /api/analytics/kpi/{campaignId}` for a specific campaign. `breakdown` is always `{}` here.
* KPIs whose key ends in `_today` (e.g. `revenue_today`) cover the current UTC day only.

**No data.** Numbers are `0`, never `null`: `successRate` is `0` when there were no calls,
`avgDuration` is `0` when no duration KPI was recorded, and `categorySummaries` is `[]`. An `avg`
KPI with no samples has `value: 0` and `count: 0` — check `count` before reading `value`.

**Idempotency.** Read-only; safe to retry.

**Consistency.** Cached for 120 seconds per workspace and `days`; the cache is cleared whenever a
call's KPIs are processed. Browser cache: `private, max-age=30, stale-while-revalidate=900`.

**Access**

* **Required scope:** `read`. Signed-in users need the owner, admin, member or viewer role.
* **Rate limit:** Analytics — 30 requests/min per workspace, shared with the rest of `/api/analytics/*` (except `/analysis/*`); a KPI request consumes 2 units (the limiter runs in two sibling routers), so effectively 15 requests/min. Also counts against the general API limit. See [Rate limits](/rate-limits).
* **Plan:** Available on every plan (behind the `advancedDashboard` feature gate, which is enabled on every plan).

Reference: https://developer.jelliu.co/api-reference/analytics/get-analytics-kpi-tenant-summary

## Authentication

- `Authorization` header (bearer token, required) — Workspace API key: `jl_` followed by 64 lowercase hex characters, created by the workspace owner in the dashboard (**Settings → API Keys**) and sent as `Authorization: Bearer jl_...`. The plaintext is shown once, at creation; Jelliu stores only a SHA-256 hash. A workspace can hold up to 25 active keys. | Scope | GET / HEAD | POST / PUT / PATCH / DELETE | Admin-only routes | | --- | --- | --- | --- | | `read` | Yes | No | No | | `write` | Yes | Yes | No | | `full` | Yes | Yes | Yes | Operations restricted to admins or owners reject keys without the `full` scope with `403`, and say so in their description. No key, whatever its scope, can mint or revoke API keys or rotate a webhook secret — that requires a signed-in owner session. A revoked key stops authenticating within about 10 seconds. See [Authentication](/authentication).

## Request

### Query parameters

- `days` (integer, optional, default: 30) — Look-back window in days. Never rejected - a non-integer, out-of-range (1-365) or non-numeric value falls back to 30.

## Response

### 200

KPI summary.

- `data` (object, optional)
  - `tenantId` (string, optional) — The workspace id.
  - `totalCalls` (integer, optional) — Non-deleted calls created in the window.
  - `totalSuccessful` (integer, optional) — Calls in the window with a success outcome.
  - `successRate` (double, optional) — Percentage 0-100 (`totalSuccessful / totalCalls`), two decimals; `0` when there were no calls.
  - `avgDuration` (integer, optional) — Average of the duration KPIs recorded (e.g. `avg_resolution_time`), in seconds; `0` when none were recorded.
  - `categorySummaries` (list of object, optional) — One entry per campaign with KPI data in the window.
    - `category` (string, optional) — Campaign category (`general` when the campaign was deleted).
    - `kpis` (list of object, optional) — Every non-distribution KPI defined for the category, including ones with no data.
      - `key` (string, optional) — Stable KPI key.
      - `label` (string, optional) — Display label (Spanish product text).
      - `value` (double, optional) — Sum for `sum` KPIs, average (two decimals) for `avg` KPIs, equal to `count` for `count` KPIs.
      - `count` (integer, optional) — Samples behind the value (calls that contributed).
      - `unit` (enum, optional) — How to render `value`: `currency` (amount in the workspace's local currency), `count`, `percentage` (0-100), `score` (rubric points, e.g. NPS or 1-10), `seconds`.
        - Allowed values: `currency`, `count`, `percentage`, `score`, `seconds`
    - `breakdown` (object, optional) — Always `{}` in the workspace summary.

## Errors

### 401 Unauthorized Error

No usable credential. Either the `Authorization` header is missing or is not a well-formed `Bearer jl_…` key, or the key is unknown, revoked or expired. Do not retry with the same key. See [Authentication](/authentication#401-unauthorized).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 403 Forbidden Error

An API key without the `read` scope, a signed-in user whose role is not owner/admin/member/viewer, or a suspended workspace.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 429 Too Many Requests Error

Analytics or general API budget spent for this minute.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

## Examples

**Response**

```json
{
  "data": {
    "tenantId": "1f4a7c2e-8b3d-4e69-9a05-c6d2e8b7f310",
    "totalCalls": 412,
    "totalSuccessful": 97,
    "successRate": 23.54,
    "avgDuration": 142,
    "categorySummaries": [
      {
        "category": "sales",
        "kpis": [
          {
            "key": "revenue_total",
            "label": "Ingresos totales",
            "value": 18450000,
            "count": 38,
            "unit": "currency"
          },
          {
            "key": "revenue_today",
            "label": "Ingresos hoy",
            "value": 950000,
            "count": 2,
            "unit": "currency"
          },
          {
            "key": "avg_ticket",
            "label": "Ticket promedio",
            "value": 485526.32,
            "count": 38,
            "unit": "currency"
          },
          {
            "key": "units_sold",
            "label": "Unidades vendidas",
            "value": 61,
            "count": 35,
            "unit": "count"
          }
        ],
        "breakdown": {}
      },
      {
        "category": "support",
        "kpis": [
          {
            "key": "issues_resolved",
            "label": "Casos resueltos",
            "value": 54,
            "count": 54,
            "unit": "count"
          },
          {
            "key": "tickets_created",
            "label": "Tickets creados",
            "value": 21,
            "count": 21,
            "unit": "count"
          },
          {
            "key": "escalated_count",
            "label": "No resueltos / escalados",
            "value": 9,
            "count": 9,
            "unit": "count"
          },
          {
            "key": "avg_resolution_time",
            "label": "Tiempo promedio de resolucion",
            "value": 142.37,
            "count": 54,
            "unit": "seconds"
          }
        ],
        "breakdown": {}
      }
    ]
  }
}
```

**SDK Code**

```python Analytics_getAnalyticsKpiTenantSummary_example
import requests

url = "https://api.jelliu.co/api/analytics/kpi/tenant-summary"

querystring = {"days":"30"}

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers, params=querystring)

print(response.json())
```

```javascript Analytics_getAnalyticsKpiTenantSummary_example
const url = 'https://api.jelliu.co/api/analytics/kpi/tenant-summary?days=30';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Analytics_getAnalyticsKpiTenantSummary_example
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/analytics/kpi/tenant-summary?days=30"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Analytics_getAnalyticsKpiTenantSummary_example
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/analytics/kpi/tenant-summary?days=30")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java Analytics_getAnalyticsKpiTenantSummary_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.jelliu.co/api/analytics/kpi/tenant-summary?days=30")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php Analytics_getAnalyticsKpiTenantSummary_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.jelliu.co/api/analytics/kpi/tenant-summary?days=30', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp Analytics_getAnalyticsKpiTenantSummary_example
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/analytics/kpi/tenant-summary?days=30");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Analytics_getAnalyticsKpiTenantSummary_example
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/analytics/kpi/tenant-summary?days=30")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```