> 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 agent rankings

GET https://api.jelliu.co/api/analytics/agent-ranking

Returns a leaderboard of agents with, for each, conversion, rejections, sentiment,
frustration, duration, response latency, interruption rate, talk share, cost and the
voice provider's success score. It compares agents on how they converse, not only on
whether they closed.

Calls are attributed to the agent that served them, so manual calls without a campaign
count, and a campaign reassigned to another agent does not move its history. Older
calls that predate that attribution fall back to the campaign's agent. Only agents with
at least one call in the window appear; deleted agents never do.

**Filters.** `days` sets the window (rolling, ending now; missing or out-of-range values
become 30). `direction`, `campaignId` and `agentId` narrow the calls counted; with
`agentId` you get a single row (or none), which is the per-agent scorecard. `timezone`
is accepted but not used (a value longer than 50 characters is still a `400`).

**Ordering.** `successes` descending, then `totalCalls` descending; at most 50 rows.

**No data.** An empty array when no agent had calls. Within a row, counts and rates are
`0`, while averages, `interruptionRate`, `agentTalkShare` and the cost fields are `null`
when no call carried the measurement (see `sentimentCallCount` and `latencyCallCount`).

**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. 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-agent-ranking

## 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`
- `campaignId` (string, optional) — Scope every number to one campaign of the workspace. Omit it to aggregate across all campaigns and manual calls.
- `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.

## Response

### 200

Agents ranked by successes.

- `data` (list of object, optional)
  - `agentId` (string, optional) — Agent id.
  - `agentName` (string, optional) — Agent name.
  - `totalCalls` (integer, optional) — Calls served in the window, any status. Second ranking key.
  - `completed` (integer, optional) — Calls with status `completed`.
  - `successes` (integer, optional) — Completed calls whose outcome is a success outcome of any category. First ranking key.
  - `rejected` (integer, optional) — Calls with an explicit refusal outcome.
  - `conversionRate` (double, optional) — Percentage 0-100 (successes / completed), two decimals.
  - `rejectionRate` (double, optional) — Percentage 0-100 (rejected / totalCalls), two decimals.
  - `avgSentiment` (double, optional, nullable) — Mean sentiment (-1 to 1) of scored calls, two decimals. `null` when none were scored.
  - `sentimentCallCount` (integer, optional) — Calls that carried a sentiment score, the denominator of `avgSentiment`.
  - `avgFrustration` (double, optional, nullable) — Mean overall frustration (0 to 1), two decimals. `null` when no call carried it.
  - `avgDurationSeconds` (integer, optional, nullable) — Mean duration of completed calls, whole seconds. `null` when nothing completed.
  - `avgLatencyMs` (integer, optional, nullable) — Mean agent response latency in milliseconds. `null` without telemetry.
  - `latencyCallCount` (integer, optional) — Calls that carried latency telemetry, the denominator of `avgLatencyMs`.
  - `interruptionRate` (double, optional, nullable) — Percentage 0-100 of agent turns the person cut off, one decimal. `null` without turn data.
  - `agentTalkShare` (double, optional, nullable) — Percentage 0-100 of spoken time that was the agent, one decimal. `null` without talk-time data.
  - `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.
  - `avgSuccessScore` (double, optional, nullable) — Mean of the voice provider's 0-100 confidence that each call met its goal, one decimal. `null` when no call carried it.

## Errors

### 400 Bad Request Error

`direction` is not `inbound`, `outbound` or `all`, `campaignId` or `agentId` is not a UUID, or `timezone` is longer than 50 characters.

- `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": [
    {
      "agentId": "8d4a6e2f-3b1c-4f7a-9e05-2c6b8d1f4a93",
      "agentName": "Valentina — Ventas",
      "totalCalls": 268,
      "completed": 231,
      "successes": 71,
      "rejected": 52,
      "conversionRate": 30.74,
      "rejectionRate": 19.4,
      "avgSentiment": 0.3,
      "sentimentCallCount": 180,
      "avgFrustration": 0.21,
      "avgDurationSeconds": 201,
      "avgLatencyMs": 1210,
      "latencyCallCount": 190,
      "interruptionRate": 8.6,
      "agentTalkShare": 63.2,
      "costUsd": 33.1204,
      "costPerCallUsd": 0.1743,
      "avgSuccessScore": 68.9
    },
    {
      "agentId": "c25f9a70-6e8b-4d13-b4a2-7f1e3c9d5b68",
      "agentName": "Andrés — Soporte",
      "totalCalls": 144,
      "completed": 125,
      "successes": 26,
      "rejected": 12,
      "conversionRate": 20.8,
      "rejectionRate": 8.33,
      "avgSentiment": 0.41,
      "sentimentCallCount": 118,
      "avgFrustration": 0.13,
      "avgDurationSeconds": 163,
      "avgLatencyMs": 1110,
      "latencyCallCount": 97,
      "interruptionRate": 6.9,
      "agentTalkShare": 57.4,
      "costUsd": 15.1406,
      "costPerCallUsd": 0.1561,
      "avgSuccessScore": 76.2
    }
  ]
}
```

**SDK Code**

```python Analytics_getAnalyticsAgentRanking_example
import requests

url = "https://api.jelliu.co/api/analytics/agent-ranking"

querystring = {"agentId":"2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11","campaignId":"0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90","days":"30","direction":"outbound"}

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

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

print(response.json())
```

```javascript Analytics_getAnalyticsAgentRanking_example
const url = 'https://api.jelliu.co/api/analytics/agent-ranking?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&campaignId=0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90&days=30&direction=outbound';
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_getAnalyticsAgentRanking_example
package main

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

func main() {

	url := "https://api.jelliu.co/api/analytics/agent-ranking?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&campaignId=0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90&days=30&direction=outbound"

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

url = URI("https://api.jelliu.co/api/analytics/agent-ranking?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&campaignId=0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90&days=30&direction=outbound")

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

HttpResponse<String> response = Unirest.get("https://api.jelliu.co/api/analytics/agent-ranking?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&campaignId=0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90&days=30&direction=outbound")
  .header("Authorization", "Bearer <token>")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.jelliu.co/api/analytics/agent-ranking?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&campaignId=0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90&days=30&direction=outbound', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

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

```csharp Analytics_getAnalyticsAgentRanking_example
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/analytics/agent-ranking?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&campaignId=0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90&days=30&direction=outbound");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Analytics_getAnalyticsAgentRanking_example
import Foundation

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/analytics/agent-ranking?agentId=2f2d8b4d-aa6f-41e0-9d3e-4d61e1b07a11&campaignId=0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90&days=30&direction=outbound")! 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()
```