# Analytics Report

> Totals and daily, campaign, step and inbox breakdowns for a date range.

Requires an API key with the `read` scope.

Counts follow the Reports page: a bounced email counts as sent, replies are distinct leads, and replies from bounce, warmup and unrelated mail are left out. Days are bucketed in the timezone of the user who created the API key.

With no filters the report covers every campaign in the workspace for the last 30 days.

## Query Parameters

<ParamField query="start" type="string">First day, `YYYY-MM-DD`. Defaults to 30 days before `end`.</ParamField>
<ParamField query="end" type="string">Last day, `YYYY-MM-DD`. Defaults to today in the key owner's timezone. A range is at most 365 days.</ParamField>
<ParamField query="campaignIds" type="string">Comma-separated campaign ids to include.</ParamField>
<ParamField query="accountIds" type="string">Comma-separated email account ids to include.</ParamField>

## Response

<ResponseField name="data" type="object">
  <Expandable title="data">
    <ResponseField name="range" type="object">`start`, `end` and the `timezone` used to bucket days.</ResponseField>
    <ResponseField name="totals" type="object">
      <Expandable title="totals">
    <ResponseField name="sent" type="integer">Emails that left the system (sent, delivered or bounced).</ResponseField>
    <ResponseField name="replied" type="integer">Distinct leads that replied.</ResponseField>
    <ResponseField name="interested" type="integer">Distinct leads categorised interested.</ResponseField>
    <ResponseField name="bounced" type="integer">Emails that bounced.</ResponseField>
    <ResponseField name="opened" type="integer">Opens, from campaigns with open tracking on.</ResponseField>
    <ResponseField name="clicked" type="integer">Link clicks, from campaigns with click tracking on.</ResponseField>
    <ResponseField name="notInterested" type="integer">Distinct leads categorised not interested.</ResponseField>
    <ResponseField name="outOfOffice" type="integer">Distinct leads categorised out of office.</ResponseField>
    <ResponseField name="unsubscribed" type="integer">Distinct leads categorised unsubscribe.</ResponseField>
    <ResponseField name="replyRate" type="number">Replies divided by sent, or `null` when nothing was sent.</ResponseField>
    <ResponseField name="positiveRate" type="number">Interested divided by replies, or `null`.</ResponseField>
    <ResponseField name="bounceRate" type="number">Bounced divided by sent, or `null`.</ResponseField>
    <ResponseField name="openRate" type="number">Opened divided by sent, or `null` when open tracking was off.</ResponseField>
    <ResponseField name="clickRate" type="number">Clicked divided by sent, or `null` when click tracking was off.</ResponseField>
      </Expandable>
    </ResponseField>
    <ResponseField name="daily" type="array">One row per day with the same counters and a `date`.</ResponseField>
    <ResponseField name="campaigns" type="array">One row per campaign with `id`, `name`, `status` and the counters.</ResponseField>
    <ResponseField name="steps" type="array">One row per campaign, step and variant.</ResponseField>
    <ResponseField name="accounts" type="array">One row per sending inbox with `accountId`, `email` and the counters.</ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
```bash cURL
curl "https://api.foxreach.io/api/v1/analytics/reports?start=2026-10-01&end=2026-10-31" \
  -H "X-API-Key: otr_your_key"
```

```python Python SDK
from foxreach import FoxReach

client = FoxReach(api_key="otr_your_key")
report = client.analytics.report(start="2026-10-01", end="2026-10-31")
```

```typescript TypeScript SDK
import { FoxReach } from "foxreach";

const client = new FoxReach({ apiKey: "otr_your_key" });
const report = await client.analytics.report({ start: "2026-10-01", end: "2026-10-31" });
```
</RequestExample>

<ResponseExample>
```json
{
  "data": {
    "range": {"start": "2026-10-01", "end": "2026-10-31", "timezone": "UTC"},
    "totals": {"sent": 4210, "replied": 160, "interested": 41, "bounced": 52, "opened": 0, "clicked": 0},
    "daily": [],
    "campaigns": [],
    "steps": [],
    "accounts": []
  }
}
```
</ResponseExample>

## Errors

| Status | Description |
|--------|-------------|
| `401` | Missing, invalid, revoked or expired API key |
| `403` | API key does not have the required scope: `read` |
| `400` | `start` is after `end`, or the range is longer than 365 days |
| `404` | A campaign or account id is not in this workspace |

## Rate Limit

- **300 requests per minute, shared by every `read` call in your workspace.** Fixed 60-second window.
- Every response includes `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (unix epoch seconds) and `X-RateLimit-Bucket` (`read`).
- A `429` response includes a `Retry-After` header (seconds until the bucket resets).

For client-side patterns, see [Rate Limiting](/rate-limiting).
