> 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.

# List top campaigns

GET https://api.jelliu.co/api/analytics/top-campaigns

Returns campaigns ranked by success rate (successes over completed calls, highest
first), each with volume, rejections, duration, sentiment, frustration, latency and
cost. Only campaigns with at least one call in the window appear; deleted campaigns
never do. The system "Manual Conversations" bucket can appear when it has calls.

**Filters.** `days` sets the window (rolling, ending now; missing or out-of-range values
become 30). `direction` and `agentId` narrow the calls counted (`agentId` matches the
agent that served each call). `campaignId` and `timezone` are not accepted and are
ignored, even when malformed. `limit` caps the rows.

**What counts as a success.** A completed call whose outcome is a success outcome of
any category, as in `GET /api/analytics/overview`. `rejected` counts explicit refusals
only (`rejected`, `appointment_canceled`, `payment_refused`, `churned`,
`candidate_unqualified`), not voicemails or no-answers.

**Ordering.** `successRate` descending; campaigns with no completed call sort last.
Ties have no guaranteed order.

**No data.** An empty array when no campaign had calls. Within a row, averages are
`null` when nothing carried the measurement, except `avgDurationSeconds`, which is `0`
for a voice campaign without completed calls and `null` for other channels.

**Idempotency.** Safe to retry: this is a read.

**Consistency.** Cached for 5 minutes, then served stale for up to 30 minutes while a
background refresh runs; each API instance also keeps a 60-second local copy. The
response carries a weak `ETag` for `If-None-Match`.

**Access**
- **Required scope:** `read`. Signed-in users need the owner, admin, member or viewer role.
- **Rate limit:** Analytics — 30 requests/min per workspace, in addition to the general API budget of your plan. See [Rate limits](/rate-limits).
- **Plan:** Available on every plan. A workspace without an active subscription (active, trialing or past due) gets `403 BILLING_ERROR`.


Reference: https://developer.jelliu.co/api-reference/analytics/get-analytics-top-campaigns

## 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) — Size of the reporting window in days, ending now. On most analytics routes a missing or out-of-range value falls back to `30` instead of being rejected; the operation says when it is strict.
- `direction` (enum, optional, default: all) — Which calls to count: `inbound` (calls the agent answered), `outbound` (calls the agent placed) or `all` (both).
  - Allowed values: `inbound`, `outbound`, `all`
- `agentId` (string, optional) — Scope every number to one agent — the agent that served each call, so manual (campaign-less) calls count. Omit it to aggregate across agents.
- `limit` (integer, optional, default: 10) — Maximum number of campaigns to return. Missing, non-integer or out-of-range values silently become 10; they are never rejected.

## Response

### 200

Campaigns ranked by success rate.

- `data` (list of object, optional)
  - `campaignId` (string, optional) — Campaign id.
  - `campaignName` (string, optional) — Campaign name.
  - `agentName` (string, optional, nullable) — Name of the agent currently assigned to the campaign. `null` when that agent was deleted.
  - `status` (enum, optional) — Current campaign status.
    - Allowed values: `draft`, `active`, `paused`, `completed`, `archived`
  - `channel` (string, optional) — Campaign channel: `voice`, `whatsapp`, `webchat` or `email` (`voice` when unset).
  - `category` (string, optional) — Campaign category as stored (`sales` when unset).
  - `totalInteractions` (integer, optional) — Calls of the campaign in the window, any status.
  - `completed` (integer, optional) — Calls with status `completed`.
  - `successes` (integer, optional) — Completed calls whose outcome is a success outcome of any category.
  - `rejected` (integer, optional) — Calls with an explicit refusal outcome.
  - `successRate` (double, optional) — Percentage 0-100, `successes / completed`, two decimals. The ranking key.
  - `rejectionRate` (double, optional) — Percentage 0-100, `rejected / totalInteractions`, two decimals.
  - `successRateLabel` (enum, optional) — Category-specific name of the success rate (see `AnalyticsChannelBreakdownItem.successRateLabel`).
    - Allowed values: `conversionRate`, `resolutionRate`, `bookingRate`, `completionRate`, `recoveryRate`, `retentionRate`, `deliveryRate`, `interviewRate`, `assessmentRate`, `successRate`
  - `avgDurationSeconds` (integer, optional, nullable) — Mean duration of completed calls, whole seconds. `0` for a voice campaign without completed calls; `null` for non-voice campaigns.
  - `avgSentiment` (double, optional, nullable) — Mean sentiment (-1 to 1) of scored calls. `null` when none were scored.
  - `avgFrustration` (double, optional, nullable) — Mean overall frustration (0 to 1). `null` when no call carried it.
  - `avgLatencyMs` (integer, optional, nullable) — Mean agent response latency in milliseconds. `null` without telemetry.
  - `costUsd` (double, optional, nullable) — Total voice-provider cost in USD, four decimals. `null` when no call carried a cost.
  - `costPerCallUsd` (double, optional, nullable) — `costUsd` divided by the calls that carried a cost, four decimals. `null` when none did.

## Errors

### 400 Bad Request Error

`direction` is not `inbound`, `outbound` or `all`, or `agentId` is not a UUID.

- `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).

### 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

No active subscription, a signed-in role outside owner/admin/member/viewer, an API key without the `read` scope, 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

The analytics budget (30 requests/min) or the plan's general API budget is exhausted.

- `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": [
    {
      "campaignId": "3f2b8c1e-7a4d-4e9b-9c21-5d8e6f1a2b30",
      "campaignName": "Reactivación clientes septiembre",
      "agentName": "Valentina — Ventas",
      "status": "active",
      "channel": "voice",
      "category": "sales",
      "totalInteractions": 268,
      "completed": 231,
      "successes": 71,
      "rejected": 52,
      "successRate": 30.74,
      "rejectionRate": 19.4,
      "successRateLabel": "conversionRate",
      "avgDurationSeconds": 201,
      "avgSentiment": 0.3,
      "avgFrustration": 0.21,
      "avgLatencyMs": 1210,
      "costUsd": 33.1204,
      "costPerCallUsd": 0.1743
    },
    {
      "campaignId": "b71e0d42-19c5-4a8f-a3e6-0c94d2f7e815",
      "campaignName": "Recordatorio de citas Clínica Norte",
      "agentName": "Andrés — Soporte",
      "status": "active",
      "channel": "voice",
      "category": "scheduling",
      "totalInteractions": 96,
      "completed": 88,
      "successes": 22,
      "rejected": 5,
      "successRate": 25,
      "rejectionRate": 5.21,
      "successRateLabel": "bookingRate",
      "avgDurationSeconds": 142,
      "avgSentiment": 0.44,
      "avgFrustration": 0.09,
      "avgLatencyMs": 1080,
      "costUsd": 9.8712,
      "costPerCallUsd": 0.1175
    }
  ]
}
```

**SDK Code**

```python Analytics_getAnalyticsTopCampaigns_example
import requests

url = "https://api.jelliu.co/api/analytics/top-campaigns"

querystring = {"agentId":"2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11","days":"30","direction":"outbound","limit":"5"}

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

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

print(response.json())
```

```javascript Analytics_getAnalyticsTopCampaigns_example
const url = 'https://api.jelliu.co/api/analytics/top-campaigns?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&days=30&direction=outbound&limit=5';
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_getAnalyticsTopCampaigns_example
package main

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

func main() {

	url := "https://api.jelliu.co/api/analytics/top-campaigns?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&days=30&direction=outbound&limit=5"

	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_getAnalyticsTopCampaigns_example
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/analytics/top-campaigns?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&days=30&direction=outbound&limit=5")

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_getAnalyticsTopCampaigns_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.jelliu.co/api/analytics/top-campaigns?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&days=30&direction=outbound&limit=5")
  .header("Authorization", "Bearer <token>")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.jelliu.co/api/analytics/top-campaigns?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&days=30&direction=outbound&limit=5', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

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

```csharp Analytics_getAnalyticsTopCampaigns_example
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/analytics/top-campaigns?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&days=30&direction=outbound&limit=5");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Analytics_getAnalyticsTopCampaigns_example
import Foundation

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/analytics/top-campaigns?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&days=30&direction=outbound&limit=5")! 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()
```