> 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 conversation quality

GET https://api.jelliu.co/api/analytics/quality

Returns how the calls went, as opposed to how they ended: agent response latency
(average, median, p90, worst), who did the talking, turns and interruptions, sentiment
and frustration, cost in credits and USD, the voice provider's success score, average
duration, and the most common termination reasons, languages and call sources.

Every block carries its own `sampleSize`: the calls that actually had that measurement.
Telemetry (latency, talk time, turns, cost, success score, termination reason, language,
source) was first captured on 2026-09-03, so older calls are excluded from those blocks
unless they were backfilled. Always read an average together with its sample size.

**Filters.** `days` sets the window (rolling, ending now; missing or out-of-range values
become 30; echoed as `windowDays`). `direction`, `campaignId` and `agentId` narrow every
block. `timezone` is accepted but not used (a value longer than 50 characters is still a
`400`).

**No data.** Sample sizes, `totalCalls` and `frustratedCalls` are `0`; every average,
sum and derived rate is `null`; `terminationReasons`, `languages` and `sources` are
empty arrays. Each list holds at most 12 entries, most frequent first.

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

**Consistency.** Cached for 120 seconds, then served stale for up to 15 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-quality

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

Quality overview for the window.

- `data` (object, optional) — How the calls in the window went. Each block reports the `sampleSize` it was computed over (calls that carry that measurement). Telemetry exists for calls completed from 2026-09-03 onwards, plus any backfilled ones. Without data, sample sizes are `0`, values are `null` and lists are empty.
  - `windowDays` (integer, optional) — The window actually used, in days.
  - `totalCalls` (integer, optional) — Calls in the window after filters, any status, with or without telemetry.
  - `latency` (object, optional) — Agent response latency, computed from each call's average and maximum latency.
    - `sampleSize` (integer, optional) — Calls with latency telemetry.
    - `avgMs` (integer, optional, nullable) — Mean of the per-call average latency, milliseconds.
    - `p50Ms` (integer, optional, nullable) — Median of the per-call average latency, milliseconds.
    - `p90Ms` (integer, optional, nullable) — 90th percentile of the per-call average latency, milliseconds.
    - `maxMs` (integer, optional, nullable) — Slowest single agent response in any call, milliseconds.
  - `talk` (object, optional) — Who did the talking.
    - `sampleSize` (integer, optional) — Calls with agent or user talk time.
    - `agentSeconds` (double, optional, nullable) — Total seconds the agent spoke, one decimal.
    - `userSeconds` (double, optional, nullable) — Total seconds the person spoke, one decimal.
    - `agentShare` (double, optional, nullable) — Percentage 0-100 of spoken time that was the agent, one decimal.
  - `turns` (object, optional) — Conversation turns and interruptions (backchannel "mm-hm" turns are not counted).
    - `sampleSize` (integer, optional) — Calls with turn data.
    - `agentTurns` (integer, optional, nullable) — Total agent turns.
    - `userTurns` (integer, optional, nullable) — Total user turns.
    - `interruptions` (integer, optional, nullable) — Total agent turns the person cut off.
    - `interruptionRate` (double, optional, nullable) — Percentage 0-100 of agent turns that were interrupted, one decimal.
    - `interruptionsPerCall` (double, optional, nullable) — Interruptions divided by `sampleSize`, two decimals.
  - `sentiment` (object, optional) — How callers felt, each signal with its own sample size.
    - `sampleSize` (integer, optional) — Calls with an overall sentiment score.
    - `avg` (double, optional, nullable) — Mean overall sentiment (-1 to 1), two decimals.
    - `frustrationSampleSize` (integer, optional) — Calls with a frustration score.
    - `avgFrustration` (double, optional, nullable) — Mean overall frustration (0 to 1), two decimals.
    - `frustratedCalls` (integer, optional) — Calls whose frustration score is 0.5 or above. `0` without data.
    - `lowSampleSize` (integer, optional) — Calls with a worst-moment sentiment.
    - `avgLow` (double, optional, nullable) — Mean of each call's lowest per-turn sentiment, two decimals.
  - `cost` (object, optional) — What the calls cost at the voice provider.
    - `sampleSize` (integer, optional) — Calls that carry a USD cost.
    - `credits` (double, optional, nullable) — Total cost in voice-provider credits.
    - `usd` (double, optional, nullable) — Total cost in USD, four decimals.
    - `llmUsd` (double, optional, nullable) — Share of `usd` spent on the language model, four decimals.
    - `perCallUsd` (double, optional, nullable) — `usd` divided by `sampleSize`, four decimals.
    - `perMinuteUsd` (double, optional, nullable) — `usd` divided by the minutes of the calls that carry a cost, four decimals.
  - `successScore` (object, optional) — The voice provider's own 0-100 confidence that each call met its goal.
    - `sampleSize` (integer, optional) — Calls with a success score.
    - `avg` (double, optional, nullable) — Mean success score, one decimal.
  - `duration` (object, optional) — Call length of completed calls.
    - `sampleSize` (integer, optional) — Completed calls with a duration.
    - `avgSeconds` (integer, optional, nullable) — Mean duration, whole seconds.
  - `terminationReasons` (list of object, optional) — Why calls ended, as reported by the voice provider (free text), most frequent first, at most 12.
    - `reason` (string, optional) — Termination reason text.
    - `count` (integer, optional) — Calls that ended for this reason.
  - `languages` (list of object, optional) — Main language detected per call (ISO 639-1 code), most frequent first, at most 12.
    - `language` (string, optional) — Language code.
    - `count` (integer, optional) — Calls in this language.
  - `sources` (list of object, optional) — How each conversation was initiated (for example `twilio` for phone calls, `js_sdk` for the web widget), most frequent first, at most 12.
    - `source` (string, optional) — Initiation source reported by the voice provider.
    - `count` (integer, optional) — Calls from this source.

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

### A workspace with measured calls

**Response**

```json
{
  "data": {
    "windowDays": 30,
    "totalCalls": 412,
    "latency": {
      "sampleSize": 287,
      "avgMs": 1180,
      "p50Ms": 1090,
      "p90Ms": 1720,
      "maxMs": 4830
    },
    "talk": {
      "sampleSize": 287,
      "agentSeconds": 31240.5,
      "userSeconds": 19870.2,
      "agentShare": 61.1
    },
    "turns": {
      "sampleSize": 287,
      "agentTurns": 5166,
      "userTurns": 4988,
      "interruptions": 413,
      "interruptionRate": 8,
      "interruptionsPerCall": 1.44
    },
    "sentiment": {
      "sampleSize": 298,
      "avg": 0.34,
      "frustrationSampleSize": 281,
      "avgFrustration": 0.18,
      "frustratedCalls": 29,
      "lowSampleSize": 281,
      "avgLow": -0.21
    },
    "cost": {
      "sampleSize": 287,
      "credits": 402150,
      "usd": 48.261,
      "llmUsd": 38.6088,
      "perCallUsd": 0.1682,
      "perMinuteUsd": 0.0914
    },
    "successScore": {
      "sampleSize": 265,
      "avg": 71.4
    },
    "duration": {
      "sampleSize": 356,
      "avgSeconds": 184
    },
    "terminationReasons": [
      {
        "reason": "Call ended by remote party",
        "count": 182
      },
      {
        "reason": "end_call tool was called.",
        "count": 121
      },
      {
        "reason": "Client disconnected: 1006",
        "count": 9
      }
    ],
    "languages": [
      {
        "language": "es",
        "count": 279
      },
      {
        "language": "en",
        "count": 8
      }
    ],
    "sources": [
      {
        "source": "twilio",
        "count": 274
      },
      {
        "source": "js_sdk",
        "count": 13
      }
    ]
  }
}
```

**SDK Code**

```python A workspace with measured calls
import requests

url = "https://api.jelliu.co/api/analytics/quality"

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 A workspace with measured calls
const url = 'https://api.jelliu.co/api/analytics/quality?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 A workspace with measured calls
package main

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

func main() {

	url := "https://api.jelliu.co/api/analytics/quality?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 A workspace with measured calls
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/analytics/quality?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 A workspace with measured calls
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

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

```php A workspace with measured calls
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

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

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

```csharp A workspace with measured calls
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/analytics/quality?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 A workspace with measured calls
import Foundation

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/analytics/quality?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()
```

### No calls in the window

**Response**

```json
{
  "data": {
    "windowDays": 7,
    "totalCalls": 0,
    "latency": {
      "sampleSize": 0,
      "avgMs": null,
      "p50Ms": null,
      "p90Ms": null,
      "maxMs": null
    },
    "talk": {
      "sampleSize": 0,
      "agentSeconds": null,
      "userSeconds": null,
      "agentShare": null
    },
    "turns": {
      "sampleSize": 0,
      "agentTurns": null,
      "userTurns": null,
      "interruptions": null,
      "interruptionRate": null,
      "interruptionsPerCall": null
    },
    "sentiment": {
      "sampleSize": 0,
      "avg": null,
      "frustrationSampleSize": 0,
      "avgFrustration": null,
      "frustratedCalls": 0,
      "lowSampleSize": 0,
      "avgLow": null
    },
    "cost": {
      "sampleSize": 0,
      "credits": null,
      "usd": null,
      "llmUsd": null,
      "perCallUsd": null,
      "perMinuteUsd": null
    },
    "successScore": {
      "sampleSize": 0,
      "avg": null
    },
    "duration": {
      "sampleSize": 0,
      "avgSeconds": null
    },
    "terminationReasons": [],
    "languages": [],
    "sources": []
  }
}
```

**SDK Code**

```python No calls in the window
import requests

url = "https://api.jelliu.co/api/analytics/quality"

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 No calls in the window
const url = 'https://api.jelliu.co/api/analytics/quality?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 No calls in the window
package main

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

func main() {

	url := "https://api.jelliu.co/api/analytics/quality?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 No calls in the window
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/analytics/quality?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 No calls in the window
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

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

```php No calls in the window
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

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

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

```csharp No calls in the window
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/analytics/quality?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 No calls in the window
import Foundation

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/analytics/quality?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()
```